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.
This commit is contained in:
Ellen Emilia Anna Zscheile 2025-01-09 19:28:54 +01:00 committed by mikolaj
parent a16eba8891
commit ea6df23b95
7 changed files with 69 additions and 16 deletions

View File

@ -246,7 +246,9 @@ impl MenuBar {
workspace.overlay.unselect_all(); workspace.overlay.unselect_all();
} else if actions.edit.select_all.consume_key_triggered(ctx, ui) { } else if actions.edit.select_all.consume_key_triggered(ctx, ui) {
let board = workspace.interactor.invoker().autorouter().board(); 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( } else if actions.place.place_via.consume_key_enabled(
ctx, ctx,
ui, ui,

View File

@ -25,6 +25,8 @@ use topola::{
}, },
}; };
use crate::appearance_panel::AppearancePanel;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SelectionMode { pub enum SelectionMode {
Addition, Addition,
@ -55,9 +57,14 @@ impl Overlay {
core::mem::replace(&mut self.selection, Selection::new()) core::mem::replace(&mut self.selection, Selection::new())
} }
pub fn select_all(&mut self, board: &Board<impl AccessMesadata>) { pub fn select_all(
&mut self,
board: &Board<impl AccessMesadata>,
appearance_panel: &AppearancePanel,
) {
self.select_all_in_bbox( self.select_all_in_bbox(
board, board,
appearance_panel,
&AABB::from_corners([-INF, -INF], [INF, INF]), &AABB::from_corners([-INF, -INF], [INF, INF]),
BboxSelectionKind::CompletelyInside, BboxSelectionKind::CompletelyInside,
); );
@ -71,6 +78,7 @@ impl Overlay {
pub fn drag_start( pub fn drag_start(
&mut self, &mut self,
board: &Board<impl AccessMesadata>, board: &Board<impl AccessMesadata>,
appearance_panel: &AppearancePanel,
at: Point, at: Point,
modifiers: &egui::Modifiers, modifiers: &egui::Modifiers,
) { ) {
@ -87,7 +95,12 @@ impl Overlay {
} }
} }
pub fn drag_stop(&mut self, board: &Board<impl AccessMesadata>, at: Point) { pub fn drag_stop(
&mut self,
board: &Board<impl AccessMesadata>,
appearance_panel: &AppearancePanel,
at: Point,
) {
if let Some((selmode, bsk, aabb)) = self.get_bbox_reselect(at) { if let Some((selmode, bsk, aabb)) = self.get_bbox_reselect(at) {
// handle bounding box selection // handle bounding box selection
self.reselect_bbox = None; self.reselect_bbox = None;
@ -95,25 +108,30 @@ impl Overlay {
match selmode { match selmode {
SelectionMode::Substitution => { SelectionMode::Substitution => {
self.selection = Selection::new(); self.selection = Selection::new();
self.select_all_in_bbox(board, &aabb, bsk); self.select_all_in_bbox(board, appearance_panel, &aabb, bsk);
} }
SelectionMode::Addition => { SelectionMode::Addition => {
self.select_all_in_bbox(board, &aabb, bsk); self.select_all_in_bbox(board, appearance_panel, &aabb, bsk);
} }
SelectionMode::Toggling => { SelectionMode::Toggling => {
let old_selection = self.take_selection(); 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; self.selection ^= &old_selection;
} }
} }
} }
} }
pub fn click(&mut self, board: &Board<impl AccessMesadata>, at: Point) { pub fn click(
&mut self,
board: &Board<impl AccessMesadata>,
appearance_panel: &AppearancePanel,
at: Point,
) {
if self.reselect_bbox.is_some() { if self.reselect_bbox.is_some() {
// handle bounding box selection (takes precendence over other interactions) // 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 // 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; return;
} }
@ -141,11 +159,12 @@ impl Overlay {
pub fn select_all_in_bbox( pub fn select_all_in_bbox(
&mut self, &mut self,
board: &Board<impl AccessMesadata>, board: &Board<impl AccessMesadata>,
appearance_panel: &AppearancePanel,
aabb: &AABB<[f64; 2]>, aabb: &AABB<[f64; 2]>,
bsk: BboxSelectionKind, bsk: BboxSelectionKind,
) { ) {
self.selection 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 { pub fn ratsnest(&self) -> &Ratsnest {

View File

@ -90,16 +90,17 @@ impl Viewport {
maybe_net: Some(1234), maybe_net: Some(1234),
})); }));
} else { } else {
overlay.click(board, latest_point); overlay.click(board, layers, latest_point);
} }
} else if response.drag_started_by(egui::PointerButton::Primary) { } else if response.drag_started_by(egui::PointerButton::Primary) {
overlay.drag_start( overlay.drag_start(
board, board,
layers,
latest_point, latest_point,
&response.ctx.input(|i| i.modifiers), &response.ctx.input(|i| i.modifiers),
); );
} else if response.drag_stopped_by(egui::PointerButton::Primary) { } 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)) = } else if let Some((_, bsk, cur_bbox)) =
overlay.get_bbox_reselect(latest_point) overlay.get_bbox_reselect(latest_point)
{ {

View File

@ -188,7 +188,7 @@ impl Selection {
&mut self, &mut self,
board: &Board<impl AccessMesadata>, board: &Board<impl AccessMesadata>,
aabb: &AABB<[f64; 2]>, aabb: &AABB<[f64; 2]>,
active_layer: usize, layers: &[bool],
kind: BboxSelectionKind, kind: BboxSelectionKind,
) { ) {
const INF: f64 = f64::INFINITY; const INF: f64 = f64::INFINITY;
@ -208,7 +208,7 @@ impl Selection {
&AABB::<[f64; 3]>::from_corners([-INF, -INF, -INF], [INF, INF, INF]), &AABB::<[f64; 3]>::from_corners([-INF, -INF, -INF], [INF, INF, INF]),
) { ) {
let node = geom.data; 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) { if let Some(rsel) = ResolvedSelector::try_from_node(board, node) {
let rseli = selectors.entry(rsel).or_default(); let rseli = selectors.entry(rsel).or_default();
rseli.0.insert(node); rseli.0.insert(node);
@ -236,7 +236,7 @@ impl Selection {
), ),
) { ) {
let node = geom.data; 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)) && kind.matches(aabb, &layout.node_shape(node))
{ {
if let Some(rsel) = ResolvedSelector::try_from_node(board, node) { if let Some(rsel) = ResolvedSelector::try_from_node(board, node) {

View File

@ -28,7 +28,7 @@ use crate::{
collect::Collect, collect::Collect,
dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight, LooseDotIndex, LooseDotWeight}, dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight, LooseDotIndex, LooseDotWeight},
gear::{GearIndex, GetNextGear}, gear::{GearIndex, GetNextGear},
graph::{GetLayer, GetMaybeNet, MakePrimitive, PrimitiveIndex, PrimitiveWeight}, graph::{GetLayer, GetMaybeNet, IsInLayer, MakePrimitive, PrimitiveIndex, PrimitiveWeight},
guide::Guide, guide::Guide,
loose::{GetPrevNextLoose, Loose, LooseIndex}, loose::{GetPrevNextLoose, Loose, LooseIndex},
primitive::{ primitive::{
@ -1039,6 +1039,24 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
} }
} }
pub fn is_node_in_any_layer_of(
&self,
index: GenericNode<PrimitiveIndex, GenericIndex<CW>>,
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 { fn are_connectable(&self, node1: PrimitiveIndex, node2: PrimitiveIndex) -> bool {
if let (Some(node1_net_id), Some(node2_net_id)) = ( if let (Some(node1_net_id), Some(node2_net_id)) = (
node1.primitive(self).maybe_net(), node1.primitive(self).maybe_net(),

View File

@ -31,6 +31,8 @@ pub trait GetLayer {
#[enum_dispatch] #[enum_dispatch]
pub trait IsInLayer { pub trait IsInLayer {
fn is_in_layer(&self, layer: usize) -> bool; fn is_in_layer(&self, layer: usize) -> bool;
fn is_in_any_layer_of(&self, layers: &[bool]) -> bool;
} }
impl<T: GetLayer> IsInLayer for T { impl<T: GetLayer> IsInLayer for T {
@ -38,6 +40,10 @@ impl<T: GetLayer> IsInLayer for T {
fn is_in_layer(&self, layer: usize) -> bool { fn is_in_layer(&self, layer: usize) -> bool {
self.layer() == layer self.layer() == layer
} }
fn is_in_any_layer_of(&self, layers: &[bool]) -> bool {
*layers.get(self.layer()).unwrap_or(&false)
}
} }
#[enum_dispatch] #[enum_dispatch]

View File

@ -69,7 +69,14 @@ impl GetMaybeNet for ViaWeight {
impl IsInLayer for ViaWeight { impl IsInLayer for ViaWeight {
fn is_in_layer(&self, layer: usize) -> bool { 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)
} }
} }