diff --git a/topola-egui/src/app.rs b/topola-egui/src/app.rs index a4bbb3e..079f5f6 100644 --- a/topola-egui/src/app.rs +++ b/topola-egui/src/app.rs @@ -9,7 +9,7 @@ use topola::board::Board; use unic_langid::langid; use crate::{ - menu_bar::MenuBar, translator::Translator, viewport::Viewport, workspace::GuiWorkspace, + controller::Controller, menu_bar::MenuBar, translator::Translator, viewport::Viewport, }; pub struct App { @@ -22,7 +22,7 @@ pub struct App { menu_bar: MenuBar, viewport: Viewport, - workspace: Option, + controller: Option, } impl Default for App { @@ -32,7 +32,7 @@ impl Default for App { content_channel: channel(), menu_bar: MenuBar::new(), viewport: Viewport::new(), - workspace: None, + controller: None, } } } @@ -52,7 +52,7 @@ impl App { fn update_state(&mut self) { if let Ok(data) = self.content_channel.1.try_recv() { - self.workspace = Some(GuiWorkspace::new( + self.controller = Some(Controller::new( Board::from_specctra(data.unwrap()), &self.translator, )); @@ -130,15 +130,15 @@ impl eframe::App for App { self.update_state(); - if let Some(ref mut workspace) = self.workspace { - workspace.update_appearance_panel(ctx); + if let Some(ref mut controller) = self.controller { + controller.update_appearance_panel(ctx); } self.viewport.update( &self.translator, ctx, &self.menu_bar, - self.workspace.as_mut(), + self.controller.as_mut(), ); self.update_locale(); diff --git a/topola-egui/src/controller.rs b/topola-egui/src/controller.rs new file mode 100644 index 0000000..9fa2206 --- /dev/null +++ b/topola-egui/src/controller.rs @@ -0,0 +1,187 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::{ops::ControlFlow, time::Instant}; + +use topola::{Interactor, MasterInteractor, Vector2, Workspace, board::Board}; + +use crate::{layers_panel::LayersPanel, translator::Translator}; + +pub struct Controller { + pub workspace: Workspace, + pub appearance_panel: LayersPanel, + pub master_interactor: Option, + pub dt_accum: f64, +} + +impl Controller { + pub fn new(board: Board, tr: &Translator) -> Self { + let appearance_panel = LayersPanel::new(&board); + + Self { + workspace: Workspace::new_board(board), + appearance_panel, + master_interactor: None, + dt_accum: 0.0, + } + } + + pub fn advance_state_by_dt( + &mut self, + tr: &Translator, + step_rate: Option, + dt: f64, + ) -> bool { + let instant = Instant::now(); + + if step_rate.is_some() { + self.dt_accum += dt; + } + + while step_rate.is_none_or(|step_rate| self.dt_accum >= 1.0 / step_rate) { + if let Some(step_rate) = step_rate { + self.dt_accum -= 1.0 / step_rate; + } + + if let ControlFlow::Break(()) = self.step(tr) { + return true; + } + + // Hard limit: never spend more time on advancing state than the + // duration of last frame to prevent stuttering. + // Of course, this does not safeguard against infinite loops. + if instant.elapsed().as_secs_f64() >= dt { + return false; + } + } + + true + } + + pub fn step(&mut self, tr: &Translator) -> ControlFlow<()> { + self.master_interactor + .as_mut() + .map(|master_interactor| master_interactor.step(self.workspace.board_mut())); + ControlFlow::Continue(()) + } + + pub fn update_appearance_panel(&mut self, ctx: &egui::Context) { + let Self { + workspace, + appearance_panel, + master_interactor: _, + dt_accum, + } = self; + + appearance_panel.update(ctx, workspace.board_mut()); + } + + pub fn handle_input( + &mut self, + tr: &Translator, + ctx: &egui::Context, + step_rate: Option, + scene_to_viewport: egui::emath::TSTransform, + scene_hovered: bool, + ui: &mut egui::Ui, + ) { + if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { + if let (Some(interactor), Workspace::Board(workspace)) = + (&mut self.master_interactor, &mut self.workspace) + { + interactor.abort(&mut workspace.board); + *self.workspace.selection_mut() = interactor.selection().clone(); + } + self.master_interactor = None; + } + + let primary_pressed = ctx.input(|i| i.pointer.button_pressed(egui::PointerButton::Primary)); + let primary_down = ctx.input(|i| i.pointer.button_down(egui::PointerButton::Primary)); + let primary_released = + 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> = None; + + if let Some(pointer_viewport_pos) = ctx.input(|i| i.pointer.interact_pos()) { + let pointer_on_scene_pos = scene_to_viewport.inverse() * pointer_viewport_pos; + let pointer_on_scene = + Vector2::new(pointer_on_scene_pos.x as i64, pointer_on_scene_pos.y as i64); + maybe_pointer_on_scene = Some(pointer_on_scene); + + if primary_pressed && scene_hovered { + self.master_interactor = + Some(MasterInteractor::new(self.workspace.selection().clone())); + } + + if let (Some(interactor), Workspace::Board(workspace)) = + (&mut self.master_interactor, &mut self.workspace) + { + if primary_down { + interactor.hold( + &mut workspace.board, + self.appearance_panel.active, + pointer_on_scene, + ); + + if let Some(select_interactor) = 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) + }; + + 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, + ); + } + } + } + } + + if primary_released { + if let Some(mut interactor) = self.master_interactor.take() { + let active = self.appearance_panel.active; + let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| { + interactor + .select_interactor() + .as_ref() + .map(|select_interactor| *select_interactor.origin()) + .unwrap_or(Vector2::new(0, 0)) + }); + if let Workspace::Board(workspace) = &mut self.workspace { + interactor.release(&mut workspace.board, active, pointer_for_scene); + *self.workspace.selection_mut() = interactor.selection().clone(); + } + } + } + + if delete_pressed { + let mut interactor = MasterInteractor::new(self.workspace.selection().clone()); + if let Workspace::Board(workspace) = &mut self.workspace { + interactor.delete(&mut workspace.board); + *self.workspace.selection_mut() = interactor.selection().clone(); + } + } + } +} diff --git a/topola-egui/src/display.rs b/topola-egui/src/display.rs index 05614fb..2d3e57b 100644 --- a/topola-egui/src/display.rs +++ b/topola-egui/src/display.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::{viewport::Viewport, workspace::GuiWorkspace}; +use crate::{controller::Controller, viewport::Viewport}; use topola::{ Orientation, Vector2, Workspace, layout::primitives::{Joint, Polygon, Segment, Via}, @@ -21,7 +21,7 @@ impl Display { ui: &egui::Ui, //menu_bar: &MenuBar, viewport: &Viewport, - workspace: &GuiWorkspace, + workspace: &Controller, ) { self.display_layout(ctx, ui, /*menu_bar,*/ viewport, workspace); self.display_repulsions(ui, viewport, workspace); @@ -37,7 +37,7 @@ impl Display { ui: &egui::Ui, //menu_bar: &MenuBar, viewport: &Viewport, - workspace: &GuiWorkspace, + workspace: &Controller, ) { let board = workspace.workspace.board(); let layout = board.layout(); @@ -160,7 +160,7 @@ impl Display { ); } - fn display_repulsions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &GuiWorkspace) { + fn display_repulsions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) { let board = workspace.workspace.board(); let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::YELLOW); @@ -201,12 +201,7 @@ impl Display { } } - fn display_attractions( - &mut self, - ui: &egui::Ui, - viewport: &Viewport, - workspace: &GuiWorkspace, - ) { + fn display_attractions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) { let board = workspace.workspace.board(); let layout = board.layout(); let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::BLUE); @@ -336,7 +331,7 @@ impl Display { ctx: &egui::Context, ui: &egui::Ui, viewport: &Viewport, - workspace: &GuiWorkspace, + workspace: &Controller, ) { let board = workspace.workspace.board(); let layout = board.layout(); @@ -413,7 +408,7 @@ impl Display { ctx: &egui::Context, ui: &egui::Ui, viewport: &Viewport, - workspace: &GuiWorkspace, + workspace: &Controller, ) { let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else { return; @@ -483,7 +478,7 @@ impl Display { _ctx: &egui::Context, ui: &egui::Ui, _viewport: &Viewport, - workspace: &GuiWorkspace, + workspace: &Controller, ) { let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else { return; diff --git a/topola-egui/src/main.rs b/topola-egui/src/main.rs index 2843e72..157008f 100644 --- a/topola-egui/src/main.rs +++ b/topola-egui/src/main.rs @@ -7,12 +7,12 @@ mod action; mod actions; mod app; +mod controller; mod display; mod layers_panel; mod menu_bar; mod translator; mod viewport; -mod workspace; use crate::app::App; diff --git a/topola-egui/src/viewport.rs b/topola-egui/src/viewport.rs index 7b386f9..c509a91 100644 --- a/topola-egui/src/viewport.rs +++ b/topola-egui/src/viewport.rs @@ -3,15 +3,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use egui::Pos2; -use topola::{Interactor, MasterInteractor, Vector2, Workspace}; -use crate::{display::Display, menu_bar::MenuBar, translator::Translator, workspace::GuiWorkspace}; +use crate::{controller::Controller, display::Display, menu_bar::MenuBar, translator::Translator}; pub struct Viewport { pub scene_rect: egui::Rect, pub ref_scene_rect: egui::Rect, pub scheduled_zoom_to_fit: bool, - master_interactor: Option, } impl Viewport { @@ -20,7 +18,6 @@ impl Viewport { 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, - master_interactor: None, } } @@ -29,7 +26,7 @@ impl Viewport { tr: &Translator, ctx: &egui::Context, menu_bar: &MenuBar, - workspace: Option<&mut GuiWorkspace>, + controller: Option<&mut Controller>, ) { egui::CentralPanel::default().show(ctx, |ui| { egui::Frame::canvas(ui.style()).show(ui, |ui| { @@ -44,9 +41,9 @@ impl Viewport { .zoom_range(zoom_range.clone()) .drag_pan_buttons(egui::DragPanButtons::MIDDLE) .show(ui, &mut scene_rect, |ui| { - if let Some(ref workspace) = workspace { + if let Some(ref controller) = controller { let mut display = Display::new(); - display.update(ctx, ui, &self, workspace); + display.update(ctx, ui, &self, controller); } }) .response; @@ -56,143 +53,29 @@ impl Viewport { let scene_to_viewport = Self::fit_to_rect_in_scene(viewport_rect, scene_rect, zoom_range.into()); - if let Some(workspace) = workspace { - workspace.advance_state_by_dt( + if let Some(controller) = controller { + controller.advance_state_by_dt( tr, menu_bar.fix_step_rate.then_some(menu_bar.step_rate), ctx.input(|i| { if i.stable_dt <= i.predicted_dt { i.stable_dt } else { - // Clamp dt to egui's predicted dt to - // additionally safeguard against stuttering. i.predicted_dt } }) as f64, ); - let escape_pressed = ctx.input(|i| i.key_pressed(egui::Key::Escape)); - if escape_pressed { - if let Some(interactor) = self.master_interactor.as_mut() { - let board = match &mut workspace.workspace { - Workspace::Board(workspace) => &mut workspace.board, - Workspace::Autorouter(_) => panic!("expected board workspace"), - }; - interactor.abort(board); - *workspace.workspace.selection_mut() = interactor.selection().clone(); - } - self.master_interactor = None; - } + controller.handle_input( + tr, + ctx, + menu_bar.fix_step_rate.then_some(menu_bar.step_rate), + scene_to_viewport, + response.hovered(), + ui, + ); - let primary_pressed = - ctx.input(|i| i.pointer.button_pressed(egui::PointerButton::Primary)); - let primary_down = - ctx.input(|i| i.pointer.button_down(egui::PointerButton::Primary)); - let primary_released = - 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> = None; - - if let Some(pointer_viewport_pos) = ctx.input(|i| i.pointer.interact_pos()) { - let pointer_on_scene_pos = - scene_to_viewport.inverse() * pointer_viewport_pos; - let pointer_on_scene = Vector2::new( - pointer_on_scene_pos.x as i64, - pointer_on_scene_pos.y as i64, - ); - maybe_pointer_on_scene = Some(pointer_on_scene); - - if primary_pressed && response.hovered() { - self.master_interactor = Some(MasterInteractor::new( - workspace.workspace.selection().clone(), - )); - } - - if let Some(interactor) = self.master_interactor.as_mut() { - if primary_down { - let board = match &mut workspace.workspace { - Workspace::Board(workspace) => &mut workspace.board, - Workspace::Autorouter(_) => panic!("expected board workspace"), - }; - interactor.hold( - board, - workspace.appearance_panel.active, - pointer_on_scene, - ); - - if let Some(select_interactor) = - 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) - }; - - 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, - ); - } - } - } - } - - if primary_released { - if let Some(mut interactor) = self.master_interactor.take() { - let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| { - interactor - .select_interactor() - .as_ref() - .map(|select_interactor| *select_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.release( - board, - workspace.appearance_panel.active, - pointer_for_scene, - ); - - *workspace.workspace.selection_mut() = interactor.selection().clone(); - } - } - - if delete_pressed { - let mut interactor = - MasterInteractor::new(workspace.workspace.selection().clone()); - let board = match &mut workspace.workspace { - Workspace::Board(workspace) => &mut workspace.board, - Workspace::Autorouter(_) => panic!("expected board workspace"), - }; - interactor.delete(board); - *workspace.workspace.selection_mut() = interactor.selection().clone(); - } - - self.zoom_to_fit_if_scheduled(workspace); + self.zoom_to_fit_if_scheduled(controller); } }) }); @@ -210,7 +93,8 @@ impl Viewport { rect_in_scene: egui::Rect, zoom_range: egui::Rangef, ) -> egui::emath::TSTransform { - // Compute the scale factor to fit the bounding rectangle into the available screen size: + // Compute the scale factor to fit the bounding rect into the available + // screen size: let scale = rect_in_viewport.size() / rect_in_scene.size(); // Use the smaller of the two scales to ensure the whole rectangle fits on the screen: @@ -228,24 +112,24 @@ impl Viewport { * egui::emath::TSTransform::from_scaling(scale) } - fn zoom_to_fit_if_scheduled(&mut self, workspace: &GuiWorkspace) { + fn zoom_to_fit_if_scheduled(&mut self, controller: &Controller) { if self.scheduled_zoom_to_fit { - self.scene_rect = Self::boundary_bounding_box(workspace); + self.scene_rect = Self::boundary_bounding_box(controller); self.ref_scene_rect = self.scene_rect.clone(); } self.scheduled_zoom_to_fit = false; } - fn boundary_bounding_box(workspace: &GuiWorkspace) -> egui::Rect { - let first = workspace.workspace.board().layout().boundary()[0]; + fn boundary_bounding_box(controller: &Controller) -> egui::Rect { + let first = controller.workspace.board().layout().boundary()[0]; let mut min_x = first[0]; let mut max_x = first[0]; let mut min_y = first[1]; let mut max_y = first[1]; - for point in workspace.workspace.board().layout().boundary()[1..].iter() { + for point in controller.workspace.board().layout().boundary()[1..].iter() { if point[0] < min_x { min_x = point[0]; } diff --git a/topola-egui/src/workspace.rs b/topola-egui/src/workspace.rs deleted file mode 100644 index 040bc2c..0000000 --- a/topola-egui/src/workspace.rs +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Topola contributors -// -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::{ops::ControlFlow, time::Instant}; - -use topola::{Workspace, board::Board}; - -use crate::{layers_panel::LayersPanel, translator::Translator}; - -pub struct GuiWorkspace { - pub workspace: Workspace, - pub appearance_panel: LayersPanel, - pub dt_accum: f64, -} - -impl GuiWorkspace { - pub fn new(board: Board, tr: &Translator) -> Self { - let appearance_panel = LayersPanel::new(&board); - - Self { - workspace: Workspace::new_board(board), - appearance_panel, - dt_accum: 0.0, - } - } - - pub fn advance_state_by_dt( - &mut self, - tr: &Translator, - step_rate: Option, - dt: f64, - ) -> bool { - let instant = Instant::now(); - - if step_rate.is_some() { - self.dt_accum += dt; - } - - while step_rate.is_none_or(|step_rate| self.dt_accum >= 1.0 / step_rate) { - if let Some(step_rate) = step_rate { - self.dt_accum -= 1.0 / step_rate; - } - - if let ControlFlow::Break(()) = self.step(tr) { - return true; - } - - // Hard limit: never spend more time on advancing state than the - // duration of last frame to prevent stuttering. - // Of course, this does not safeguard against infinite loops. - if instant.elapsed().as_secs_f64() >= dt { - return false; - } - } - - true - } - - pub fn step(&mut self, tr: &Translator) -> ControlFlow<()> { - ControlFlow::Continue(()) - } - - pub fn update_appearance_panel(&mut self, ctx: &egui::Context) { - let Self { - workspace, - appearance_panel, - dt_accum, - } = self; - - appearance_panel.update(ctx, workspace.board()); - } -} diff --git a/topola/src/autoplacer/interactors/master.rs b/topola/src/autoplacer/interactors/master.rs new file mode 100644 index 0000000..5154153 --- /dev/null +++ b/topola/src/autoplacer/interactors/master.rs @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{ + autoplacer::{Autoplacer, AutoplacerSchedule}, + board::{ + Board, + interactors::{MasterInteractor as BoardMasterInteractor, SelectInteractor}, + selections::PersistableSelection, + }, + interactor::Interactor, + layout::LayerId, + vector::Vector2, +}; + +pub struct MasterInteractor { + board_master: BoardMasterInteractor, + autoplacer: Autoplacer, +} + +impl MasterInteractor { + pub fn new( + board: &mut Board, + selection: PersistableSelection, + schedule: AutoplacerSchedule, + ) -> Self { + Self { + board_master: BoardMasterInteractor::new(selection.clone()), + autoplacer: Autoplacer::new(board, selection.components.clone(), schedule), + } + } + + pub fn selection(&self) -> &PersistableSelection { + self.board_master.selection() + } + + pub fn select_interactor(&self) -> &Option { + self.board_master.select_interactor() + } +} + +impl Interactor for MasterInteractor { + fn step(&mut self, board: &mut Board) { + self.autoplacer.step(board); + } + + fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { + 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 abort(&mut self, board: &mut Board) { + self.board_master.abort(board); + } +} diff --git a/topola/src/autoplacer/interactors/mod.rs b/topola/src/autoplacer/interactors/mod.rs new file mode 100644 index 0000000..c29522b --- /dev/null +++ b/topola/src/autoplacer/interactors/mod.rs @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +mod master; + +pub use master::MasterInteractor; diff --git a/topola/src/autoplacer.rs b/topola/src/autoplacer/mod.rs similarity index 99% rename from topola/src/autoplacer.rs rename to topola/src/autoplacer/mod.rs index cda9d6c..89abcf0 100644 --- a/topola/src/autoplacer.rs +++ b/topola/src/autoplacer/mod.rs @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +pub mod interactors; + use rand::RngExt; use rand_distr::{Distribution, Normal}; use undoredo::{FlushDelta, ResetDelta}; diff --git a/topola/src/board/bbox.rs b/topola/src/board/bbox.rs index 44ba20d..ba0a03c 100644 --- a/topola/src/board/bbox.rs +++ b/topola/src/board/bbox.rs @@ -3,10 +3,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::{ - board::Board, - layout::compounds::ComponentId, - rect::Rect2, - selections::ComponentSelection, + board::Board, layout::compounds::ComponentId, rect::Rect2, selections::ComponentSelection, }; impl Board { diff --git a/topola/src/board/interactors/drag_move.rs b/topola/src/board/interactors/drag_move.rs index 9700d9c..7ddf543 100644 --- a/topola/src/board/interactors/drag_move.rs +++ b/topola/src/board/interactors/drag_move.rs @@ -7,10 +7,7 @@ use derive_more::Constructor; use undoredo::ResetDelta; use crate::{ - board::Board, - interactor::Interactor, - layout::LayerId, - selections::ComponentSelection, + board::Board, interactor::Interactor, layout::LayerId, selections::ComponentSelection, vector::Vector2, }; @@ -22,8 +19,6 @@ pub struct DragMoveInteractor { } impl Interactor for DragMoveInteractor { - fn delete(&mut self, _board: &mut Board) {} - fn hold(&mut self, board: &mut Board, _layer: LayerId, pointer: Vector2) { board.reset_delta(); diff --git a/topola/src/board/interactors/drag_select.rs b/topola/src/board/interactors/drag_select.rs index a6ab71c..b4b3c4d 100644 --- a/topola/src/board/interactors/drag_select.rs +++ b/topola/src/board/interactors/drag_select.rs @@ -51,8 +51,6 @@ impl DragSelectInteractor { } impl Interactor for DragSelectInteractor { - fn delete(&mut self, _board: &mut Board) {} - fn hold(&mut self, board: &mut Board, _layer: LayerId, pointer: Vector2) { self.selection = PersistableSelection::new(); diff --git a/topola/src/board/interactors/select.rs b/topola/src/board/interactors/select.rs index e4c70f2..55bb838 100644 --- a/topola/src/board/interactors/select.rs +++ b/topola/src/board/interactors/select.rs @@ -41,8 +41,6 @@ impl SelectInteractor { } impl Interactor for SelectInteractor { - fn delete(&mut self, _board: &mut Board) {} - fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { let contain = if pointer.x >= self.origin.x { SelectionContainMode::Window diff --git a/topola/src/board/transforms/move_by.rs b/topola/src/board/transforms/move_by.rs index 22aaf32..6660245 100644 --- a/topola/src/board/transforms/move_by.rs +++ b/topola/src/board/transforms/move_by.rs @@ -3,10 +3,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::{ - board::Board, - layout::compounds::ComponentId, - selections::ComponentSelection, - vector::Vector2, + board::Board, layout::compounds::ComponentId, selections::ComponentSelection, vector::Vector2, }; impl Board { diff --git a/topola/src/compass.rs b/topola/src/compass.rs index 75b396d..5bbbec9 100644 --- a/topola/src/compass.rs +++ b/topola/src/compass.rs @@ -22,20 +22,12 @@ pub trait CompassDirection: Copy + PartialEq + Into> { let zero = T::zero(); if axis.y.is_zero() { - let x = if axis.x < zero { - -vector.x - } else { - vector.x - }; + let x = if axis.x < zero { -vector.x } else { vector.x }; return Vector2::new(x, zero); } if axis.x.is_zero() { - let y = if axis.y < zero { - -vector.y - } else { - vector.y - }; + let y = if axis.y < zero { -vector.y } else { vector.y }; return Vector2::new(zero, y); } diff --git a/topola/src/interactor.rs b/topola/src/interactor.rs index 1b78345..c10acab 100644 --- a/topola/src/interactor.rs +++ b/topola/src/interactor.rs @@ -3,24 +3,22 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::{ - board::{ - Board, - interactors::SelectInteractor, - selections::PersistableSelection, - }, + board::{Board, interactors::SelectInteractor, selections::PersistableSelection}, layout::LayerId, vector::Vector2, }; pub trait Interactor { - 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 step(&mut self, board: &mut Board) {} + 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) {} } pub enum MasterInteractor { Board(crate::board::interactors::MasterInteractor), + Autoplacer(crate::autoplacer::interactors::MasterInteractor), } impl MasterInteractor { @@ -31,38 +29,51 @@ impl MasterInteractor { pub fn selection(&self) -> &PersistableSelection { match self { Self::Board(interactor) => interactor.selection(), + Self::Autoplacer(interactor) => interactor.selection(), } } pub fn select_interactor(&self) -> &Option { match self { Self::Board(interactor) => interactor.select_interactor(), + Self::Autoplacer(interactor) => interactor.select_interactor(), } } } impl Interactor for MasterInteractor { + fn step(&mut self, board: &mut Board) { + match self { + Self::Board(interactor) => interactor.step(board), + Self::Autoplacer(interactor) => interactor.step(board), + } + } + fn delete(&mut self, board: &mut Board) { match self { Self::Board(interactor) => interactor.delete(board), + Self::Autoplacer(interactor) => interactor.delete(board), } } fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { match self { Self::Board(interactor) => interactor.hold(board, layer, pointer), + Self::Autoplacer(interactor) => interactor.hold(board, layer, pointer), } } fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { match self { Self::Board(interactor) => interactor.release(board, layer, pointer), + Self::Autoplacer(interactor) => interactor.release(board, layer, pointer), } } fn abort(&mut self, board: &mut Board) { match self { Self::Board(interactor) => interactor.abort(board), + Self::Autoplacer(interactor) => interactor.abort(board), } } } diff --git a/topola/src/layout/infringement.rs b/topola/src/layout/infringement.rs index 67a7eb3..cd883ca 100644 --- a/topola/src/layout/infringement.rs +++ b/topola/src/layout/infringement.rs @@ -176,12 +176,10 @@ impl Layout { .iter() .copied() .flat_map(|joint_id| self.locate_joint_infringements(joint_id).map(Into::into)) - .chain( - pin.segments.iter().copied().flat_map(|segment_id| { - self.locate_segment_infringements(segment_id) - .map(Into::into) - }), - ) + .chain(pin.segments.iter().copied().flat_map(|segment_id| { + self.locate_segment_infringements(segment_id) + .map(Into::into) + })) .chain( pin.vias .iter() diff --git a/topola/src/layout/overlap.rs b/topola/src/layout/overlap.rs index 148ae38..68feaa1 100644 --- a/topola/src/layout/overlap.rs +++ b/topola/src/layout/overlap.rs @@ -121,11 +121,7 @@ impl Layout { infringer_bbox.intersection(infringee_bbox) } - pub fn via_via_rect_overlap( - &self, - infringer: ViaId, - infringee: ViaId, - ) -> Option> { + pub fn via_via_rect_overlap(&self, infringer: ViaId, infringee: ViaId) -> Option> { let infringer_bbox = self.via(infringer).bbox().xy(); let infringee_bbox = self.via(infringee).bbox().xy(); diff --git a/topola/src/specctra.rs b/topola/src/specctra.rs index 76d6da7..6cd1407 100644 --- a/topola/src/specctra.rs +++ b/topola/src/specctra.rs @@ -12,11 +12,11 @@ use specctra::{ use crate::{ board::{Board, LayerDesc, LayerSide, LayerType}, + layout::primitives::{JointSpec, Polygon, Segment, SegmentSpec}, layout::{ LayerId, compounds::{ComponentId, NetId, PinId, PinSpec}, }, - layout::primitives::{JointSpec, Polygon, Segment, SegmentSpec}, vector::Vector2, }; diff --git a/topola/src/workspace.rs b/topola/src/workspace.rs index 92bb04a..3dd22c5 100644 --- a/topola/src/workspace.rs +++ b/topola/src/workspace.rs @@ -44,6 +44,16 @@ impl Workspace { } } } + + 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() + }*/ + } + } } pub struct BoardWorkspace {