mirror of https://codeberg.org/topola/topola.git
egui: add structure Workspace for two-phase workflow
This commit is contained in:
parent
f02b7be878
commit
69f2ce3c2e
|
|
@ -29,36 +29,24 @@ use crate::{
|
||||||
status_bar::StatusBar,
|
status_bar::StatusBar,
|
||||||
translator::Translator,
|
translator::Translator,
|
||||||
viewport::Viewport,
|
viewport::Viewport,
|
||||||
|
workspace::Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
config: Config,
|
config: Config,
|
||||||
translator: Translator,
|
translator: Translator,
|
||||||
|
|
||||||
maybe_overlay: Option<Overlay>,
|
|
||||||
|
|
||||||
arc_mutex_maybe_invoker: Arc<Mutex<Option<Invoker<SpecctraMesadata>>>>,
|
|
||||||
|
|
||||||
maybe_activity: Option<ActivityStepperWithStatus>,
|
|
||||||
|
|
||||||
content_channel: (
|
content_channel: (
|
||||||
Sender<Result<SpecctraDesign, SpecctraLoadingError>>,
|
Sender<Result<SpecctraDesign, SpecctraLoadingError>>,
|
||||||
Receiver<Result<SpecctraDesign, SpecctraLoadingError>>,
|
Receiver<Result<SpecctraDesign, SpecctraLoadingError>>,
|
||||||
),
|
),
|
||||||
history_channel: (
|
|
||||||
Sender<std::io::Result<Result<History, serde_json::Error>>>,
|
|
||||||
Receiver<std::io::Result<Result<History, serde_json::Error>>>,
|
|
||||||
),
|
|
||||||
|
|
||||||
viewport: Viewport,
|
viewport: Viewport,
|
||||||
|
|
||||||
menu_bar: MenuBar,
|
menu_bar: MenuBar,
|
||||||
status_bar: StatusBar,
|
status_bar: StatusBar,
|
||||||
|
|
||||||
error_dialog: ErrorDialog,
|
error_dialog: ErrorDialog,
|
||||||
|
|
||||||
maybe_layers: Option<Layers>,
|
maybe_workspace: Option<Workspace>,
|
||||||
maybe_design: Option<SpecctraDesign>,
|
|
||||||
|
|
||||||
update_counter: f32,
|
update_counter: f32,
|
||||||
}
|
}
|
||||||
|
|
@ -68,17 +56,12 @@ impl Default for App {
|
||||||
Self {
|
Self {
|
||||||
config: Config::default(),
|
config: Config::default(),
|
||||||
translator: Translator::new(langid!("en-US")),
|
translator: Translator::new(langid!("en-US")),
|
||||||
maybe_overlay: None,
|
|
||||||
arc_mutex_maybe_invoker: Arc::new(Mutex::new(None)),
|
|
||||||
maybe_activity: None,
|
|
||||||
content_channel: channel(),
|
content_channel: channel(),
|
||||||
history_channel: channel(),
|
|
||||||
viewport: Viewport::new(),
|
viewport: Viewport::new(),
|
||||||
menu_bar: MenuBar::new(),
|
menu_bar: MenuBar::new(),
|
||||||
status_bar: StatusBar::new(),
|
status_bar: StatusBar::new(),
|
||||||
error_dialog: ErrorDialog::new(),
|
error_dialog: ErrorDialog::new(),
|
||||||
maybe_layers: None,
|
maybe_workspace: None,
|
||||||
maybe_design: None,
|
|
||||||
update_counter: 0.0,
|
update_counter: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,8 +96,11 @@ impl App {
|
||||||
fn update_state(&mut self) -> bool {
|
fn update_state(&mut self) -> bool {
|
||||||
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 self.load_specctra_dsn(design) {
|
Ok(design) => match Workspace::new(design, &self.translator) {
|
||||||
Ok(()) => {}
|
Ok(ws) => {
|
||||||
|
self.maybe_workspace = Some(ws);
|
||||||
|
self.viewport.scheduled_zoom_to_fit = true;
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.error_dialog
|
self.error_dialog
|
||||||
.push_error("tr-module-specctra-dsn-file-loader", err);
|
.push_error("tr-module-specctra-dsn-file-loader", err);
|
||||||
|
|
@ -144,76 +130,11 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_mut() {
|
if let Some(workspace) = &mut self.maybe_workspace {
|
||||||
if let Ok(data) = self.history_channel.1.try_recv() {
|
return workspace.update_state(&self.translator, &mut self.error_dialog);
|
||||||
let tr = &self.translator;
|
|
||||||
match data {
|
|
||||||
Ok(Ok(data)) => {
|
|
||||||
invoker.replay(data);
|
|
||||||
}
|
|
||||||
Ok(Err(err)) => {
|
|
||||||
self.error_dialog.push_error(
|
|
||||||
"tr-module-history-file-loader",
|
|
||||||
format!(
|
|
||||||
"{}; {}",
|
|
||||||
tr.text("tr-error-failed-to-parse-as-history-json"),
|
|
||||||
err
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
self.error_dialog.push_error(
|
|
||||||
"tr-module-history-file-loader",
|
|
||||||
format!("{}; {}", tr.text("tr-error-unable-to-read-file"), err),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref mut activity) = self.maybe_activity {
|
|
||||||
return match activity.step(&mut ActivityContext {
|
|
||||||
interaction: InteractionContext {},
|
|
||||||
invoker,
|
|
||||||
}) {
|
|
||||||
Ok(ActivityStatus::Running) => true,
|
|
||||||
Ok(ActivityStatus::Finished(..)) => false,
|
|
||||||
Err(err) => {
|
|
||||||
self.error_dialog
|
|
||||||
.push_error("tr-module-invoker", format!("{}", err));
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_specctra_dsn(&mut self, design: SpecctraDesign) -> Result<(), String> {
|
|
||||||
let tr = &self.translator;
|
|
||||||
let board = design.make_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"),
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
self.maybe_overlay = Some(overlay);
|
|
||||||
self.maybe_layers = Some(layers);
|
|
||||||
self.maybe_design = Some(design);
|
|
||||||
self.arc_mutex_maybe_invoker = Arc::new(Mutex::new(Some(Invoker::new(autorouter))));
|
|
||||||
self.viewport.scheduled_zoom_to_fit = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for App {
|
impl eframe::App for App {
|
||||||
|
|
@ -228,37 +149,32 @@ impl eframe::App for App {
|
||||||
ctx,
|
ctx,
|
||||||
&self.translator,
|
&self.translator,
|
||||||
self.content_channel.0.clone(),
|
self.content_channel.0.clone(),
|
||||||
self.history_channel.0.clone(),
|
|
||||||
self.arc_mutex_maybe_invoker.clone(),
|
|
||||||
&mut self.maybe_activity,
|
|
||||||
&mut self.viewport,
|
&mut self.viewport,
|
||||||
&mut self.maybe_overlay,
|
self.maybe_workspace.as_mut(),
|
||||||
&self.maybe_design,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
self.advance_state_by_dt(ctx.input(|i| i.stable_dt));
|
self.advance_state_by_dt(ctx.input(|i| i.stable_dt));
|
||||||
|
|
||||||
self.status_bar
|
self.status_bar.update(
|
||||||
.update(ctx, &self.translator, &self.viewport, &self.maybe_activity);
|
ctx,
|
||||||
|
&self.translator,
|
||||||
|
&self.viewport,
|
||||||
|
self.maybe_workspace
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|w| w.maybe_activity.as_ref()),
|
||||||
|
);
|
||||||
|
|
||||||
if self.menu_bar.show_layer_manager {
|
if self.menu_bar.show_layer_manager {
|
||||||
if let Some(ref mut layers) = self.maybe_layers {
|
if let Some(workspace) = &mut self.maybe_workspace {
|
||||||
if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_ref() {
|
workspace.update_layers(ctx);
|
||||||
layers.update(ctx, invoker.autorouter().board());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.error_dialog.update(ctx, &self.translator);
|
self.error_dialog.update(ctx, &self.translator);
|
||||||
|
|
||||||
let _viewport_rect = self.viewport.update(
|
let _viewport_rect =
|
||||||
ctx,
|
self.viewport
|
||||||
&self.menu_bar,
|
.update(ctx, &self.menu_bar, self.maybe_workspace.as_mut());
|
||||||
&mut self.arc_mutex_maybe_invoker.lock().unwrap(),
|
|
||||||
&mut self.maybe_activity,
|
|
||||||
&mut self.maybe_overlay,
|
|
||||||
&self.maybe_layers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ mod painter;
|
||||||
mod status_bar;
|
mod status_bar;
|
||||||
mod translator;
|
mod translator;
|
||||||
mod viewport;
|
mod viewport;
|
||||||
|
mod workspace;
|
||||||
|
|
||||||
use app::App;
|
use app::App;
|
||||||
use sys_locale::get_locale;
|
use sys_locale::get_locale;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ use crate::{
|
||||||
overlay::Overlay,
|
overlay::Overlay,
|
||||||
translator::Translator,
|
translator::Translator,
|
||||||
viewport::Viewport,
|
viewport::Viewport,
|
||||||
|
workspace::Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct MenuBar {
|
pub struct MenuBar {
|
||||||
|
|
@ -64,12 +65,8 @@ impl MenuBar {
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
tr: &Translator,
|
tr: &Translator,
|
||||||
content_sender: Sender<Result<SpecctraDesign, SpecctraLoadingError>>,
|
content_sender: Sender<Result<SpecctraDesign, SpecctraLoadingError>>,
|
||||||
history_sender: Sender<std::io::Result<Result<History, serde_json::Error>>>,
|
|
||||||
arc_mutex_maybe_invoker: Arc<Mutex<Option<Invoker<SpecctraMesadata>>>>,
|
|
||||||
maybe_activity: &mut Option<ActivityStepperWithStatus>,
|
|
||||||
viewport: &mut Viewport,
|
viewport: &mut Viewport,
|
||||||
maybe_overlay: &mut Option<Overlay>,
|
maybe_workspace: Option<&mut Workspace>,
|
||||||
maybe_design: &Option<SpecctraDesign>,
|
|
||||||
) -> Result<(), InvokerError> {
|
) -> Result<(), InvokerError> {
|
||||||
let mut open_design = Trigger::new(Action::new(
|
let mut open_design = Trigger::new(Action::new(
|
||||||
tr.text("tr-menu-file-open"),
|
tr.text("tr-menu-file-open"),
|
||||||
|
|
@ -258,53 +255,57 @@ impl MenuBar {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if export_session.consume_key_triggered(ctx, ui) {
|
} else if export_session.consume_key_triggered(ctx, ui) {
|
||||||
if let Some(design) = maybe_design {
|
if let Some(workspace) = maybe_workspace {
|
||||||
if let Some(invoker) = arc_mutex_maybe_invoker.lock().unwrap().as_ref() {
|
let invoker = workspace.invoker.lock().unwrap();
|
||||||
let ctx = ui.ctx().clone();
|
let ctx = ui.ctx().clone();
|
||||||
let board = invoker.autorouter().board();
|
let board = 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![];
|
||||||
|
|
||||||
design.write_ses(board, &mut writebuf);
|
workspace.design.write_ses(board, &mut writebuf);
|
||||||
|
|
||||||
let mut dialog = rfd::AsyncFileDialog::new();
|
let mut dialog = rfd::AsyncFileDialog::new();
|
||||||
if let Some(filename) = Path::new(design.get_name()).file_stem() {
|
if let Some(filename) = Path::new(workspace.design.get_name()).file_stem() {
|
||||||
if let Some(filename) = filename.to_str() {
|
if let Some(filename) = filename.to_str() {
|
||||||
dialog = dialog.set_file_name(filename);
|
dialog = dialog.set_file_name(filename);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let task = dialog
|
|
||||||
.add_filter(tr.text("tr-menu-open-specctra-session-file"), &["ses"])
|
|
||||||
.save_file();
|
|
||||||
|
|
||||||
execute(async move {
|
|
||||||
if let Some(file_handle) = task.await {
|
|
||||||
file_handle.write(&writebuf).await;
|
|
||||||
ctx.request_repaint();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let task = dialog
|
||||||
|
.add_filter(tr.text("tr-menu-open-specctra-session-file"), &["ses"])
|
||||||
|
.save_file();
|
||||||
|
|
||||||
|
execute(async move {
|
||||||
|
if let Some(file_handle) = task.await {
|
||||||
|
file_handle.write(&writebuf).await;
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if import_history.consume_key_triggered(ctx, ui) {
|
} else if import_history.consume_key_triggered(ctx, ui) {
|
||||||
let ctx = ctx.clone();
|
if let Some(workspace) = maybe_workspace {
|
||||||
let task = rfd::AsyncFileDialog::new().pick_file();
|
let ctx = ctx.clone();
|
||||||
|
let task = rfd::AsyncFileDialog::new().pick_file();
|
||||||
|
let history_sender = workspace.history_channel.0.clone();
|
||||||
|
|
||||||
execute(async move {
|
execute(async move {
|
||||||
if let Some(file_handle) = task.await {
|
if let Some(file_handle) = task.await {
|
||||||
let data = handle_file(&file_handle).await.and_then(|data| {
|
let data = handle_file(&file_handle).await.and_then(|data| {
|
||||||
match serde_json::from_reader(data) {
|
match serde_json::from_reader(data) {
|
||||||
Ok(history) => Ok(Ok(history)),
|
Ok(history) => Ok(Ok(history)),
|
||||||
Err(err) if err.is_io() => Err(err.into()),
|
Err(err) if err.is_io() => Err(err.into()),
|
||||||
Err(err) => Ok(Err(err)),
|
Err(err) => Ok(Err(err)),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
history_sender.send(data);
|
history_sender.send(data);
|
||||||
ctx.request_repaint();
|
ctx.request_repaint();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else if export_history.consume_key_triggered(ctx, ui) {
|
} else if export_history.consume_key_triggered(ctx, ui) {
|
||||||
if let Some(invoker) = arc_mutex_maybe_invoker.lock().unwrap().as_ref() {
|
if let Some(workspace) = maybe_workspace {
|
||||||
|
let invoker = workspace.invoker.lock().unwrap();
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
let task = rfd::AsyncFileDialog::new().save_file();
|
let task = rfd::AsyncFileDialog::new().save_file();
|
||||||
|
|
||||||
|
|
@ -322,87 +323,81 @@ impl MenuBar {
|
||||||
} else if quit.consume_key_triggered(ctx, ui) {
|
} else if quit.consume_key_triggered(ctx, ui) {
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
} else if undo.consume_key_triggered(ctx, ui) {
|
} else if undo.consume_key_triggered(ctx, ui) {
|
||||||
if let Some(invoker) = arc_mutex_maybe_invoker.lock().unwrap().as_mut() {
|
if let Some(workspace) = maybe_workspace {
|
||||||
invoker.undo();
|
workspace.invoker.lock().unwrap().undo();
|
||||||
}
|
}
|
||||||
} else if redo.consume_key_triggered(ctx, ui) {
|
} else if redo.consume_key_triggered(ctx, ui) {
|
||||||
if let Some(invoker) = arc_mutex_maybe_invoker.lock().unwrap().as_mut() {
|
if let Some(workspace) = maybe_workspace {
|
||||||
invoker.redo();
|
workspace.invoker.lock().unwrap().redo();
|
||||||
}
|
}
|
||||||
} else if abort.consume_key_triggered(ctx, ui) {
|
} else if abort.consume_key_triggered(ctx, ui) {
|
||||||
if let Some(activity) = maybe_activity {
|
if let Some(workspace) = maybe_workspace {
|
||||||
if let Some(invoker) = arc_mutex_maybe_invoker.lock().unwrap().as_mut() {
|
if let Some(activity) = &mut workspace.maybe_activity {
|
||||||
activity.abort(&mut ActivityContext {
|
activity.abort(&mut ActivityContext {
|
||||||
interaction: InteractionContext {},
|
interaction: InteractionContext {},
|
||||||
invoker,
|
invoker: &mut *workspace.invoker.lock().unwrap(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if remove_bands.consume_key_triggered(ctx, ui) {
|
} else if remove_bands.consume_key_triggered(ctx, ui) {
|
||||||
if maybe_activity.as_mut().map_or(true, |activity| {
|
if let Some(workspace) = maybe_workspace {
|
||||||
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
if workspace.maybe_activity.as_mut().map_or(true, |activity| {
|
||||||
}) {
|
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
||||||
if let (Some(invoker), Some(ref mut overlay)) = (
|
}) {
|
||||||
arc_mutex_maybe_invoker.lock().unwrap().as_mut(),
|
let mut invoker = workspace.invoker.lock().unwrap();
|
||||||
maybe_overlay,
|
let selection = workspace.overlay.take_selection();
|
||||||
) {
|
workspace.maybe_activity = Some(
|
||||||
let selection = overlay.take_selection();
|
ActivityStepperWithStatus::new_execution(invoker.execute_stepper(
|
||||||
*maybe_activity = Some(ActivityStepperWithStatus::new_execution(
|
Command::RemoveBands(selection.band_selection),
|
||||||
invoker.execute_stepper(Command::RemoveBands(
|
)?),
|
||||||
selection.band_selection,
|
);
|
||||||
))?,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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 autoroute.consume_key_triggered(ctx, ui) {
|
} else if autoroute.consume_key_triggered(ctx, ui) {
|
||||||
if maybe_activity.as_mut().map_or(true, |activity| {
|
if let Some(workspace) = maybe_workspace {
|
||||||
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
if workspace.maybe_activity.as_mut().map_or(true, |activity| {
|
||||||
}) {
|
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
||||||
if let (Some(invoker), Some(ref mut overlay)) = (
|
}) {
|
||||||
arc_mutex_maybe_invoker.lock().unwrap().as_mut(),
|
let mut invoker = workspace.invoker.lock().unwrap();
|
||||||
maybe_overlay,
|
let selection = workspace.overlay.take_selection();
|
||||||
) {
|
workspace.maybe_activity =
|
||||||
let selection = overlay.take_selection();
|
Some(ActivityStepperWithStatus::new_execution(
|
||||||
*maybe_activity = Some(ActivityStepperWithStatus::new_execution(
|
invoker.execute_stepper(Command::Autoroute(
|
||||||
invoker.execute_stepper(Command::Autoroute(
|
selection.pin_selection,
|
||||||
selection.pin_selection,
|
self.autorouter_options,
|
||||||
self.autorouter_options,
|
))?,
|
||||||
))?,
|
));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if compare_detours.consume_key_triggered(ctx, ui) {
|
} else if compare_detours.consume_key_triggered(ctx, ui) {
|
||||||
if maybe_activity.as_mut().map_or(true, |activity| {
|
if let Some(workspace) = maybe_workspace {
|
||||||
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
if workspace.maybe_activity.as_mut().map_or(true, |activity| {
|
||||||
}) {
|
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
||||||
if let (Some(invoker), Some(ref mut overlay)) = (
|
}) {
|
||||||
arc_mutex_maybe_invoker.lock().unwrap().as_mut(),
|
let mut invoker = workspace.invoker.lock().unwrap();
|
||||||
maybe_overlay,
|
let selection = workspace.overlay.take_selection();
|
||||||
) {
|
workspace.maybe_activity =
|
||||||
let selection = overlay.take_selection();
|
Some(ActivityStepperWithStatus::new_execution(
|
||||||
*maybe_activity = Some(ActivityStepperWithStatus::new_execution(
|
invoker.execute_stepper(Command::CompareDetours(
|
||||||
invoker.execute_stepper(Command::CompareDetours(
|
selection.pin_selection,
|
||||||
selection.pin_selection,
|
self.autorouter_options,
|
||||||
self.autorouter_options,
|
))?,
|
||||||
))?,
|
));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if measure_length.consume_key_triggered(ctx, ui) {
|
} else if measure_length.consume_key_triggered(ctx, ui) {
|
||||||
if maybe_activity.as_mut().map_or(true, |activity| {
|
if let Some(workspace) = maybe_workspace {
|
||||||
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
if workspace.maybe_activity.as_mut().map_or(true, |activity| {
|
||||||
}) {
|
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
|
||||||
if let (Some(invoker), Some(ref mut overlay)) = (
|
}) {
|
||||||
arc_mutex_maybe_invoker.lock().unwrap().as_mut(),
|
let mut invoker = workspace.invoker.lock().unwrap();
|
||||||
maybe_overlay,
|
let selection = workspace.overlay.take_selection();
|
||||||
) {
|
workspace.maybe_activity = Some(
|
||||||
let selection = overlay.take_selection();
|
ActivityStepperWithStatus::new_execution(invoker.execute_stepper(
|
||||||
*maybe_activity = Some(ActivityStepperWithStatus::new_execution(
|
Command::MeasureLength(selection.band_selection),
|
||||||
invoker.execute_stepper(Command::MeasureLength(
|
)?),
|
||||||
selection.band_selection,
|
);
|
||||||
))?,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ impl StatusBar {
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
tr: &Translator,
|
tr: &Translator,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
maybe_activity: &Option<ActivityStepperWithStatus>,
|
maybe_activity: Option<&ActivityStepperWithStatus>,
|
||||||
) {
|
) {
|
||||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||||
let latest_pos = viewport.transform.inverse()
|
let latest_pos = viewport.transform.inverse()
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use topola::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
activity::ActivityStepperWithStatus, layers::Layers, menu_bar::MenuBar, overlay::Overlay,
|
activity::ActivityStepperWithStatus, layers::Layers, menu_bar::MenuBar, overlay::Overlay,
|
||||||
painter::Painter,
|
painter::Painter, workspace::Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Viewport {
|
pub struct Viewport {
|
||||||
|
|
@ -41,22 +41,13 @@ impl Viewport {
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
top: &MenuBar,
|
top: &MenuBar,
|
||||||
maybe_invoker: &mut Option<Invoker<SpecctraMesadata>>,
|
mut maybe_workspace: Option<&mut Workspace>,
|
||||||
maybe_activity: &mut Option<ActivityStepperWithStatus>,
|
|
||||||
maybe_overlay: &mut Option<Overlay>,
|
|
||||||
maybe_layers: &Option<Layers>,
|
|
||||||
) -> egui::Rect {
|
) -> egui::Rect {
|
||||||
let viewport_rect = self.paint(
|
let viewport_rect = self.paint(ctx, top, maybe_workspace.as_deref_mut());
|
||||||
ctx,
|
|
||||||
top,
|
|
||||||
maybe_invoker,
|
|
||||||
maybe_activity,
|
|
||||||
maybe_overlay,
|
|
||||||
maybe_layers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.scheduled_zoom_to_fit {
|
if self.scheduled_zoom_to_fit {
|
||||||
self.zoom_to_fit(maybe_invoker, &viewport_rect);
|
let mut maybe_invoker = maybe_workspace.as_ref().map(|w| w.invoker.lock().unwrap());
|
||||||
|
self.zoom_to_fit(maybe_invoker.as_deref_mut(), &viewport_rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
viewport_rect
|
viewport_rect
|
||||||
|
|
@ -66,10 +57,7 @@ impl Viewport {
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
top: &MenuBar,
|
top: &MenuBar,
|
||||||
maybe_invoker: &mut Option<Invoker<SpecctraMesadata>>,
|
maybe_workspace: Option<&mut Workspace>,
|
||||||
maybe_activity: &mut Option<ActivityStepperWithStatus>,
|
|
||||||
maybe_overlay: &mut Option<Overlay>,
|
|
||||||
maybe_layers: &Option<Layers>,
|
|
||||||
) -> egui::Rect {
|
) -> egui::Rect {
|
||||||
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| {
|
||||||
|
|
@ -86,7 +74,11 @@ 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(ref mut invoker) = maybe_invoker {
|
if let Some(workspace) = maybe_workspace {
|
||||||
|
let mut invoker = workspace.invoker.lock().unwrap();
|
||||||
|
let layers = &mut workspace.layers;
|
||||||
|
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(
|
invoker.execute(
|
||||||
|
|
@ -100,7 +92,7 @@ impl Viewport {
|
||||||
maybe_net: Some(1234),
|
maybe_net: Some(1234),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else if let Some(overlay) = maybe_overlay {
|
} else {
|
||||||
overlay.click(
|
overlay.click(
|
||||||
invoker.autorouter().board(),
|
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},
|
||||||
|
|
@ -108,169 +100,156 @@ impl Viewport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(invoker), Some(overlay)) = (
|
let board = invoker.autorouter().board();
|
||||||
maybe_invoker,
|
for i in (0..layers.visible.len()).rev() {
|
||||||
maybe_overlay,
|
if layers.visible[i] {
|
||||||
) {
|
for primitive in board.layout().drawing().layer_primitive_nodes(i) {
|
||||||
let board = invoker.autorouter().board();
|
let shape = primitive.primitive(board.layout().drawing()).shape();
|
||||||
|
|
||||||
if let Some(layers) = maybe_layers {
|
let color = if overlay
|
||||||
for i in (0..layers.visible.len()).rev() {
|
.selection()
|
||||||
if layers.visible[i] {
|
.contains_node(board, GenericNode::Primitive(primitive))
|
||||||
for primitive in board.layout().drawing().layer_primitive_nodes(i) {
|
{
|
||||||
let shape = primitive.primitive(board.layout().drawing()).shape();
|
layers.highlight_colors[i]
|
||||||
|
} else if let Some(activity) = &mut workspace.maybe_activity {
|
||||||
let color = if overlay
|
if activity.obstacles().contains(&primitive) {
|
||||||
.selection()
|
layers.highlight_colors[i]
|
||||||
.contains_node(board, GenericNode::Primitive(primitive))
|
} else {
|
||||||
{
|
layers.colors[i]
|
||||||
layers.highlight_colors[i]
|
|
||||||
} else {
|
|
||||||
if let Some(activity) = maybe_activity {
|
|
||||||
if activity.obstacles().contains(&primitive) {
|
|
||||||
layers.highlight_colors[i]
|
|
||||||
} else {
|
|
||||||
layers.colors[i]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
layers.colors[i]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
painter.paint_primitive(&shape, color);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
layers.colors[i]
|
||||||
|
};
|
||||||
|
|
||||||
for poly in board.layout().layer_poly_nodes(i) {
|
painter.paint_primitive(&shape, color);
|
||||||
let color = if overlay
|
|
||||||
.selection()
|
|
||||||
.contains_node(board, GenericNode::Compound(poly.into()))
|
|
||||||
{
|
|
||||||
layers.highlight_colors[i]
|
|
||||||
} else {
|
|
||||||
layers.colors[i]
|
|
||||||
};
|
|
||||||
|
|
||||||
painter.paint_polygon(&board.layout().poly(poly).shape().polygon, color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if top.show_ratsnest {
|
|
||||||
for edge in overlay.ratsnest().graph().edge_references() {
|
|
||||||
let from = overlay
|
|
||||||
.ratsnest()
|
|
||||||
.graph()
|
|
||||||
.node_weight(edge.source())
|
|
||||||
.unwrap()
|
|
||||||
.pos;
|
|
||||||
let to = overlay
|
|
||||||
.ratsnest()
|
|
||||||
.graph()
|
|
||||||
.node_weight(edge.target())
|
|
||||||
.unwrap()
|
|
||||||
.pos;
|
|
||||||
|
|
||||||
painter.paint_edge(
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
egui::Stroke::new(1.0, egui::Color32::from_rgb(90, 90, 200)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if top.show_navmesh {
|
|
||||||
if let Some(activity) = 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)
|
|
||||||
.primitive(board.layout().drawing())
|
|
||||||
.shape()
|
|
||||||
.center();
|
|
||||||
let mut to = PrimitiveIndex::from(navmesh.node_weight(edge.target()).unwrap().node)
|
|
||||||
.primitive(board.layout().drawing())
|
|
||||||
.shape()
|
|
||||||
.center();
|
|
||||||
|
|
||||||
if let Some(from_cw) = navmesh.node_weight(edge.source()).unwrap().maybe_cw {
|
|
||||||
if from_cw {
|
|
||||||
from -= [0.0, 150.0].into();
|
|
||||||
} else {
|
|
||||||
from += [0.0, 150.0].into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(to_cw) = navmesh.node_weight(edge.target()).unwrap().maybe_cw {
|
|
||||||
if to_cw {
|
|
||||||
to -= [0.0, 150.0].into();
|
|
||||||
} else {
|
|
||||||
to += [0.0, 150.0].into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let stroke = 'blk: {
|
|
||||||
if let (Some(source_pos), Some(target_pos)) = (
|
|
||||||
activity.maybe_trace().map(|trace|
|
|
||||||
trace.path
|
|
||||||
.iter()
|
|
||||||
.position(|node| *node == edge.source())).flatten(),
|
|
||||||
activity.maybe_trace().map(|trace|
|
|
||||||
trace.path
|
|
||||||
.iter()
|
|
||||||
.position(|node| *node == edge.target())).flatten(),
|
|
||||||
) {
|
|
||||||
if target_pos == source_pos + 1
|
|
||||||
|| source_pos == target_pos + 1
|
|
||||||
{
|
|
||||||
break 'blk egui::Stroke::new(
|
|
||||||
5.0,
|
|
||||||
egui::Color32::from_rgb(250, 250, 0),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
egui::Stroke::new(1.0, egui::Color32::from_rgb(125, 125, 125))
|
|
||||||
};
|
|
||||||
|
|
||||||
painter.paint_edge(from, to, stroke);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if top.show_bboxes {
|
|
||||||
let root_bbox3d = board.layout().drawing().rtree().root().envelope();
|
|
||||||
|
|
||||||
let root_bbox = AABB::<[f64; 2]>::from_corners([root_bbox3d.lower()[0], root_bbox3d.lower()[1]].into(), [root_bbox3d.upper()[0], root_bbox3d.upper()[1]].into());
|
|
||||||
painter.paint_bbox(root_bbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(activity) = maybe_activity {
|
|
||||||
for ghost in activity.ghosts().iter() {
|
|
||||||
painter.paint_primitive(&ghost, egui::Color32::from_rgb(75, 75, 150));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for poly in board.layout().layer_poly_nodes(i) {
|
||||||
|
let color = if overlay
|
||||||
|
.selection()
|
||||||
|
.contains_node(board, GenericNode::Compound(poly.into()))
|
||||||
|
{
|
||||||
|
layers.highlight_colors[i]
|
||||||
|
} else {
|
||||||
|
layers.colors[i]
|
||||||
|
};
|
||||||
|
|
||||||
|
painter.paint_polygon(&board.layout().poly(poly).shape().polygon, color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if top.show_ratsnest {
|
||||||
|
let graph = overlay.ratsnest().graph();
|
||||||
|
for edge in graph.edge_references() {
|
||||||
|
let from = graph
|
||||||
|
.node_weight(edge.source())
|
||||||
|
.unwrap()
|
||||||
|
.pos;
|
||||||
|
let to = graph
|
||||||
|
.node_weight(edge.target())
|
||||||
|
.unwrap()
|
||||||
|
.pos;
|
||||||
|
|
||||||
|
painter.paint_edge(
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
egui::Stroke::new(1.0, egui::Color32::from_rgb(90, 90, 200)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if top.show_navmesh {
|
||||||
|
if let Some(activity) = &mut workspace.maybe_activity {
|
||||||
if let Some(navmesh) = activity.maybe_navmesh() {
|
if let Some(navmesh) = activity.maybe_navmesh() {
|
||||||
if top.show_origin_destination {
|
for edge in navmesh.edge_references() {
|
||||||
let (origin, destination) = (navmesh.origin(), navmesh.destination());
|
let mut from = PrimitiveIndex::from(navmesh.node_weight(edge.source()).unwrap().node)
|
||||||
painter.paint_dot(
|
.primitive(board.layout().drawing())
|
||||||
Circle {
|
.shape()
|
||||||
pos: board.layout().drawing().primitive(origin).shape().center(),
|
.center();
|
||||||
r: 150.0,
|
let mut to = PrimitiveIndex::from(navmesh.node_weight(edge.target()).unwrap().node)
|
||||||
},
|
.primitive(board.layout().drawing())
|
||||||
egui::Color32::from_rgb(255, 255, 100),
|
.shape()
|
||||||
);
|
.center();
|
||||||
painter.paint_dot(
|
|
||||||
Circle {
|
if let Some(from_cw) = navmesh.node_weight(edge.source()).unwrap().maybe_cw {
|
||||||
pos: board.layout().drawing().primitive(destination).shape().center(),
|
if from_cw {
|
||||||
r: 150.0,
|
from -= [0.0, 150.0].into();
|
||||||
},
|
} else {
|
||||||
egui::Color32::from_rgb(255, 255, 100),
|
from += [0.0, 150.0].into();
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(to_cw) = navmesh.node_weight(edge.target()).unwrap().maybe_cw {
|
||||||
|
if to_cw {
|
||||||
|
to -= [0.0, 150.0].into();
|
||||||
|
} else {
|
||||||
|
to += [0.0, 150.0].into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stroke = 'blk: {
|
||||||
|
if let (Some(source_pos), Some(target_pos)) = (
|
||||||
|
activity.maybe_trace().map(|trace|
|
||||||
|
trace.path
|
||||||
|
.iter()
|
||||||
|
.position(|node| *node == edge.source())).flatten(),
|
||||||
|
activity.maybe_trace().map(|trace|
|
||||||
|
trace.path
|
||||||
|
.iter()
|
||||||
|
.position(|node| *node == edge.target())).flatten(),
|
||||||
|
) {
|
||||||
|
if target_pos == source_pos + 1
|
||||||
|
|| source_pos == target_pos + 1
|
||||||
|
{
|
||||||
|
break 'blk egui::Stroke::new(
|
||||||
|
5.0,
|
||||||
|
egui::Color32::from_rgb(250, 250, 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
egui::Stroke::new(1.0, egui::Color32::from_rgb(125, 125, 125))
|
||||||
|
};
|
||||||
|
|
||||||
|
painter.paint_edge(from, to, stroke);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if top.show_bboxes {
|
||||||
|
let root_bbox3d = board.layout().drawing().rtree().root().envelope();
|
||||||
|
|
||||||
|
let root_bbox = AABB::<[f64; 2]>::from_corners([root_bbox3d.lower()[0], root_bbox3d.lower()[1]].into(), [root_bbox3d.upper()[0], root_bbox3d.upper()[1]].into());
|
||||||
|
painter.paint_bbox(root_bbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(activity) = &mut workspace.maybe_activity {
|
||||||
|
for ghost in activity.ghosts().iter() {
|
||||||
|
painter.paint_primitive(&ghost, egui::Color32::from_rgb(75, 75, 150));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(navmesh) = activity.maybe_navmesh() {
|
||||||
|
if top.show_origin_destination {
|
||||||
|
let (origin, destination) = (navmesh.origin(), navmesh.destination());
|
||||||
|
painter.paint_dot(
|
||||||
|
Circle {
|
||||||
|
pos: board.layout().drawing().primitive(origin).shape().center(),
|
||||||
|
r: 150.0,
|
||||||
|
},
|
||||||
|
egui::Color32::from_rgb(255, 255, 100),
|
||||||
|
);
|
||||||
|
painter.paint_dot(
|
||||||
|
Circle {
|
||||||
|
pos: board.layout().drawing().primitive(destination).shape().center(),
|
||||||
|
r: 150.0,
|
||||||
|
},
|
||||||
|
egui::Color32::from_rgb(255, 255, 100),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewport_rect
|
viewport_rect
|
||||||
|
|
@ -280,7 +259,7 @@ impl Viewport {
|
||||||
|
|
||||||
fn zoom_to_fit(
|
fn zoom_to_fit(
|
||||||
&mut self,
|
&mut self,
|
||||||
maybe_invoker: &mut Option<Invoker<SpecctraMesadata>>,
|
maybe_invoker: Option<&mut Invoker<SpecctraMesadata>>,
|
||||||
viewport_rect: &egui::Rect,
|
viewport_rect: &egui::Rect,
|
||||||
) {
|
) {
|
||||||
if self.scheduled_zoom_to_fit {
|
if self.scheduled_zoom_to_fit {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
io,
|
||||||
|
sync::{
|
||||||
|
mpsc::{channel, Receiver, Sender},
|
||||||
|
Arc, Mutex,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use unic_langid::{langid, LanguageIdentifier};
|
||||||
|
|
||||||
|
use topola::{
|
||||||
|
autorouter::{history::History, invoker::Invoker, Autorouter},
|
||||||
|
specctra::{
|
||||||
|
design::{LoadingError as SpecctraLoadingError, SpecctraDesign},
|
||||||
|
mesadata::SpecctraMesadata,
|
||||||
|
},
|
||||||
|
stepper::Step,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
activity::{ActivityContext, ActivityStatus, ActivityStepperWithStatus},
|
||||||
|
error_dialog::ErrorDialog,
|
||||||
|
interaction::InteractionContext,
|
||||||
|
layers::Layers,
|
||||||
|
menu_bar::MenuBar,
|
||||||
|
overlay::Overlay,
|
||||||
|
status_bar::StatusBar,
|
||||||
|
translator::Translator,
|
||||||
|
viewport::Viewport,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A loaded design and associated structures
|
||||||
|
pub struct Workspace {
|
||||||
|
pub design: SpecctraDesign,
|
||||||
|
pub layers: Layers,
|
||||||
|
pub overlay: Overlay,
|
||||||
|
pub invoker: Arc<Mutex<Invoker<SpecctraMesadata>>>,
|
||||||
|
|
||||||
|
pub maybe_activity: Option<ActivityStepperWithStatus>,
|
||||||
|
|
||||||
|
pub history_channel: (
|
||||||
|
Sender<std::io::Result<Result<History, serde_json::Error>>>,
|
||||||
|
Receiver<std::io::Result<Result<History, serde_json::Error>>>,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Workspace {
|
||||||
|
pub fn new(design: SpecctraDesign, tr: &Translator) -> Result<Self, String> {
|
||||||
|
let board = design.make_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"),
|
||||||
|
err
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok(Self {
|
||||||
|
design,
|
||||||
|
layers,
|
||||||
|
overlay,
|
||||||
|
invoker: Arc::new(Mutex::new(Invoker::new(autorouter))),
|
||||||
|
maybe_activity: None,
|
||||||
|
history_channel: channel(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_state(&mut self, tr: &Translator, error_dialog: &mut ErrorDialog) -> bool {
|
||||||
|
if let Ok(data) = self.history_channel.1.try_recv() {
|
||||||
|
match data {
|
||||||
|
Ok(Ok(data)) => {
|
||||||
|
self.invoker.lock().unwrap().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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(activity) = &mut self.maybe_activity {
|
||||||
|
return match activity.step(&mut ActivityContext {
|
||||||
|
interaction: InteractionContext {},
|
||||||
|
invoker: &mut *self.invoker.lock().unwrap(),
|
||||||
|
}) {
|
||||||
|
Ok(ActivityStatus::Running) => true,
|
||||||
|
Ok(ActivityStatus::Finished(..)) => false,
|
||||||
|
Err(err) => {
|
||||||
|
error_dialog.push_error("tr-module-invoker", format!("{}", err));
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_layers(&mut self, ctx: &egui::Context) {
|
||||||
|
let invoker = self.invoker.lock().unwrap();
|
||||||
|
self.layers.update(ctx, invoker.autorouter().board());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue