feat(egui/overlay/bbox): allow multiple selection kinds of drag-selected BBoxes

Toggling is triggered by holding down `Ctrl` during `drag_start`.
Addition is triggered by holding down `Shift` during `drag_start`.
Substitution is used otherwise/by default.
This commit is contained in:
Alain Emilia Anna Zscheile 2025-01-02 20:38:25 +01:00
parent 044457e6bb
commit e9ad380a58
3 changed files with 80 additions and 32 deletions

View File

@ -22,10 +22,17 @@ use topola::{
},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SelectionMode {
Addition,
Substitution,
Toggling,
}
pub struct Overlay {
ratsnest: Ratsnest,
selection: Selection,
reselect_bbox: Option<Point>,
reselect_bbox: Option<(SelectionMode, Point)>,
active_layer: usize,
}
@ -54,29 +61,52 @@ impl Overlay {
self.reselect_bbox = None;
}
pub fn drag_start(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
pub fn drag_start(
&mut self,
board: &Board<impl AccessMesadata>,
at: Point,
modifiers: &egui::Modifiers,
) {
if self.reselect_bbox.is_none() {
// handle bounding box selection
self.reselect_bbox = Some(at);
let selmode = if modifiers.ctrl {
SelectionMode::Toggling
} else if modifiers.shift {
SelectionMode::Addition
} else {
SelectionMode::Substitution
};
self.reselect_bbox = Some((selmode, at));
}
}
pub fn drag_stop(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
if let Some(aabb) = self.get_bbox_reselect(at) {
if let Some((selmode, aabb)) = self.get_bbox_reselect(at) {
// handle bounding box selection
self.select_all_in_bbox(board, &aabb);
self.reselect_bbox = None;
match selmode {
SelectionMode::Substitution => {
self.selection = Selection::new();
self.select_all_in_bbox(board, &aabb);
}
SelectionMode::Addition => {
self.select_all_in_bbox(board, &aabb);
}
SelectionMode::Toggling => {
let old_selection = self.take_selection();
self.select_all_in_bbox(board, &aabb);
self.selection ^= &old_selection;
}
}
}
}
pub fn click(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
if let Some(pt) = self.reselect_bbox.take() {
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.select_all_in_bbox(
board,
&AABB::from_corners([pt.x(), pt.y()], [at.x(), at.y()]),
);
self.drag_stop(board, at);
return;
}
@ -105,23 +135,8 @@ impl Overlay {
board: &Board<impl AccessMesadata>,
aabb: &AABB<[f64; 2]>,
) {
use rstar::Envelope;
for &geom in board
.layout()
.drawing()
.rtree()
.locate_in_envelope_intersecting(&AABB::<[f64; 3]>::from_corners(
[aabb.lower()[0], aabb.lower()[1], -f64::INFINITY],
[aabb.upper()[0], aabb.upper()[1], f64::INFINITY],
))
{
let node = geom.data;
if aabb.contains_envelope(&board.layout().node_bbox(node))
&& board.layout().is_node_in_layer(node, self.active_layer)
{
self.selection.select_at_node(board, node);
}
}
self.selection
.select_all_in_bbox(board, aabb, self.active_layer);
}
pub fn ratsnest(&self) -> &Ratsnest {
@ -133,8 +148,12 @@ impl Overlay {
}
/// Returns the currently selected bounding box of a bounding-box reselect
pub fn get_bbox_reselect(&self, at: Point) -> Option<AABB<[f64; 2]>> {
self.reselect_bbox
.map(|pt| AABB::from_corners([pt.x(), pt.y()], [at.x(), at.y()]))
pub fn get_bbox_reselect(&self, at: Point) -> Option<(SelectionMode, AABB<[f64; 2]>)> {
self.reselect_bbox.map(|(selmode, pt)| {
(
selmode,
AABB::from_corners([pt.x(), pt.y()], [at.x(), at.y()]),
)
})
}
}

View File

@ -93,10 +93,15 @@ impl Viewport {
overlay.click(board, latest_point);
}
} else if response.drag_started_by(egui::PointerButton::Primary) {
overlay.drag_start(board, latest_point);
overlay.drag_start(
board,
latest_point,
&response.ctx.input(|i| i.modifiers),
);
} else if response.drag_stopped_by(egui::PointerButton::Primary) {
overlay.drag_stop(board, latest_point);
} else if let Some(cur_bbox) = overlay.get_bbox_reselect(latest_point) {
} else if let Some((_, cur_bbox)) = overlay.get_bbox_reselect(latest_point)
{
painter.paint_bbox_with_color(cur_bbox, egui::Color32::RED);
}

View File

@ -4,6 +4,7 @@
use std::collections::HashSet;
use rstar::AABB;
use serde::{Deserialize, Serialize};
use crate::{
@ -145,6 +146,29 @@ impl Selection {
Self::default()
}
pub fn select_all_in_bbox(
&mut self,
board: &Board<impl AccessMesadata>,
aabb: &AABB<[f64; 2]>,
active_layer: usize,
) {
use rstar::Envelope;
let layout = board.layout();
for &geom in layout.drawing().rtree().locate_in_envelope_intersecting(
&AABB::<[f64; 3]>::from_corners(
[aabb.lower()[0], aabb.lower()[1], -f64::INFINITY],
[aabb.upper()[0], aabb.upper()[1], f64::INFINITY],
),
) {
let node = geom.data;
if aabb.contains_envelope(&layout.node_bbox(node))
&& layout.is_node_in_layer(node, active_layer)
{
self.select_at_node(board, node);
}
}
}
pub fn select_at_node(&mut self, board: &Board<impl AccessMesadata>, node: NodeIndex) {
if let Some(selector) = PinSelector::try_from_node(board, node) {
self.pin_selection.0.insert(selector);