Make containment mode dependent on left-to-right, right-to-left mouse drag

This commit is contained in:
Mikolaj Wielgus 2026-05-27 02:02:05 +02:00
parent 15a7bc65f1
commit 5a3a870a13
8 changed files with 201 additions and 90 deletions

View File

@ -3,10 +3,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use egui::Pos2;
use topola::{
DragSelectionInteractor, InteractiveInput, SelectionCombineMode, SelectionContainMode,
SelectionOptions, Vector2, Vector3,
};
use topola::{InteractiveInput, SelectionCombineMode, SelectionInteractor, Vector2};
use crate::{display::Display, workspace::Workspace};
@ -14,7 +11,7 @@ pub struct Viewport {
pub scene_rect: egui::Rect,
pub ref_scene_rect: egui::Rect,
pub scheduled_zoom_to_fit: bool,
drag_selection_interactor: Option<DragSelectionInteractor>,
selection_interactor: Option<SelectionInteractor>,
}
impl Viewport {
@ -23,7 +20,7 @@ impl Viewport {
scene_rect: egui::Rect::from_min_max(egui::pos2(-1.0, -1.0), egui::pos2(1.0, 1.0)),
ref_scene_rect: egui::Rect::from_min_max(egui::pos2(-1.0, -1.0), egui::pos2(1.0, 1.0)),
scheduled_zoom_to_fit: false,
drag_selection_interactor: None,
selection_interactor: None,
}
}
@ -55,7 +52,7 @@ impl Viewport {
if let Some(workspace) = workspace {
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
self.drag_selection_interactor = None;
self.selection_interactor = None;
}
let primary_pressed =
@ -64,49 +61,46 @@ impl Viewport {
ctx.input(|i| i.pointer.button_down(egui::PointerButton::Primary));
let primary_released =
ctx.input(|i| i.pointer.button_released(egui::PointerButton::Primary));
let mut maybe_pointer_on_scene: Option<Vector2<i64>> = None;
if let Some(pointer_viewport_pos) = ctx.input(|i| i.pointer.interact_pos()) {
let pointer_scene_pos = scene_to_viewport.inverse() * pointer_viewport_pos;
let pointer_scene =
Vector2::new(pointer_scene_pos.x as i64, pointer_scene_pos.y as i64);
let pointer_on_scene_pos =
scene_to_viewport.inverse() * pointer_viewport_pos;
let pointer_on_scene = Vector2::new(
pointer_on_scene_pos.x as i64,
pointer_on_scene_pos.y as i64,
);
maybe_pointer_on_scene = Some(pointer_on_scene);
if primary_pressed && response.hovered() {
self.drag_selection_interactor = Some(DragSelectionInteractor::new(
pointer_scene,
self.selection_interactor = Some(SelectionInteractor::new(
pointer_on_scene,
workspace.selection.clone(),
SelectionOptions::new(
SelectionCombineMode::Replace,
SelectionContainMode::Crossing,
),
SelectionCombineMode::Replace,
));
}
if response.clicked() {
if let Some(pin_selector) = workspace
.autorouter
.router()
.navmesher_board()
.board()
.locate_pin_at_point(Vector3::new(
pointer_scene.x,
pointer_scene.y,
workspace.appearance_panel.active.index() as i64,
))
{
workspace.selection.pins.xor(std::iter::once(pin_selector));
}
} else if let Some(interactor) = self.drag_selection_interactor.as_mut() {
if primary_down || primary_released {
if let Some(interactor) = self.selection_interactor.as_mut() {
if primary_down {
let _ = interactor.update(
workspace.autorouter.router().navmesher_board().board(),
InteractiveInput::new(pointer_scene, false),
workspace.appearance_panel.active,
InteractiveInput::new(pointer_on_scene, false, false),
);
}
}
}
if primary_released {
if let Some(interactor) = self.drag_selection_interactor.take() {
if let Some(mut interactor) = self.selection_interactor.take() {
let pointer_for_scene =
maybe_pointer_on_scene.unwrap_or(*interactor.origin());
let _ = interactor.update(
workspace.autorouter.router().navmesher_board().board(),
workspace.appearance_panel.active,
InteractiveInput::new(pointer_for_scene, true, false),
);
workspace.selection = interactor.selection().clone();
}
}

View File

@ -3,31 +3,37 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use derive_getters::Getters;
use derive_more::Constructor;
use serde::{Deserialize, Serialize};
use crate::{
Rect3, Vector2, Vector3,
board::{
Board,
interactors::{
InteractiveInput, SelectionCombineMode, SelectionContainMode, SelectionOptions,
},
interactors::{InteractiveInput, SelectionCombineMode, SelectionContainMode},
selections::PersistableSelection,
},
};
#[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct DragSelectionOptions {
combine: SelectionCombineMode,
contain: SelectionContainMode,
}
#[derive(Clone, Debug, Eq, Getters, Ord, PartialEq, PartialOrd)]
pub struct DragSelectionInteractor {
origin: Vector2<i64>,
original_selection: PersistableSelection,
selection: PersistableSelection,
options: SelectionOptions,
options: DragSelectionOptions,
}
impl DragSelectionInteractor {
pub fn new(
origin: Vector2<i64>,
original_selection: PersistableSelection,
options: SelectionOptions,
options: DragSelectionOptions,
) -> Self {
Self {
origin,

View File

@ -3,9 +3,10 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
mod drag_selection;
mod selection;
use derive_more::Constructor;
pub use drag_selection::DragSelectionInteractor;
pub use drag_selection::{DragSelectionInteractor, DragSelectionOptions};
pub use selection::SelectionInteractor;
use serde::{Deserialize, Serialize};
use crate::Vector2;
@ -13,12 +14,17 @@ use crate::Vector2;
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct InteractiveInput {
pointer: Vector2<i64>,
released: bool,
cancel: bool,
}
impl InteractiveInput {
pub fn new(pointer: Vector2<i64>, cancel: bool) -> Self {
Self { pointer, cancel }
pub fn new(pointer: Vector2<i64>, released: bool, cancel: bool) -> Self {
Self {
pointer,
released,
cancel,
}
}
}
@ -35,9 +41,3 @@ pub enum SelectionContainMode {
Crossing,
Window,
}
#[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct SelectionOptions {
combine: SelectionCombineMode,
contain: SelectionContainMode,
}

View File

@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: 2026 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use derive_getters::Getters;
use crate::{
Vector2, Vector3,
board::{
Board,
interactors::{
DragSelectionInteractor, DragSelectionOptions, InteractiveInput, SelectionCombineMode,
SelectionContainMode,
},
selections::PersistableSelection,
},
layout::LayerId,
};
#[derive(Clone, Debug, Eq, Getters, Ord, PartialEq, PartialOrd)]
pub struct SelectionInteractor {
origin: Vector2<i64>,
original_selection: PersistableSelection,
selection: PersistableSelection,
combine: SelectionCombineMode,
}
impl SelectionInteractor {
pub fn new(
origin: Vector2<i64>,
original_selection: PersistableSelection,
combine: SelectionCombineMode,
) -> Self {
Self {
origin,
original_selection,
selection: PersistableSelection::new(),
combine,
}
}
pub fn update(
&mut self,
board: &Board,
layer: LayerId,
input: InteractiveInput,
) -> Option<PersistableSelection> {
if input.cancel {
self.selection = self.original_selection.clone();
return Some(self.selection.clone());
}
if input.released && input.pointer == self.origin {
let mut selection = self.original_selection.clone();
// Pins have intentional precedence over nets and components.
if let Some(pin_selector) = board.locate_pin_at_point(Vector3::new(
input.pointer.x,
input.pointer.y,
layer.index() as i64,
)) {
selection.pins.xor(std::iter::once(pin_selector));
} else if let Some(net_selector) = board.locate_net_at_point(Vector3::new(
input.pointer.x,
input.pointer.y,
layer.index() as i64,
)) {
selection.nets.xor(std::iter::once(net_selector));
} else if let Some(component_selector) = board.locate_component_at_point(Vector3::new(
input.pointer.x,
input.pointer.y,
layer.index() as i64,
)) {
selection
.components
.xor(std::iter::once(component_selector));
}
self.selection = selection.clone();
return Some(selection);
}
let contain = if input.pointer.x >= self.origin.x {
SelectionContainMode::Window
} else {
SelectionContainMode::Crossing
};
let options = DragSelectionOptions::new(self.combine.clone(), contain);
let mut drag_selection_interactor =
DragSelectionInteractor::new(self.origin, self.original_selection.clone(), options);
let selection = drag_selection_interactor.update(board, input)?;
self.selection = selection.clone();
Some(selection)
}
}

View File

@ -79,21 +79,24 @@ impl Board {
)
}
pub fn locate_nets_at_point(
&self,
point: Vector3<i64>,
) -> impl Iterator<Item = NetSelector> + '_ {
let mut selectors = BTreeSet::new();
for net_id in self.layout.locate_nets_at_point(point) {
let Some(net_name) = self.net_name(net_id) else {
continue;
};
selectors.insert(NetSelector::new(net_name.to_string()));
pub fn locate_net_at_point(&self, point: Vector3<i64>) -> Option<NetSelector> {
if let Some(joint_id) = self.layout.locate_joints_at_point(point).next() {
return self.joint_net_selector(joint_id);
}
selectors.into_iter()
if let Some(segment_id) = self.layout.locate_segments_at_point(point).next() {
return self.segment_net_selector(segment_id);
}
if let Some(via_id) = self.layout.locate_vias_at_point(point).next() {
return self.via_net_selector(via_id);
}
if let Some(polygon_id) = self.layout.locate_polygons_at_point(point).next() {
return self.polygon_net_selector(polygon_id);
}
None
}
pub fn locate_nets_intersecting_rect(
@ -131,6 +134,11 @@ impl Board {
}
pub fn locate_pin_at_point(&self, point: Vector3<i64>) -> Option<PinSelector> {
// Polygons have intentional precedence for pins.
if let Some(polygon_id) = self.layout.locate_polygons_at_point(point).next() {
return self.polygon_pin_selector(polygon_id);
}
if let Some(joint_id) = self.layout.locate_joints_at_point(point).next() {
return self.joint_pin_selector(joint_id);
}
@ -143,10 +151,6 @@ impl Board {
return self.via_pin_selector(via_id);
}
if let Some(polygon_id) = self.layout.locate_polygons_at_point(point).next() {
return self.polygon_pin_selector(polygon_id);
}
None
}

View File

@ -194,6 +194,38 @@ impl Board {
.contains(&NetSelector::new(net_name.to_string()))
}
pub fn joint_net_selector(&self, id: JointId) -> Option<NetSelector> {
let joint = self.layout.joint(id);
Some(NetSelector {
net: self.net_name(joint.spec.net)?.to_string(),
})
}
pub fn segment_net_selector(&self, id: SegmentId) -> Option<NetSelector> {
let segment = self.layout.segment(id);
Some(NetSelector {
net: self.net_name(segment.net)?.to_string(),
})
}
pub fn via_net_selector(&self, id: ViaId) -> Option<NetSelector> {
let via = self.layout.via(id);
Some(NetSelector {
net: self.net_name(via.net)?.to_string(),
})
}
pub fn polygon_net_selector(&self, id: PolygonId) -> Option<NetSelector> {
let polygon = self.layout.polygon(id);
Some(NetSelector {
net: self.net_name(polygon.net)?.to_string(),
})
}
pub fn pins_contain_joint(&self, selection: &PinSelection, id: JointId) -> bool {
let Some(selector) = self.joint_pin_selector(id) else {
return false;

View File

@ -121,28 +121,6 @@ impl Layout {
.map(|geom_with_data| geom_with_data.data)
}
pub fn locate_nets_at_point(&self, point: Vector3<i64>) -> impl Iterator<Item = NetId> {
let mut nets = BTreeSet::new();
for joint_id in self.locate_joints_at_point(point) {
nets.insert(self.joint(joint_id).spec.net);
}
for segment_id in self.locate_segments_at_point(point) {
nets.insert(self.segment(segment_id).net);
}
for via_id in self.locate_vias_at_point(point) {
nets.insert(self.via(via_id).net);
}
for polygon_id in self.locate_polygons_at_point(point) {
nets.insert(self.polygon(polygon_id).net);
}
nets.into_iter()
}
pub fn locate_nets_intersecting_rect(&self, rect: Rect3<i64>) -> impl Iterator<Item = NetId> {
let mut nets = BTreeSet::new();

View File

@ -19,8 +19,8 @@ pub use crate::board::LayerDesc;
pub use crate::board::LayerSide;
pub use crate::board::LayerType;
pub use crate::board::interactors::{
DragSelectionInteractor, InteractiveInput, SelectionCombineMode, SelectionContainMode,
SelectionOptions,
DragSelectionInteractor, DragSelectionOptions, InteractiveInput, SelectionCombineMode,
SelectionContainMode, SelectionInteractor,
};
pub use crate::board::selections;
pub use crate::layout::LayerId;