Move some GUI logic and workspace to new `Controller` struct

This commit is contained in:
Mikolaj Wielgus 2026-06-05 13:43:22 +02:00
parent b2ef23c06c
commit 65f96f1fc2
20 changed files with 334 additions and 281 deletions

View File

@ -9,7 +9,7 @@ use topola::board::Board;
use unic_langid::langid;
use crate::{
menu_bar::MenuBar, translator::Translator, viewport::Viewport, workspace::GuiWorkspace,
controller::Controller, menu_bar::MenuBar, translator::Translator, viewport::Viewport,
};
pub struct App {
@ -22,7 +22,7 @@ pub struct App {
menu_bar: MenuBar,
viewport: Viewport,
workspace: Option<GuiWorkspace>,
controller: Option<Controller>,
}
impl Default for App {
@ -32,7 +32,7 @@ impl Default for App {
content_channel: channel(),
menu_bar: MenuBar::new(),
viewport: Viewport::new(),
workspace: None,
controller: None,
}
}
}
@ -52,7 +52,7 @@ impl App {
fn update_state(&mut self) {
if let Ok(data) = self.content_channel.1.try_recv() {
self.workspace = Some(GuiWorkspace::new(
self.controller = Some(Controller::new(
Board::from_specctra(data.unwrap()),
&self.translator,
));
@ -130,15 +130,15 @@ impl eframe::App for App {
self.update_state();
if let Some(ref mut workspace) = self.workspace {
workspace.update_appearance_panel(ctx);
if let Some(ref mut controller) = self.controller {
controller.update_appearance_panel(ctx);
}
self.viewport.update(
&self.translator,
ctx,
&self.menu_bar,
self.workspace.as_mut(),
self.controller.as_mut(),
);
self.update_locale();

View File

@ -0,0 +1,187 @@
// SPDX-FileCopyrightText: 2026 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::{ops::ControlFlow, time::Instant};
use topola::{Interactor, MasterInteractor, Vector2, Workspace, board::Board};
use crate::{layers_panel::LayersPanel, translator::Translator};
pub struct Controller {
pub workspace: Workspace,
pub appearance_panel: LayersPanel,
pub master_interactor: Option<MasterInteractor>,
pub dt_accum: f64,
}
impl Controller {
pub fn new(board: Board, tr: &Translator) -> Self {
let appearance_panel = LayersPanel::new(&board);
Self {
workspace: Workspace::new_board(board),
appearance_panel,
master_interactor: None,
dt_accum: 0.0,
}
}
pub fn advance_state_by_dt(
&mut self,
tr: &Translator,
step_rate: Option<f64>,
dt: f64,
) -> bool {
let instant = Instant::now();
if step_rate.is_some() {
self.dt_accum += dt;
}
while step_rate.is_none_or(|step_rate| self.dt_accum >= 1.0 / step_rate) {
if let Some(step_rate) = step_rate {
self.dt_accum -= 1.0 / step_rate;
}
if let ControlFlow::Break(()) = self.step(tr) {
return true;
}
// Hard limit: never spend more time on advancing state than the
// duration of last frame to prevent stuttering.
// Of course, this does not safeguard against infinite loops.
if instant.elapsed().as_secs_f64() >= dt {
return false;
}
}
true
}
pub fn step(&mut self, tr: &Translator) -> ControlFlow<()> {
self.master_interactor
.as_mut()
.map(|master_interactor| master_interactor.step(self.workspace.board_mut()));
ControlFlow::Continue(())
}
pub fn update_appearance_panel(&mut self, ctx: &egui::Context) {
let Self {
workspace,
appearance_panel,
master_interactor: _,
dt_accum,
} = self;
appearance_panel.update(ctx, workspace.board_mut());
}
pub fn handle_input(
&mut self,
tr: &Translator,
ctx: &egui::Context,
step_rate: Option<f64>,
scene_to_viewport: egui::emath::TSTransform,
scene_hovered: bool,
ui: &mut egui::Ui,
) {
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
if let (Some(interactor), Workspace::Board(workspace)) =
(&mut self.master_interactor, &mut self.workspace)
{
interactor.abort(&mut workspace.board);
*self.workspace.selection_mut() = interactor.selection().clone();
}
self.master_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));
let delete_pressed = ctx.input(|i| i.key_pressed(egui::Key::Delete));
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_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 && scene_hovered {
self.master_interactor =
Some(MasterInteractor::new(self.workspace.selection().clone()));
}
if let (Some(interactor), Workspace::Board(workspace)) =
(&mut self.master_interactor, &mut self.workspace)
{
if primary_down {
interactor.hold(
&mut workspace.board,
self.appearance_panel.active,
pointer_on_scene,
);
if let Some(select_interactor) = interactor.select_interactor().as_ref() {
let origin = *select_interactor.origin();
let drag_rect_scene = egui::Rect::from_min_max(
egui::pos2(
origin.x.min(pointer_on_scene.x) as f32,
origin.y.min(pointer_on_scene.y) as f32,
),
egui::pos2(
origin.x.max(pointer_on_scene.x) as f32,
origin.y.max(pointer_on_scene.y) as f32,
),
);
let drag_rect_on_viewport = egui::Rect::from_min_max(
scene_to_viewport * drag_rect_scene.min,
scene_to_viewport * drag_rect_scene.max,
);
let boundary_color = if pointer_on_scene.x >= origin.x {
egui::Color32::YELLOW
} else {
egui::Color32::from_rgb(80, 160, 255)
};
ui.painter().rect(
drag_rect_on_viewport,
egui::CornerRadius::ZERO,
egui::Color32::from_rgba_unmultiplied(80, 160, 255, 48),
egui::Stroke::new(1.5, boundary_color),
egui::StrokeKind::Outside,
);
}
}
}
}
if primary_released {
if let Some(mut interactor) = self.master_interactor.take() {
let active = self.appearance_panel.active;
let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| {
interactor
.select_interactor()
.as_ref()
.map(|select_interactor| *select_interactor.origin())
.unwrap_or(Vector2::new(0, 0))
});
if let Workspace::Board(workspace) = &mut self.workspace {
interactor.release(&mut workspace.board, active, pointer_for_scene);
*self.workspace.selection_mut() = interactor.selection().clone();
}
}
}
if delete_pressed {
let mut interactor = MasterInteractor::new(self.workspace.selection().clone());
if let Workspace::Board(workspace) = &mut self.workspace {
interactor.delete(&mut workspace.board);
*self.workspace.selection_mut() = interactor.selection().clone();
}
}
}
}

View File

@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{viewport::Viewport, workspace::GuiWorkspace};
use crate::{controller::Controller, viewport::Viewport};
use topola::{
Orientation, Vector2, Workspace,
layout::primitives::{Joint, Polygon, Segment, Via},
@ -21,7 +21,7 @@ impl Display {
ui: &egui::Ui,
//menu_bar: &MenuBar,
viewport: &Viewport,
workspace: &GuiWorkspace,
workspace: &Controller,
) {
self.display_layout(ctx, ui, /*menu_bar,*/ viewport, workspace);
self.display_repulsions(ui, viewport, workspace);
@ -37,7 +37,7 @@ impl Display {
ui: &egui::Ui,
//menu_bar: &MenuBar,
viewport: &Viewport,
workspace: &GuiWorkspace,
workspace: &Controller,
) {
let board = workspace.workspace.board();
let layout = board.layout();
@ -160,7 +160,7 @@ impl Display {
);
}
fn display_repulsions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &GuiWorkspace) {
fn display_repulsions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) {
let board = workspace.workspace.board();
let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::YELLOW);
@ -201,12 +201,7 @@ impl Display {
}
}
fn display_attractions(
&mut self,
ui: &egui::Ui,
viewport: &Viewport,
workspace: &GuiWorkspace,
) {
fn display_attractions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) {
let board = workspace.workspace.board();
let layout = board.layout();
let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::BLUE);
@ -336,7 +331,7 @@ impl Display {
ctx: &egui::Context,
ui: &egui::Ui,
viewport: &Viewport,
workspace: &GuiWorkspace,
workspace: &Controller,
) {
let board = workspace.workspace.board();
let layout = board.layout();
@ -413,7 +408,7 @@ impl Display {
ctx: &egui::Context,
ui: &egui::Ui,
viewport: &Viewport,
workspace: &GuiWorkspace,
workspace: &Controller,
) {
let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else {
return;
@ -483,7 +478,7 @@ impl Display {
_ctx: &egui::Context,
ui: &egui::Ui,
_viewport: &Viewport,
workspace: &GuiWorkspace,
workspace: &Controller,
) {
let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else {
return;

View File

@ -7,12 +7,12 @@
mod action;
mod actions;
mod app;
mod controller;
mod display;
mod layers_panel;
mod menu_bar;
mod translator;
mod viewport;
mod workspace;
use crate::app::App;

View File

@ -3,15 +3,13 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use egui::Pos2;
use topola::{Interactor, MasterInteractor, Vector2, Workspace};
use crate::{display::Display, menu_bar::MenuBar, translator::Translator, workspace::GuiWorkspace};
use crate::{controller::Controller, display::Display, menu_bar::MenuBar, translator::Translator};
pub struct Viewport {
pub scene_rect: egui::Rect,
pub ref_scene_rect: egui::Rect,
pub scheduled_zoom_to_fit: bool,
master_interactor: Option<MasterInteractor>,
}
impl Viewport {
@ -20,7 +18,6 @@ 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,
master_interactor: None,
}
}
@ -29,7 +26,7 @@ impl Viewport {
tr: &Translator,
ctx: &egui::Context,
menu_bar: &MenuBar,
workspace: Option<&mut GuiWorkspace>,
controller: Option<&mut Controller>,
) {
egui::CentralPanel::default().show(ctx, |ui| {
egui::Frame::canvas(ui.style()).show(ui, |ui| {
@ -44,9 +41,9 @@ impl Viewport {
.zoom_range(zoom_range.clone())
.drag_pan_buttons(egui::DragPanButtons::MIDDLE)
.show(ui, &mut scene_rect, |ui| {
if let Some(ref workspace) = workspace {
if let Some(ref controller) = controller {
let mut display = Display::new();
display.update(ctx, ui, &self, workspace);
display.update(ctx, ui, &self, controller);
}
})
.response;
@ -56,143 +53,29 @@ impl Viewport {
let scene_to_viewport =
Self::fit_to_rect_in_scene(viewport_rect, scene_rect, zoom_range.into());
if let Some(workspace) = workspace {
workspace.advance_state_by_dt(
if let Some(controller) = controller {
controller.advance_state_by_dt(
tr,
menu_bar.fix_step_rate.then_some(menu_bar.step_rate),
ctx.input(|i| {
if i.stable_dt <= i.predicted_dt {
i.stable_dt
} else {
// Clamp dt to egui's predicted dt to
// additionally safeguard against stuttering.
i.predicted_dt
}
}) as f64,
);
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;
}
controller.handle_input(
tr,
ctx,
menu_bar.fix_step_rate.then_some(menu_bar.step_rate),
scene_to_viewport,
response.hovered(),
ui,
);
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));
let delete_pressed = ctx.input(|i| i.key_pressed(egui::Key::Delete));
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_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.master_interactor = Some(MasterInteractor::new(
workspace.workspace.selection().clone(),
));
}
if let Some(interactor) = self.master_interactor.as_mut() {
if primary_down {
let board = match &mut workspace.workspace {
Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_) => panic!("expected board workspace"),
};
interactor.hold(
board,
workspace.appearance_panel.active,
pointer_on_scene,
);
if let Some(select_interactor) =
interactor.select_interactor().as_ref()
{
let origin = *select_interactor.origin();
let drag_rect_scene = egui::Rect::from_min_max(
egui::pos2(
origin.x.min(pointer_on_scene.x) as f32,
origin.y.min(pointer_on_scene.y) as f32,
),
egui::pos2(
origin.x.max(pointer_on_scene.x) as f32,
origin.y.max(pointer_on_scene.y) as f32,
),
);
let drag_rect_on_viewport = egui::Rect::from_min_max(
scene_to_viewport * drag_rect_scene.min,
scene_to_viewport * drag_rect_scene.max,
);
let boundary_color = if pointer_on_scene.x >= origin.x {
egui::Color32::YELLOW
} else {
egui::Color32::from_rgb(80, 160, 255)
};
ui.painter().rect(
drag_rect_on_viewport,
egui::CornerRadius::ZERO,
egui::Color32::from_rgba_unmultiplied(80, 160, 255, 48),
egui::Stroke::new(1.5, boundary_color),
egui::StrokeKind::Outside,
);
}
}
}
}
if primary_released {
if let Some(mut interactor) = self.master_interactor.take() {
let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| {
interactor
.select_interactor()
.as_ref()
.map(|select_interactor| *select_interactor.origin())
.unwrap_or(Vector2::new(0, 0))
});
let board = match &mut workspace.workspace {
Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_) => panic!("expected board workspace"),
};
interactor.release(
board,
workspace.appearance_panel.active,
pointer_for_scene,
);
*workspace.workspace.selection_mut() = interactor.selection().clone();
}
}
if delete_pressed {
let mut interactor =
MasterInteractor::new(workspace.workspace.selection().clone());
let board = match &mut workspace.workspace {
Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_) => panic!("expected board workspace"),
};
interactor.delete(board);
*workspace.workspace.selection_mut() = interactor.selection().clone();
}
self.zoom_to_fit_if_scheduled(workspace);
self.zoom_to_fit_if_scheduled(controller);
}
})
});
@ -210,7 +93,8 @@ impl Viewport {
rect_in_scene: egui::Rect,
zoom_range: egui::Rangef,
) -> egui::emath::TSTransform {
// Compute the scale factor to fit the bounding rectangle into the available screen size:
// Compute the scale factor to fit the bounding rect into the available
// screen size:
let scale = rect_in_viewport.size() / rect_in_scene.size();
// Use the smaller of the two scales to ensure the whole rectangle fits on the screen:
@ -228,24 +112,24 @@ impl Viewport {
* egui::emath::TSTransform::from_scaling(scale)
}
fn zoom_to_fit_if_scheduled(&mut self, workspace: &GuiWorkspace) {
fn zoom_to_fit_if_scheduled(&mut self, controller: &Controller) {
if self.scheduled_zoom_to_fit {
self.scene_rect = Self::boundary_bounding_box(workspace);
self.scene_rect = Self::boundary_bounding_box(controller);
self.ref_scene_rect = self.scene_rect.clone();
}
self.scheduled_zoom_to_fit = false;
}
fn boundary_bounding_box(workspace: &GuiWorkspace) -> egui::Rect {
let first = workspace.workspace.board().layout().boundary()[0];
fn boundary_bounding_box(controller: &Controller) -> egui::Rect {
let first = controller.workspace.board().layout().boundary()[0];
let mut min_x = first[0];
let mut max_x = first[0];
let mut min_y = first[1];
let mut max_y = first[1];
for point in workspace.workspace.board().layout().boundary()[1..].iter() {
for point in controller.workspace.board().layout().boundary()[1..].iter() {
if point[0] < min_x {
min_x = point[0];
}

View File

@ -1,73 +0,0 @@
// SPDX-FileCopyrightText: 2026 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::{ops::ControlFlow, time::Instant};
use topola::{Workspace, board::Board};
use crate::{layers_panel::LayersPanel, translator::Translator};
pub struct GuiWorkspace {
pub workspace: Workspace,
pub appearance_panel: LayersPanel,
pub dt_accum: f64,
}
impl GuiWorkspace {
pub fn new(board: Board, tr: &Translator) -> Self {
let appearance_panel = LayersPanel::new(&board);
Self {
workspace: Workspace::new_board(board),
appearance_panel,
dt_accum: 0.0,
}
}
pub fn advance_state_by_dt(
&mut self,
tr: &Translator,
step_rate: Option<f64>,
dt: f64,
) -> bool {
let instant = Instant::now();
if step_rate.is_some() {
self.dt_accum += dt;
}
while step_rate.is_none_or(|step_rate| self.dt_accum >= 1.0 / step_rate) {
if let Some(step_rate) = step_rate {
self.dt_accum -= 1.0 / step_rate;
}
if let ControlFlow::Break(()) = self.step(tr) {
return true;
}
// Hard limit: never spend more time on advancing state than the
// duration of last frame to prevent stuttering.
// Of course, this does not safeguard against infinite loops.
if instant.elapsed().as_secs_f64() >= dt {
return false;
}
}
true
}
pub fn step(&mut self, tr: &Translator) -> ControlFlow<()> {
ControlFlow::Continue(())
}
pub fn update_appearance_panel(&mut self, ctx: &egui::Context) {
let Self {
workspace,
appearance_panel,
dt_accum,
} = self;
appearance_panel.update(ctx, workspace.board());
}
}

View File

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2026 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{
autoplacer::{Autoplacer, AutoplacerSchedule},
board::{
Board,
interactors::{MasterInteractor as BoardMasterInteractor, SelectInteractor},
selections::PersistableSelection,
},
interactor::Interactor,
layout::LayerId,
vector::Vector2,
};
pub struct MasterInteractor {
board_master: BoardMasterInteractor,
autoplacer: Autoplacer,
}
impl MasterInteractor {
pub fn new(
board: &mut Board,
selection: PersistableSelection,
schedule: AutoplacerSchedule,
) -> Self {
Self {
board_master: BoardMasterInteractor::new(selection.clone()),
autoplacer: Autoplacer::new(board, selection.components.clone(), schedule),
}
}
pub fn selection(&self) -> &PersistableSelection {
self.board_master.selection()
}
pub fn select_interactor(&self) -> &Option<SelectInteractor> {
self.board_master.select_interactor()
}
}
impl Interactor for MasterInteractor {
fn step(&mut self, board: &mut Board) {
self.autoplacer.step(board);
}
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
self.board_master.hold(board, layer, pointer);
}
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
self.board_master.release(board, layer, pointer);
}
fn abort(&mut self, board: &mut Board) {
self.board_master.abort(board);
}
}

View File

@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: 2026 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0
mod master;
pub use master::MasterInteractor;

View File

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0
pub mod interactors;
use rand::RngExt;
use rand_distr::{Distribution, Normal};
use undoredo::{FlushDelta, ResetDelta};

View File

@ -3,10 +3,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{
board::Board,
layout::compounds::ComponentId,
rect::Rect2,
selections::ComponentSelection,
board::Board, layout::compounds::ComponentId, rect::Rect2, selections::ComponentSelection,
};
impl Board {

View File

@ -7,10 +7,7 @@ use derive_more::Constructor;
use undoredo::ResetDelta;
use crate::{
board::Board,
interactor::Interactor,
layout::LayerId,
selections::ComponentSelection,
board::Board, interactor::Interactor, layout::LayerId, selections::ComponentSelection,
vector::Vector2,
};
@ -22,8 +19,6 @@ pub struct DragMoveInteractor {
}
impl Interactor for DragMoveInteractor {
fn delete(&mut self, _board: &mut Board) {}
fn hold(&mut self, board: &mut Board, _layer: LayerId, pointer: Vector2<i64>) {
board.reset_delta();

View File

@ -51,8 +51,6 @@ impl DragSelectInteractor {
}
impl Interactor for DragSelectInteractor {
fn delete(&mut self, _board: &mut Board) {}
fn hold(&mut self, board: &mut Board, _layer: LayerId, pointer: Vector2<i64>) {
self.selection = PersistableSelection::new();

View File

@ -41,8 +41,6 @@ impl SelectInteractor {
}
impl Interactor for SelectInteractor {
fn delete(&mut self, _board: &mut Board) {}
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
let contain = if pointer.x >= self.origin.x {
SelectionContainMode::Window

View File

@ -3,10 +3,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{
board::Board,
layout::compounds::ComponentId,
selections::ComponentSelection,
vector::Vector2,
board::Board, layout::compounds::ComponentId, selections::ComponentSelection, vector::Vector2,
};
impl Board {

View File

@ -22,20 +22,12 @@ pub trait CompassDirection<T>: Copy + PartialEq + Into<Vector2<T>> {
let zero = T::zero();
if axis.y.is_zero() {
let x = if axis.x < zero {
-vector.x
} else {
vector.x
};
let x = if axis.x < zero { -vector.x } else { vector.x };
return Vector2::new(x, zero);
}
if axis.x.is_zero() {
let y = if axis.y < zero {
-vector.y
} else {
vector.y
};
let y = if axis.y < zero { -vector.y } else { vector.y };
return Vector2::new(zero, y);
}

View File

@ -3,24 +3,22 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{
board::{
Board,
interactors::SelectInteractor,
selections::PersistableSelection,
},
board::{Board, interactors::SelectInteractor, selections::PersistableSelection},
layout::LayerId,
vector::Vector2,
};
pub trait Interactor {
fn delete(&mut self, board: &mut Board);
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>);
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>);
fn abort(&mut self, board: &mut Board);
fn step(&mut self, board: &mut Board) {}
fn delete(&mut self, board: &mut Board) {}
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {}
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {}
fn abort(&mut self, board: &mut Board) {}
}
pub enum MasterInteractor {
Board(crate::board::interactors::MasterInteractor),
Autoplacer(crate::autoplacer::interactors::MasterInteractor),
}
impl MasterInteractor {
@ -31,38 +29,51 @@ impl MasterInteractor {
pub fn selection(&self) -> &PersistableSelection {
match self {
Self::Board(interactor) => interactor.selection(),
Self::Autoplacer(interactor) => interactor.selection(),
}
}
pub fn select_interactor(&self) -> &Option<SelectInteractor> {
match self {
Self::Board(interactor) => interactor.select_interactor(),
Self::Autoplacer(interactor) => interactor.select_interactor(),
}
}
}
impl Interactor for MasterInteractor {
fn step(&mut self, board: &mut Board) {
match self {
Self::Board(interactor) => interactor.step(board),
Self::Autoplacer(interactor) => interactor.step(board),
}
}
fn delete(&mut self, board: &mut Board) {
match self {
Self::Board(interactor) => interactor.delete(board),
Self::Autoplacer(interactor) => interactor.delete(board),
}
}
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
match self {
Self::Board(interactor) => interactor.hold(board, layer, pointer),
Self::Autoplacer(interactor) => interactor.hold(board, layer, pointer),
}
}
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
match self {
Self::Board(interactor) => interactor.release(board, layer, pointer),
Self::Autoplacer(interactor) => interactor.release(board, layer, pointer),
}
}
fn abort(&mut self, board: &mut Board) {
match self {
Self::Board(interactor) => interactor.abort(board),
Self::Autoplacer(interactor) => interactor.abort(board),
}
}
}

View File

@ -176,12 +176,10 @@ impl Layout {
.iter()
.copied()
.flat_map(|joint_id| self.locate_joint_infringements(joint_id).map(Into::into))
.chain(
pin.segments.iter().copied().flat_map(|segment_id| {
self.locate_segment_infringements(segment_id)
.map(Into::into)
}),
)
.chain(pin.segments.iter().copied().flat_map(|segment_id| {
self.locate_segment_infringements(segment_id)
.map(Into::into)
}))
.chain(
pin.vias
.iter()

View File

@ -121,11 +121,7 @@ impl Layout {
infringer_bbox.intersection(infringee_bbox)
}
pub fn via_via_rect_overlap(
&self,
infringer: ViaId,
infringee: ViaId,
) -> Option<Rect2<i64>> {
pub fn via_via_rect_overlap(&self, infringer: ViaId, infringee: ViaId) -> Option<Rect2<i64>> {
let infringer_bbox = self.via(infringer).bbox().xy();
let infringee_bbox = self.via(infringee).bbox().xy();

View File

@ -12,11 +12,11 @@ use specctra::{
use crate::{
board::{Board, LayerDesc, LayerSide, LayerType},
layout::primitives::{JointSpec, Polygon, Segment, SegmentSpec},
layout::{
LayerId,
compounds::{ComponentId, NetId, PinId, PinSpec},
},
layout::primitives::{JointSpec, Polygon, Segment, SegmentSpec},
vector::Vector2,
};

View File

@ -44,6 +44,16 @@ impl Workspace {
}
}
}
pub fn board_mut(&mut self) -> &mut Board {
match self {
Workspace::Board(workspace) => &mut workspace.board,
Workspace::Autorouter(_workspace) => todo!(),
/*Workspace::Autorouter(workspace) => {
workspace.autorouter.router().navmesher_board().board()
}*/
}
}
}
pub struct BoardWorkspace {