mirror of https://codeberg.org/topola/topola.git
refactor(egui): split out some activity code to new module, `interactor`
This commit is contained in:
parent
3e9e3c69c9
commit
489f55a8b0
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
ops::ControlFlow,
|
||||
sync::mpsc::{channel, Receiver, Sender},
|
||||
};
|
||||
use unic_langid::{langid, LanguageIdentifier};
|
||||
|
|
@ -67,13 +68,13 @@ impl App {
|
|||
while self.update_counter >= self.menu_bar.frame_timestep {
|
||||
self.update_counter -= self.menu_bar.frame_timestep;
|
||||
|
||||
if !self.update_state() {
|
||||
if let ControlFlow::Break(()) = self.update_state() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_state(&mut self) -> bool {
|
||||
fn update_state(&mut self) -> ControlFlow<()> {
|
||||
if let Ok(data) = self.content_channel.1.try_recv() {
|
||||
match data {
|
||||
Ok(design) => match Workspace::new(design, &self.translator) {
|
||||
|
|
@ -113,7 +114,8 @@ impl App {
|
|||
if let Some(workspace) = &mut self.maybe_workspace {
|
||||
return workspace.update_state(&self.translator, &mut self.error_dialog);
|
||||
}
|
||||
false
|
||||
|
||||
ControlFlow::Break(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +143,7 @@ impl eframe::App for App {
|
|||
&self.viewport,
|
||||
self.maybe_workspace
|
||||
.as_ref()
|
||||
.and_then(|w| w.maybe_activity.as_ref()),
|
||||
.and_then(|w| w.interactor.maybe_activity().as_ref()),
|
||||
);
|
||||
|
||||
if self.menu_bar.show_layer_manager {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use spade::InsertionError;
|
||||
use topola::{
|
||||
autorouter::{
|
||||
execution::{Command, ExecutionStepper},
|
||||
history::History,
|
||||
invoker::{Invoker, InvokerError},
|
||||
Autorouter,
|
||||
},
|
||||
board::{mesadata::AccessMesadata, Board},
|
||||
stepper::{Abort, Step},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
activity::{ActivityContext, ActivityStatus, ActivityStepperWithStatus},
|
||||
interaction::InteractionContext,
|
||||
};
|
||||
|
||||
pub struct Interactor<M: AccessMesadata> {
|
||||
invoker: Invoker<M>,
|
||||
activity: Option<ActivityStepperWithStatus>,
|
||||
}
|
||||
|
||||
impl<M: AccessMesadata> Interactor<M> {
|
||||
pub fn new(board: Board<M>) -> Result<Self, InsertionError> {
|
||||
Ok(Self {
|
||||
invoker: Invoker::new(Autorouter::new(board)?),
|
||||
activity: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn execute(&mut self, command: Command) -> Result<(), InvokerError> {
|
||||
self.invoker.execute(command)
|
||||
}
|
||||
|
||||
pub fn schedule(&mut self, command: Command) -> Result<(), InvokerError> {
|
||||
self.activity = Some(ActivityStepperWithStatus::new_execution(
|
||||
self.invoker.execute_stepper(command)?,
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn undo(&mut self) -> Result<(), InvokerError> {
|
||||
self.invoker.undo()
|
||||
}
|
||||
|
||||
pub fn redo(&mut self) -> Result<(), InvokerError> {
|
||||
self.invoker.redo()
|
||||
}
|
||||
|
||||
pub fn abort(&mut self) {
|
||||
if let Some(ref mut activity) = self.activity {
|
||||
activity.abort(&mut ActivityContext {
|
||||
interaction: InteractionContext {},
|
||||
invoker: &mut self.invoker,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replay(&mut self, history: History) {
|
||||
self.invoker.replay(history);
|
||||
}
|
||||
|
||||
pub fn update(&mut self) -> ControlFlow<()> {
|
||||
if let Some(ref mut activity) = self.activity {
|
||||
return match activity.step(&mut ActivityContext {
|
||||
interaction: InteractionContext {},
|
||||
invoker: &mut self.invoker,
|
||||
}) {
|
||||
Ok(ActivityStatus::Running) => ControlFlow::Continue(()),
|
||||
Ok(ActivityStatus::Finished(..)) => ControlFlow::Break(()),
|
||||
Err(err) => {
|
||||
//error_dialog.push_error("tr-module-invoker", format!("{}", err));
|
||||
self.activity = None;
|
||||
ControlFlow::Break(())
|
||||
}
|
||||
};
|
||||
}
|
||||
ControlFlow::Break(())
|
||||
}
|
||||
|
||||
pub fn invoker(&self) -> &Invoker<M> {
|
||||
&self.invoker
|
||||
}
|
||||
|
||||
pub fn maybe_activity(&self) -> &Option<ActivityStepperWithStatus> {
|
||||
&self.activity
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ mod app;
|
|||
mod config;
|
||||
mod error_dialog;
|
||||
mod interaction;
|
||||
mod interactor;
|
||||
mod layers;
|
||||
mod menu_bar;
|
||||
mod overlay;
|
||||
|
|
|
|||
|
|
@ -125,9 +125,13 @@ impl MenuBar {
|
|||
));
|
||||
|
||||
let workspace_activities_enabled = match &maybe_workspace {
|
||||
Some(w) => w.maybe_activity.as_ref().map_or(true, |activity| {
|
||||
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
||||
}),
|
||||
Some(w) => w
|
||||
.interactor
|
||||
.maybe_activity()
|
||||
.as_ref()
|
||||
.map_or(true, |activity| {
|
||||
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
||||
}),
|
||||
None => false,
|
||||
};
|
||||
|
||||
|
|
@ -277,7 +281,7 @@ impl MenuBar {
|
|||
} else if let Some(workspace) = maybe_workspace {
|
||||
if export_session.consume_key_triggered(ctx, ui) {
|
||||
let ctx = ui.ctx().clone();
|
||||
let board = workspace.invoker.autorouter().board();
|
||||
let board = workspace.interactor.invoker().autorouter().board();
|
||||
|
||||
// FIXME: I don't know how to avoid buffering the entire exported file
|
||||
let mut writebuf = vec![];
|
||||
|
|
@ -325,7 +329,10 @@ impl MenuBar {
|
|||
|
||||
// FIXME: I don't think we should be buffering everything in a `Vec<u8>`.
|
||||
let mut writebuf = vec![];
|
||||
serde_json::to_writer_pretty(&mut writebuf, workspace.invoker.history());
|
||||
serde_json::to_writer_pretty(
|
||||
&mut writebuf,
|
||||
workspace.interactor.invoker().history(),
|
||||
);
|
||||
|
||||
execute(async move {
|
||||
if let Some(file_handle) = task.await {
|
||||
|
|
@ -334,26 +341,18 @@ impl MenuBar {
|
|||
}
|
||||
});
|
||||
} else if undo.consume_key_triggered(ctx, ui) {
|
||||
workspace.invoker.undo();
|
||||
workspace.interactor.undo();
|
||||
} else if redo.consume_key_triggered(ctx, ui) {
|
||||
workspace.invoker.redo();
|
||||
workspace.interactor.redo();
|
||||
} else if abort.consume_key_triggered(ctx, ui) {
|
||||
if let Some(activity) = &mut workspace.maybe_activity {
|
||||
activity.abort(&mut ActivityContext {
|
||||
interaction: InteractionContext {},
|
||||
invoker: &mut workspace.invoker,
|
||||
});
|
||||
}
|
||||
workspace.interactor.abort()
|
||||
} else if place_via.consume_key_enabled(ctx, ui, &mut self.is_placing_via) {
|
||||
} else if workspace_activities_enabled {
|
||||
let mut schedule = |op: fn(Selection, AutorouterOptions) -> Command| {
|
||||
let selection = workspace.overlay.take_selection();
|
||||
workspace.maybe_activity =
|
||||
Some(ActivityStepperWithStatus::new_execution(
|
||||
workspace
|
||||
.invoker
|
||||
.execute_stepper(op(selection, self.autorouter_options))?,
|
||||
));
|
||||
workspace
|
||||
.interactor
|
||||
.schedule(op(selection, self.autorouter_options));
|
||||
Ok::<(), InvokerError>(())
|
||||
};
|
||||
if remove_bands.consume_key_triggered(ctx, ui) {
|
||||
|
|
|
|||
|
|
@ -56,13 +56,12 @@ impl Viewport {
|
|||
let mut painter = Painter::new(ui, self.transform, top.show_bboxes);
|
||||
|
||||
if let Some(workspace) = maybe_workspace {
|
||||
let invoker = &mut workspace.invoker;
|
||||
let layers = &mut workspace.layers;
|
||||
let overlay = &mut workspace.overlay;
|
||||
|
||||
if ctx.input(|i| i.pointer.any_click()) {
|
||||
if top.is_placing_via {
|
||||
invoker.execute(
|
||||
workspace.interactor.execute(
|
||||
Command::PlaceVia(ViaWeight {
|
||||
from_layer: 0,
|
||||
to_layer: 0,
|
||||
|
|
@ -75,13 +74,14 @@ impl Viewport {
|
|||
);
|
||||
} else {
|
||||
overlay.click(
|
||||
invoker.autorouter().board(),
|
||||
workspace.interactor.invoker().autorouter().board(),
|
||||
point! {x: latest_pos.x as f64, y: -latest_pos.y as f64},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let board = invoker.autorouter().board();
|
||||
let board = workspace.interactor.invoker().autorouter().board();
|
||||
|
||||
for i in (0..layers.visible.len()).rev() {
|
||||
if layers.visible[i] {
|
||||
for primitive in board.layout().drawing().layer_primitive_nodes(i) {
|
||||
|
|
@ -92,7 +92,7 @@ impl Viewport {
|
|||
.contains_node(board, GenericNode::Primitive(primitive))
|
||||
{
|
||||
layers.highlight_colors[i]
|
||||
} else if let Some(activity) = &mut workspace.maybe_activity {
|
||||
} else if let Some(activity) = &mut workspace.interactor.maybe_activity() {
|
||||
if activity.obstacles().contains(&primitive) {
|
||||
layers.highlight_colors[i]
|
||||
} else {
|
||||
|
|
@ -141,7 +141,7 @@ impl Viewport {
|
|||
}
|
||||
|
||||
if top.show_navmesh {
|
||||
if let Some(activity) = &mut workspace.maybe_activity {
|
||||
if let Some(activity) = workspace.interactor.maybe_activity() {
|
||||
if let Some(navmesh) = activity.maybe_navmesh() {
|
||||
for edge in navmesh.edge_references() {
|
||||
let mut from = PrimitiveIndex::from(navmesh.node_weight(edge.source()).unwrap().node)
|
||||
|
|
@ -206,7 +206,7 @@ impl Viewport {
|
|||
painter.paint_bbox(root_bbox);
|
||||
}
|
||||
|
||||
if let Some(activity) = &mut workspace.maybe_activity {
|
||||
if let Some(activity) = &mut workspace.interactor.maybe_activity() {
|
||||
for ghost in activity.ghosts().iter() {
|
||||
painter.paint_primitive(&ghost, egui::Color32::from_rgb(75, 75, 150));
|
||||
}
|
||||
|
|
@ -233,7 +233,7 @@ impl Viewport {
|
|||
}
|
||||
|
||||
if self.scheduled_zoom_to_fit {
|
||||
let root_bbox = invoker
|
||||
let root_bbox = workspace.interactor.invoker()
|
||||
.autorouter()
|
||||
.board()
|
||||
.layout()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::{
|
||||
ops::ControlFlow,
|
||||
sync::mpsc::{channel, Receiver, Sender},
|
||||
};
|
||||
|
||||
use topola::{
|
||||
autorouter::{history::History, invoker::Invoker, Autorouter},
|
||||
|
|
@ -10,6 +13,7 @@ use crate::{
|
|||
activity::{ActivityContext, ActivityStatus, ActivityStepperWithStatus},
|
||||
error_dialog::ErrorDialog,
|
||||
interaction::InteractionContext,
|
||||
interactor::Interactor,
|
||||
layers::Layers,
|
||||
overlay::Overlay,
|
||||
translator::Translator,
|
||||
|
|
@ -20,9 +24,7 @@ pub struct Workspace {
|
|||
pub design: SpecctraDesign,
|
||||
pub layers: Layers,
|
||||
pub overlay: Overlay,
|
||||
pub invoker: Invoker<SpecctraMesadata>,
|
||||
|
||||
pub maybe_activity: Option<ActivityStepperWithStatus>,
|
||||
pub interactor: Interactor<SpecctraMesadata>,
|
||||
|
||||
pub history_channel: (
|
||||
Sender<std::io::Result<Result<History, serde_json::Error>>>,
|
||||
|
|
@ -33,18 +35,11 @@ pub struct Workspace {
|
|||
impl Workspace {
|
||||
pub fn new(design: SpecctraDesign, tr: &Translator) -> Result<Self, String> {
|
||||
let board = design.make_board();
|
||||
let layers = Layers::new(&board);
|
||||
let overlay = Overlay::new(&board).map_err(|err| {
|
||||
format!(
|
||||
"{}; {}",
|
||||
tr.text("tr-error_unable-to-initialize-overlay"),
|
||||
err
|
||||
)
|
||||
})?;
|
||||
let layers = Layers::new(&board);
|
||||
let autorouter = Autorouter::new(board).map_err(|err| {
|
||||
format!(
|
||||
"{}; {}",
|
||||
tr.text("tr-error_unable-to-initialize-autorouter"),
|
||||
tr.text("tr-error-unable-to-initialize-overlay"),
|
||||
err
|
||||
)
|
||||
})?;
|
||||
|
|
@ -52,24 +47,33 @@ impl Workspace {
|
|||
design,
|
||||
layers,
|
||||
overlay,
|
||||
invoker: Invoker::new(autorouter),
|
||||
maybe_activity: None,
|
||||
interactor: Interactor::new(board).map_err(|err| {
|
||||
format!(
|
||||
"{}; {}",
|
||||
tr.text("tr-error_unable-to-initialize-overlay"),
|
||||
err
|
||||
)
|
||||
})?,
|
||||
history_channel: channel(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_state(&mut self, tr: &Translator, error_dialog: &mut ErrorDialog) -> bool {
|
||||
pub fn update_state(
|
||||
&mut self,
|
||||
tr: &Translator,
|
||||
error_dialog: &mut ErrorDialog,
|
||||
) -> ControlFlow<()> {
|
||||
if let Ok(data) = self.history_channel.1.try_recv() {
|
||||
match data {
|
||||
Ok(Ok(data)) => {
|
||||
self.invoker.replay(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"),
|
||||
tr.text("tr-error-failed-to-parse-as-history-json"),
|
||||
err
|
||||
),
|
||||
);
|
||||
|
|
@ -77,30 +81,17 @@ impl Workspace {
|
|||
Err(err) => {
|
||||
error_dialog.push_error(
|
||||
"tr-module-history-file-loader",
|
||||
format!("{}; {}", tr.text("tr-error_unable-to-read-file"), err),
|
||||
format!("{}; {}", tr.text("tr-error-unable-to-read-file"), err),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(activity) = &mut self.maybe_activity {
|
||||
return match activity.step(&mut ActivityContext {
|
||||
interaction: InteractionContext {},
|
||||
invoker: &mut self.invoker,
|
||||
}) {
|
||||
Ok(ActivityStatus::Running) => true,
|
||||
Ok(ActivityStatus::Finished(..)) => false,
|
||||
Err(err) => {
|
||||
error_dialog.push_error("tr-module-invoker", format!("{}", err));
|
||||
self.maybe_activity = None;
|
||||
false
|
||||
}
|
||||
};
|
||||
}
|
||||
false
|
||||
self.interactor.update()
|
||||
}
|
||||
|
||||
pub fn update_layers(&mut self, ctx: &egui::Context) {
|
||||
self.layers.update(ctx, self.invoker.autorouter().board());
|
||||
self.layers
|
||||
.update(ctx, self.interactor.invoker().autorouter().board());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue