Add interactor for moving components

This commit is contained in:
Mikolaj Wielgus 2026-05-29 12:28:35 +02:00
parent a6d1fe7025
commit 77950109fc
8 changed files with 162 additions and 99 deletions

View File

@ -3,8 +3,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{viewport::Viewport, workspace::GuiWorkspace}; use crate::{viewport::Viewport, workspace::GuiWorkspace};
use topola::Workspace;
use topola::primitives::{Joint, Polygon, Segment, Via}; use topola::primitives::{Joint, Polygon, Segment, Via};
use topola::{LayerId, Workspace};
pub struct Display {} pub struct Display {}
@ -102,8 +102,8 @@ impl Display {
board.pins_contain_via(&workspace.workspace.selection().pins, via_id); board.pins_contain_via(&workspace.workspace.selection().pins, via_id);
let net_selected = let net_selected =
board.nets_contain_via(&workspace.workspace.selection().nets, via_id); board.nets_contain_via(&workspace.workspace.selection().nets, via_id);
let component_selected = let component_selected = board
board.components_contain_via(&workspace.workspace.selection().components, via_id); .components_contain_via(&workspace.workspace.selection().components, via_id);
self.paint_via( self.paint_via(
ctx, ctx,
ui, ui,

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use egui::Pos2; use egui::Pos2;
use topola::{InteractiveInput, MasterInteractor, Vector2, Workspace}; use topola::{MasterInteractor, Vector2, Workspace};
use crate::{display::Display, workspace::GuiWorkspace}; use crate::{display::Display, workspace::GuiWorkspace};
@ -51,7 +51,16 @@ impl Viewport {
Self::fit_to_rect_in_scene(viewport_rect, scene_rect, zoom_range.into()); Self::fit_to_rect_in_scene(viewport_rect, scene_rect, zoom_range.into());
if let Some(workspace) = workspace { if let Some(workspace) = workspace {
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { let escape_pressed = ctx.input(|i| i.key_pressed(egui::Key::Escape));
if escape_pressed {
if let Some(interactor) = self.master_interactor.as_mut() {
let board = match &mut workspace.workspace {
Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_) => panic!("expected board workspace"),
};
interactor.abort(board);
*workspace.workspace.selection_mut() = interactor.selection().clone();
}
self.master_interactor = None; self.master_interactor = None;
} }
@ -75,7 +84,6 @@ impl Viewport {
if primary_pressed && response.hovered() { if primary_pressed && response.hovered() {
self.master_interactor = Some(MasterInteractor::new( self.master_interactor = Some(MasterInteractor::new(
None,
workspace.workspace.selection().clone(), workspace.workspace.selection().clone(),
)); ));
} }
@ -86,16 +94,16 @@ impl Viewport {
Workspace::Board(workspace) => &mut workspace.board, Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_) => panic!("expected board workspace"), Workspace::Autorouter(_) => panic!("expected board workspace"),
}; };
interactor.update( interactor.hold(
board, board,
workspace.appearance_panel.active, workspace.appearance_panel.active,
InteractiveInput::new(pointer_on_scene, false, false, false), pointer_on_scene,
); );
if let Some(selection_interactor) = if let Some(select_interactor) =
interactor.selection_interactor().as_ref() interactor.select_interactor().as_ref()
{ {
let origin = *selection_interactor.origin(); let origin = *select_interactor.origin();
let drag_rect_scene = egui::Rect::from_min_max( let drag_rect_scene = egui::Rect::from_min_max(
egui::pos2( egui::pos2(
origin.x.min(pointer_on_scene.x) as f32, origin.x.min(pointer_on_scene.x) as f32,
@ -133,19 +141,19 @@ impl Viewport {
if let Some(mut interactor) = self.master_interactor.take() { if let Some(mut interactor) = self.master_interactor.take() {
let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| { let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| {
interactor interactor
.selection_interactor() .select_interactor()
.as_ref() .as_ref()
.map(|selection_interactor| *selection_interactor.origin()) .map(|select_interactor| *select_interactor.origin())
.unwrap_or(Vector2::new(0, 0)) .unwrap_or(Vector2::new(0, 0))
}); });
let board = match &mut workspace.workspace { let board = match &mut workspace.workspace {
Workspace::Board(workspace) => &mut workspace.board, Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_) => panic!("expected board workspace"), Workspace::Autorouter(_) => panic!("expected board workspace"),
}; };
interactor.update( interactor.release(
board, board,
workspace.appearance_panel.active, workspace.appearance_panel.active,
InteractiveInput::new(pointer_for_scene, true, false, false), pointer_for_scene,
); );
*workspace.workspace.selection_mut() = interactor.selection().clone(); *workspace.workspace.selection_mut() = interactor.selection().clone();
@ -153,19 +161,13 @@ impl Viewport {
} }
if delete_pressed { if delete_pressed {
let pointer_for_scene =
maybe_pointer_on_scene.unwrap_or(Vector2::new(0, 0));
let mut interactor = let mut interactor =
MasterInteractor::new(None, workspace.workspace.selection().clone()); MasterInteractor::new(workspace.workspace.selection().clone());
let board = match &mut workspace.workspace { let board = match &mut workspace.workspace {
Workspace::Board(workspace) => &mut workspace.board, Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_) => panic!("expected board workspace"), Workspace::Autorouter(_) => panic!("expected board workspace"),
}; };
interactor.update( interactor.delete(board);
board,
workspace.appearance_panel.active,
InteractiveInput::new(pointer_for_scene, false, true, false),
);
*workspace.workspace.selection_mut() = interactor.selection().clone(); *workspace.workspace.selection_mut() = interactor.selection().clone();
} }

View File

@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2026 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use derive_getters::Getters;
use derive_more::Constructor;
use undoredo::DiscardDelta;
use crate::{Board, LayerId, Vector2, selections::ComponentSelection};
#[derive(Clone, Constructor, Debug, Eq, Getters, PartialEq)]
pub struct DragMoveInteractor {
origin: Vector2<i64>,
layer: LayerId,
selection: ComponentSelection,
}
impl DragMoveInteractor {
pub fn abort(&mut self, board: &mut Board) {
board.discard_delta();
}
pub fn hold(&mut self, board: &mut Board, pointer: Vector2<i64>) {
board.discard_delta();
board.move_components_by(self.selection.clone(), pointer - self.origin);
}
pub fn release(&mut self, board: &mut Board, pointer: Vector2<i64>) {
self.hold(board, pointer);
}
}

View File

@ -10,33 +10,33 @@ use crate::{
Rect3, Vector2, Vector3, Rect3, Vector2, Vector3,
board::{ board::{
Board, Board,
interactors::{InteractiveInput, SelectionCombineMode, SelectionContainMode}, interactors::{SelectionCombineMode, SelectionContainMode},
selections::PersistableSelection, selections::PersistableSelection,
}, },
layout::LayerId, layout::LayerId,
}; };
#[derive(Clone, Constructor, Debug, Deserialize, Eq, PartialEq, Serialize)] #[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct DragSelectionOptions { pub struct DragSelectOptions {
combine: SelectionCombineMode, combine: SelectionCombineMode,
contain: SelectionContainMode, contain: SelectionContainMode,
} }
#[derive(Clone, Debug, Eq, Getters, PartialEq)] #[derive(Clone, Debug, Eq, Getters, PartialEq)]
pub struct DragSelectionInteractor { pub struct DragSelectInteractor {
origin: Vector2<i64>, origin: Vector2<i64>,
layer: LayerId, layer: LayerId,
original_selection: PersistableSelection, original_selection: PersistableSelection,
selection: PersistableSelection, selection: PersistableSelection,
options: DragSelectionOptions, options: DragSelectOptions,
} }
impl DragSelectionInteractor { impl DragSelectInteractor {
pub fn new( pub fn new(
origin: Vector2<i64>, origin: Vector2<i64>,
layer: LayerId, layer: LayerId,
original_selection: PersistableSelection, original_selection: PersistableSelection,
options: DragSelectionOptions, options: DragSelectOptions,
) -> Self { ) -> Self {
Self { Self {
origin, origin,
@ -47,17 +47,16 @@ impl DragSelectionInteractor {
} }
} }
pub fn update(&mut self, board: &Board, input: InteractiveInput) { pub fn abort(&mut self) {
if input.cancel { self.selection = self.original_selection.clone();
self.selection = self.original_selection.clone(); }
return;
}
pub fn hold(&mut self, board: &Board, pointer: Vector2<i64>) {
self.selection = PersistableSelection::new(); self.selection = PersistableSelection::new();
let rect = Rect3::new( let rect = Rect3::new(
Vector3::new(self.origin.x, self.origin.y, self.layer.index() as i64), Vector3::new(self.origin.x, self.origin.y, self.layer.index() as i64),
Vector3::new(input.pointer.x, input.pointer.y, self.layer.index() as i64), Vector3::new(pointer.x, pointer.y, self.layer.index() as i64),
); );
let all_belong_to_pins = match self.options.contain { let all_belong_to_pins = match self.options.contain {

View File

@ -3,45 +3,85 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use derive_getters::Getters; use derive_getters::Getters;
use derive_more::Constructor;
use crate::{ use crate::{
InteractiveInput, Vector2,
board::{ board::{
Board, Board,
interactors::{SelectionCombineMode, SelectionInteractor}, interactors::{DragMoveInteractor, SelectInteractor, SelectionCombineMode},
selections::PersistableSelection, selections::PersistableSelection,
}, },
layout::LayerId, layout::LayerId,
}; };
#[derive(Clone, Constructor, Debug, Eq, Getters, PartialEq)] #[derive(Clone, Debug, Eq, Getters, PartialEq)]
pub struct MasterInteractor { pub struct MasterInteractor {
selection_interactor: Option<SelectionInteractor>, select_interactor: Option<SelectInteractor>,
drag_move_interactor: Option<DragMoveInteractor>,
selection: PersistableSelection, selection: PersistableSelection,
} }
impl MasterInteractor { impl MasterInteractor {
pub fn update(&mut self, board: &mut Board, layer: LayerId, input: InteractiveInput) { pub fn new(selection: PersistableSelection) -> Self {
if input.delete { Self {
board.delete_net_free_primitives(self.selection.nets.clone()); select_interactor: None,
drag_move_interactor: None,
selection,
} }
}
if self.selection_interactor.is_none() { pub fn delete(&mut self, board: &mut Board) {
self.selection_interactor = Some(SelectionInteractor::new( board.delete_net_free_primitives(self.selection.nets.clone());
input.pointer, }
pub fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
if self.select_interactor.is_none() && self.drag_move_interactor.is_none() {
/*if board.selected_components_contain_point(&self.selection.components, input.pointer) {
self.drag_move_interactor = Some(DragMoveInteractor::new(
input.pointer,
layer,
self.selection.components.clone(),
));
} else {*/
self.select_interactor = Some(SelectInteractor::new(
pointer,
self.selection.clone(), self.selection.clone(),
SelectionCombineMode::Replace, SelectionCombineMode::Replace,
)); ));
//}
} }
if let Some(selection_interactor) = self.selection_interactor.as_mut() { if let Some(drag_move_interactor) = self.drag_move_interactor.as_mut() {
selection_interactor.update(board, layer, input.clone()); drag_move_interactor.hold(board, pointer);
self.selection = selection_interactor.selection().clone(); } else if let Some(select_interactor) = self.select_interactor.as_mut() {
} select_interactor.hold(board, layer, pointer);
self.selection = select_interactor.selection().clone();
if input.release || input.cancel {
self.selection_interactor = None;
} }
} }
pub fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
if let Some(drag_move_interactor) = self.drag_move_interactor.as_mut() {
drag_move_interactor.release(board, pointer);
} else if let Some(select_interactor) = self.select_interactor.as_mut() {
select_interactor.release(board, layer, pointer);
self.selection = select_interactor.selection().clone();
}
self.select_interactor = None;
self.drag_move_interactor = None;
}
pub fn abort(&mut self, board: &mut Board) {
if let Some(drag_move_interactor) = self.drag_move_interactor.as_mut() {
drag_move_interactor.abort(board);
}
if let Some(select_interactor) = self.select_interactor.as_mut() {
select_interactor.abort();
self.selection = select_interactor.original_selection().clone();
}
self.select_interactor = None;
self.drag_move_interactor = None;
}
} }

View File

@ -2,26 +2,17 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
mod drag_selection; mod drag_move;
mod drag_select;
mod master; mod master;
mod selection; mod select;
use derive_more::Constructor; pub use drag_move::DragMoveInteractor;
pub use drag_selection::{DragSelectionInteractor, DragSelectionOptions}; pub use drag_select::{DragSelectInteractor, DragSelectOptions};
pub use master::MasterInteractor; pub use master::MasterInteractor;
pub use selection::SelectionInteractor; pub use select::SelectInteractor;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::Vector2;
#[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct InteractiveInput {
pointer: Vector2<i64>,
release: bool,
delete: bool,
cancel: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum SelectionCombineMode { pub enum SelectionCombineMode {
Replace, Replace,

View File

@ -9,8 +9,7 @@ use crate::{
board::{ board::{
Board, Board,
interactors::{ interactors::{
DragSelectionInteractor, DragSelectionOptions, InteractiveInput, SelectionCombineMode, DragSelectInteractor, DragSelectOptions, SelectionCombineMode, SelectionContainMode,
SelectionContainMode,
}, },
selections::PersistableSelection, selections::PersistableSelection,
}, },
@ -18,14 +17,14 @@ use crate::{
}; };
#[derive(Clone, Debug, Eq, Getters, PartialEq)] #[derive(Clone, Debug, Eq, Getters, PartialEq)]
pub struct SelectionInteractor { pub struct SelectInteractor {
origin: Vector2<i64>, origin: Vector2<i64>,
original_selection: PersistableSelection, original_selection: PersistableSelection,
selection: PersistableSelection, selection: PersistableSelection,
combine: SelectionCombineMode, combine: SelectionCombineMode,
} }
impl SelectionInteractor { impl SelectInteractor {
pub fn new( pub fn new(
origin: Vector2<i64>, origin: Vector2<i64>,
original_selection: PersistableSelection, original_selection: PersistableSelection,
@ -39,20 +38,35 @@ impl SelectionInteractor {
} }
} }
pub fn update(&mut self, board: &Board, layer: LayerId, input: InteractiveInput) { pub fn abort(&mut self) {
if input.cancel { self.selection = self.original_selection.clone();
self.selection = self.original_selection.clone(); }
return;
}
if input.release && input.pointer == self.origin { pub fn hold(&mut self, board: &Board, layer: LayerId, pointer: Vector2<i64>) {
let contain = if pointer.x >= self.origin.x {
SelectionContainMode::Window
} else {
SelectionContainMode::Crossing
};
let options = DragSelectOptions::new(self.combine.clone(), contain);
let mut drag_selection_interactor =
DragSelectInteractor::new(self.origin, layer, self.original_selection.clone(), options);
drag_selection_interactor.hold(board, pointer);
self.selection = drag_selection_interactor.selection().clone();
}
pub fn release(&mut self, board: &Board, layer: LayerId, pointer: Vector2<i64>) {
if pointer == self.origin {
let mut selection = self.original_selection.clone(); let mut selection = self.original_selection.clone();
let point = Vector3::new(input.pointer.x, input.pointer.y, layer.index() as i64); let point = Vector3::new(pointer.x, pointer.y, layer.index() as i64);
// Pins have intentional precedence over nets and components. // Pins have intentional precedence over nets and components.
if let Some(pin_selector) = board.locate_pins_prefer_layer_at_point(point).next() { if let Some(pin_selector) = board.locate_pins_prefer_layer_at_point(point).next() {
selection.pins.xor(std::iter::once(pin_selector)); selection.pins.xor(std::iter::once(pin_selector));
} else if let Some(net_selector) = board.locate_nets_prefer_layer_at_point(point).next() { } else if let Some(net_selector) = board.locate_nets_prefer_layer_at_point(point).next()
{
selection.nets.xor(std::iter::once(net_selector)); selection.nets.xor(std::iter::once(net_selector));
} else if let Some(component_selector) = } else if let Some(component_selector) =
board.locate_components_prefer_layer_at_point(point).next() board.locate_components_prefer_layer_at_point(point).next()
@ -66,21 +80,6 @@ impl SelectionInteractor {
return; return;
} }
let contain = if input.pointer.x >= self.origin.x { self.hold(board, layer, pointer);
SelectionContainMode::Window
} else {
SelectionContainMode::Crossing
};
let options = DragSelectionOptions::new(self.combine.clone(), contain);
let mut drag_selection_interactor = DragSelectionInteractor::new(
self.origin,
layer,
self.original_selection.clone(),
options,
);
drag_selection_interactor.update(board, input);
self.selection = drag_selection_interactor.selection().clone();
} }
} }

View File

@ -22,8 +22,8 @@ pub use crate::board::LayerDesc;
pub use crate::board::LayerSide; pub use crate::board::LayerSide;
pub use crate::board::LayerType; pub use crate::board::LayerType;
pub use crate::board::interactors::{ pub use crate::board::interactors::{
DragSelectionInteractor, DragSelectionOptions, InteractiveInput, MasterInteractor, DragSelectInteractor, DragSelectOptions, MasterInteractor, SelectInteractor,
SelectionCombineMode, SelectionContainMode, SelectionInteractor, SelectionCombineMode, SelectionContainMode,
}; };
pub use crate::board::selections; pub use crate::board::selections;
pub use crate::layout::LayerId; pub use crate::layout::LayerId;