diff --git a/topola-egui/src/viewport.rs b/topola-egui/src/viewport.rs index b3f4295..192d82d 100644 --- a/topola-egui/src/viewport.rs +++ b/topola-egui/src/viewport.rs @@ -85,7 +85,7 @@ impl Viewport { let _ = interactor.update( workspace.autorouter.router().navmesher_board().board(), workspace.appearance_panel.active, - InteractiveInput::new(pointer_on_scene, false, false), + InteractiveInput::new(pointer_on_scene, false, false, false), ); } } @@ -98,7 +98,7 @@ impl Viewport { let _ = interactor.update( workspace.autorouter.router().navmesher_board().board(), workspace.appearance_panel.active, - InteractiveInput::new(pointer_for_scene, true, false), + InteractiveInput::new(pointer_for_scene, true, false, false), ); workspace.selection = interactor.selection().clone(); diff --git a/topola/src/board/delete.rs b/topola/src/board/delete.rs deleted file mode 100644 index e6ac40c..0000000 --- a/topola/src/board/delete.rs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Topola contributors -// -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use crate::{ - board::Board, - primitives::{JointId, PolygonId, SegmentId, ViaId}, -}; - -impl Board { - pub fn delete_joint(&mut self, joint_id: JointId) { - self.layout.delete_joint(joint_id); - } - - pub fn delete_segment(&mut self, segment_id: SegmentId) { - self.layout.delete_segment(segment_id); - } - - pub fn delete_via(&mut self, via_id: ViaId) { - self.layout.delete_via(via_id); - } - - pub fn delete_polygon(&mut self, polygon_id: PolygonId) { - self.layout.delete_polygon(polygon_id); - } -} diff --git a/topola/src/board/interactors/drag_selection.rs b/topola/src/board/interactors/drag_selection.rs index d7bc3b6..0b57053 100644 --- a/topola/src/board/interactors/drag_selection.rs +++ b/topola/src/board/interactors/drag_selection.rs @@ -15,13 +15,13 @@ use crate::{ }, }; -#[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +#[derive(Clone, Constructor, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct DragSelectionOptions { combine: SelectionCombineMode, contain: SelectionContainMode, } -#[derive(Clone, Debug, Eq, Getters, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Eq, Getters, PartialEq)] pub struct DragSelectionInteractor { origin: Vector2, original_selection: PersistableSelection, diff --git a/topola/src/board/interactors/master.rs b/topola/src/board/interactors/master.rs new file mode 100644 index 0000000..b688907 --- /dev/null +++ b/topola/src/board/interactors/master.rs @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use derive_getters::Getters; +use derive_more::Constructor; + +use crate::{ + InteractiveInput, + board::{ + Board, + interactors::{SelectionCombineMode, SelectionInteractor}, + selections::PersistableSelection, + }, + layout::LayerId, +}; + +#[derive(Clone, Constructor, Debug, Eq, Getters, PartialEq)] +pub struct MasterInteractor { + selection: PersistableSelection, + selection_interactor: Option, +} + +impl MasterInteractor { + pub fn update(&mut self, board: &mut Board, input: InteractiveInput) { + if input.delete { + board.delete_net_free_primitives(self.selection.nets.clone()); + } + + if self.selection_interactor.is_none() { + self.selection_interactor = Some(SelectionInteractor::new( + input.pointer, + self.selection.clone(), + SelectionCombineMode::Additive, + )); + } + + if let Some(selection_interactor) = self.selection_interactor.as_mut() { + if let Some(selection) = + selection_interactor.update(board, LayerId::new(0), input.clone()) + { + self.selection = selection; + } + } + + if input.release || input.cancel { + self.selection_interactor = None; + } + } +} diff --git a/topola/src/board/interactors/mod.rs b/topola/src/board/interactors/mod.rs index 54e5f9e..ccb19f7 100644 --- a/topola/src/board/interactors/mod.rs +++ b/topola/src/board/interactors/mod.rs @@ -3,31 +3,24 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 mod drag_selection; +mod master; mod selection; +use derive_more::Constructor; pub use drag_selection::{DragSelectionInteractor, DragSelectionOptions}; pub use selection::SelectionInteractor; use serde::{Deserialize, Serialize}; use crate::Vector2; -#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +#[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] pub struct InteractiveInput { pointer: Vector2, - released: bool, + release: bool, + delete: bool, cancel: bool, } -impl InteractiveInput { - pub fn new(pointer: Vector2, released: bool, cancel: bool) -> Self { - Self { - pointer, - released, - cancel, - } - } -} - #[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/selection.rs index fd86807..5b60e06 100644 --- a/topola/src/board/interactors/selection.rs +++ b/topola/src/board/interactors/selection.rs @@ -17,7 +17,7 @@ use crate::{ layout::LayerId, }; -#[derive(Clone, Debug, Eq, Getters, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Eq, Getters, PartialEq)] pub struct SelectionInteractor { origin: Vector2, original_selection: PersistableSelection, @@ -50,7 +50,7 @@ impl SelectionInteractor { return Some(self.selection.clone()); } - if input.released && input.pointer == self.origin { + if input.release && input.pointer == self.origin { let mut selection = self.original_selection.clone(); // Pins have intentional precedence over nets and components. diff --git a/topola/src/board/mod.rs b/topola/src/board/mod.rs index acdb928..671e2f9 100644 --- a/topola/src/board/mod.rs +++ b/topola/src/board/mod.rs @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -mod delete; mod insert; pub mod interactors; mod layer; diff --git a/topola/src/board/resolve.rs b/topola/src/board/resolve.rs index 8e4074a..28982cd 100644 --- a/topola/src/board/resolve.rs +++ b/topola/src/board/resolve.rs @@ -5,15 +5,91 @@ use crate::{ board::{Board, selections::ComponentSelection}, layout::compounds::ComponentId, + primitives::{JointId, PolygonId, SegmentId, ViaId}, + selections::NetSelection, }; impl Board { - pub fn resolve_components(&self, selection: ComponentSelection) -> Vec { + pub fn resolve_components( + &self, + selection: ComponentSelection, + ) -> impl Iterator { selection .0 .clone() .into_iter() .filter_map(|selector| self.component_id(&selector.component)) - .collect() + } + + pub fn resolve_net_joints(&self, selection: NetSelection) -> impl Iterator { + let mut resolved_joints = Vec::new(); + + for (index, _) in self.layout.joints().container() { + let joint_id = JointId::new(index); + + let Some(selector) = self.joint_net_selector(joint_id) else { + continue; + }; + + if selection.0.contains(&selector) { + resolved_joints.push(joint_id); + } + } + + resolved_joints.into_iter() + } + + pub fn resolve_net_segments(&self, selection: NetSelection) -> impl Iterator { + let mut resolved_segments = Vec::new(); + + for (index, _) in self.layout.segments().container() { + let segment_id = SegmentId::new(index); + + let Some(selector) = self.segment_net_selector(segment_id) else { + continue; + }; + + if selection.0.contains(&selector) { + resolved_segments.push(segment_id); + } + } + + resolved_segments.into_iter() + } + + pub fn resolve_net_vias(&self, selection: NetSelection) -> impl Iterator { + let mut resolved_vias = Vec::new(); + + for (index, _) in self.layout.vias().container() { + let via_id = ViaId::new(index); + + let Some(selector) = self.via_net_selector(via_id) else { + continue; + }; + + if selection.0.contains(&selector) { + resolved_vias.push(via_id); + } + } + + resolved_vias.into_iter() + } + + pub fn resolve_net_polygons(&self, selection: NetSelection) -> impl Iterator { + let mut resolved_polygons = Vec::new(); + + for (index, _) in self.layout.polygons().container() { + let polygon_id = PolygonId::new(index); + + let Some(selector) = self.polygon_net_selector(polygon_id) else { + continue; + }; + + if selection.0.contains(&selector) { + resolved_polygons.push(polygon_id); + } + } + + resolved_polygons.into_iter() } } diff --git a/topola/src/board/selections/component.rs b/topola/src/board/selections/component.rs index a4f47f1..38a8c7c 100644 --- a/topola/src/board/selections/component.rs +++ b/topola/src/board/selections/component.rs @@ -4,7 +4,7 @@ use std::collections::BTreeSet; -use derive_more::Constructor; +use derive_more::{Constructor, IntoIterator}; use serde::{Deserialize, Serialize}; #[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] @@ -12,7 +12,9 @@ pub struct ComponentSelector { pub component: String, } -#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Eq, IntoIterator, Ord, PartialEq, PartialOrd, Serialize, +)] pub struct ComponentSelection(pub BTreeSet); impl ComponentSelection { diff --git a/topola/src/board/selections/net.rs b/topola/src/board/selections/net.rs index 74aa80f..d34676e 100644 --- a/topola/src/board/selections/net.rs +++ b/topola/src/board/selections/net.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; +use derive_more::IntoIterator; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] @@ -17,7 +18,9 @@ impl NetSelector { } } -#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Eq, IntoIterator, Ord, PartialEq, PartialOrd, Serialize, +)] pub struct NetSelection(pub BTreeSet); impl NetSelection { diff --git a/topola/src/board/selections/pin.rs b/topola/src/board/selections/pin.rs index fd9f245..d124182 100644 --- a/topola/src/board/selections/pin.rs +++ b/topola/src/board/selections/pin.rs @@ -4,7 +4,7 @@ use std::collections::BTreeSet; -use derive_more::Constructor; +use derive_more::{Constructor, IntoIterator}; use serde::{Deserialize, Serialize}; #[derive( @@ -15,7 +15,9 @@ pub struct PinSelector { pub layer: String, } -#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Eq, IntoIterator, Ord, PartialEq, PartialOrd, Serialize, +)] pub struct PinSelection(pub BTreeSet); impl PinSelection { diff --git a/topola/src/board/transforms/delete.rs b/topola/src/board/transforms/delete.rs new file mode 100644 index 0000000..8186fff --- /dev/null +++ b/topola/src/board/transforms/delete.rs @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{Board, selections::NetSelection}; + +impl Board { + pub fn delete_net_free_primitives(&mut self, selection: NetSelection) { + for joint_id in self + .resolve_net_joints(selection.clone()) + .collect::>() + .clone() + { + self.layout.delete_joint(joint_id); + } + + for segment_id in self + .resolve_net_segments(selection.clone()) + .collect::>() + .clone() + { + self.layout.delete_segment(segment_id); + } + + for via_id in self + .resolve_net_vias(selection.clone()) + .collect::>() + .clone() + { + self.layout.delete_via(via_id); + } + + for polygon_id in self + .resolve_net_polygons(selection.clone()) + .collect::>() + .clone() + { + self.layout.delete_polygon(polygon_id); + } + } +} diff --git a/topola/src/board/transforms/mod.rs b/topola/src/board/transforms/mod.rs index 3253d71..ec97855 100644 --- a/topola/src/board/transforms/mod.rs +++ b/topola/src/board/transforms/mod.rs @@ -2,4 +2,5 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +mod delete; mod move_by; diff --git a/topola/src/board/transforms/move_by.rs b/topola/src/board/transforms/move_by.rs index ecaa573..deb370b 100644 --- a/topola/src/board/transforms/move_by.rs +++ b/topola/src/board/transforms/move_by.rs @@ -5,12 +5,11 @@ use crate::{Board, Vector2, layout::compounds::ComponentId, selections::ComponentSelection}; impl Board { - pub fn move_components_by( - &mut self, - selection: &ComponentSelection, - translation: Vector2, - ) { - self.move_resolved_components_by(&self.resolve_components(selection.clone()), translation); + pub fn move_components_by(&mut self, selection: ComponentSelection, translation: Vector2) { + self.move_resolved_components_by( + &self.resolve_components(selection).collect::>(), + translation, + ); } pub fn move_resolved_components_by( diff --git a/topola/src/layout/primitives/joint.rs b/topola/src/layout/primitives/joint.rs index da7a6db..7431245 100644 --- a/topola/src/layout/primitives/joint.rs +++ b/topola/src/layout/primitives/joint.rs @@ -9,8 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::layout::LayerId; use crate::layout::compounds::{ComponentId, NetId, PinId}; use crate::math::Vector2; - -use super::{SegmentId, ViaId}; +use crate::primitives::{SegmentId, ViaId}; #[derive( Clone, diff --git a/topola/src/layout/primitives/mod.rs b/topola/src/layout/primitives/mod.rs index f234d8d..3846b98 100644 --- a/topola/src/layout/primitives/mod.rs +++ b/topola/src/layout/primitives/mod.rs @@ -18,5 +18,6 @@ pub use via::*; pub enum PrimitiveId { Joint(JointId), Segment(SegmentId), + Via(ViaId), Polygon(PolygonId), }