mirror of https://codeberg.org/topola/topola.git
Move some GUI logic and workspace to new `Controller` struct
This commit is contained in:
parent
b2ef23c06c
commit
65f96f1fc2
|
|
@ -9,7 +9,7 @@ use topola::board::Board;
|
||||||
use unic_langid::langid;
|
use unic_langid::langid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
menu_bar::MenuBar, translator::Translator, viewport::Viewport, workspace::GuiWorkspace,
|
controller::Controller, menu_bar::MenuBar, translator::Translator, viewport::Viewport,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
|
@ -22,7 +22,7 @@ pub struct App {
|
||||||
|
|
||||||
menu_bar: MenuBar,
|
menu_bar: MenuBar,
|
||||||
viewport: Viewport,
|
viewport: Viewport,
|
||||||
workspace: Option<GuiWorkspace>,
|
controller: Option<Controller>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for App {
|
impl Default for App {
|
||||||
|
|
@ -32,7 +32,7 @@ impl Default for App {
|
||||||
content_channel: channel(),
|
content_channel: channel(),
|
||||||
menu_bar: MenuBar::new(),
|
menu_bar: MenuBar::new(),
|
||||||
viewport: Viewport::new(),
|
viewport: Viewport::new(),
|
||||||
workspace: None,
|
controller: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +52,7 @@ impl App {
|
||||||
|
|
||||||
fn update_state(&mut self) {
|
fn update_state(&mut self) {
|
||||||
if let Ok(data) = self.content_channel.1.try_recv() {
|
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()),
|
Board::from_specctra(data.unwrap()),
|
||||||
&self.translator,
|
&self.translator,
|
||||||
));
|
));
|
||||||
|
|
@ -130,15 +130,15 @@ impl eframe::App for App {
|
||||||
|
|
||||||
self.update_state();
|
self.update_state();
|
||||||
|
|
||||||
if let Some(ref mut workspace) = self.workspace {
|
if let Some(ref mut controller) = self.controller {
|
||||||
workspace.update_appearance_panel(ctx);
|
controller.update_appearance_panel(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.viewport.update(
|
self.viewport.update(
|
||||||
&self.translator,
|
&self.translator,
|
||||||
ctx,
|
ctx,
|
||||||
&self.menu_bar,
|
&self.menu_bar,
|
||||||
self.workspace.as_mut(),
|
self.controller.as_mut(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.update_locale();
|
self.update_locale();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use crate::{viewport::Viewport, workspace::GuiWorkspace};
|
use crate::{controller::Controller, viewport::Viewport};
|
||||||
use topola::{
|
use topola::{
|
||||||
Orientation, Vector2, Workspace,
|
Orientation, Vector2, Workspace,
|
||||||
layout::primitives::{Joint, Polygon, Segment, Via},
|
layout::primitives::{Joint, Polygon, Segment, Via},
|
||||||
|
|
@ -21,7 +21,7 @@ impl Display {
|
||||||
ui: &egui::Ui,
|
ui: &egui::Ui,
|
||||||
//menu_bar: &MenuBar,
|
//menu_bar: &MenuBar,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
workspace: &GuiWorkspace,
|
workspace: &Controller,
|
||||||
) {
|
) {
|
||||||
self.display_layout(ctx, ui, /*menu_bar,*/ viewport, workspace);
|
self.display_layout(ctx, ui, /*menu_bar,*/ viewport, workspace);
|
||||||
self.display_repulsions(ui, viewport, workspace);
|
self.display_repulsions(ui, viewport, workspace);
|
||||||
|
|
@ -37,7 +37,7 @@ impl Display {
|
||||||
ui: &egui::Ui,
|
ui: &egui::Ui,
|
||||||
//menu_bar: &MenuBar,
|
//menu_bar: &MenuBar,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
workspace: &GuiWorkspace,
|
workspace: &Controller,
|
||||||
) {
|
) {
|
||||||
let board = workspace.workspace.board();
|
let board = workspace.workspace.board();
|
||||||
let layout = board.layout();
|
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 board = workspace.workspace.board();
|
||||||
let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::YELLOW);
|
let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::YELLOW);
|
||||||
|
|
||||||
|
|
@ -201,12 +201,7 @@ impl Display {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_attractions(
|
fn display_attractions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) {
|
||||||
&mut self,
|
|
||||||
ui: &egui::Ui,
|
|
||||||
viewport: &Viewport,
|
|
||||||
workspace: &GuiWorkspace,
|
|
||||||
) {
|
|
||||||
let board = workspace.workspace.board();
|
let board = workspace.workspace.board();
|
||||||
let layout = board.layout();
|
let layout = board.layout();
|
||||||
let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::BLUE);
|
let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::BLUE);
|
||||||
|
|
@ -336,7 +331,7 @@ impl Display {
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
ui: &egui::Ui,
|
ui: &egui::Ui,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
workspace: &GuiWorkspace,
|
workspace: &Controller,
|
||||||
) {
|
) {
|
||||||
let board = workspace.workspace.board();
|
let board = workspace.workspace.board();
|
||||||
let layout = board.layout();
|
let layout = board.layout();
|
||||||
|
|
@ -413,7 +408,7 @@ impl Display {
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
ui: &egui::Ui,
|
ui: &egui::Ui,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
workspace: &GuiWorkspace,
|
workspace: &Controller,
|
||||||
) {
|
) {
|
||||||
let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else {
|
let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -483,7 +478,7 @@ impl Display {
|
||||||
_ctx: &egui::Context,
|
_ctx: &egui::Context,
|
||||||
ui: &egui::Ui,
|
ui: &egui::Ui,
|
||||||
_viewport: &Viewport,
|
_viewport: &Viewport,
|
||||||
workspace: &GuiWorkspace,
|
workspace: &Controller,
|
||||||
) {
|
) {
|
||||||
let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else {
|
let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@
|
||||||
mod action;
|
mod action;
|
||||||
mod actions;
|
mod actions;
|
||||||
mod app;
|
mod app;
|
||||||
|
mod controller;
|
||||||
mod display;
|
mod display;
|
||||||
mod layers_panel;
|
mod layers_panel;
|
||||||
mod menu_bar;
|
mod menu_bar;
|
||||||
mod translator;
|
mod translator;
|
||||||
mod viewport;
|
mod viewport;
|
||||||
mod workspace;
|
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,13 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use egui::Pos2;
|
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 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,
|
||||||
master_interactor: Option<MasterInteractor>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Viewport {
|
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)),
|
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,
|
||||||
master_interactor: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,7 +26,7 @@ impl Viewport {
|
||||||
tr: &Translator,
|
tr: &Translator,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
menu_bar: &MenuBar,
|
menu_bar: &MenuBar,
|
||||||
workspace: Option<&mut GuiWorkspace>,
|
controller: Option<&mut Controller>,
|
||||||
) {
|
) {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||||
|
|
@ -44,9 +41,9 @@ impl Viewport {
|
||||||
.zoom_range(zoom_range.clone())
|
.zoom_range(zoom_range.clone())
|
||||||
.drag_pan_buttons(egui::DragPanButtons::MIDDLE)
|
.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 controller) = controller {
|
||||||
let mut display = Display::new();
|
let mut display = Display::new();
|
||||||
display.update(ctx, ui, &self, workspace);
|
display.update(ctx, ui, &self, controller);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.response;
|
.response;
|
||||||
|
|
@ -56,143 +53,29 @@ impl Viewport {
|
||||||
let scene_to_viewport =
|
let scene_to_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(controller) = controller {
|
||||||
workspace.advance_state_by_dt(
|
controller.advance_state_by_dt(
|
||||||
tr,
|
tr,
|
||||||
menu_bar.fix_step_rate.then_some(menu_bar.step_rate),
|
menu_bar.fix_step_rate.then_some(menu_bar.step_rate),
|
||||||
ctx.input(|i| {
|
ctx.input(|i| {
|
||||||
if i.stable_dt <= i.predicted_dt {
|
if i.stable_dt <= i.predicted_dt {
|
||||||
i.stable_dt
|
i.stable_dt
|
||||||
} else {
|
} else {
|
||||||
// Clamp dt to egui's predicted dt to
|
|
||||||
// additionally safeguard against stuttering.
|
|
||||||
i.predicted_dt
|
i.predicted_dt
|
||||||
}
|
}
|
||||||
}) as f64,
|
}) as f64,
|
||||||
);
|
);
|
||||||
|
|
||||||
let escape_pressed = ctx.input(|i| i.key_pressed(egui::Key::Escape));
|
controller.handle_input(
|
||||||
if escape_pressed {
|
tr,
|
||||||
if let Some(interactor) = self.master_interactor.as_mut() {
|
ctx,
|
||||||
let board = match &mut workspace.workspace {
|
menu_bar.fix_step_rate.then_some(menu_bar.step_rate),
|
||||||
Workspace::Board(workspace) => &mut workspace.board,
|
scene_to_viewport,
|
||||||
Workspace::Autorouter(_) => panic!("expected board workspace"),
|
response.hovered(),
|
||||||
};
|
ui,
|
||||||
interactor.abort(board);
|
);
|
||||||
*workspace.workspace.selection_mut() = interactor.selection().clone();
|
|
||||||
}
|
|
||||||
self.master_interactor = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let primary_pressed =
|
self.zoom_to_fit_if_scheduled(controller);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -210,7 +93,8 @@ impl Viewport {
|
||||||
rect_in_scene: egui::Rect,
|
rect_in_scene: egui::Rect,
|
||||||
zoom_range: egui::Rangef,
|
zoom_range: egui::Rangef,
|
||||||
) -> egui::emath::TSTransform {
|
) -> 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();
|
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:
|
// 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)
|
* 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 {
|
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.ref_scene_rect = self.scene_rect.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scheduled_zoom_to_fit = false;
|
self.scheduled_zoom_to_fit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn boundary_bounding_box(workspace: &GuiWorkspace) -> egui::Rect {
|
fn boundary_bounding_box(controller: &Controller) -> egui::Rect {
|
||||||
let first = workspace.workspace.board().layout().boundary()[0];
|
let first = controller.workspace.board().layout().boundary()[0];
|
||||||
|
|
||||||
let mut min_x = first[0];
|
let mut min_x = first[0];
|
||||||
let mut max_x = first[0];
|
let mut max_x = first[0];
|
||||||
let mut min_y = first[1];
|
let mut min_y = first[1];
|
||||||
let mut max_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 {
|
if point[0] < min_x {
|
||||||
min_x = point[0];
|
min_x = point[0];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
mod master;
|
||||||
|
|
||||||
|
pub use master::MasterInteractor;
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
pub mod interactors;
|
||||||
|
|
||||||
use rand::RngExt;
|
use rand::RngExt;
|
||||||
use rand_distr::{Distribution, Normal};
|
use rand_distr::{Distribution, Normal};
|
||||||
use undoredo::{FlushDelta, ResetDelta};
|
use undoredo::{FlushDelta, ResetDelta};
|
||||||
|
|
@ -3,10 +3,7 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::Board,
|
board::Board, layout::compounds::ComponentId, rect::Rect2, selections::ComponentSelection,
|
||||||
layout::compounds::ComponentId,
|
|
||||||
rect::Rect2,
|
|
||||||
selections::ComponentSelection,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,7 @@ use derive_more::Constructor;
|
||||||
use undoredo::ResetDelta;
|
use undoredo::ResetDelta;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::Board,
|
board::Board, interactor::Interactor, layout::LayerId, selections::ComponentSelection,
|
||||||
interactor::Interactor,
|
|
||||||
layout::LayerId,
|
|
||||||
selections::ComponentSelection,
|
|
||||||
vector::Vector2,
|
vector::Vector2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -22,8 +19,6 @@ pub struct DragMoveInteractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interactor for DragMoveInteractor {
|
impl Interactor for DragMoveInteractor {
|
||||||
fn delete(&mut self, _board: &mut Board) {}
|
|
||||||
|
|
||||||
fn hold(&mut self, board: &mut Board, _layer: LayerId, pointer: Vector2<i64>) {
|
fn hold(&mut self, board: &mut Board, _layer: LayerId, pointer: Vector2<i64>) {
|
||||||
board.reset_delta();
|
board.reset_delta();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,6 @@ impl DragSelectInteractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interactor for DragSelectInteractor {
|
impl Interactor for DragSelectInteractor {
|
||||||
fn delete(&mut self, _board: &mut Board) {}
|
|
||||||
|
|
||||||
fn hold(&mut self, board: &mut Board, _layer: LayerId, pointer: Vector2<i64>) {
|
fn hold(&mut self, board: &mut Board, _layer: LayerId, pointer: Vector2<i64>) {
|
||||||
self.selection = PersistableSelection::new();
|
self.selection = PersistableSelection::new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,6 @@ impl SelectInteractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interactor for SelectInteractor {
|
impl Interactor for SelectInteractor {
|
||||||
fn delete(&mut self, _board: &mut Board) {}
|
|
||||||
|
|
||||||
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
|
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
|
||||||
let contain = if pointer.x >= self.origin.x {
|
let contain = if pointer.x >= self.origin.x {
|
||||||
SelectionContainMode::Window
|
SelectionContainMode::Window
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,7 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::Board,
|
board::Board, layout::compounds::ComponentId, selections::ComponentSelection, vector::Vector2,
|
||||||
layout::compounds::ComponentId,
|
|
||||||
selections::ComponentSelection,
|
|
||||||
vector::Vector2,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
|
|
|
||||||
|
|
@ -22,20 +22,12 @@ pub trait CompassDirection<T>: Copy + PartialEq + Into<Vector2<T>> {
|
||||||
let zero = T::zero();
|
let zero = T::zero();
|
||||||
|
|
||||||
if axis.y.is_zero() {
|
if axis.y.is_zero() {
|
||||||
let x = if axis.x < zero {
|
let x = if axis.x < zero { -vector.x } else { vector.x };
|
||||||
-vector.x
|
|
||||||
} else {
|
|
||||||
vector.x
|
|
||||||
};
|
|
||||||
return Vector2::new(x, zero);
|
return Vector2::new(x, zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
if axis.x.is_zero() {
|
if axis.x.is_zero() {
|
||||||
let y = if axis.y < zero {
|
let y = if axis.y < zero { -vector.y } else { vector.y };
|
||||||
-vector.y
|
|
||||||
} else {
|
|
||||||
vector.y
|
|
||||||
};
|
|
||||||
return Vector2::new(zero, y);
|
return Vector2::new(zero, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,22 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::{
|
board::{Board, interactors::SelectInteractor, selections::PersistableSelection},
|
||||||
Board,
|
|
||||||
interactors::SelectInteractor,
|
|
||||||
selections::PersistableSelection,
|
|
||||||
},
|
|
||||||
layout::LayerId,
|
layout::LayerId,
|
||||||
vector::Vector2,
|
vector::Vector2,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait Interactor {
|
pub trait Interactor {
|
||||||
fn delete(&mut self, board: &mut Board);
|
fn step(&mut self, board: &mut Board) {}
|
||||||
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>);
|
fn delete(&mut self, board: &mut Board) {}
|
||||||
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>);
|
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {}
|
||||||
fn abort(&mut self, board: &mut Board);
|
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {}
|
||||||
|
fn abort(&mut self, board: &mut Board) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MasterInteractor {
|
pub enum MasterInteractor {
|
||||||
Board(crate::board::interactors::MasterInteractor),
|
Board(crate::board::interactors::MasterInteractor),
|
||||||
|
Autoplacer(crate::autoplacer::interactors::MasterInteractor),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MasterInteractor {
|
impl MasterInteractor {
|
||||||
|
|
@ -31,38 +29,51 @@ impl MasterInteractor {
|
||||||
pub fn selection(&self) -> &PersistableSelection {
|
pub fn selection(&self) -> &PersistableSelection {
|
||||||
match self {
|
match self {
|
||||||
Self::Board(interactor) => interactor.selection(),
|
Self::Board(interactor) => interactor.selection(),
|
||||||
|
Self::Autoplacer(interactor) => interactor.selection(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_interactor(&self) -> &Option<SelectInteractor> {
|
pub fn select_interactor(&self) -> &Option<SelectInteractor> {
|
||||||
match self {
|
match self {
|
||||||
Self::Board(interactor) => interactor.select_interactor(),
|
Self::Board(interactor) => interactor.select_interactor(),
|
||||||
|
Self::Autoplacer(interactor) => interactor.select_interactor(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interactor for MasterInteractor {
|
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) {
|
fn delete(&mut self, board: &mut Board) {
|
||||||
match self {
|
match self {
|
||||||
Self::Board(interactor) => interactor.delete(board),
|
Self::Board(interactor) => interactor.delete(board),
|
||||||
|
Self::Autoplacer(interactor) => interactor.delete(board),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
|
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Board(interactor) => interactor.hold(board, layer, pointer),
|
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>) {
|
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Board(interactor) => interactor.release(board, layer, pointer),
|
Self::Board(interactor) => interactor.release(board, layer, pointer),
|
||||||
|
Self::Autoplacer(interactor) => interactor.release(board, layer, pointer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn abort(&mut self, board: &mut Board) {
|
fn abort(&mut self, board: &mut Board) {
|
||||||
match self {
|
match self {
|
||||||
Self::Board(interactor) => interactor.abort(board),
|
Self::Board(interactor) => interactor.abort(board),
|
||||||
|
Self::Autoplacer(interactor) => interactor.abort(board),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,12 +176,10 @@ impl Layout {
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.flat_map(|joint_id| self.locate_joint_infringements(joint_id).map(Into::into))
|
.flat_map(|joint_id| self.locate_joint_infringements(joint_id).map(Into::into))
|
||||||
.chain(
|
.chain(pin.segments.iter().copied().flat_map(|segment_id| {
|
||||||
pin.segments.iter().copied().flat_map(|segment_id| {
|
self.locate_segment_infringements(segment_id)
|
||||||
self.locate_segment_infringements(segment_id)
|
.map(Into::into)
|
||||||
.map(Into::into)
|
}))
|
||||||
}),
|
|
||||||
)
|
|
||||||
.chain(
|
.chain(
|
||||||
pin.vias
|
pin.vias
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
|
|
@ -121,11 +121,7 @@ impl Layout {
|
||||||
infringer_bbox.intersection(infringee_bbox)
|
infringer_bbox.intersection(infringee_bbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn via_via_rect_overlap(
|
pub fn via_via_rect_overlap(&self, infringer: ViaId, infringee: ViaId) -> Option<Rect2<i64>> {
|
||||||
&self,
|
|
||||||
infringer: ViaId,
|
|
||||||
infringee: ViaId,
|
|
||||||
) -> Option<Rect2<i64>> {
|
|
||||||
let infringer_bbox = self.via(infringer).bbox().xy();
|
let infringer_bbox = self.via(infringer).bbox().xy();
|
||||||
let infringee_bbox = self.via(infringee).bbox().xy();
|
let infringee_bbox = self.via(infringee).bbox().xy();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ use specctra::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::{Board, LayerDesc, LayerSide, LayerType},
|
board::{Board, LayerDesc, LayerSide, LayerType},
|
||||||
|
layout::primitives::{JointSpec, Polygon, Segment, SegmentSpec},
|
||||||
layout::{
|
layout::{
|
||||||
LayerId,
|
LayerId,
|
||||||
compounds::{ComponentId, NetId, PinId, PinSpec},
|
compounds::{ComponentId, NetId, PinId, PinSpec},
|
||||||
},
|
},
|
||||||
layout::primitives::{JointSpec, Polygon, Segment, SegmentSpec},
|
|
||||||
vector::Vector2,
|
vector::Vector2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub struct BoardWorkspace {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue