Add functional Undo/Redo buttons

This commit is contained in:
Mikolaj Wielgus 2026-06-08 16:48:58 +02:00
parent 2cc4c52a59
commit ffa94cff82
15 changed files with 227 additions and 223 deletions

View File

@ -12,6 +12,7 @@ use crate::{
pub struct Actions { pub struct Actions {
pub file: FileActions, pub file: FileActions,
pub edit: EditActions,
pub run: RunActions, pub run: RunActions,
pub debug: DebugActions, pub debug: DebugActions,
} }
@ -20,6 +21,7 @@ impl Actions {
pub fn new(tr: &Translator) -> Self { pub fn new(tr: &Translator) -> Self {
Self { Self {
file: FileActions::new(tr), file: FileActions::new(tr),
edit: EditActions::new(tr),
run: RunActions::new(tr), run: RunActions::new(tr),
debug: DebugActions::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 struct RunActions {
pub autoplace: Trigger, pub autoplace: Trigger,
} }

View File

@ -18,7 +18,7 @@ pub struct Controller {
impl Controller { impl Controller {
pub fn new(board: Board, tr: &Translator) -> Self { pub fn new(board: Board, tr: &Translator) -> Self {
let appearance_panel = LayersPanel::new(&board); let appearance_panel = LayersPanel::new(&board);
let workspace = Workspace::new_board(board); let workspace = Workspace::new(board);
Self { Self {
master_interactor: MasterInteractor::new(workspace.selection().clone()), master_interactor: MasterInteractor::new(workspace.selection().clone()),
@ -95,15 +95,13 @@ impl Controller {
None None
}; };
if let Workspace::Board(workspace) = &mut self.workspace { self.master_interactor.abort(self.workspace.board_mut());
self.master_interactor.abort(&mut workspace.board);
if let Some(board_master) = board_master { if let Some(board_master) = board_master {
self.master_interactor = MasterInteractor::Board(board_master); self.master_interactor = MasterInteractor::Board(board_master);
}
*self.workspace.selection_mut() = self.master_interactor.selection().clone();
} }
*self.workspace.selection_mut() = self.master_interactor.selection().clone();
} }
let primary_pressed = ctx.input(|i| i.pointer.button_pressed(egui::PointerButton::Primary)); 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()); self.master_interactor = MasterInteractor::new(self.workspace.selection().clone());
} }
if let Workspace::Board(workspace) = &mut self.workspace { if primary_down {
if primary_down { self.master_interactor.hold(
self.master_interactor.hold( self.workspace.board_mut(),
&mut workspace.board, self.appearance_panel.active,
self.appearance_panel.active, pointer_on_scene,
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) = let drag_rect_on_viewport = egui::Rect::from_min_max(
self.master_interactor.select_interactor().as_ref() scene_to_viewport * drag_rect_scene.min,
{ scene_to_viewport * drag_rect_scene.max,
let origin = *select_interactor.origin(); );
let drag_rect_scene = egui::Rect::from_min_max( let boundary_color = if pointer_on_scene.x >= origin.x {
egui::pos2( egui::Color32::YELLOW
origin.x.min(pointer_on_scene.x) as f32, } else {
origin.y.min(pointer_on_scene.y) as f32, egui::Color32::from_rgb(80, 160, 255)
), };
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( ui.painter().rect(
scene_to_viewport * drag_rect_scene.min, drag_rect_on_viewport,
scene_to_viewport * drag_rect_scene.max, egui::CornerRadius::ZERO,
); egui::Color32::from_rgba_unmultiplied(80, 160, 255, 48),
let boundary_color = if pointer_on_scene.x >= origin.x { egui::Stroke::new(1.5, boundary_color),
egui::Color32::YELLOW egui::StrokeKind::Outside,
} 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,
);
}
} }
} }
} }
@ -177,18 +172,22 @@ impl Controller {
.map(|select_interactor| *select_interactor.origin()) .map(|select_interactor| *select_interactor.origin())
.unwrap_or(Vector2::new(0, 0)) .unwrap_or(Vector2::new(0, 0))
}); });
if let Workspace::Board(workspace) = &mut self.workspace {
self.master_interactor if self
.release(&mut workspace.board, active, pointer_for_scene); .master_interactor
*self.workspace.selection_mut() = self.master_interactor.selection().clone(); .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 delete_pressed {
if let Workspace::Board(workspace) = &mut self.workspace { self.master_interactor.delete(self.workspace.board_mut());
self.master_interactor.delete(&mut workspace.board); self.workspace.commit();
*self.workspace.selection_mut() = self.master_interactor.selection().clone(); *self.workspace.selection_mut() = self.master_interactor.selection().clone();
}
} }
} }
} }

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{controller::Controller, viewport::Viewport}; use crate::{controller::Controller, viewport::Viewport};
use topola::{Orientation, Vector2, Workspace}; use topola::{Orientation, Vector2};
pub struct DebugOverlay {} pub struct DebugOverlay {}
@ -267,72 +267,10 @@ impl DebugOverlay {
fn display_navmeshes( fn display_navmeshes(
&mut self, &mut self,
ctx: &egui::Context, _ctx: &egui::Context,
ui: &egui::Ui, _ui: &egui::Ui,
viewport: &Viewport, _viewport: &Viewport,
workspace: &Controller, _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,*/
),
);
}
}
}
}
} }
} }

