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::{
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -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 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;
|
||||||
|
|
|
||||||
|
|
@ -125,9 +125,13 @@ 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
|
||||||
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
.interactor
|
||||||
}),
|
.maybe_activity()
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |activity| {
|
||||||
|
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 =
|
workspace
|
||||||
Some(ActivityStepperWithStatus::new_execution(
|
.interactor
|
||||||
workspace
|
.schedule(op(selection, self.autorouter_options));
|
||||||
.invoker
|
|
||||||
.execute_stepper(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) {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue