From ffa94cff82f0c0df757a51ab9c1f505cea80f1a3 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Mon, 8 Jun 2026 16:48:58 +0200 Subject: [PATCH] Add functional Undo/Redo buttons --- topola-egui/src/actions.rs | 44 ++++++++ topola-egui/src/controller.rs | 107 ++++++++++---------- topola-egui/src/debug_overlay.rs | 72 +------------ topola-egui/src/display.rs | 13 +-- topola-egui/src/menu_bar.rs | 30 +++++- topola/src/autoplacer/interactors/master.rs | 4 +- topola/src/autorouter.rs | 6 +- topola/src/board/interactors/drag_move.rs | 5 +- topola/src/board/interactors/drag_select.rs | 5 +- topola/src/board/interactors/master.rs | 15 ++- topola/src/board/interactors/select.rs | 7 +- topola/src/interactor.rs | 20 ++-- topola/src/lib.rs | 3 +- topola/src/ratsnest.rs | 12 +-- topola/src/workspace.rs | 107 ++++++++------------ 15 files changed, 227 insertions(+), 223 deletions(-) diff --git a/topola-egui/src/actions.rs b/topola-egui/src/actions.rs index 2637b34..7c984e3 100644 --- a/topola-egui/src/actions.rs +++ b/topola-egui/src/actions.rs @@ -12,6 +12,7 @@ use crate::{ pub struct Actions { pub file: FileActions, + pub edit: EditActions, pub run: RunActions, pub debug: DebugActions, } @@ -20,6 +21,7 @@ impl Actions { pub fn new(tr: &Translator) -> Self { Self { file: FileActions::new(tr), + edit: EditActions::new(tr), run: RunActions::new(tr), debug: DebugActions::new(tr), } @@ -76,6 +78,48 @@ impl FileActions { } } +pub struct EditActions { + pub undo: Trigger, + pub redo: Trigger, +} + +impl EditActions { + pub fn new(tr: &Translator) -> Self { + Self { + undo: Action::new( + tr.text("tr-menu-edit-undo"), + egui::Modifiers::CTRL, + egui::Key::Z, + ) + .into_trigger(), + redo: Action::new( + tr.text("tr-menu-edit-redo"), + egui::Modifiers::CTRL, + egui::Key::Y, + ) + .into_trigger(), + } + } + + pub fn render_menu( + &mut self, + ctx: &Context, + ui: &mut Ui, + have_workspace: bool, + can_undo: bool, + can_redo: bool, + ) { + ui.add_enabled_ui(have_workspace, |ui| { + ui.add_enabled_ui(can_undo, |ui| { + self.undo.button(ctx, ui); + }); + ui.add_enabled_ui(can_redo, |ui| { + self.redo.button(ctx, ui); + }); + }); + } +} + pub struct RunActions { pub autoplace: Trigger, } diff --git a/topola-egui/src/controller.rs b/topola-egui/src/controller.rs index bdcab14..6d253e1 100644 --- a/topola-egui/src/controller.rs +++ b/topola-egui/src/controller.rs @@ -18,7 +18,7 @@ pub struct Controller { impl Controller { pub fn new(board: Board, tr: &Translator) -> Self { let appearance_panel = LayersPanel::new(&board); - let workspace = Workspace::new_board(board); + let workspace = Workspace::new(board); Self { master_interactor: MasterInteractor::new(workspace.selection().clone()), @@ -95,15 +95,13 @@ impl Controller { None }; - if let Workspace::Board(workspace) = &mut self.workspace { - self.master_interactor.abort(&mut workspace.board); + self.master_interactor.abort(self.workspace.board_mut()); - if let Some(board_master) = board_master { - self.master_interactor = MasterInteractor::Board(board_master); - } - - *self.workspace.selection_mut() = self.master_interactor.selection().clone(); + if let Some(board_master) = board_master { + self.master_interactor = MasterInteractor::Board(board_master); } + + *self.workspace.selection_mut() = self.master_interactor.selection().clone(); } let primary_pressed = ctx.input(|i| i.pointer.button_pressed(egui::PointerButton::Primary)); @@ -123,47 +121,44 @@ impl Controller { self.master_interactor = MasterInteractor::new(self.workspace.selection().clone()); } - if let Workspace::Board(workspace) = &mut self.workspace { - if primary_down { - self.master_interactor.hold( - &mut workspace.board, - self.appearance_panel.active, - pointer_on_scene, + if primary_down { + self.master_interactor.hold( + self.workspace.board_mut(), + self.appearance_panel.active, + pointer_on_scene, + ); + + if let Some(select_interactor) = self.master_interactor.select_interactor().as_ref() + { + let origin = *select_interactor.origin(); + let drag_rect_scene = egui::Rect::from_min_max( + egui::pos2( + origin.x.min(pointer_on_scene.x) as f32, + origin.y.min(pointer_on_scene.y) as f32, + ), + egui::pos2( + origin.x.max(pointer_on_scene.x) as f32, + origin.y.max(pointer_on_scene.y) as f32, + ), ); - if let Some(select_interactor) = - self.master_interactor.select_interactor().as_ref() - { - let origin = *select_interactor.origin(); - let drag_rect_scene = egui::Rect::from_min_max( - egui::pos2( - origin.x.min(pointer_on_scene.x) as f32, - origin.y.min(pointer_on_scene.y) as f32, - ), - egui::pos2( - origin.x.max(pointer_on_scene.x) as f32, - origin.y.max(pointer_on_scene.y) as f32, - ), - ); + let drag_rect_on_viewport = egui::Rect::from_min_max( + scene_to_viewport * drag_rect_scene.min, + scene_to_viewport * drag_rect_scene.max, + ); + let boundary_color = if pointer_on_scene.x >= origin.x { + egui::Color32::YELLOW + } else { + egui::Color32::from_rgb(80, 160, 255) + }; - let drag_rect_on_viewport = egui::Rect::from_min_max( - scene_to_viewport * drag_rect_scene.min, - scene_to_viewport * drag_rect_scene.max, - ); - let boundary_color = if pointer_on_scene.x >= origin.x { - egui::Color32::YELLOW - } else { - egui::Color32::from_rgb(80, 160, 255) - }; - - ui.painter().rect( - drag_rect_on_viewport, - egui::CornerRadius::ZERO, - egui::Color32::from_rgba_unmultiplied(80, 160, 255, 48), - egui::Stroke::new(1.5, boundary_color), - egui::StrokeKind::Outside, - ); - } + ui.painter().rect( + drag_rect_on_viewport, + egui::CornerRadius::ZERO, + egui::Color32::from_rgba_unmultiplied(80, 160, 255, 48), + egui::Stroke::new(1.5, boundary_color), + egui::StrokeKind::Outside, + ); } } } @@ -177,18 +172,22 @@ impl Controller { .map(|select_interactor| *select_interactor.origin()) .unwrap_or(Vector2::new(0, 0)) }); - if let Workspace::Board(workspace) = &mut self.workspace { - self.master_interactor - .release(&mut workspace.board, active, pointer_for_scene); - *self.workspace.selection_mut() = self.master_interactor.selection().clone(); + + if self + .master_interactor + .release(self.workspace.board_mut(), active, pointer_for_scene) + .is_break() + { + self.workspace.commit(); } + + *self.workspace.selection_mut() = self.master_interactor.selection().clone(); } if delete_pressed { - if let Workspace::Board(workspace) = &mut self.workspace { - self.master_interactor.delete(&mut workspace.board); - *self.workspace.selection_mut() = self.master_interactor.selection().clone(); - } + self.master_interactor.delete(self.workspace.board_mut()); + self.workspace.commit(); + *self.workspace.selection_mut() = self.master_interactor.selection().clone(); } } } diff --git a/topola-egui/src/debug_overlay.rs b/topola-egui/src/debug_overlay.rs index e5797cb..a44e50d 100644 --- a/topola-egui/src/debug_overlay.rs +++ b/topola-egui/src/debug_overlay.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::{controller::Controller, viewport::Viewport}; -use topola::{Orientation, Vector2, Workspace}; +use topola::{Orientation, Vector2}; pub struct DebugOverlay {} @@ -267,72 +267,10 @@ impl DebugOverlay { fn display_navmeshes( &mut self, - ctx: &egui::Context, - ui: &egui::Ui, - viewport: &Viewport, - workspace: &Controller, + _ctx: &egui::Context, + _ui: &egui::Ui, + _viewport: &Viewport, + _workspace: &Controller, ) { - crate::profile_function!(); - let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else { - return; - }; - let autorouter = &autorouter_workspace.autorouter; - - for layer in workspace - .appearance_panel - .layers_in_display_order(*workspace.workspace.board().layout().layer_count()) - { - if workspace.appearance_panel.visible[layer.index()] { - for navmesh in autorouter - .router() - .navmesher_board() - .navmesher() - .layer_navmeshers()[layer.index()] - .navmeshes() - { - for edge_geom in navmesh - .triangulation() - .rtreed_dcel() - .edges_rtree() - .as_ref() - .iter() - { - let (from_vertex, to_vertex) = navmesh - .triangulation() - .rtreed_dcel() - .dcel() - .edge_endpoints(edge_geom.data); - let from = navmesh - .triangulation() - .rtreed_dcel() - .dcel() - .vertex_weight(from_vertex) - .position(); - let to = navmesh - .triangulation() - .rtreed_dcel() - .dcel() - .vertex_weight(to_vertex) - .position(); - ui.painter().line_segment( - [ - egui::pos2(*from.x() as f32, *from.y() as f32), - egui::pos2(*to.x() as f32, *to.y() as f32), - ], - egui::Stroke::new( - 10.0, - egui::Color32::WHITE, - /*workspace - .appearance_panel - .colors(ctx) - .layers - .color(workspace.autorouter.navmesher_board().board().layer_name(layer)) - .normal,*/ - ), - ); - } - } - } - } } } diff --git a/topola-egui/src/display.rs b/topola-egui/src/display.rs index d9305cc..3fd11f0 100644 --- a/topola-egui/src/display.rs +++ b/topola-egui/src/display.rs @@ -8,10 +8,7 @@ use crate::{ menu_bar::MenuBar, viewport::Viewport, }; -use topola::{ - Workspace, - layout::primitives::{Joint, Poly, Seg, Via}, -}; +use topola::layout::primitives::{Joint, Poly, Seg, Via}; pub struct Display {} @@ -252,12 +249,8 @@ impl Display { workspace: &Controller, ) { crate::profile_function!(); - let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else { - return; - }; - let autorouter = &autorouter_workspace.autorouter; - for ratline in autorouter.ratsnest().ratlines() { + for ratline in workspace.workspace.ratsnest().ratlines() { let layers = *ratline.endpoint_layers(); let endpoints = *ratline.endpoints(); @@ -267,8 +260,6 @@ impl Display { continue; } - //let stroke_width = 2.0 / viewport.scale_factor().max(1e-6); - ui.painter().line_segment( [ egui::pos2(endpoints[0].x as f32, endpoints[0].y as f32), diff --git a/topola-egui/src/menu_bar.rs b/topola-egui/src/menu_bar.rs index b2ea1c4..0c104a9 100644 --- a/topola-egui/src/menu_bar.rs +++ b/topola-egui/src/menu_bar.rs @@ -65,6 +65,14 @@ impl MenuBar { crate::profile_function!(); let mut actions = Actions::new(tr); + let mut controller = controller; + + let can_undo = controller + .as_ref() + .is_some_and(|controller| !controller.workspace.history().done().is_empty()); + let can_redo = controller + .as_ref() + .is_some_and(|controller| !controller.workspace.history().undone().is_empty()); egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::MenuBar::new().ui(ui, |ui| { @@ -74,6 +82,14 @@ impl MenuBar { ui.separator(); + //ui.menu_button(tr.text("tr-menu-edit"), |ui| { + actions + .edit + .render_menu(ctx, ui, controller.is_some(), can_undo, can_redo); + //}); + + ui.separator(); + actions.run.render_menu(ctx, ui, controller.is_some()); ui.separator(); @@ -132,8 +148,20 @@ impl MenuBar { }); } + if can_undo && actions.edit.undo.consume_key_triggered(ctx, ui) { + if let Some(controller) = controller.as_mut() { + controller.workspace.undo(); + } + } + + if can_redo && actions.edit.redo.consume_key_triggered(ctx, ui) { + if let Some(controller) = controller.as_mut() { + controller.workspace.redo(); + } + } + if actions.run.autoplace.consume_key_triggered(ctx, ui) { - if let Some(controller) = controller { + if let Some(controller) = controller.as_mut() { controller.master_interactor.autoplace( controller.workspace.board_mut(), AutoplacerSchedule { diff --git a/topola/src/autoplacer/interactors/master.rs b/topola/src/autoplacer/interactors/master.rs index 95411fc..7f69077 100644 --- a/topola/src/autoplacer/interactors/master.rs +++ b/topola/src/autoplacer/interactors/master.rs @@ -57,8 +57,8 @@ impl Interactor for AutoplacerMasterInteractor { self.board_master.hold(board, layer, pointer); } - fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { - self.board_master.release(board, layer, pointer); + fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) -> ControlFlow<()> { + self.board_master.release(board, layer, pointer) } fn abort(&mut self, board: &mut Board) { diff --git a/topola/src/autorouter.rs b/topola/src/autorouter.rs index 0590520..d96d7d8 100644 --- a/topola/src/autorouter.rs +++ b/topola/src/autorouter.rs @@ -4,21 +4,17 @@ use derive_getters::Getters; -use crate::{board::Board, ratsnest::Ratsnest, router::Router}; +use crate::{board::Board, router::Router}; #[derive(Clone, Debug, Getters)] pub struct Autorouter { - ratsnest: Ratsnest, router: Router, } impl Autorouter { pub fn new(board: Board) -> Self { - let ratsnest = Ratsnest::new(&board); - Self { router: Router::new(board), - ratsnest, } } } diff --git a/topola/src/board/interactors/drag_move.rs b/topola/src/board/interactors/drag_move.rs index 7ddf543..c284771 100644 --- a/topola/src/board/interactors/drag_move.rs +++ b/topola/src/board/interactors/drag_move.rs @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use std::ops::ControlFlow; + use derive_getters::Getters; use derive_more::Constructor; use undoredo::ResetDelta; @@ -25,8 +27,9 @@ impl Interactor for DragMoveInteractor { board.move_components_by(self.selection.clone(), pointer - self.origin); } - fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { + fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) -> ControlFlow<()> { self.hold(board, layer, pointer); + ControlFlow::Break(()) } fn abort(&mut self, board: &mut Board) { diff --git a/topola/src/board/interactors/drag_select.rs b/topola/src/board/interactors/drag_select.rs index 0452263..159bb3f 100644 --- a/topola/src/board/interactors/drag_select.rs +++ b/topola/src/board/interactors/drag_select.rs @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use std::ops::ControlFlow; + use derive_getters::Getters; use derive_more::Constructor; use serde::{Deserialize, Serialize}; @@ -185,8 +187,9 @@ impl Interactor for DragSelectInteractor { self.selection = combined_selection; } - fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { + fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) -> ControlFlow<()> { self.hold(board, layer, pointer); + ControlFlow::Continue(()) } fn abort(&mut self, _board: &mut Board) { diff --git a/topola/src/board/interactors/master.rs b/topola/src/board/interactors/master.rs index f509ecf..b169ec2 100644 --- a/topola/src/board/interactors/master.rs +++ b/topola/src/board/interactors/master.rs @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use std::ops::ControlFlow; + use derive_getters::Getters; use crate::{ @@ -62,16 +64,21 @@ impl Interactor for BoardMasterInteractor { } } - fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { - if let Some(drag_move_interactor) = self.drag_move_interactor.as_mut() { - drag_move_interactor.release(board, layer, pointer); + fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) -> ControlFlow<()> { + let commit = if let Some(drag_move_interactor) = self.drag_move_interactor.as_mut() { + drag_move_interactor.release(board, layer, pointer) } else if let Some(select_interactor) = self.select_interactor.as_mut() { select_interactor.release(board, layer, pointer); self.selection = select_interactor.selection().clone(); - } + ControlFlow::Continue(()) + } else { + ControlFlow::Continue(()) + }; self.select_interactor = None; self.drag_move_interactor = None; + + commit } fn abort(&mut self, board: &mut Board) { diff --git a/topola/src/board/interactors/select.rs b/topola/src/board/interactors/select.rs index 55bb838..bec3771 100644 --- a/topola/src/board/interactors/select.rs +++ b/topola/src/board/interactors/select.rs @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use std::ops::ControlFlow; + use derive_getters::Getters; use crate::{ @@ -56,7 +58,7 @@ impl Interactor for SelectInteractor { self.selection = drag_selection_interactor.selection().clone(); } - fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { + fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) -> ControlFlow<()> { if pointer == self.origin { let mut selection = self.original_selection.clone(); let point = Vector3::new(pointer.x, pointer.y, layer.index() as i64); @@ -76,10 +78,11 @@ impl Interactor for SelectInteractor { } self.selection = selection; - return; + return ControlFlow::Continue(()); } self.hold(board, layer, pointer); + ControlFlow::Continue(()) } fn abort(&mut self, _board: &mut Board) { diff --git a/topola/src/interactor.rs b/topola/src/interactor.rs index 178f55e..a455fcb 100644 --- a/topola/src/interactor.rs +++ b/topola/src/interactor.rs @@ -19,10 +19,19 @@ pub trait Interactor { fn step(&mut self, _board: &mut Board) -> ControlFlow<()> { ControlFlow::Continue(()) } - fn delete(&mut self, board: &mut Board) {} - fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) {} - fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) {} - fn abort(&mut self, board: &mut Board) {} + fn delete(&mut self, board: &mut Board) { + let _ = board; + } + fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { + let _ = (board, layer, pointer); + } + fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) -> ControlFlow<()> { + let _ = (board, layer, pointer); + ControlFlow::Continue(()) + } + fn abort(&mut self, board: &mut Board) { + let _ = board; + } } pub enum MasterInteractor { @@ -47,7 +56,6 @@ impl MasterInteractor { )); } _ => (), - //_ => panic!("autoplacement can be only started from board at rest"), } } @@ -94,7 +102,7 @@ impl Interactor for MasterInteractor { } } - fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { + fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) -> ControlFlow<()> { match self { Self::Board(interactor) => interactor.release(board, layer, pointer), Self::Autoplacer(interactor) => interactor.release(board, layer, pointer), diff --git a/topola/src/lib.rs b/topola/src/lib.rs index 00af393..6f74167 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -22,6 +22,7 @@ mod specctra; mod vector; mod workspace; +pub use crate::board::BoardDelta; pub use crate::autoplacer::AutoplacerSchedule; pub use crate::autorouter::Autorouter; pub use crate::board::selections; @@ -30,4 +31,4 @@ pub use crate::orientation::Orientation; pub use crate::ratsnest::{Ratline, Ratsnest}; pub use crate::rect::{Rect2, Rect3}; pub use crate::vector::{Vector2, Vector3}; -pub use crate::workspace::{AutorouterWorkspace, BoardWorkspace, Workspace}; +pub use crate::workspace::Workspace; diff --git a/topola/src/ratsnest.rs b/topola/src/ratsnest.rs index c8d27bf..53fd5b1 100644 --- a/topola/src/ratsnest.rs +++ b/topola/src/ratsnest.rs @@ -9,11 +9,11 @@ use serde::{Deserialize, Serialize}; use spade::{DelaunayTriangulation, HasPosition, Triangulation, handles::FixedVertexHandle}; use crate::{ - board::Board, layout::{ LayerId, compounds::NetId, primitives::{JointId, PolyId, PrimitiveId, SegId}, + Layout, }, vector::Vector2, }; @@ -40,19 +40,19 @@ impl HasPosition for DelaunayVertex { } } -#[derive(Clone, Debug, Deserialize, Getters, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Getters, Serialize)] pub struct Ratsnest { ratlines: Vec, } impl Ratsnest { - pub fn new(board: &Board) -> Self { + pub fn new(layout: &Layout) -> Self { let mut ratlines = Vec::new(); let mut triangulations: BTreeMap<(NetId, LayerId), DelaunayTriangulation> = BTreeMap::new(); - for (i, joint) in board.layout().joints().container().iter() { + for (i, joint) in layout.joints().container().iter() { let Some(net) = joint.spec.net else { continue; }; @@ -68,7 +68,7 @@ impl Ratsnest { }); } - for (i, seg) in board.layout().segs().container().iter() { + for (i, seg) in layout.segs().container().iter() { let Some(net) = seg.net else { continue; }; @@ -85,7 +85,7 @@ impl Ratsnest { }); } - for (i, poly) in board.layout().polys().container().iter() { + for (i, poly) in layout.polys().container().iter() { let Some(net) = poly.spec.net else { continue; }; diff --git a/topola/src/workspace.rs b/topola/src/workspace.rs index 3dd22c5..e600aaf 100644 --- a/topola/src/workspace.rs +++ b/topola/src/workspace.rs @@ -2,84 +2,67 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use undoredo::FlushDelta; +use derive_getters::Getters; +use undoredo::{FlushDelta, UndoRedo}; -use crate::{autorouter::Autorouter, board::Board, selections::PersistableSelection}; +use crate::{ + board::{Board, BoardDelta, selections::PersistableSelection}, + ratsnest::Ratsnest, +}; -pub enum Workspace { - Board(BoardWorkspace), - Autorouter(AutorouterWorkspace), +#[derive(Getters)] +pub struct Workspace { + board: Board, + selection: PersistableSelection, + history: UndoRedo, + ratsnest: Ratsnest, } impl Workspace { - pub fn new_board(mut board: Board) -> Self { + pub fn new(mut board: Board) -> Self { board.flush_delta(); + let ratsnest = Ratsnest::new(board.layout()); - Self::Board(BoardWorkspace::new(board)) - } - - pub fn new_autorouter(board: Board) -> Self { - Self::Autorouter(AutorouterWorkspace::new(board)) - } - - pub fn selection(&self) -> &PersistableSelection { - match self { - Workspace::Board(workspace) => &workspace.selection, - Workspace::Autorouter(workspace) => &workspace.selection, - } - } - - pub fn selection_mut(&mut self) -> &mut PersistableSelection { - match self { - Workspace::Board(workspace) => &mut workspace.selection, - Workspace::Autorouter(workspace) => &mut workspace.selection, - } - } - - pub fn board(&self) -> &Board { - match self { - Workspace::Board(workspace) => &workspace.board, - Workspace::Autorouter(workspace) => { - workspace.autorouter.router().navmesher_board().board() - } + Self { + board, + selection: PersistableSelection::new(), + history: UndoRedo::new(), + ratsnest, } } pub fn board_mut(&mut self) -> &mut Board { - match self { - Workspace::Board(workspace) => &mut workspace.board, - Workspace::Autorouter(_workspace) => todo!(), - /*Workspace::Autorouter(workspace) => { - workspace.autorouter.router().navmesher_board().board() - }*/ + &mut self.board + } + + pub fn selection_mut(&mut self) -> &mut PersistableSelection { + &mut self.selection + } + + pub fn commit(&mut self) { + self.history.commit(&mut self.board); + self.rebuild_ratsnest(); + } + + pub fn undo(&mut self) -> bool { + if self.history.undo(&mut self.board).is_some() { + self.rebuild_ratsnest(); + true + } else { + false } } -} -pub struct BoardWorkspace { - pub board: Board, - pub selection: PersistableSelection, -} - -impl BoardWorkspace { - pub fn new(board: Board) -> Self { - Self { - board, - selection: PersistableSelection::new(), + pub fn redo(&mut self) -> bool { + if self.history.redo(&mut self.board).is_some() { + self.rebuild_ratsnest(); + true + } else { + false } } -} -pub struct AutorouterWorkspace { - pub autorouter: Autorouter, - pub selection: PersistableSelection, -} - -impl AutorouterWorkspace { - pub fn new(board: Board) -> Self { - Self { - autorouter: Autorouter::new(board), - selection: PersistableSelection::new(), - } + fn rebuild_ratsnest(&mut self) { + self.ratsnest = Ratsnest::new(self.board.layout()); } }