mirror of https://codeberg.org/topola/topola.git
201 lines
6.8 KiB
Rust
201 lines
6.8 KiB
Rust
// SPDX-FileCopyrightText: 2024 Topola contributors
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
use std::{
|
|
ops::ControlFlow,
|
|
sync::mpsc::{channel, Receiver, Sender},
|
|
};
|
|
|
|
use topola::{
|
|
autorouter::{execution::Command, history::History},
|
|
board::edit::BoardEdit,
|
|
interactor::{
|
|
activity::{InteractiveEvent, InteractiveEventKind, InteractiveInput},
|
|
Interactor,
|
|
},
|
|
layout::via::ViaWeight,
|
|
math::Circle,
|
|
specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata},
|
|
};
|
|
|
|
use crate::{
|
|
appearance_panel::AppearancePanel, error_dialog::ErrorDialog, menu_bar::MenuBar,
|
|
overlay::Overlay, translator::Translator,
|
|
};
|
|
|
|
/// A loaded design and associated structures.
|
|
pub struct Workspace {
|
|
pub design: SpecctraDesign,
|
|
pub appearance_panel: AppearancePanel,
|
|
pub overlay: Overlay,
|
|
pub interactor: Interactor<SpecctraMesadata>,
|
|
|
|
pub history_channel: (
|
|
Sender<std::io::Result<Result<History, serde_json::Error>>>,
|
|
Receiver<std::io::Result<Result<History, serde_json::Error>>>,
|
|
),
|
|
|
|
update_counter: f32,
|
|
}
|
|
|
|
impl Workspace {
|
|
pub fn new(design: SpecctraDesign, tr: &Translator) -> Result<Self, String> {
|
|
let board = design.make_board(&mut BoardEdit::new());
|
|
let appearance_panel = AppearancePanel::new(&board);
|
|
let overlay = Overlay::new(&board).map_err(|err| {
|
|
format!(
|
|
"{}; {}",
|
|
tr.text("tr-error-unable-to-initialize-overlay"),
|
|
err
|
|
)
|
|
})?;
|
|
Ok(Self {
|
|
design,
|
|
appearance_panel,
|
|
overlay,
|
|
interactor: Interactor::new(board).map_err(|err| {
|
|
format!(
|
|
"{}; {}",
|
|
tr.text("tr-error-unable-to-initialize-overlay"),
|
|
err
|
|
)
|
|
})?,
|
|
history_channel: channel(),
|
|
update_counter: 0.0,
|
|
})
|
|
}
|
|
|
|
/// Advances the app's state by the delta time `dt`. May call
|
|
/// `.update_state()` more than once if the delta time is more than a multiple of
|
|
/// the timestep.
|
|
pub fn advance_state_by_dt(
|
|
&mut self,
|
|
tr: &Translator,
|
|
error_dialog: &mut ErrorDialog,
|
|
frame_timestep: f32,
|
|
interactive_input: &InteractiveInput,
|
|
) {
|
|
self.update_counter += interactive_input.dt;
|
|
while self.update_counter >= frame_timestep {
|
|
self.update_counter -= frame_timestep;
|
|
if let ControlFlow::Break(()) = self.update_state(tr, error_dialog, interactive_input) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn update_state_for_event(
|
|
&mut self,
|
|
tr: &Translator,
|
|
error_dialog: &mut ErrorDialog,
|
|
menu_bar: &MenuBar,
|
|
interactive_input: &InteractiveInput,
|
|
interactive_event: InteractiveEvent,
|
|
) {
|
|
if !self
|
|
.interactor
|
|
.maybe_activity()
|
|
.as_ref()
|
|
.map_or(true, |activity| {
|
|
matches!(activity.maybe_status(), Some(ControlFlow::Break(..)))
|
|
})
|
|
{
|
|
match self
|
|
.interactor
|
|
.update_for_event(interactive_input, interactive_event)
|
|
{
|
|
ControlFlow::Continue(()) | ControlFlow::Break(Ok(())) => {}
|
|
ControlFlow::Break(Err(err)) => {
|
|
error_dialog.push_error("tr-module-invoker", format!("{}", err));
|
|
}
|
|
}
|
|
} else {
|
|
match interactive_event.kind {
|
|
InteractiveEventKind::PointerPrimaryButtonClicked => {
|
|
if menu_bar.is_placing_via {
|
|
self.interactor.execute(Command::PlaceVia(ViaWeight {
|
|
from_layer: 0,
|
|
to_layer: 0,
|
|
circle: Circle {
|
|
pos: interactive_input.pointer_pos,
|
|
r: menu_bar.autorouter_options.router_options.routed_band_width
|
|
/ 2.0,
|
|
},
|
|
maybe_net: Some(1234),
|
|
}));
|
|
} else {
|
|
self.overlay.click(
|
|
self.interactor.invoker().autorouter(),
|
|
&self.appearance_panel,
|
|
interactive_input.pointer_pos,
|
|
);
|
|
}
|
|
}
|
|
InteractiveEventKind::PointerPrimaryButtonDragStarted => {
|
|
self.overlay.drag_start(
|
|
self.interactor.invoker().autorouter(),
|
|
&self.appearance_panel,
|
|
interactive_input.pointer_pos,
|
|
interactive_event.ctrl,
|
|
interactive_event.shift,
|
|
);
|
|
}
|
|
InteractiveEventKind::PointerPrimaryButtonDragStopped => {
|
|
self.overlay.drag_stop(
|
|
self.interactor.invoker().autorouter(),
|
|
&self.appearance_panel,
|
|
interactive_input.pointer_pos,
|
|
);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn update_state(
|
|
&mut self,
|
|
tr: &Translator,
|
|
error_dialog: &mut ErrorDialog,
|
|
interactive_input: &InteractiveInput,
|
|
) -> ControlFlow<()> {
|
|
if let Ok(data) = self.history_channel.1.try_recv() {
|
|
match data {
|
|
Ok(Ok(data)) => {
|
|
self.interactor.replay(data);
|
|
}
|
|
Ok(Err(err)) => {
|
|
error_dialog.push_error(
|
|
"tr-module-history-file-loader",
|
|
format!(
|
|
"{}; {}",
|
|
tr.text("tr-error-failed-to-parse-as-history-json"),
|
|
err
|
|
),
|
|
);
|
|
}
|
|
Err(err) => {
|
|
error_dialog.push_error(
|
|
"tr-module-history-file-loader",
|
|
format!("{}; {}", tr.text("tr-error-unable-to-read-file"), err),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
match self.interactor.update(interactive_input) {
|
|
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
|
ControlFlow::Break(Ok(())) => ControlFlow::Break(()),
|
|
ControlFlow::Break(Err(err)) => {
|
|
error_dialog.push_error("tr-module-invoker", format!("{}", err));
|
|
ControlFlow::Break(())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn update_appearance_panel(&mut self, ctx: &egui::Context) {
|
|
self.appearance_panel
|
|
.update(ctx, self.interactor.invoker().autorouter().board());
|
|
}
|
|
}
|