From ea6df23b95367a60db799a6c6efe586b8cfe5adc Mon Sep 17 00:00:00 2001 From: Ellen Emilia Anna Zscheile Date: Thu, 9 Jan 2025 19:28:54 +0100 Subject: [PATCH] feat(selection): BBox selection should span all currently visible layers In Via, is_in_layer, `from_layer` and `to_layer` were swapped, this has been also fixed here. --- crates/topola-egui/src/menu_bar.rs | 4 +++- crates/topola-egui/src/overlay.rs | 35 +++++++++++++++++++++++------- crates/topola-egui/src/viewport.rs | 5 +++-- src/autorouter/selection.rs | 6 ++--- src/drawing/drawing.rs | 20 ++++++++++++++++- src/drawing/graph.rs | 6 +++++ src/layout/via.rs | 9 +++++++- 7 files changed, 69 insertions(+), 16 deletions(-) diff --git a/crates/topola-egui/src/menu_bar.rs b/crates/topola-egui/src/menu_bar.rs index cb3320b..bf20bab 100644 --- a/crates/topola-egui/src/menu_bar.rs +++ b/crates/topola-egui/src/menu_bar.rs @@ -246,7 +246,9 @@ impl MenuBar { workspace.overlay.unselect_all(); } else if actions.edit.select_all.consume_key_triggered(ctx, ui) { let board = workspace.interactor.invoker().autorouter().board(); - workspace.overlay.select_all(board); + workspace + .overlay + .select_all(board, &workspace.appearance_panel); } else if actions.place.place_via.consume_key_enabled( ctx, ui, diff --git a/crates/topola-egui/src/overlay.rs b/crates/topola-egui/src/overlay.rs index a45ffd0..cfe432b 100644 --- a/crates/topola-egui/src/overlay.rs +++ b/crates/topola-egui/src/overlay.rs @@ -25,6 +25,8 @@ use topola::{ }, }; +use crate::appearance_panel::AppearancePanel; + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SelectionMode { Addition, @@ -55,9 +57,14 @@ impl Overlay { core::mem::replace(&mut self.selection, Selection::new()) } - pub fn select_all(&mut self, board: &Board) { + pub fn select_all( + &mut self, + board: &Board, + appearance_panel: &AppearancePanel, + ) { self.select_all_in_bbox( board, + appearance_panel, &AABB::from_corners([-INF, -INF], [INF, INF]), BboxSelectionKind::CompletelyInside, ); @@ -71,6 +78,7 @@ impl Overlay { pub fn drag_start( &mut self, board: &Board, + appearance_panel: &AppearancePanel, at: Point, modifiers: &egui::Modifiers, ) { @@ -87,7 +95,12 @@ impl Overlay { } } - pub fn drag_stop(&mut self, board: &Board, at: Point) { + pub fn drag_stop( + &mut self, + board: &Board, + appearance_panel: &AppearancePanel, + at: Point, + ) { if let Some((selmode, bsk, aabb)) = self.get_bbox_reselect(at) { // handle bounding box selection self.reselect_bbox = None; @@ -95,25 +108,30 @@ impl Overlay { match selmode { SelectionMode::Substitution => { self.selection = Selection::new(); - self.select_all_in_bbox(board, &aabb, bsk); + self.select_all_in_bbox(board, appearance_panel, &aabb, bsk); } SelectionMode::Addition => { - self.select_all_in_bbox(board, &aabb, bsk); + self.select_all_in_bbox(board, appearance_panel, &aabb, bsk); } SelectionMode::Toggling => { let old_selection = self.take_selection(); - self.select_all_in_bbox(board, &aabb, bsk); + self.select_all_in_bbox(board, appearance_panel, &aabb, bsk); self.selection ^= &old_selection; } } } } - pub fn click(&mut self, board: &Board, at: Point) { + pub fn click( + &mut self, + board: &Board, + appearance_panel: &AppearancePanel, + at: Point, + ) { if self.reselect_bbox.is_some() { // handle bounding box selection (takes precendence over other interactions) // this is mostly in order to allow the user to recover from a missed/dropped drag_stop event - self.drag_stop(board, at); + self.drag_stop(board, appearance_panel, at); return; } @@ -141,11 +159,12 @@ impl Overlay { pub fn select_all_in_bbox( &mut self, board: &Board, + appearance_panel: &AppearancePanel, aabb: &AABB<[f64; 2]>, bsk: BboxSelectionKind, ) { self.selection - .select_all_in_bbox(board, aabb, self.active_layer, bsk); + .select_all_in_bbox(board, aabb, &appearance_panel.visible[..], bsk); } pub fn ratsnest(&self) -> &Ratsnest { diff --git a/crates/topola-egui/src/viewport.rs b/crates/topola-egui/src/viewport.rs index 6648dcf..0cfcc82 100644 --- a/crates/topola-egui/src/viewport.rs +++ b/crates/topola-egui/src/viewport.rs @@ -90,16 +90,17 @@ impl Viewport { maybe_net: Some(1234), })); } else { - overlay.click(board, latest_point); + overlay.click(board, layers, latest_point); } } else if response.drag_started_by(egui::PointerButton::Primary) { overlay.drag_start( board, + layers, latest_point, &response.ctx.input(|i| i.modifiers), ); } else if response.drag_stopped_by(egui::PointerButton::Primary) { - overlay.drag_stop(board, latest_point); + overlay.drag_stop(board, layers, latest_point); } else if let Some((_, bsk, cur_bbox)) = overlay.get_bbox_reselect(latest_point) { diff --git a/src/autorouter/selection.rs b/src/autorouter/selection.rs index d79ff1a..763a17c 100644 --- a/src/autorouter/selection.rs +++ b/src/autorouter/selection.rs @@ -188,7 +188,7 @@ impl Selection { &mut self, board: &Board, aabb: &AABB<[f64; 2]>, - active_layer: usize, + layers: &[bool], kind: BboxSelectionKind, ) { const INF: f64 = f64::INFINITY; @@ -208,7 +208,7 @@ impl Selection { &AABB::<[f64; 3]>::from_corners([-INF, -INF, -INF], [INF, INF, INF]), ) { let node = geom.data; - if layout.drawing().is_node_in_layer(node, active_layer) { + if layout.drawing().is_node_in_any_layer_of(node, layers) { if let Some(rsel) = ResolvedSelector::try_from_node(board, node) { let rseli = selectors.entry(rsel).or_default(); rseli.0.insert(node); @@ -236,7 +236,7 @@ impl Selection { ), ) { let node = geom.data; - if layout.drawing().is_node_in_layer(node, active_layer) + if layout.drawing().is_node_in_any_layer_of(node, layers) && kind.matches(aabb, &layout.node_shape(node)) { if let Some(rsel) = ResolvedSelector::try_from_node(board, node) { diff --git a/src/drawing/drawing.rs b/src/drawing/drawing.rs index 72fbf86..9359cf9 100644 --- a/src/drawing/drawing.rs +++ b/src/drawing/drawing.rs @@ -28,7 +28,7 @@ use crate::{ collect::Collect, dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight, LooseDotIndex, LooseDotWeight}, gear::{GearIndex, GetNextGear}, - graph::{GetLayer, GetMaybeNet, MakePrimitive, PrimitiveIndex, PrimitiveWeight}, + graph::{GetLayer, GetMaybeNet, IsInLayer, MakePrimitive, PrimitiveIndex, PrimitiveWeight}, guide::Guide, loose::{GetPrevNextLoose, Loose, LooseIndex}, primitive::{ @@ -1039,6 +1039,24 @@ impl Drawing { } } + pub fn is_node_in_any_layer_of( + &self, + index: GenericNode>, + layers: &[bool], + ) -> bool + where + CW: IsInLayer, + { + match index { + GenericNode::Primitive(primitive) => { + primitive.primitive(self).is_in_any_layer_of(layers) + } + GenericNode::Compound(compound) => { + self.compound_weight(compound).is_in_any_layer_of(layers) + } + } + } + fn are_connectable(&self, node1: PrimitiveIndex, node2: PrimitiveIndex) -> bool { if let (Some(node1_net_id), Some(node2_net_id)) = ( node1.primitive(self).maybe_net(), diff --git a/src/drawing/graph.rs b/src/drawing/graph.rs index 37f4f08..944c1ef 100644 --- a/src/drawing/graph.rs +++ b/src/drawing/graph.rs @@ -31,6 +31,8 @@ pub trait GetLayer { #[enum_dispatch] pub trait IsInLayer { fn is_in_layer(&self, layer: usize) -> bool; + + fn is_in_any_layer_of(&self, layers: &[bool]) -> bool; } impl IsInLayer for T { @@ -38,6 +40,10 @@ impl IsInLayer for T { fn is_in_layer(&self, layer: usize) -> bool { self.layer() == layer } + + fn is_in_any_layer_of(&self, layers: &[bool]) -> bool { + *layers.get(self.layer()).unwrap_or(&false) + } } #[enum_dispatch] diff --git a/src/layout/via.rs b/src/layout/via.rs index 667d8fe..dbfc2ec 100644 --- a/src/layout/via.rs +++ b/src/layout/via.rs @@ -69,7 +69,14 @@ impl GetMaybeNet for ViaWeight { impl IsInLayer for ViaWeight { fn is_in_layer(&self, layer: usize) -> bool { - self.from_layer >= layer && self.to_layer <= layer + (self.from_layer..=self.to_layer).contains(&layer) + } + + fn is_in_any_layer_of(&self, layers: &[bool]) -> bool { + layers + .get(self.from_layer..=core::cmp::min(self.to_layer, layers.len())) + .map(|i| i.iter().any(|j| *j)) + .unwrap_or(false) } }