View File

@ -8,10 +8,7 @@ use crate::{
menu_bar::MenuBar, menu_bar::MenuBar,
viewport::Viewport, viewport::Viewport,
}; };
use topola::{ use topola::layout::primitives::{Joint, Poly, Seg, Via};
Workspace,
layout::primitives::{Joint, Poly, Seg, Via},
};
pub struct Display {} pub struct Display {}
@ -252,12 +249,8 @@ impl Display {
workspace: &Controller, workspace: &Controller,
) { ) {
crate::profile_function!(); 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 layers = *ratline.endpoint_layers();
let endpoints = *ratline.endpoints(); let endpoints = *ratline.endpoints();
@ -267,8 +260,6 @@ impl Display {
continue; continue;
} }
//let stroke_width = 2.0 / viewport.scale_factor().max(1e-6);
ui.painter().line_segment( ui.painter().line_segment(
[ [
egui::pos2(endpoints[0].x as f32, endpoints[0].y as f32), egui::pos2(endpoints[0].x as f32, endpoints[0].y as f32),

View File

@ -65,6 +65,14 @@ impl MenuBar {
crate::profile_function!(); crate::profile_function!();
let mut actions = Actions::new(tr); 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::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
egui::MenuBar::new().ui(ui, |ui| { egui::MenuBar::new().ui(ui, |ui| {
@ -74,6 +82,14 @@ impl MenuBar {
ui.separator(); 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()); actions.run.render_menu(ctx, ui, controller.is_some());
ui.separator(); 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 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.master_interactor.autoplace(
controller.workspace.board_mut(), controller.workspace.board_mut(),
AutoplacerSchedule { AutoplacerSchedule {

View File

@ -57,8 +57,8 @@ impl Interactor for AutoplacerMasterInteractor {
self.board_master.hold(board, layer, pointer); self.board_master.hold(board, layer, pointer);
} }
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) { fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) -> ControlFlow<()> {
self.board_master.release(board, layer, pointer); self.board_master.release(board, layer, pointer)
} }
fn abort(&mut self, board: &mut Board) { fn abort(&mut self, board: &mut Board) {

View File

@ -4,21 +4,17 @@
use derive_getters::Getters; use derive_getters::Getters;
use crate::{board::Board, ratsnest::Ratsnest, router::Router}; use crate::{board::Board, router::Router};
#[derive(Clone, Debug, Getters)] #[derive(Clone, Debug, Getters)]
pub struct Autorouter { pub struct Autorouter {
ratsnest: Ratsnest,
router: Router, router: Router,
} }
impl Autorouter { impl Autorouter {
pub fn new(board: Board) -> Self { pub fn new(board: Board) -> Self {
let ratsnest = Ratsnest::new(&board);
Self { Self {
router: Router::new(board), router: Router::new(board),
ratsnest,
} }
} }
} }

View File

@ -2,6 +2,8 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use std::ops::ControlFlow;
use derive_getters::Getters; use derive_getters::Getters;
use derive_more::Constructor; use derive_more::Constructor;
use undoredo::ResetDelta; use undoredo::ResetDelta;
@ -25,8 +27,9 @@ impl Interactor for DragMoveInteractor {
board.move_components_by(self.selection.clone(), pointer - self.origin); board.move_components_by(self.selection.clone(), pointer - self.origin);
} }
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) { fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) -> ControlFlow<()> {
self.hold(board, layer, pointer); self.hold(board, layer, pointer);
ControlFlow::Break(())
} }
fn abort(&mut self, board: &mut Board) { fn abort(&mut self, board: &mut Board) {

View File

@ -2,6 +2,8 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use std::ops::ControlFlow;
use derive_getters::Getters; use derive_getters::Getters;
use derive_more::Constructor; use derive_more::Constructor;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -185,8 +187,9 @@ impl Interactor for DragSelectInteractor {
self.selection = combined_selection; self.selection = combined_selection;
} }
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) { fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) -> ControlFlow<()> {
self.hold(board, layer, pointer); self.hold(board, layer, pointer);
ControlFlow::Continue(())
} }
fn abort(&mut self, _board: &mut Board) { fn abort(&mut self, _board: &mut Board) {

View File

@ -2,6 +2,8 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use std::ops::ControlFlow;
use derive_getters::Getters; use derive_getters::Getters;
use crate::{ use crate::{
@ -62,16 +64,21 @@ impl Interactor for BoardMasterInteractor {
} }
} }
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) { fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) -> ControlFlow<()> {
if let Some(drag_move_interactor) = self.drag_move_interactor.as_mut() { let commit = if let Some(drag_move_interactor) = self.drag_move_interactor.as_mut() {
drag_move_interactor.release(board, layer, pointer); drag_move_interactor.release(board, layer, pointer)
} else if let Some(select_interactor) = self.select_interactor.as_mut() { } else if let Some(select_interactor) = self.select_interactor.as_mut() {
select_interactor.release(board, layer, pointer); select_interactor.release(board, layer, pointer);
self.selection = select_interactor.selection().clone(); self.selection = select_interactor.selection().clone();
} ControlFlow::Continue(())
} else {
ControlFlow::Continue(())
};
self.select_interactor = None; self.select_interactor = None;
self.drag_move_interactor = None; self.drag_move_interactor = None;
commit
} }
fn abort(&mut self, board: &mut Board) { fn abort(&mut self, board: &mut Board) {

View File

@ -2,6 +2,8 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use std::ops::ControlFlow;
use derive_getters::Getters; use derive_getters::Getters;
use crate::{ use crate::{
@ -56,7 +58,7 @@ impl Interactor for SelectInteractor {
self.selection = drag_selection_interactor.selection().clone(); self.selection = drag_selection_interactor.selection().clone();
} }
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) { fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) -> ControlFlow<()> {
if pointer == self.origin { if pointer == self.origin {
let mut selection = self.original_selection.clone(); let mut selection = self.original_selection.clone();
let point = Vector3::new(pointer.x, pointer.y, layer.index() as i64); let point = Vector3::new(pointer.x, pointer.y, layer.index() as i64);
@ -76,10 +78,11 @@ impl Interactor for SelectInteractor {
} }
self.selection = selection; self.selection = selection;
return; return ControlFlow::Continue(());
} }
self.hold(board, layer, pointer); self.hold(board, layer, pointer);
ControlFlow::Continue(())
} }
fn abort(&mut self, _board: &mut Board) { fn abort(&mut self, _board: &mut Board) {

View File

@ -19,10 +19,19 @@ pub trait Interactor {
fn step(&mut self, _board: &mut Board) -> ControlFlow<()> { fn step(&mut self, _board: &mut Board) -> ControlFlow<()> {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn delete(&mut self, board: &mut Board) {} fn delete(&mut self, board: &mut Board) {
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {} let _ = board;
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {} }
fn abort(&mut self, board: &mut Board) {} fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
let _ = (board, layer, pointer);
}
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) -> ControlFlow<()> {
let _ = (board, layer, pointer);
ControlFlow::Continue(())
}
fn abort(&mut self, board: &mut Board) {
let _ = board;
}
} }
pub enum MasterInteractor { 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<i64>) { fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) -> ControlFlow<()> {
match self { match self {
Self::Board(interactor) => interactor.release(board, layer, pointer), Self::Board(interactor) => interactor.release(board, layer, pointer),
Self::Autoplacer(interactor) => interactor.release(board, layer, pointer), Self::Autoplacer(interactor) => interactor.release(board, layer, pointer),

View File

@ -22,6 +22,7 @@ mod specctra;
mod vector; mod vector;
mod workspace; mod workspace;
pub use crate::board::BoardDelta;
pub use crate::autoplacer::AutoplacerSchedule; pub use crate::autoplacer::AutoplacerSchedule;
pub use crate::autorouter::Autorouter; pub use crate::autorouter::Autorouter;
pub use crate::board::selections; pub use crate::board::selections;
@ -30,4 +31,4 @@ pub use crate::orientation::Orientation;
pub use crate::ratsnest::{Ratline, Ratsnest}; pub use crate::ratsnest::{Ratline, Ratsnest};
pub use crate::rect::{Rect2, Rect3}; pub use crate::rect::{Rect2, Rect3};
pub use crate::vector::{Vector2, Vector3}; pub use crate::vector::{Vector2, Vector3};
pub use crate::workspace::{AutorouterWorkspace, BoardWorkspace, Workspace}; pub use crate::workspace::Workspace;

View File

@ -9,11 +9,11 @@ use serde::{Deserialize, Serialize};
use spade::{DelaunayTriangulation, HasPosition, Triangulation, handles::FixedVertexHandle}; use spade::{DelaunayTriangulation, HasPosition, Triangulation, handles::FixedVertexHandle};
use crate::{ use crate::{
board::Board,
layout::{ layout::{
LayerId, LayerId,
compounds::NetId, compounds::NetId,
primitives::{JointId, PolyId, PrimitiveId, SegId}, primitives::{JointId, PolyId, PrimitiveId, SegId},
Layout,
}, },
vector::Vector2, 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 { pub struct Ratsnest {
ratlines: Vec<Ratline>, ratlines: Vec<Ratline>,
} }
impl Ratsnest { impl Ratsnest {
pub fn new(board: &Board) -> Self { pub fn new(layout: &Layout) -> Self {
let mut ratlines = Vec::new(); let mut ratlines = Vec::new();
let mut triangulations: BTreeMap<(NetId, LayerId), DelaunayTriangulation<DelaunayVertex>> = let mut triangulations: BTreeMap<(NetId, LayerId), DelaunayTriangulation<DelaunayVertex>> =
BTreeMap::new(); 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 { let Some(net) = joint.spec.net else {
continue; 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 { let Some(net) = seg.net else {
continue; 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 { let Some(net) = poly.spec.net else {
continue; continue;
}; };

View File

@ -2,84 +2,67 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // 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 { #[derive(Getters)]
Board(BoardWorkspace), pub struct Workspace {
Autorouter(AutorouterWorkspace), board: Board,
selection: PersistableSelection,
history: UndoRedo<BoardDelta>,
ratsnest: Ratsnest,
} }
impl Workspace { impl Workspace {
pub fn new_board(mut board: Board) -> Self { pub fn new(mut board: Board) -> Self {
board.flush_delta(); board.flush_delta();
let ratsnest = Ratsnest::new(board.layout());
Self::Board(BoardWorkspace::new(board)) Self {
} board,
selection: PersistableSelection::new(),
pub fn new_autorouter(board: Board) -> Self { history: UndoRedo::new(),
Self::Autorouter(AutorouterWorkspace::new(board)) ratsnest,
}
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()
}
} }
} }
pub fn board_mut(&mut self) -> &mut Board { pub fn board_mut(&mut self) -> &mut Board {
match self { &mut self.board
Workspace::Board(workspace) => &mut workspace.board, }
Workspace::Autorouter(_workspace) => todo!(),
/*Workspace::Autorouter(workspace) => { pub fn selection_mut(&mut self) -> &mut PersistableSelection {
workspace.autorouter.router().navmesher_board().board() &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 fn redo(&mut self) -> bool {
pub board: Board, if self.history.redo(&mut self.board).is_some() {
pub selection: PersistableSelection, self.rebuild_ratsnest();
} true
} else {
impl BoardWorkspace { false
pub fn new(board: Board) -> Self {
Self {
board,
selection: PersistableSelection::new(),
} }
} }
}
pub struct AutorouterWorkspace { fn rebuild_ratsnest(&mut self) {
pub autorouter: Autorouter, self.ratsnest = Ratsnest::new(self.board.layout());
pub selection: PersistableSelection,
}
impl AutorouterWorkspace {
pub fn new(board: Board) -> Self {
Self {
autorouter: Autorouter::new(board),
selection: PersistableSelection::new(),
}
} }
} }