mirror of https://codeberg.org/topola/topola.git
Add drag selection interactors
This commit is contained in:
parent
1b4bb49a89
commit
61c0134db4
|
|
@ -25,8 +25,9 @@ use topola::{
|
||||||
|
|
||||||
pub fn load_design(filename: &str) -> Autorouter<SpecctraMesadata> {
|
pub fn load_design(filename: &str) -> Autorouter<SpecctraMesadata> {
|
||||||
let design_file = File::open(filename).unwrap();
|
let design_file = File::open(filename).unwrap();
|
||||||
let design_bufread = BufReader::new(design_file);
|
let design_bufreader = BufReader::new(design_file);
|
||||||
let design = SpecctraDesign::load(design_bufread).unwrap();
|
let design = SpecctraDesign::load(design_bufreader).unwrap();
|
||||||
|
|
||||||
Autorouter::new(design.make_board(&mut BoardEdit::new())).unwrap()
|
Autorouter::new(design.make_board(&mut BoardEdit::new())).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
derive-getters.workspace = true
|
derive-getters.workspace = true
|
||||||
egui = "0.33"
|
egui = { version = "0.33", features = ["serde"] }
|
||||||
eframe = { version = "0.33", default-features = false, features = [
|
eframe = { version = "0.33", default-features = false, features = [
|
||||||
#"accesskit", # Make egui compatible with screen readers. NOTE: adds a lot of dependencies.
|
#"accesskit", # Make egui compatible with screen readers. NOTE: adds a lot of dependencies.
|
||||||
"default_fonts", # Embed the default egui fonts.
|
"default_fonts", # Embed the default egui fonts.
|
||||||
|
|
|
||||||
|
|
@ -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::Vector2;
|
use topola::{CrossingDragSelectionInteractor, InteractiveInput, Vector2};
|
||||||
|
|
||||||
use crate::{display::Display, workspace::Workspace};
|
use crate::{display::Display, workspace::Workspace};
|
||||||
|
|
||||||
|
|
@ -11,6 +11,7 @@ pub struct Viewport {
|
||||||
pub scene_rect: egui::Rect,
|
pub scene_rect: egui::Rect,
|
||||||
pub ref_scene_rect: egui::Rect,
|
pub ref_scene_rect: egui::Rect,
|
||||||
pub scheduled_zoom_to_fit: bool,
|
pub scheduled_zoom_to_fit: bool,
|
||||||
|
crossing_drag_selection_interactor: Option<CrossingDragSelectionInteractor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Viewport {
|
impl Viewport {
|
||||||
|
|
@ -19,6 +20,7 @@ impl Viewport {
|
||||||
scene_rect: egui::Rect::from_min_max(egui::pos2(-1.0, -1.0), egui::pos2(1.0, 1.0)),
|
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)),
|
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,
|
scheduled_zoom_to_fit: false,
|
||||||
|
crossing_drag_selection_interactor: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,7 +36,7 @@ impl Viewport {
|
||||||
|
|
||||||
let response = egui::Scene::new()
|
let response = egui::Scene::new()
|
||||||
.zoom_range(zoom_range.clone())
|
.zoom_range(zoom_range.clone())
|
||||||
//.sense(egui::Sense::hover())
|
.drag_pan_buttons(egui::DragPanButtons::MIDDLE)
|
||||||
.show(ui, &mut scene_rect, |ui| {
|
.show(ui, &mut scene_rect, |ui| {
|
||||||
if let Some(ref workspace) = workspace {
|
if let Some(ref workspace) = workspace {
|
||||||
let mut display = Display::new();
|
let mut display = Display::new();
|
||||||
|
|
@ -49,10 +51,35 @@ 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)) {
|
||||||
|
self.crossing_drag_selection_interactor = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let primary_pressed =
|
||||||
|
ctx.input(|i| i.pointer.button_pressed(egui::PointerButton::Primary));
|
||||||
|
let primary_down =
|
||||||
|
ctx.input(|i| i.pointer.button_down(egui::PointerButton::Primary));
|
||||||
|
let primary_released =
|
||||||
|
ctx.input(|i| i.pointer.button_released(egui::PointerButton::Primary));
|
||||||
|
|
||||||
if let Some(pointer_viewport_pos) = ctx.input(|i| i.pointer.interact_pos()) {
|
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_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);
|
||||||
|
|
||||||
if response.clicked() {
|
if primary_pressed && response.hovered() {
|
||||||
|
self.crossing_drag_selection_interactor =
|
||||||
|
Some(CrossingDragSelectionInteractor::new(pointer_scene));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(interactor) = self.crossing_drag_selection_interactor.as_mut() {
|
||||||
|
if primary_down || primary_released {
|
||||||
|
interactor.update(
|
||||||
|
workspace.autorouter.router().navmesher_board().board(),
|
||||||
|
InteractiveInput::new(pointer_scene),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if response.clicked() {
|
||||||
if let Some(pin_selector) = workspace
|
if let Some(pin_selector) = workspace
|
||||||
.autorouter
|
.autorouter
|
||||||
.router()
|
.router()
|
||||||
|
|
@ -60,10 +87,7 @@ impl Viewport {
|
||||||
.board()
|
.board()
|
||||||
.locate_pin_at_point(
|
.locate_pin_at_point(
|
||||||
workspace.appearance_panel.active,
|
workspace.appearance_panel.active,
|
||||||
Vector2::new(
|
pointer_scene,
|
||||||
pointer_scene_pos.x as i64,
|
|
||||||
pointer_scene_pos.y as i64,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
workspace.selection.pins.toggle(pin_selector);
|
workspace.selection.pins.toggle(pin_selector);
|
||||||
|
|
@ -71,6 +95,12 @@ impl Viewport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if primary_released {
|
||||||
|
if let Some(interactor) = self.crossing_drag_selection_interactor.take() {
|
||||||
|
workspace.selection = interactor.selection().clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.zoom_to_fit_if_scheduled(workspace);
|
self.zoom_to_fit_if_scheduled(workspace);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
use derive_getters::Getters;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Rect2, Vector2,
|
||||||
|
board::{Board, interactors::InteractiveInput, selections::PersistableSelection},
|
||||||
|
layout::LayerId,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, Getters, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct CrossingDragSelectionInteractor {
|
||||||
|
origin: Vector2<i64>,
|
||||||
|
selection: PersistableSelection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CrossingDragSelectionInteractor {
|
||||||
|
pub fn new(origin: Vector2<i64>) -> Self {
|
||||||
|
Self {
|
||||||
|
origin,
|
||||||
|
selection: PersistableSelection::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, board: &Board, input: InteractiveInput) {
|
||||||
|
let rect = Rect2::new(self.origin, input.pointer);
|
||||||
|
|
||||||
|
self.selection = PersistableSelection::new();
|
||||||
|
|
||||||
|
for layer_index in 0..*board.layout().layer_count() {
|
||||||
|
let layer = LayerId::new(layer_index);
|
||||||
|
|
||||||
|
for selector in board.locate_components_intersecting_rect(layer, rect) {
|
||||||
|
self.selection.components.0.insert(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
for selector in board.locate_pins_intersecting_rect(layer, rect) {
|
||||||
|
self.selection.pins.0.insert(selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, Getters, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct WindowDragSelectionInteractor {
|
||||||
|
origin: Vector2<i64>,
|
||||||
|
selection: PersistableSelection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowDragSelectionInteractor {
|
||||||
|
pub fn new(origin: Vector2<i64>) -> Self {
|
||||||
|
Self {
|
||||||
|
origin,
|
||||||
|
selection: PersistableSelection::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, board: &Board, input: InteractiveInput) {
|
||||||
|
let rect = Rect2::new(self.origin, input.pointer);
|
||||||
|
|
||||||
|
self.selection = PersistableSelection::new();
|
||||||
|
|
||||||
|
for layer_index in 0..*board.layout().layer_count() {
|
||||||
|
let layer = LayerId::new(layer_index);
|
||||||
|
|
||||||
|
for selector in board.locate_components_inside_rect(layer, rect) {
|
||||||
|
self.selection.components.0.insert(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
for selector in board.locate_pins_inside_rect(layer, rect) {
|
||||||
|
self.selection.pins.0.insert(selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
mod drag_selection;
|
||||||
|
|
||||||
|
pub use drag_selection::CrossingDragSelectionInteractor;
|
||||||
|
|
||||||
|
use crate::Vector2;
|
||||||
|
|
||||||
|
pub struct InteractiveInput {
|
||||||
|
pointer: Vector2<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InteractiveInput {
|
||||||
|
pub fn new(pointer: Vector2<i64>) -> Self {
|
||||||
|
Self { pointer }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ impl Board {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn locate_component_intersecting_rect(
|
pub fn locate_components_intersecting_rect(
|
||||||
&self,
|
&self,
|
||||||
layer: LayerId,
|
layer: LayerId,
|
||||||
rect: Rect2<i64>,
|
rect: Rect2<i64>,
|
||||||
|
|
@ -59,7 +59,7 @@ impl Board {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn locate_component_inside_rect(
|
pub fn locate_components_inside_rect(
|
||||||
&self,
|
&self,
|
||||||
layer: LayerId,
|
layer: LayerId,
|
||||||
rect: Rect2<i64>,
|
rect: Rect2<i64>,
|
||||||
|
|
@ -104,7 +104,7 @@ impl Board {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn locate_pin_intersecting_rect(
|
pub fn locate_pins_intersecting_rect(
|
||||||
&self,
|
&self,
|
||||||
layer: LayerId,
|
layer: LayerId,
|
||||||
rect: Rect2<i64>,
|
rect: Rect2<i64>,
|
||||||
|
|
@ -129,7 +129,7 @@ impl Board {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn locate_pin_inside_rect(
|
pub fn locate_pins_inside_rect(
|
||||||
&self,
|
&self,
|
||||||
layer: LayerId,
|
layer: LayerId,
|
||||||
rect: Rect2<i64>,
|
rect: Rect2<i64>,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
pub mod interactors;
|
||||||
mod layer;
|
mod layer;
|
||||||
mod locate;
|
mod locate;
|
||||||
mod resolve;
|
mod resolve;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ pub use crate::board::Board;
|
||||||
pub use crate::board::LayerDesc;
|
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::{CrossingDragSelectionInteractor, InteractiveInput};
|
||||||
pub use crate::board::selections;
|
pub use crate::board::selections;
|
||||||
pub use crate::layout::LayerId;
|
pub use crate::layout::LayerId;
|
||||||
pub use crate::layout::Layout;
|
pub use crate::layout::Layout;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue