mirror of https://codeberg.org/topola/topola.git
fix(egui): BBox selection should be solely triggered by drag
To complete the interface, menu buttons for selecting everything and nothing were added. Trigger short-keys were taken from KiCAD. Requested-By: @mikolaj https://codeberg.org/topola/topola/pulls/139#issuecomment-2561084
This commit is contained in:
parent
d703d5c3e3
commit
044457e6bb
|
|
@ -78,9 +78,8 @@ pub struct EditActions {
|
||||||
pub undo: Trigger,
|
pub undo: Trigger,
|
||||||
pub redo: Trigger,
|
pub redo: Trigger,
|
||||||
pub abort: Trigger,
|
pub abort: Trigger,
|
||||||
pub reset_bbox: Trigger,
|
pub select_all: Trigger,
|
||||||
pub reselect_bbox: Trigger,
|
pub unselect_all: Trigger,
|
||||||
pub toggle_all_in_bbox: Trigger,
|
|
||||||
pub remove_bands: Trigger,
|
pub remove_bands: Trigger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,21 +104,15 @@ impl EditActions {
|
||||||
egui::Key::Escape,
|
egui::Key::Escape,
|
||||||
)
|
)
|
||||||
.into_trigger(),
|
.into_trigger(),
|
||||||
reset_bbox: Action::new(
|
select_all: Action::new(
|
||||||
tr.text("tr-menu-edit-reset-bbox"),
|
tr.text("tr-menu-edit-select-all"),
|
||||||
egui::Modifiers::CTRL,
|
egui::Modifiers::CTRL,
|
||||||
egui::Key::B,
|
egui::Key::A, // taken from KiCAD
|
||||||
)
|
)
|
||||||
.into_trigger(),
|
.into_trigger(),
|
||||||
reselect_bbox: Action::new(
|
unselect_all: Action::new(
|
||||||
tr.text("tr-menu-edit-reselect-bbox"),
|
tr.text("tr-menu-edit-unselect-all"),
|
||||||
egui::Modifiers::NONE,
|
egui::Modifiers::CTRL | egui::Modifiers::SHIFT,
|
||||||
egui::Key::B,
|
|
||||||
)
|
|
||||||
.into_trigger(),
|
|
||||||
toggle_all_in_bbox: Action::new(
|
|
||||||
tr.text("tr-menu-edit-toggle-all-in-bbox"),
|
|
||||||
egui::Modifiers::NONE,
|
|
||||||
egui::Key::A,
|
egui::Key::A,
|
||||||
)
|
)
|
||||||
.into_trigger(),
|
.into_trigger(),
|
||||||
|
|
@ -149,9 +142,10 @@ impl EditActions {
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
self.reset_bbox.button(ctx, ui);
|
self.select_all.button(ctx, ui);
|
||||||
self.reselect_bbox.button(ctx, ui);
|
self.unselect_all.button(ctx, ui);
|
||||||
self.toggle_all_in_bbox.button(ctx, ui);
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
//ui.add_enabled_ui(workspace_activities_enabled, |ui| {
|
//ui.add_enabled_ui(workspace_activities_enabled, |ui| {
|
||||||
self.remove_bands.button(ctx, ui);
|
self.remove_bands.button(ctx, ui);
|
||||||
|
|
@ -199,7 +193,7 @@ impl RouteActions {
|
||||||
autoroute: Action::new(
|
autoroute: Action::new(
|
||||||
tr.text("tr-menu-route-autoroute"),
|
tr.text("tr-menu-route-autoroute"),
|
||||||
egui::Modifiers::CTRL,
|
egui::Modifiers::CTRL,
|
||||||
egui::Key::A,
|
egui::Key::R,
|
||||||
)
|
)
|
||||||
.into_trigger(),
|
.into_trigger(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -232,17 +232,12 @@ impl MenuBar {
|
||||||
workspace.interactor.redo();
|
workspace.interactor.redo();
|
||||||
} else if actions.edit.abort.consume_key_triggered(ctx, ui) {
|
} else if actions.edit.abort.consume_key_triggered(ctx, ui) {
|
||||||
workspace.interactor.abort();
|
workspace.interactor.abort();
|
||||||
} else if actions.edit.reset_bbox.consume_key_triggered(ctx, ui) {
|
} else if actions.edit.unselect_all.consume_key_triggered(ctx, ui) {
|
||||||
workspace.overlay.reset_selected_bbox();
|
// NOTE: we need to check `unselect` first because `Ctrl+A` would also match `Ctrl+Shift+A`
|
||||||
} else if actions.edit.reselect_bbox.consume_key_triggered(ctx, ui) {
|
workspace.overlay.unselect_all();
|
||||||
workspace.overlay.start_bbox_reselect();
|
} else if actions.edit.select_all.consume_key_triggered(ctx, ui) {
|
||||||
} else if actions
|
|
||||||
.edit
|
|
||||||
.toggle_all_in_bbox
|
|
||||||
.consume_key_triggered(ctx, ui)
|
|
||||||
{
|
|
||||||
let board = workspace.interactor.invoker().autorouter().board();
|
let board = workspace.interactor.invoker().autorouter().board();
|
||||||
workspace.overlay.toggle_all_in_selected_bbox(board);
|
workspace.overlay.select_all(board);
|
||||||
} else if actions.place.place_via.consume_key_enabled(
|
} else if actions.place.place_via.consume_key_enabled(
|
||||||
ctx,
|
ctx,
|
||||||
ui,
|
ui,
|
||||||
|
|
|
||||||
|
|
@ -25,18 +25,17 @@ use topola::{
|
||||||
pub struct Overlay {
|
pub struct Overlay {
|
||||||
ratsnest: Ratsnest,
|
ratsnest: Ratsnest,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
pub selected_bbox: AABB<[f64; 2]>,
|
reselect_bbox: Option<Point>,
|
||||||
reselect_bbox: Option<Option<Point>>,
|
|
||||||
active_layer: usize,
|
active_layer: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INF: f64 = f64::INFINITY;
|
||||||
|
|
||||||
impl Overlay {
|
impl Overlay {
|
||||||
pub fn new(board: &Board<impl AccessMesadata>) -> Result<Self, InsertionError> {
|
pub fn new(board: &Board<impl AccessMesadata>) -> Result<Self, InsertionError> {
|
||||||
const INF: f64 = f64::INFINITY;
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ratsnest: Ratsnest::new(board.layout())?,
|
ratsnest: Ratsnest::new(board.layout())?,
|
||||||
selection: Selection::new(),
|
selection: Selection::new(),
|
||||||
selected_bbox: AABB::from_corners([-INF, -INF], [INF, INF]),
|
|
||||||
reselect_bbox: None,
|
reselect_bbox: None,
|
||||||
active_layer: 0,
|
active_layer: 0,
|
||||||
})
|
})
|
||||||
|
|
@ -46,44 +45,38 @@ impl Overlay {
|
||||||
core::mem::replace(&mut self.selection, Selection::new())
|
core::mem::replace(&mut self.selection, Selection::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_selection(&mut self) {
|
pub fn select_all(&mut self, board: &Board<impl AccessMesadata>) {
|
||||||
|
self.select_all_in_bbox(board, &AABB::from_corners([-INF, -INF], [INF, INF]));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unselect_all(&mut self) {
|
||||||
self.selection = Selection::new();
|
self.selection = Selection::new();
|
||||||
}
|
self.reselect_bbox = None;
|
||||||
|
|
||||||
pub fn reset_selected_bbox(&mut self) {
|
|
||||||
const INF: f64 = f64::INFINITY;
|
|
||||||
self.selected_bbox = AABB::from_corners([-INF, -INF], [INF, INF]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_bbox_reselect(&mut self) {
|
|
||||||
self.reselect_bbox = Some(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) {
|
||||||
if let None | Some(None) = self.reselect_bbox {
|
if self.reselect_bbox.is_none() {
|
||||||
// handle bounding box selection
|
// handle bounding box selection
|
||||||
self.reselect_bbox = Some(Some(at));
|
self.reselect_bbox = Some(at);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drag_stop(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
|
pub fn drag_stop(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
|
||||||
if let Some(aabb) = self.get_bbox_reselect(at) {
|
if let Some(aabb) = self.get_bbox_reselect(at) {
|
||||||
// handle bounding box selection
|
// handle bounding box selection
|
||||||
self.selected_bbox = aabb;
|
self.select_all_in_bbox(board, &aabb);
|
||||||
self.reselect_bbox = None;
|
self.reselect_bbox = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn click(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
|
pub fn click(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
|
||||||
if let Some(rsbb) = self.reselect_bbox.take() {
|
if let Some(pt) = self.reselect_bbox.take() {
|
||||||
// handle bounding box selection (takes precendence over other interactions)
|
// handle bounding box selection (takes precendence over other interactions)
|
||||||
self.reselect_bbox = match rsbb {
|
// this is mostly in order to allow the user to recover from a missed/dropped drag_stop event
|
||||||
None => Some(Some(at)),
|
self.select_all_in_bbox(
|
||||||
Some(pt) => {
|
board,
|
||||||
self.selected_bbox = AABB::from_corners([pt.x(), pt.y()], [at.x(), at.y()]);
|
&AABB::from_corners([pt.x(), pt.y()], [at.x(), at.y()]),
|
||||||
None
|
);
|
||||||
}
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,11 +100,13 @@ impl Overlay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_all_in_selected_bbox(&mut self, board: &Board<impl AccessMesadata>) {
|
pub fn select_all_in_bbox(
|
||||||
|
&mut self,
|
||||||
|
board: &Board<impl AccessMesadata>,
|
||||||
|
aabb: &AABB<[f64; 2]>,
|
||||||
|
) {
|
||||||
use rstar::Envelope;
|
use rstar::Envelope;
|
||||||
let aabb = self.selected_bbox;
|
for &geom in board
|
||||||
let mut temp_selection = Selection::new();
|
|
||||||
for node in board
|
|
||||||
.layout()
|
.layout()
|
||||||
.drawing()
|
.drawing()
|
||||||
.rtree()
|
.rtree()
|
||||||
|
|
@ -119,15 +114,14 @@ impl Overlay {
|
||||||
[aabb.lower()[0], aabb.lower()[1], -f64::INFINITY],
|
[aabb.lower()[0], aabb.lower()[1], -f64::INFINITY],
|
||||||
[aabb.upper()[0], aabb.upper()[1], f64::INFINITY],
|
[aabb.upper()[0], aabb.upper()[1], f64::INFINITY],
|
||||||
))
|
))
|
||||||
.map(|&geom| geom.data)
|
|
||||||
.filter(|&node| {
|
|
||||||
aabb.contains_envelope(&board.layout().node_bbox(node))
|
|
||||||
&& board.layout().is_node_in_layer(node, self.active_layer)
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
temp_selection.select_at_node(board, node);
|
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 ^= &temp_selection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ratsnest(&self) -> &Ratsnest {
|
pub fn ratsnest(&self) -> &Ratsnest {
|
||||||
|
|
@ -141,8 +135,6 @@ impl Overlay {
|
||||||
/// Returns the currently selected bounding box of a bounding-box reselect
|
/// Returns the currently selected bounding box of a bounding-box reselect
|
||||||
pub fn get_bbox_reselect(&self, at: Point) -> Option<AABB<[f64; 2]>> {
|
pub fn get_bbox_reselect(&self, at: Point) -> Option<AABB<[f64; 2]>> {
|
||||||
self.reselect_bbox
|
self.reselect_bbox
|
||||||
.as_ref()
|
|
||||||
.and_then(|pt| *pt)
|
|
||||||
.map(|pt| AABB::from_corners([pt.x(), pt.y()], [at.x(), at.y()]))
|
.map(|pt| AABB::from_corners([pt.x(), pt.y()], [at.x(), at.y()]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ impl Viewport {
|
||||||
let latest_point = point! {x: latest_pos.x as f64, y: -latest_pos.y as f64};
|
let latest_point = point! {x: latest_pos.x as f64, y: -latest_pos.y as f64};
|
||||||
let board = workspace.interactor.invoker().autorouter().board();
|
let board = workspace.interactor.invoker().autorouter().board();
|
||||||
|
|
||||||
if response.clicked() {
|
if response.clicked_by(egui::PointerButton::Primary) {
|
||||||
if menu_bar.is_placing_via {
|
if menu_bar.is_placing_via {
|
||||||
workspace.interactor.execute(Command::PlaceVia(ViaWeight {
|
workspace.interactor.execute(Command::PlaceVia(ViaWeight {
|
||||||
from_layer: 0,
|
from_layer: 0,
|
||||||
|
|
@ -92,9 +92,9 @@ impl Viewport {
|
||||||
} else {
|
} else {
|
||||||
overlay.click(board, latest_point);
|
overlay.click(board, latest_point);
|
||||||
}
|
}
|
||||||
} else if response.drag_started() {
|
} else if response.drag_started_by(egui::PointerButton::Primary) {
|
||||||
overlay.drag_start(board, latest_point);
|
overlay.drag_start(board, latest_point);
|
||||||
} else if response.drag_stopped() {
|
} else if response.drag_stopped_by(egui::PointerButton::Primary) {
|
||||||
overlay.drag_stop(board, latest_point);
|
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);
|
painter.paint_bbox_with_color(cur_bbox, egui::Color32::RED);
|
||||||
|
|
@ -271,12 +271,6 @@ impl Viewport {
|
||||||
[root_bbox3d.upper()[0], root_bbox3d.upper()[1]].into(),
|
[root_bbox3d.upper()[0], root_bbox3d.upper()[1]].into(),
|
||||||
);
|
);
|
||||||
painter.paint_bbox(root_bbox);
|
painter.paint_bbox(root_bbox);
|
||||||
|
|
||||||
let selected_bbox = overlay.selected_bbox;
|
|
||||||
const INF: f64 = f64::INFINITY;
|
|
||||||
if selected_bbox != AABB::from_corners([-INF, -INF], [INF, INF]) {
|
|
||||||
painter.paint_bbox_with_color(selected_bbox, egui::Color32::BLUE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(activity) = workspace.interactor.maybe_activity() {
|
if let Some(activity) = workspace.interactor.maybe_activity() {
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,8 @@ tr-menu-edit = Edit
|
||||||
tr-menu-edit-undo = Undo
|
tr-menu-edit-undo = Undo
|
||||||
tr-menu-edit-redo = Redo
|
tr-menu-edit-redo = Redo
|
||||||
tr-menu-edit-abort = Abort
|
tr-menu-edit-abort = Abort
|
||||||
tr-menu-edit-reset-bbox = Reset selected BBox
|
tr-menu-edit-select-all = Select All
|
||||||
tr-menu-edit-reselect-bbox = (Re-)select BBox
|
tr-menu-edit-unselect-all = Unselect All
|
||||||
tr-menu-edit-toggle-all-in-bbox = Toggle all entries in selected BBox
|
|
||||||
tr-menu-edit-remove-bands = Remove Bands
|
tr-menu-edit-remove-bands = Remove Bands
|
||||||
|
|
||||||
tr-menu-view = View
|
tr-menu-view = View
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue