diff --git a/topola-egui/src/display.rs b/topola-egui/src/display.rs index 5435a18..daaaccf 100644 --- a/topola-egui/src/display.rs +++ b/topola-egui/src/display.rs @@ -3,8 +3,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::{viewport::Viewport, workspace::GuiWorkspace}; +use topola::Workspace; use topola::primitives::{Joint, Polygon, Segment, Via}; -use topola::{LayerId, Workspace}; pub struct Display {} @@ -102,8 +102,8 @@ impl Display { board.pins_contain_via(&workspace.workspace.selection().pins, via_id); let net_selected = board.nets_contain_via(&workspace.workspace.selection().nets, via_id); - let component_selected = - board.components_contain_via(&workspace.workspace.selection().components, via_id); + let component_selected = board + .components_contain_via(&workspace.workspace.selection().components, via_id); self.paint_via( ctx, ui, diff --git a/topola-egui/src/viewport.rs b/topola-egui/src/viewport.rs index d8986ea..d975ec8 100644 --- a/topola-egui/src/viewport.rs +++ b/topola-egui/src/viewport.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use egui::Pos2; -use topola::{InteractiveInput, MasterInteractor, Vector2, Workspace}; +use topola::{MasterInteractor, Vector2, Workspace}; use crate::{display::Display, workspace::GuiWorkspace}; @@ -51,7 +51,16 @@ impl Viewport { Self::fit_to_rect_in_scene(viewport_rect, scene_rect, zoom_range.into()); if let Some(workspace) = workspace { - if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { + 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; } @@ -75,7 +84,6 @@ impl Viewport { if primary_pressed && response.hovered() { self.master_interactor = Some(MasterInteractor::new( - None, workspace.workspace.selection().clone(), )); } @@ -86,16 +94,16 @@ impl Viewport { Workspace::Board(workspace) => &mut workspace.board, Workspace::Autorouter(_) => panic!("expected board workspace"), }; - interactor.update( + interactor.hold( board, workspace.appearance_panel.active, - InteractiveInput::new(pointer_on_scene, false, false, false), + pointer_on_scene, ); - if let Some(selection_interactor) = - interactor.selection_interactor().as_ref() + if let Some(select_interactor) = + interactor.select_interactor().as_ref() { - let origin = *selection_interactor.origin(); + 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, @@ -133,19 +141,19 @@ impl Viewport { if let Some(mut interactor) = self.master_interactor.take() { let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| { interactor - .selection_interactor() + .select_interactor() .as_ref() - .map(|selection_interactor| *selection_interactor.origin()) + .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.update( + interactor.release( board, workspace.appearance_panel.active, - InteractiveInput::new(pointer_for_scene, true, false, false), + pointer_for_scene, ); *workspace.workspace.selection_mut() = interactor.selection().clone(); @@ -153,19 +161,13 @@ impl Viewport { } 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()); + 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.update( - board, - workspace.appearance_panel.active, - InteractiveInput::new(pointer_for_scene, false, true, false), - ); + interactor.delete(board); *workspace.workspace.selection_mut() = interactor.selection().clone(); } diff --git a/topola/src/board/interactors/drag_move.rs b/topola/src/board/interactors/drag_move.rs new file mode 100644 index 0000000..0467508 --- /dev/null +++ b/topola/src/board/interactors/drag_move.rs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use derive_getters::Getters; +use derive_more::Constructor; +use undoredo::DiscardDelta; + +use crate::{Board, LayerId, Vector2, selections::ComponentSelection}; + +#[derive(Clone, Constructor, Debug, Eq, Getters, PartialEq)] +pub struct DragMoveInteractor { + origin: Vector2, + layer: LayerId, + selection: ComponentSelection, +} + +impl DragMoveInteractor { + pub fn abort(&mut self, board: &mut Board) { + board.discard_delta(); + } + + pub fn hold(&mut self, board: &mut Board, pointer: Vector2) { + board.discard_delta(); + + board.move_components_by(self.selection.clone(), pointer - self.origin); + } + + pub fn release(&mut self, board: &mut Board, pointer: Vector2) { + self.hold(board, pointer); + } +} diff --git a/topola/src/board/interactors/drag_selection.rs b/topola/src/board/interactors/drag_select.rs similarity index 91% rename from topola/src/board/interactors/drag_selection.rs rename to topola/src/board/interactors/drag_select.rs index 7ebdc69..d7d5c11 100644 --- a/topola/src/board/interactors/drag_selection.rs +++ b/topola/src/board/interactors/drag_select.rs @@ -10,33 +10,33 @@ use crate::{ Rect3, Vector2, Vector3, board::{ Board, - interactors::{InteractiveInput, SelectionCombineMode, SelectionContainMode}, + interactors::{SelectionCombineMode, SelectionContainMode}, selections::PersistableSelection, }, layout::LayerId, }; -#[derive(Clone, Constructor, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct DragSelectionOptions { +#[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub struct DragSelectOptions { combine: SelectionCombineMode, contain: SelectionContainMode, } #[derive(Clone, Debug, Eq, Getters, PartialEq)] -pub struct DragSelectionInteractor { +pub struct DragSelectInteractor { origin: Vector2, layer: LayerId, original_selection: PersistableSelection, selection: PersistableSelection, - options: DragSelectionOptions, + options: DragSelectOptions, } -impl DragSelectionInteractor { +impl DragSelectInteractor { pub fn new( origin: Vector2, layer: LayerId, original_selection: PersistableSelection, - options: DragSelectionOptions, + options: DragSelectOptions, ) -> Self { Self { origin, @@ -47,17 +47,16 @@ impl DragSelectionInteractor { } } - pub fn update(&mut self, board: &Board, input: InteractiveInput) { - if input.cancel { - self.selection = self.original_selection.clone(); - return; - } + pub fn abort(&mut self) { + self.selection = self.original_selection.clone(); + } + pub fn hold(&mut self, board: &Board, pointer: Vector2) { self.selection = PersistableSelection::new(); let rect = Rect3::new( Vector3::new(self.origin.x, self.origin.y, self.layer.index() as i64), - Vector3::new(input.pointer.x, input.pointer.y, self.layer.index() as i64), + Vector3::new(pointer.x, pointer.y, self.layer.index() as i64), ); let all_belong_to_pins = match self.options.contain { diff --git a/topola/src/board/interactors/master.rs b/topola/src/board/interactors/master.rs index ede432f..3c0750c 100644 --- a/topola/src/board/interactors/master.rs +++ b/topola/src/board/interactors/master.rs @@ -3,45 +3,85 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use derive_getters::Getters; -use derive_more::Constructor; use crate::{ - InteractiveInput, + Vector2, board::{ Board, - interactors::{SelectionCombineMode, SelectionInteractor}, + interactors::{DragMoveInteractor, SelectInteractor, SelectionCombineMode}, selections::PersistableSelection, }, layout::LayerId, }; -#[derive(Clone, Constructor, Debug, Eq, Getters, PartialEq)] +#[derive(Clone, Debug, Eq, Getters, PartialEq)] pub struct MasterInteractor { - selection_interactor: Option, + select_interactor: Option, + drag_move_interactor: Option, selection: PersistableSelection, } impl MasterInteractor { - pub fn update(&mut self, board: &mut Board, layer: LayerId, input: InteractiveInput) { - if input.delete { - board.delete_net_free_primitives(self.selection.nets.clone()); + pub fn new(selection: PersistableSelection) -> Self { + Self { + select_interactor: None, + drag_move_interactor: None, + selection, } + } - if self.selection_interactor.is_none() { - self.selection_interactor = Some(SelectionInteractor::new( - input.pointer, + pub fn delete(&mut self, board: &mut Board) { + board.delete_net_free_primitives(self.selection.nets.clone()); + } + + pub fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { + if self.select_interactor.is_none() && self.drag_move_interactor.is_none() { + /*if board.selected_components_contain_point(&self.selection.components, input.pointer) { + self.drag_move_interactor = Some(DragMoveInteractor::new( + input.pointer, + layer, + self.selection.components.clone(), + )); + } else {*/ + self.select_interactor = Some(SelectInteractor::new( + pointer, self.selection.clone(), SelectionCombineMode::Replace, )); + //} } - if let Some(selection_interactor) = self.selection_interactor.as_mut() { - selection_interactor.update(board, layer, input.clone()); - self.selection = selection_interactor.selection().clone(); - } - - if input.release || input.cancel { - self.selection_interactor = None; + if let Some(drag_move_interactor) = self.drag_move_interactor.as_mut() { + drag_move_interactor.hold(board, pointer); + } else if let Some(select_interactor) = self.select_interactor.as_mut() { + select_interactor.hold(board, layer, pointer); + self.selection = select_interactor.selection().clone(); } } + + pub 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, pointer); + } else if let Some(select_interactor) = self.select_interactor.as_mut() { + select_interactor.release(board, layer, pointer); + self.selection = select_interactor.selection().clone(); + } + + self.select_interactor = None; + self.drag_move_interactor = None; + } + + pub fn abort(&mut self, board: &mut Board) { + if let Some(drag_move_interactor) = self.drag_move_interactor.as_mut() { + drag_move_interactor.abort(board); + } + + if let Some(select_interactor) = self.select_interactor.as_mut() { + select_interactor.abort(); + self.selection = select_interactor.original_selection().clone(); + } + + self.select_interactor = None; + self.drag_move_interactor = None; + } } diff --git a/topola/src/board/interactors/mod.rs b/topola/src/board/interactors/mod.rs index e9b80d7..0b8705b 100644 --- a/topola/src/board/interactors/mod.rs +++ b/topola/src/board/interactors/mod.rs @@ -2,26 +2,17 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -mod drag_selection; +mod drag_move; +mod drag_select; mod master; -mod selection; +mod select; -use derive_more::Constructor; -pub use drag_selection::{DragSelectionInteractor, DragSelectionOptions}; +pub use drag_move::DragMoveInteractor; +pub use drag_select::{DragSelectInteractor, DragSelectOptions}; pub use master::MasterInteractor; -pub use selection::SelectionInteractor; +pub use select::SelectInteractor; use serde::{Deserialize, Serialize}; -use crate::Vector2; - -#[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] -pub struct InteractiveInput { - pointer: Vector2, - release: bool, - delete: bool, - cancel: bool, -} - #[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] pub enum SelectionCombineMode { Replace, diff --git a/topola/src/board/interactors/selection.rs b/topola/src/board/interactors/select.rs similarity index 64% rename from topola/src/board/interactors/selection.rs rename to topola/src/board/interactors/select.rs index 30d8daa..e2a5692 100644 --- a/topola/src/board/interactors/selection.rs +++ b/topola/src/board/interactors/select.rs @@ -9,8 +9,7 @@ use crate::{ board::{ Board, interactors::{ - DragSelectionInteractor, DragSelectionOptions, InteractiveInput, SelectionCombineMode, - SelectionContainMode, + DragSelectInteractor, DragSelectOptions, SelectionCombineMode, SelectionContainMode, }, selections::PersistableSelection, }, @@ -18,14 +17,14 @@ use crate::{ }; #[derive(Clone, Debug, Eq, Getters, PartialEq)] -pub struct SelectionInteractor { +pub struct SelectInteractor { origin: Vector2, original_selection: PersistableSelection, selection: PersistableSelection, combine: SelectionCombineMode, } -impl SelectionInteractor { +impl SelectInteractor { pub fn new( origin: Vector2, original_selection: PersistableSelection, @@ -39,20 +38,35 @@ impl SelectionInteractor { } } - pub fn update(&mut self, board: &Board, layer: LayerId, input: InteractiveInput) { - if input.cancel { - self.selection = self.original_selection.clone(); - return; - } + pub fn abort(&mut self) { + self.selection = self.original_selection.clone(); + } - if input.release && input.pointer == self.origin { + pub fn hold(&mut self, board: &Board, layer: LayerId, pointer: Vector2) { + let contain = if pointer.x >= self.origin.x { + SelectionContainMode::Window + } else { + SelectionContainMode::Crossing + }; + + let options = DragSelectOptions::new(self.combine.clone(), contain); + let mut drag_selection_interactor = + DragSelectInteractor::new(self.origin, layer, self.original_selection.clone(), options); + + drag_selection_interactor.hold(board, pointer); + self.selection = drag_selection_interactor.selection().clone(); + } + + pub fn release(&mut self, board: &Board, layer: LayerId, pointer: Vector2) { + if pointer == self.origin { let mut selection = self.original_selection.clone(); - let point = Vector3::new(input.pointer.x, input.pointer.y, layer.index() as i64); + let point = Vector3::new(pointer.x, pointer.y, layer.index() as i64); // Pins have intentional precedence over nets and components. if let Some(pin_selector) = board.locate_pins_prefer_layer_at_point(point).next() { selection.pins.xor(std::iter::once(pin_selector)); - } else if let Some(net_selector) = board.locate_nets_prefer_layer_at_point(point).next() { + } else if let Some(net_selector) = board.locate_nets_prefer_layer_at_point(point).next() + { selection.nets.xor(std::iter::once(net_selector)); } else if let Some(component_selector) = board.locate_components_prefer_layer_at_point(point).next() @@ -66,21 +80,6 @@ impl SelectionInteractor { return; } - let contain = if input.pointer.x >= self.origin.x { - SelectionContainMode::Window - } else { - SelectionContainMode::Crossing - }; - - let options = DragSelectionOptions::new(self.combine.clone(), contain); - let mut drag_selection_interactor = DragSelectionInteractor::new( - self.origin, - layer, - self.original_selection.clone(), - options, - ); - - drag_selection_interactor.update(board, input); - self.selection = drag_selection_interactor.selection().clone(); + self.hold(board, layer, pointer); } } diff --git a/topola/src/lib.rs b/topola/src/lib.rs index 78731df..b600261 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -22,8 +22,8 @@ pub use crate::board::LayerDesc; pub use crate::board::LayerSide; pub use crate::board::LayerType; pub use crate::board::interactors::{ - DragSelectionInteractor, DragSelectionOptions, InteractiveInput, MasterInteractor, - SelectionCombineMode, SelectionContainMode, SelectionInteractor, + DragSelectInteractor, DragSelectOptions, MasterInteractor, SelectInteractor, + SelectionCombineMode, SelectionContainMode, }; pub use crate::board::selections; pub use crate::layout::LayerId;