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();
} 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,

View File

@ -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<impl AccessMesadata>) {
pub fn select_all(
&mut self,
board: &Board<impl AccessMesadata>,
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<impl AccessMesadata>,
appearance_panel: &AppearancePanel,
at: Point,
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) {
// 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<impl AccessMesadata>, at: Point) {
pub fn click(
&mut self,
board: &Board<impl AccessMesadata>,
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<impl AccessMesadata>,
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 {

View File

@ -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)
{

View File

@ -188,7 +188,7 @@ impl Selection {
&mut self,
board: &Board<impl AccessMesadata>,
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) {

View File

@ -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<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 {
if let (Some(node1_net_id), Some(node2_net_id)) = (
node1.primitive(self).maybe_net(),

View File

@ -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<T: GetLayer> IsInLayer for T {
@ -38,6 +40,10 @@ impl<T: GetLayer> 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]

View File

@ -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)
}
}