Make selection responsibility of `topola`, not `topola-egui`, with new `Workspace` struct

Now use `MasterInteractor`.
This commit is contained in:
Mikolaj Wielgus 2026-05-27 19:30:07 +02:00
parent da7585b135
commit 1a2c4aeec2
11 changed files with 195 additions and 108 deletions

View File

@ -8,7 +8,9 @@ use specctra::{error::ParseErrorContext, structure::DsnFile};
use topola::Board; use topola::Board;
use unic_langid::langid; use unic_langid::langid;
use crate::{menu_bar::MenuBar, translator::Translator, viewport::Viewport, workspace::Workspace}; use crate::{
menu_bar::MenuBar, translator::Translator, viewport::Viewport, workspace::GuiWorkspace,
};
pub struct App { pub struct App {
translator: Translator, translator: Translator,
@ -20,7 +22,7 @@ pub struct App {
menu_bar: MenuBar, menu_bar: MenuBar,
viewport: Viewport, viewport: Viewport,
workspace: Option<Workspace>, workspace: Option<GuiWorkspace>,
} }
impl Default for App { impl Default for App {
@ -50,7 +52,7 @@ impl App {
fn update_state(&mut self) { fn update_state(&mut self) {
if let Ok(data) = self.content_channel.1.try_recv() { if let Ok(data) = self.content_channel.1.try_recv() {
self.workspace = Some(Workspace::new( self.workspace = Some(GuiWorkspace::new(
Board::from_specctra(data.unwrap()), Board::from_specctra(data.unwrap()),
&self.translator, &self.translator,
)); ));

View File

@ -2,9 +2,9 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{viewport::Viewport, workspace::Workspace}; use crate::{viewport::Viewport, workspace::GuiWorkspace};
use topola::LayerId;
use topola::primitives::{Joint, Polygon, Segment, Via}; use topola::primitives::{Joint, Polygon, Segment, Via};
use topola::{LayerId, Workspace};
pub struct Display {} pub struct Display {}
@ -19,7 +19,7 @@ impl Display {
ui: &egui::Ui, ui: &egui::Ui,
//menu_bar: &MenuBar, //menu_bar: &MenuBar,
viewport: &Viewport, viewport: &Viewport,
workspace: &Workspace, workspace: &GuiWorkspace,
) { ) {
self.display_layout(ctx, ui, /*menu_bar,*/ viewport, workspace); self.display_layout(ctx, ui, /*menu_bar,*/ viewport, workspace);
self.display_bboxes(ctx, ui, viewport, workspace); self.display_bboxes(ctx, ui, viewport, workspace);
@ -33,9 +33,9 @@ impl Display {
ui: &egui::Ui, ui: &egui::Ui,
//menu_bar: &MenuBar, //menu_bar: &MenuBar,
viewport: &Viewport, viewport: &Viewport,
workspace: &Workspace, workspace: &GuiWorkspace,
) { ) {
let board = workspace.autorouter.router().navmesher_board().board(); let board = workspace.workspace.board();
let layout = board.layout(); let layout = board.layout();
// Start from the bottom layer so that top layers are drawn on top. // Start from the bottom layer so that top layers are drawn on top.
@ -46,8 +46,10 @@ impl Display {
for joint_id in layout.layer_joints(layer) { for joint_id in layout.layer_joints(layer) {
let joint = layout.joint(joint_id); let joint = layout.joint(joint_id);
let pin_selected = board.pins_contain_joint(&workspace.selection.pins, joint_id); let pin_selected =
let net_selected = board.nets_contain_joint(&workspace.selection.nets, joint_id); board.pins_contain_joint(&workspace.workspace.selection().pins, joint_id);
let net_selected =
board.nets_contain_joint(&workspace.workspace.selection().nets, joint_id);
self.paint_joint( self.paint_joint(
ctx, ctx,
ui, ui,
@ -64,9 +66,9 @@ impl Display {
for segment_id in layout.layer_segments(layer) { for segment_id in layout.layer_segments(layer) {
let segment = layout.segment(segment_id); let segment = layout.segment(segment_id);
let pin_selected = let pin_selected =
board.pins_contain_segment(&workspace.selection.pins, segment_id); board.pins_contain_segment(&workspace.workspace.selection().pins, segment_id);
let net_selected = let net_selected =
board.nets_contain_segment(&workspace.selection.nets, segment_id); board.nets_contain_segment(&workspace.workspace.selection().nets, segment_id);
self.paint_segment( self.paint_segment(
ctx, ctx,
ui, ui,
@ -82,8 +84,10 @@ impl Display {
for via_id in layout.layer_vias(layer) { for via_id in layout.layer_vias(layer) {
let via = layout.via(via_id); let via = layout.via(via_id);
let pin_selected = board.pins_contain_via(&workspace.selection.pins, via_id); let pin_selected =
let net_selected = board.nets_contain_via(&workspace.selection.nets, via_id); board.pins_contain_via(&workspace.workspace.selection().pins, via_id);
let net_selected =
board.nets_contain_via(&workspace.workspace.selection().nets, via_id);
self.paint_via( self.paint_via(
ctx, ctx,
ui, ui,
@ -100,9 +104,9 @@ impl Display {
for polygon_id in layout.layer_polygons(layer) { for polygon_id in layout.layer_polygons(layer) {
let polygon = layout.polygon(polygon_id); let polygon = layout.polygon(polygon_id);
let pin_selected = let pin_selected =
board.pins_contain_polygon(&workspace.selection.pins, polygon_id); board.pins_contain_polygon(&workspace.workspace.selection().pins, polygon_id);
let net_selected = let net_selected =
board.nets_contain_polygon(&workspace.selection.nets, polygon_id); board.nets_contain_polygon(&workspace.workspace.selection().nets, polygon_id);
self.paint_polygon( self.paint_polygon(
ctx, ctx,
ui, ui,
@ -203,9 +207,9 @@ impl Display {
ctx: &egui::Context, ctx: &egui::Context,
ui: &egui::Ui, ui: &egui::Ui,
viewport: &Viewport, viewport: &Viewport,
workspace: &Workspace, workspace: &GuiWorkspace,
) { ) {
let board = workspace.autorouter.router().navmesher_board().board(); let board = workspace.workspace.board();
let layout = board.layout(); let layout = board.layout();
for layer in (0..*layout.layer_count()).rev().map(LayerId::new) { for layer in (0..*layout.layer_count()).rev().map(LayerId::new) {
@ -283,20 +287,16 @@ impl Display {
ctx: &egui::Context, ctx: &egui::Context,
ui: &egui::Ui, ui: &egui::Ui,
viewport: &Viewport, viewport: &Viewport,
workspace: &Workspace, workspace: &GuiWorkspace,
) { ) {
for layer in (0..*workspace let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else {
.autorouter return;
.router() };
.navmesher_board() let autorouter = &autorouter_workspace.autorouter;
.board()
.layout() for layer in (0..*workspace.workspace.board().layout().layer_count()).map(LayerId::new) {
.layer_count())
.map(LayerId::new)
{
if workspace.appearance_panel.visible[layer.index()] { if workspace.appearance_panel.visible[layer.index()] {
for navmesh in workspace for navmesh in autorouter
.autorouter
.router() .router()
.navmesher_board() .navmesher_board()
.navmesher() .navmesher()
@ -354,9 +354,14 @@ impl Display {
_ctx: &egui::Context, _ctx: &egui::Context,
ui: &egui::Ui, ui: &egui::Ui,
_viewport: &Viewport, _viewport: &Viewport,
workspace: &Workspace, workspace: &GuiWorkspace,
) { ) {
for ratline in workspace.autorouter.ratsnest().ratlines() { let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else {
return;
};
let autorouter = &autorouter_workspace.autorouter;
for ratline in autorouter.ratsnest().ratlines() {
let layers = *ratline.endpoint_layers(); let layers = *ratline.endpoint_layers();
let endpoints = *ratline.endpoints(); let endpoints = *ratline.endpoints();

View File

@ -3,15 +3,15 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use egui::Pos2; use egui::Pos2;
use topola::{InteractiveInput, SelectionCombineMode, SelectionInteractor, Vector2}; use topola::{InteractiveInput, MasterInteractor, Vector2, Workspace};
use crate::{display::Display, workspace::Workspace}; use crate::{display::Display, workspace::GuiWorkspace};
pub struct Viewport { pub struct Viewport {
pub scene_rect: egui::Rect, pub scene_rect: egui::Rect,
pub ref_scene_rect: egui::Rect, pub ref_scene_rect: egui::Rect,
pub scheduled_zoom_to_fit: bool, pub scheduled_zoom_to_fit: bool,
selection_interactor: Option<SelectionInteractor>, master_interactor: Option<MasterInteractor>,
} }
impl Viewport { impl Viewport {
@ -20,11 +20,11 @@ impl Viewport {
scene_rect: egui::Rect::from_min_max(egui::pos2(-1.0, -1.0), egui::pos2(1.0, 1.0)), scene_rect: egui::Rect::from_min_max(egui::pos2(-1.0, -1.0), egui::pos2(1.0, 1.0)),
ref_scene_rect: egui::Rect::from_min_max(egui::pos2(-1.0, -1.0), egui::pos2(1.0, 1.0)), ref_scene_rect: egui::Rect::from_min_max(egui::pos2(-1.0, -1.0), egui::pos2(1.0, 1.0)),
scheduled_zoom_to_fit: false, scheduled_zoom_to_fit: false,
selection_interactor: None, master_interactor: None,
} }
} }
pub fn update(&mut self, ctx: &egui::Context, workspace: Option<&mut Workspace>) { pub fn update(&mut self, ctx: &egui::Context, workspace: Option<&mut GuiWorkspace>) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
egui::Frame::canvas(ui.style()).show(ui, |ui| { egui::Frame::canvas(ui.style()).show(ui, |ui| {
ui.ctx().request_repaint(); ui.ctx().request_repaint();
@ -52,7 +52,7 @@ impl Viewport {
if let Some(workspace) = workspace { if let Some(workspace) = workspace {
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
self.selection_interactor = None; self.master_interactor = None;
} }
let primary_pressed = let primary_pressed =
@ -61,6 +61,7 @@ impl Viewport {
ctx.input(|i| i.pointer.button_down(egui::PointerButton::Primary)); ctx.input(|i| i.pointer.button_down(egui::PointerButton::Primary));
let primary_released = let primary_released =
ctx.input(|i| i.pointer.button_released(egui::PointerButton::Primary)); ctx.input(|i| i.pointer.button_released(egui::PointerButton::Primary));
let delete_pressed = ctx.input(|i| i.key_pressed(egui::Key::Delete));
let mut maybe_pointer_on_scene: Option<Vector2<i64>> = None; let mut maybe_pointer_on_scene: Option<Vector2<i64>> = None;
if let Some(pointer_viewport_pos) = ctx.input(|i| i.pointer.interact_pos()) { if let Some(pointer_viewport_pos) = ctx.input(|i| i.pointer.interact_pos()) {
@ -73,17 +74,20 @@ impl Viewport {
maybe_pointer_on_scene = Some(pointer_on_scene); maybe_pointer_on_scene = Some(pointer_on_scene);
if primary_pressed && response.hovered() { if primary_pressed && response.hovered() {
self.selection_interactor = Some(SelectionInteractor::new( self.master_interactor = Some(MasterInteractor::new(
pointer_on_scene, None,
workspace.selection.clone(), workspace.workspace.selection().clone(),
SelectionCombineMode::Replace,
)); ));
} }
if let Some(interactor) = self.selection_interactor.as_mut() { if let Some(interactor) = self.master_interactor.as_mut() {
if primary_down { if primary_down {
let _ = interactor.update( let board = match &mut workspace.workspace {
workspace.autorouter.router().navmesher_board().board(), Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_) => panic!("expected board workspace"),
};
interactor.update(
board,
workspace.appearance_panel.active, workspace.appearance_panel.active,
InteractiveInput::new(pointer_on_scene, false, false, false), InteractiveInput::new(pointer_on_scene, false, false, false),
); );
@ -92,19 +96,45 @@ impl Viewport {
} }
if primary_released { if primary_released {
if let Some(mut interactor) = self.selection_interactor.take() { if let Some(mut interactor) = self.master_interactor.take() {
let pointer_for_scene = let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| {
maybe_pointer_on_scene.unwrap_or(*interactor.origin()); interactor
let _ = interactor.update( .selection_interactor()
workspace.autorouter.router().navmesher_board().board(), .as_ref()
.map(|selection_interactor| *selection_interactor.origin())
.unwrap_or(Vector2::new(0, 0))
});
let board = match &mut workspace.workspace {
Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_) => panic!("expected board workspace"),
};
interactor.update(
board,
workspace.appearance_panel.active, workspace.appearance_panel.active,
InteractiveInput::new(pointer_for_scene, true, false, false), InteractiveInput::new(pointer_for_scene, true, false, false),
); );
workspace.selection = interactor.selection().clone(); *workspace.workspace.selection_mut() = interactor.selection().clone();
} }
} }
if delete_pressed {
let pointer_for_scene =
maybe_pointer_on_scene.unwrap_or(Vector2::new(0, 0));
let mut interactor =
MasterInteractor::new(None, workspace.workspace.selection().clone());
let board = match &mut workspace.workspace {
Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_) => panic!("expected board workspace"),
};
interactor.update(
board,
workspace.appearance_panel.active,
InteractiveInput::new(pointer_for_scene, false, true, false),
);
*workspace.workspace.selection_mut() = interactor.selection().clone();
}
self.zoom_to_fit_if_scheduled(workspace); self.zoom_to_fit_if_scheduled(workspace);
} }
}) })
@ -141,7 +171,7 @@ impl Viewport {
* egui::emath::TSTransform::from_scaling(scale) * egui::emath::TSTransform::from_scaling(scale)
} }
fn zoom_to_fit_if_scheduled(&mut self, workspace: &Workspace) { fn zoom_to_fit_if_scheduled(&mut self, workspace: &GuiWorkspace) {
if self.scheduled_zoom_to_fit { if self.scheduled_zoom_to_fit {
self.scene_rect = Self::boundary_bounding_box(workspace); self.scene_rect = Self::boundary_bounding_box(workspace);
self.ref_scene_rect = self.scene_rect.clone(); self.ref_scene_rect = self.scene_rect.clone();
@ -150,29 +180,15 @@ impl Viewport {
self.scheduled_zoom_to_fit = false; self.scheduled_zoom_to_fit = false;
} }
fn boundary_bounding_box(workspace: &Workspace) -> egui::Rect { fn boundary_bounding_box(workspace: &GuiWorkspace) -> egui::Rect {
let first = workspace let first = workspace.workspace.board().layout().boundary()[0];
.autorouter
.router()
.navmesher_board()
.board()
.layout()
.boundary()[0];
let mut min_x = first[0]; let mut min_x = first[0];
let mut max_x = first[0]; let mut max_x = first[0];
let mut min_y = first[1]; let mut min_y = first[1];
let mut max_y = first[1]; let mut max_y = first[1];
for point in workspace for point in workspace.workspace.board().layout().boundary()[1..].iter() {
.autorouter
.router()
.navmesher_board()
.board()
.layout()
.boundary()[1..]
.iter()
{
if point[0] < min_x { if point[0] < min_x {
min_x = point[0]; min_x = point[0];
} }

View File

@ -2,29 +2,30 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use topola::{Autorouter, Board, selections::PersistableSelection}; use topola::{Board, Workspace};
use crate::{layers_panel::LayersPanel, translator::Translator}; use crate::{layers_panel::LayersPanel, translator::Translator};
pub struct Workspace { pub struct GuiWorkspace {
pub autorouter: Autorouter, pub workspace: Workspace,
pub appearance_panel: LayersPanel, pub appearance_panel: LayersPanel,
pub selection: PersistableSelection,
} }
impl Workspace { impl GuiWorkspace {
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);
Self { Self {
autorouter: Autorouter::new(board), workspace: Workspace::new_board(board),
appearance_panel, appearance_panel,
selection: PersistableSelection::new(),
} }
} }
pub fn update_appearance_panel(&mut self, ctx: &egui::Context) { pub fn update_appearance_panel(&mut self, ctx: &egui::Context) {
self.appearance_panel let Self {
.update(ctx, &self.autorouter.router().navmesher_board().board()); workspace,
appearance_panel,
} = self;
appearance_panel.update(ctx, workspace.board());
} }
} }

View File

@ -43,14 +43,10 @@ impl DragSelectionInteractor {
} }
} }
pub fn update( pub fn update(&mut self, board: &Board, input: InteractiveInput) {
&mut self,
board: &Board,
input: InteractiveInput,
) -> Option<PersistableSelection> {
if input.cancel { if input.cancel {
self.selection = self.original_selection.clone(); self.selection = self.original_selection.clone();
return Some(self.selection.clone()); return;
} }
self.selection = PersistableSelection::new(); self.selection = PersistableSelection::new();
@ -141,6 +137,5 @@ impl DragSelectionInteractor {
} }
self.selection = combined_selection; self.selection = combined_selection;
Some(self.selection.clone())
} }
} }

View File

@ -17,12 +17,12 @@ use crate::{
#[derive(Clone, Constructor, Debug, Eq, Getters, PartialEq)] #[derive(Clone, Constructor, Debug, Eq, Getters, PartialEq)]
pub struct MasterInteractor { pub struct MasterInteractor {
selection: PersistableSelection,
selection_interactor: Option<SelectionInteractor>, selection_interactor: Option<SelectionInteractor>,
selection: PersistableSelection,
} }
impl MasterInteractor { impl MasterInteractor {
pub fn update(&mut self, board: &mut Board, input: InteractiveInput) { pub fn update(&mut self, board: &mut Board, layer: LayerId, input: InteractiveInput) {
if input.delete { if input.delete {
board.delete_net_free_primitives(self.selection.nets.clone()); board.delete_net_free_primitives(self.selection.nets.clone());
} }
@ -31,16 +31,13 @@ impl MasterInteractor {
self.selection_interactor = Some(SelectionInteractor::new( self.selection_interactor = Some(SelectionInteractor::new(
input.pointer, input.pointer,
self.selection.clone(), self.selection.clone(),
SelectionCombineMode::Additive, SelectionCombineMode::Replace,
)); ));
} }
if let Some(selection_interactor) = self.selection_interactor.as_mut() { if let Some(selection_interactor) = self.selection_interactor.as_mut() {
if let Some(selection) = selection_interactor.update(board, layer, input.clone());
selection_interactor.update(board, LayerId::new(0), input.clone()) self.selection = selection_interactor.selection().clone();
{
self.selection = selection;
}
} }
if input.release || input.cancel { if input.release || input.cancel {

View File

@ -8,6 +8,7 @@ mod selection;
use derive_more::Constructor; use derive_more::Constructor;
pub use drag_selection::{DragSelectionInteractor, DragSelectionOptions}; pub use drag_selection::{DragSelectionInteractor, DragSelectionOptions};
pub use master::MasterInteractor;
pub use selection::SelectionInteractor; pub use selection::SelectionInteractor;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -39,15 +39,10 @@ impl SelectionInteractor {
} }
} }
pub fn update( pub fn update(&mut self, board: &Board, layer: LayerId, input: InteractiveInput) {
&mut self,
board: &Board,
layer: LayerId,
input: InteractiveInput,
) -> Option<PersistableSelection> {
if input.cancel { if input.cancel {
self.selection = self.original_selection.clone(); self.selection = self.original_selection.clone();
return Some(self.selection.clone()); return;
} }
if input.release && input.pointer == self.origin { if input.release && input.pointer == self.origin {
@ -76,8 +71,8 @@ impl SelectionInteractor {
.xor(std::iter::once(component_selector)); .xor(std::iter::once(component_selector));
} }
self.selection = selection.clone(); self.selection = selection;
return Some(selection); return;
} }
let contain = if input.pointer.x >= self.origin.x { let contain = if input.pointer.x >= self.origin.x {
@ -89,9 +84,7 @@ impl SelectionInteractor {
let options = DragSelectionOptions::new(self.combine.clone(), contain); let options = DragSelectionOptions::new(self.combine.clone(), contain);
let mut drag_selection_interactor = let mut drag_selection_interactor =
DragSelectionInteractor::new(self.origin, self.original_selection.clone(), options); DragSelectionInteractor::new(self.origin, self.original_selection.clone(), options);
let selection = drag_selection_interactor.update(board, input)?; drag_selection_interactor.update(board, input);
self.selection = drag_selection_interactor.selection().clone();
self.selection = selection.clone();
Some(selection)
} }
} }

View File

@ -8,6 +8,7 @@ impl Board {
pub fn delete_net_free_primitives(&mut self, selection: NetSelection) { pub fn delete_net_free_primitives(&mut self, selection: NetSelection) {
for joint_id in self for joint_id in self
.resolve_net_joints(selection.clone()) .resolve_net_joints(selection.clone())
.filter(|&joint_id| self.layout.joint(joint_id).spec.pin.is_none())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.clone() .clone()
{ {
@ -16,6 +17,7 @@ impl Board {
for segment_id in self for segment_id in self
.resolve_net_segments(selection.clone()) .resolve_net_segments(selection.clone())
.filter(|&segment_id| self.layout.segment(segment_id).spec.pin.is_none())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.clone() .clone()
{ {
@ -24,6 +26,7 @@ impl Board {
for via_id in self for via_id in self
.resolve_net_vias(selection.clone()) .resolve_net_vias(selection.clone())
.filter(|&via_id| self.layout.via(via_id).spec.pin.is_none())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.clone() .clone()
{ {
@ -32,6 +35,7 @@ impl Board {
for polygon_id in self for polygon_id in self
.resolve_net_polygons(selection.clone()) .resolve_net_polygons(selection.clone())
.filter(|&polygon_id| self.layout.polygon(polygon_id).pin.is_none())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.clone() .clone()
{ {

View File

@ -12,6 +12,7 @@ mod pathfinder;
mod ratsnest; mod ratsnest;
mod router; mod router;
mod specctra; mod specctra;
mod workspace;
pub use crate::autorouter::Autorouter; pub use crate::autorouter::Autorouter;
pub use crate::board::Board; pub use crate::board::Board;
@ -19,8 +20,8 @@ pub use crate::board::LayerDesc;
pub use crate::board::LayerSide; pub use crate::board::LayerSide;
pub use crate::board::LayerType; pub use crate::board::LayerType;
pub use crate::board::interactors::{ pub use crate::board::interactors::{
DragSelectionInteractor, DragSelectionOptions, InteractiveInput, SelectionCombineMode, DragSelectionInteractor, DragSelectionOptions, InteractiveInput, MasterInteractor,
SelectionContainMode, SelectionInteractor, SelectionCombineMode, SelectionContainMode, SelectionInteractor,
}; };
pub use crate::board::selections; pub use crate::board::selections;
pub use crate::layout::LayerId; pub use crate::layout::LayerId;
@ -29,3 +30,4 @@ pub use crate::layout::compounds::{Pin, PinId};
pub use crate::layout::primitives; pub use crate::layout::primitives;
pub use crate::math::{Rect2, Rect3, Vector2, Vector3}; pub use crate::math::{Rect2, Rect3, Vector2, Vector3};
pub use crate::ratsnest::{Ratline, Ratsnest}; pub use crate::ratsnest::{Ratline, Ratsnest};
pub use crate::workspace::{AutorouterWorkspace, BoardWorkspace, Workspace};

71
topola/src/workspace.rs Normal file
View File

@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2026 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{Autorouter, Board, selections::PersistableSelection};
pub enum Workspace {
Board(BoardWorkspace),
Autorouter(AutorouterWorkspace),
}
impl Workspace {
pub fn new_board(board: Board) -> Self {
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()
}
}
}
}
pub struct BoardWorkspace {
pub board: Board,
pub selection: PersistableSelection,
}
impl BoardWorkspace {
pub fn new(board: Board) -> Self {
Self {
board,
selection: PersistableSelection::new(),
}
}
}
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(),
}
}
}