refactor(egui): split out some activity code to new module, `interactor`

This commit is contained in:
Mikolaj Wielgus 2024-10-11 02:50:42 +02:00
parent 3e9e3c69c9
commit 489f55a8b0
6 changed files with 149 additions and 66 deletions

View File

@ -1,6 +1,7 @@
use std::{ use std::{
future::Future, future::Future,
io, io,
ops::ControlFlow,
sync::mpsc::{channel, Receiver, Sender}, sync::mpsc::{channel, Receiver, Sender},
}; };
use unic_langid::{langid, LanguageIdentifier}; use unic_langid::{langid, LanguageIdentifier};
@ -67,13 +68,13 @@ impl App {
while self.update_counter >= self.menu_bar.frame_timestep { while self.update_counter >= self.menu_bar.frame_timestep {
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; return;
} }
} }
} }
fn update_state(&mut self) -> bool { fn update_state(&mut self) -> ControlFlow<()> {
if let Ok(data) = self.content_channel.1.try_recv() { if let Ok(data) = self.content_channel.1.try_recv() {
match data { match data {
Ok(design) => match Workspace::new(design, &self.translator) { Ok(design) => match Workspace::new(design, &self.translator) {
@ -113,7 +114,8 @@ impl App {
if let Some(workspace) = &mut self.maybe_workspace { if let Some(workspace) = &mut self.maybe_workspace {
return workspace.update_state(&self.translator, &mut self.error_dialog); 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.viewport,
self.maybe_workspace self.maybe_workspace
.as_ref() .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 { if self.menu_bar.show_layer_manager {

View File

@ -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
}
}

View File

@ -6,6 +6,7 @@ mod app;
mod config; mod config;
mod error_dialog; mod error_dialog;
mod interaction; mod interaction;
mod interactor;
mod layers; mod layers;
mod menu_bar; mod menu_bar;
mod overlay; mod overlay;

View File

@ -125,7 +125,11 @@ impl MenuBar {
)); ));
let workspace_activities_enabled = match &maybe_workspace { let workspace_activities_enabled = match &maybe_workspace {
Some(w) => w.maybe_activity.as_ref().map_or(true, |activity| { Some(w) => w
.interactor
.maybe_activity()
.as_ref()
.map_or(true, |activity| {
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..))) matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
}), }),
None => false, None => false,
@ -277,7 +281,7 @@ impl MenuBar {
} else if let Some(workspace) = maybe_workspace { } else if let Some(workspace) = maybe_workspace {
if export_session.consume_key_triggered(ctx, ui) { if export_session.consume_key_triggered(ctx, ui) {
let ctx = ui.ctx().clone(); 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 // FIXME: I don't know how to avoid buffering the entire exported file
let mut writebuf = vec![]; let mut writebuf = vec![];
@ -325,7 +329,10 @@ impl MenuBar {
// FIXME: I don't think we should be buffering everything in a `Vec<u8>`. // FIXME: I don't think we should be buffering everything in a `Vec<u8>`.
let mut writebuf = vec![]; 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 { execute(async move {
if let Some(file_handle) = task.await { if let Some(file_handle) = task.await {
@ -334,26 +341,18 @@ impl MenuBar {
} }
}); });
} else if undo.consume_key_triggered(ctx, ui) { } else if undo.consume_key_triggered(ctx, ui) {
workspace.invoker.undo(); workspace.interactor.undo();
} else if redo.consume_key_triggered(ctx, ui) { } else if redo.consume_key_triggered(ctx, ui) {
workspace.invoker.redo(); workspace.interactor.redo();
} else if abort.consume_key_triggered(ctx, ui) { } else if abort.consume_key_triggered(ctx, ui) {
if let Some(activity) = &mut workspace.maybe_activity { workspace.interactor.abort()
activity.abort(&mut ActivityContext {
interaction: InteractionContext {},
invoker: &mut workspace.invoker,
});
}
} else if place_via.consume_key_enabled(ctx, ui, &mut self.is_placing_via) { } else if place_via.consume_key_enabled(ctx, ui, &mut self.is_placing_via) {
} else if workspace_activities_enabled { } else if workspace_activities_enabled {
let mut schedule = |op: fn(Selection, AutorouterOptions) -> Command| { let mut schedule = |op: fn(Selection, AutorouterOptions) -> Command| {
let selection = workspace.overlay.take_selection(); let selection = workspace.overlay.take_selection();
workspace.maybe_activity =
Some(ActivityStepperWithStatus::new_execution(
workspace workspace
.invoker .interactor
.execute_stepper(op(selection, self.autorouter_options))?, .schedule(op(selection, self.autorouter_options));
));
Ok::<(), InvokerError>(()) Ok::<(), InvokerError>(())
}; };
if remove_bands.consume_key_triggered(ctx, ui) { if remove_bands.consume_key_triggered(ctx, ui) {

View File

@ -56,13 +56,12 @@ impl Viewport {
let mut painter = Painter::new(ui, self.transform, top.show_bboxes); let mut painter = Painter::new(ui, self.transform, top.show_bboxes);
if let Some(workspace) = maybe_workspace { if let Some(workspace) = maybe_workspace {
let invoker = &mut workspace.invoker;
let layers = &mut workspace.layers; let layers = &mut workspace.layers;
let overlay = &mut workspace.overlay; let overlay = &mut workspace.overlay;
if ctx.input(|i| i.pointer.any_click()) { if ctx.input(|i| i.pointer.any_click()) {
if top.is_placing_via { if top.is_placing_via {
invoker.execute( workspace.interactor.execute(
Command::PlaceVia(ViaWeight { Command::PlaceVia(ViaWeight {
from_layer: 0, from_layer: 0,
to_layer: 0, to_layer: 0,
@ -75,13 +74,14 @@ impl Viewport {
); );
} else { } else {
overlay.click( overlay.click(
invoker.autorouter().board(), workspace.interactor.invoker().autorouter().board(),
point! {x: latest_pos.x as f64, y: -latest_pos.y as f64}, 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() { for i in (0..layers.visible.len()).rev() {
if layers.visible[i] { if layers.visible[i] {
for primitive in board.layout().drawing().layer_primitive_nodes(i) { for primitive in board.layout().drawing().layer_primitive_nodes(i) {
@ -92,7 +92,7 @@ impl Viewport {
.contains_node(board, GenericNode::Primitive(primitive)) .contains_node(board, GenericNode::Primitive(primitive))
{ {
layers.highlight_colors[i] 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) { if activity.obstacles().contains(&primitive) {
layers.highlight_colors[i] layers.highlight_colors[i]
} else { } else {
@ -141,7 +141,7 @@ impl Viewport {
} }
if top.show_navmesh { 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() { if let Some(navmesh) = activity.maybe_navmesh() {
for edge in navmesh.edge_references() { for edge in navmesh.edge_references() {
let mut from = PrimitiveIndex::from(navmesh.node_weight(edge.source()).unwrap().node) let mut from = PrimitiveIndex::from(navmesh.node_weight(edge.source()).unwrap().node)
@ -206,7 +206,7 @@ impl Viewport {
painter.paint_bbox(root_bbox); 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() { for ghost in activity.ghosts().iter() {
painter.paint_primitive(&ghost, egui::Color32::from_rgb(75, 75, 150)); painter.paint_primitive(&ghost, egui::Color32::from_rgb(75, 75, 150));
} }
@ -233,7 +233,7 @@ impl Viewport {
} }
if self.scheduled_zoom_to_fit { if self.scheduled_zoom_to_fit {
let root_bbox = invoker let root_bbox = workspace.interactor.invoker()
.autorouter() .autorouter()
.board() .board()
.layout() .layout()

View File

@ -1,4 +1,7 @@
use std::sync::mpsc::{channel, Receiver, Sender}; use std::{
ops::ControlFlow,
sync::mpsc::{channel, Receiver, Sender},
};
use topola::{ use topola::{
autorouter::{history::History, invoker::Invoker, Autorouter}, autorouter::{history::History, invoker::Invoker, Autorouter},
@ -10,6 +13,7 @@ use crate::{
activity::{ActivityContext, ActivityStatus, ActivityStepperWithStatus}, activity::{ActivityContext, ActivityStatus, ActivityStepperWithStatus},
error_dialog::ErrorDialog, error_dialog::ErrorDialog,
interaction::InteractionContext, interaction::InteractionContext,
interactor::Interactor,
layers::Layers, layers::Layers,
overlay::Overlay, overlay::Overlay,
translator::Translator, translator::Translator,
@ -20,9 +24,7 @@ pub struct Workspace {
pub design: SpecctraDesign, pub design: SpecctraDesign,
pub layers: Layers, pub layers: Layers,
pub overlay: Overlay, pub overlay: Overlay,
pub invoker: Invoker<SpecctraMesadata>, pub interactor: Interactor<SpecctraMesadata>,
pub maybe_activity: Option<ActivityStepperWithStatus>,
pub history_channel: ( pub history_channel: (
Sender<std::io::Result<Result<History, serde_json::Error>>>, Sender<std::io::Result<Result<History, serde_json::Error>>>,
@ -33,18 +35,11 @@ pub struct Workspace {
impl Workspace { impl Workspace {
pub fn new(design: SpecctraDesign, tr: &Translator) -> Result<Self, String> { pub fn new(design: SpecctraDesign, tr: &Translator) -> Result<Self, String> {
let board = design.make_board(); let board = design.make_board();
let layers = Layers::new(&board);
let overlay = Overlay::new(&board).map_err(|err| { let overlay = Overlay::new(&board).map_err(|err| {
format!( format!(
"{}; {}", "{}; {}",
tr.text("tr-error_unable-to-initialize-overlay"), 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"),
err err
) )
})?; })?;
@ -52,24 +47,33 @@ impl Workspace {
design, design,
layers, layers,
overlay, overlay,
invoker: Invoker::new(autorouter), interactor: Interactor::new(board).map_err(|err| {
maybe_activity: None, format!(
"{}; {}",
tr.text("tr-error_unable-to-initialize-overlay"),
err
)
})?,
history_channel: channel(), 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() { if let Ok(data) = self.history_channel.1.try_recv() {
match data { match data {
Ok(Ok(data)) => { Ok(Ok(data)) => {
self.invoker.replay(data); self.interactor.replay(data);
} }
Ok(Err(err)) => { Ok(Err(err)) => {
error_dialog.push_error( error_dialog.push_error(
"tr-module-history-file-loader", "tr-module-history-file-loader",
format!( format!(
"{}; {}", "{}; {}",
tr.text("tr-error_failed-to-parse-as-history-json"), tr.text("tr-error-failed-to-parse-as-history-json"),
err err
), ),
); );
@ -77,30 +81,17 @@ impl Workspace {
Err(err) => { Err(err) => {
error_dialog.push_error( error_dialog.push_error(
"tr-module-history-file-loader", "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 { self.interactor.update()
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
} }
pub fn update_layers(&mut self, ctx: &egui::Context) { 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());
} }
} }