diff --git a/src/bin/topola-egui/app.rs b/src/bin/topola-egui/app.rs index 6c1157d..8d4ca2f 100644 --- a/src/bin/topola-egui/app.rs +++ b/src/bin/topola-egui/app.rs @@ -39,13 +39,13 @@ use topola::{ use crate::{ activity::{ActivityStatus, ActivityWithStatus}, - bottom::Bottom, error_dialog::ErrorDialog, file_receiver::FileReceiver, layers::Layers, + menu_bar::MenuBar, overlay::Overlay, painter::Painter, - top::Top, + status_bar::StatusBar, translator::Translator, viewport::Viewport, }; @@ -75,10 +75,10 @@ pub struct App { viewport: Viewport, #[serde(skip)] - top: Top, + top: MenuBar, #[serde(skip)] - bottom: Bottom, + bottom: StatusBar, #[serde(skip)] error_dialog: ErrorDialog, @@ -103,8 +103,8 @@ impl Default for App { content_channel: channel(), history_channel: channel(), viewport: Viewport::new(), - top: Top::new(), - bottom: Bottom::new(), + top: MenuBar::new(), + bottom: StatusBar::new(), error_dialog: ErrorDialog::new(), maybe_layers: None, maybe_design: None, diff --git a/src/bin/topola-egui/bottom.rs b/src/bin/topola-egui/bottom.rs deleted file mode 100644 index b64600f..0000000 --- a/src/bin/topola-egui/bottom.rs +++ /dev/null @@ -1,41 +0,0 @@ -use topola::autorouter::invoker::InvokerStatus; - -use crate::{ - activity::{ActivityStatus, ActivityWithStatus}, - translator::Translator, - viewport::Viewport, -}; - -pub struct Bottom {} - -impl Bottom { - pub fn new() -> Self { - Self {} - } - - pub fn update( - &mut self, - ctx: &egui::Context, - tr: &Translator, - viewport: &Viewport, - maybe_activity: &Option, - ) { - egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { - let latest_pos = viewport.transform.inverse() - * ctx.input(|i| i.pointer.latest_pos().unwrap_or_default()); - - let mut message = String::from(""); - - if let Some(activity) = maybe_activity { - if let Some(ActivityStatus::Finished(msg)) = activity.maybe_status() { - message = msg; - } - } - - ui.label(format!( - "x: {} y: {} \t {}", - latest_pos.x, -latest_pos.y, message - )); - }); - } -} diff --git a/src/bin/topola-egui/main.rs b/src/bin/topola-egui/main.rs index c2d61bc..edb15da 100644 --- a/src/bin/topola-egui/main.rs +++ b/src/bin/topola-egui/main.rs @@ -3,14 +3,14 @@ mod action; mod activity; mod app; -mod bottom; mod error_dialog; mod file_receiver; mod file_sender; mod layers; +mod menu_bar; mod overlay; mod painter; -mod top; +mod status_bar; mod translator; mod viewport; diff --git a/src/bin/topola-egui/top.rs b/src/bin/topola-egui/top.rs deleted file mode 100644 index 6bd0c28..0000000 --- a/src/bin/topola-egui/top.rs +++ /dev/null @@ -1,378 +0,0 @@ -use std::{ - fs::File, - path::Path, - sync::{mpsc::Sender, Arc, Mutex}, -}; - -use topola::{ - autorouter::{ - execute::Command, - invoker::{Invoker, InvokerError, InvokerStatus}, - AutorouterOptions, - }, - router::RouterOptions, - specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata}, -}; - -use crate::{ - action::{Action, Switch, Trigger}, - activity::{ActivityStatus, ActivityWithStatus}, - app::execute, - file_sender::FileSender, - overlay::Overlay, - translator::Translator, - viewport::Viewport, -}; - -pub struct Top { - pub autorouter_options: AutorouterOptions, - pub is_placing_via: bool, - pub show_ratsnest: bool, - pub show_navmesh: bool, - pub show_bboxes: bool, - pub show_origin_destination: bool, - pub show_layer_manager: bool, - pub frame_timestep: f32, -} - -impl Top { - pub fn new() -> Self { - Self { - autorouter_options: AutorouterOptions { - presort_by_pairwise_detours: false, - router_options: RouterOptions { - wrap_around_bands: true, - squeeze_under_bands: true, - }, - }, - is_placing_via: false, - show_ratsnest: false, - show_navmesh: false, - show_bboxes: false, - show_origin_destination: false, - show_layer_manager: true, - frame_timestep: 0.1, - } - } - - pub fn update( - &mut self, - ctx: &egui::Context, - tr: &Translator, - content_sender: Sender, - history_sender: Sender, - arc_mutex_maybe_invoker: Arc>>>, - maybe_activity: &mut Option, - viewport: &mut Viewport, - maybe_overlay: &mut Option, - maybe_design: &Option, - ) -> Result<(), InvokerError> { - let mut open_design = Trigger::new(Action::new( - tr.text("action-open-dsn"), - egui::Modifiers::CTRL, - egui::Key::O, - )); - let mut export_session = Trigger::new(Action::new( - tr.text("action-export-ses"), - egui::Modifiers::CTRL, - egui::Key::S, - )); - let mut import_history = Trigger::new(Action::new( - tr.text("action-import-cmd"), - egui::Modifiers::CTRL, - egui::Key::I, - )); - let mut export_history = Trigger::new(Action::new( - tr.text("action-export-cmd"), - egui::Modifiers::CTRL, - egui::Key::E, - )); - let mut quit = Trigger::new(Action::new( - tr.text("action-quit"), - egui::Modifiers::CTRL, - egui::Key::Q, - )); - let mut autoroute = Trigger::new(Action::new( - tr.text("action-autoroute"), - egui::Modifiers::CTRL, - egui::Key::A, - )); - let mut place_via = Switch::new(Action::new( - tr.text("action-place-via"), - egui::Modifiers::CTRL, - egui::Key::P, - )); - let mut remove_bands = Trigger::new(Action::new( - tr.text("action-remove-bands"), - egui::Modifiers::NONE, - egui::Key::Delete, - )); - let mut compare_detours = Trigger::new(Action::new( - tr.text("action-compare-detours"), - egui::Modifiers::NONE, - egui::Key::Minus, - )); - let mut measure_length = Trigger::new(Action::new( - tr.text("action-measure-length"), - egui::Modifiers::NONE, - egui::Key::Plus, - )); - let mut undo = Trigger::new(Action::new( - tr.text("action-undo"), - egui::Modifiers::CTRL, - egui::Key::Z, - )); - let mut redo = Trigger::new(Action::new( - tr.text("action-redo"), - egui::Modifiers::CTRL, - egui::Key::Y, - )); - - egui::TopBottomPanel::top("top_panel") - .show(ctx, |ui| { - egui::menu::bar(ui, |ui| { - ui.menu_button(tr.text("menu-file"), |ui| { - open_design.button(ctx, ui); - export_session.button(ctx, ui); - - ui.separator(); - - import_history.button(ctx, ui); - export_history.button(ctx, ui); - - ui.separator(); - - // "Quit" button wouldn't work on a Web page. - if !cfg!(target_arch = "wasm32") { - quit.button(ctx, ui); - } - }); - - ui.menu_button(tr.text("menu-edit"), |ui| { - undo.button(ctx, ui); - redo.button(ctx, ui); - - ui.separator(); - - remove_bands.button(ctx, ui); - }); - - ui.menu_button(tr.text("menu-view"), |ui| { - ui.toggle_value( - &mut viewport.scheduled_zoom_to_fit, - tr.text("zoom-to-fit"), - ); - - ui.separator(); - - ui.checkbox(&mut self.show_ratsnest, tr.text("show-ratsnest")); - ui.checkbox(&mut self.show_navmesh, tr.text("show-navmesh")); - ui.checkbox(&mut self.show_bboxes, tr.text("show-bboxes")); - ui.checkbox( - &mut self.show_origin_destination, - tr.text("show-origin-destination"), - ); - - ui.separator(); - - ui.checkbox(&mut self.show_layer_manager, tr.text("show-layer-manager")); - - ui.separator(); - - ui.label(tr.text("frame-timestep")); - ui.add( - egui::widgets::Slider::new(&mut self.frame_timestep, 0.0..=3.0) - .suffix(" s"), - ); - }); - - ui.menu_button(tr.text("menu-place"), |ui| { - place_via.toggle_widget(ctx, ui, &mut self.is_placing_via); - }); - - ui.menu_button(tr.text("menu-route"), |ui| { - autoroute.button(ctx, ui); - ui.separator(); - - ui.menu_button(tr.text("menu-options"), |ui| { - ui.checkbox( - &mut self.autorouter_options.presort_by_pairwise_detours, - tr.text("presort-by-pairwise-detours"), - ); - ui.checkbox( - &mut self.autorouter_options.router_options.squeeze_under_bands, - tr.text("squeeze-under-bands"), - ); - ui.checkbox( - &mut self.autorouter_options.router_options.wrap_around_bands, - tr.text("wrap-around-bands"), - ); - }); - }); - - ui.menu_button(tr.text("menu-inspect"), |ui| { - compare_detours.button(ctx, ui); - measure_length.button(ctx, ui); - }); - - ui.separator(); - - egui::widgets::global_dark_light_mode_buttons(ui); - }); - - if open_design.consume_key_triggered(ctx, ui) { - // NOTE: On Linux, this requires Zenity to be installed on your system. - let ctx = ctx.clone(); - let task = rfd::AsyncFileDialog::new().pick_file(); - - execute(async move { - if let Some(file_handle) = task.await { - let file_sender = FileSender::new(content_sender); - file_sender.send(file_handle).await; - ctx.request_repaint(); - } - }); - } else if export_session.consume_key_triggered(ctx, ui) { - if let Some(design) = maybe_design { - if let Some(invoker) = arc_mutex_maybe_invoker.lock().unwrap().as_ref() { - let ctx = ui.ctx().clone(); - let board = invoker.autorouter().board(); - - // FIXME: I don't know how to avoid buffering the entire exported file - let mut writebuf = vec![]; - - design.write_ses(board, &mut writebuf); - - let mut dialog = rfd::AsyncFileDialog::new(); - if let Some(filename) = Path::new(design.get_name()).file_stem() { - if let Some(filename) = filename.to_str() { - dialog = dialog.set_file_name(filename); - } - } - let task = dialog - .add_filter(tr.text("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) { - let ctx = ctx.clone(); - let task = rfd::AsyncFileDialog::new().pick_file(); - - execute(async move { - if let Some(file_handle) = task.await { - let file_sender = FileSender::new(history_sender); - file_sender.send(file_handle).await; - ctx.request_repaint(); - } - }); - } else if export_history.consume_key_triggered(ctx, ui) { - if let Some(invoker) = arc_mutex_maybe_invoker.lock().unwrap().as_ref() { - let ctx = ctx.clone(); - let task = rfd::AsyncFileDialog::new().save_file(); - - // FIXME: I don't think we should be buffering everything in a `Vec`. - let mut writebuf = vec![]; - serde_json::to_writer_pretty(&mut writebuf, invoker.history()); - - execute(async move { - if let Some(file_handle) = task.await { - file_handle.write(&writebuf).await; - ctx.request_repaint(); - } - }); - } - } else if quit.consume_key_triggered(ctx, ui) { - ctx.send_viewport_cmd(egui::ViewportCommand::Close); - } else if autoroute.consume_key_triggered(ctx, ui) { - if 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(), - maybe_overlay, - ) { - let selection = overlay.selection().clone(); - overlay.clear_selection(); - maybe_activity.insert(ActivityWithStatus::new_execute( - invoker.execute_stepper(Command::Autoroute( - selection.pin_selection, - self.autorouter_options, - ))?, - )); - } - } - } else if place_via.consume_key_enabled(ctx, ui, &mut self.is_placing_via) { - } else if remove_bands.consume_key_triggered(ctx, ui) { - if 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(), - maybe_overlay, - ) { - let selection = overlay.selection().clone(); - overlay.clear_selection(); - maybe_activity.insert(ActivityWithStatus::new_execute( - invoker.execute_stepper(Command::RemoveBands( - selection.band_selection, - ))?, - )); - } - } - } else if measure_length.consume_key_triggered(ctx, ui) { - if 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(), - maybe_overlay, - ) { - let selection = overlay.selection().clone(); - overlay.clear_selection(); - maybe_activity.insert(ActivityWithStatus::new_execute( - invoker.execute_stepper(Command::MeasureLength( - selection.band_selection, - ))?, - )); - } - } - } else if undo.consume_key_triggered(ctx, ui) { - if let Some(invoker) = arc_mutex_maybe_invoker.lock().unwrap().as_mut() { - invoker.undo(); - } - } else if redo.consume_key_triggered(ctx, ui) { - if let Some(invoker) = arc_mutex_maybe_invoker.lock().unwrap().as_mut() { - invoker.redo(); - } - } else if compare_detours.consume_key_triggered(ctx, ui) { - if 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(), - maybe_overlay, - ) { - let selection = overlay.selection().clone(); - overlay.clear_selection(); - maybe_activity.insert(ActivityWithStatus::new_execute( - invoker.execute_stepper(Command::CompareDetours( - selection.pin_selection, - self.autorouter_options, - ))?, - )); - } - } - } - - Ok::<(), InvokerError>(()) - }) - .inner - } -} diff --git a/src/bin/topola-egui/viewport.rs b/src/bin/topola-egui/viewport.rs index ee7b8de..4ab3810 100644 --- a/src/bin/topola-egui/viewport.rs +++ b/src/bin/topola-egui/viewport.rs @@ -21,8 +21,8 @@ use topola::{ }; use crate::{ - activity::ActivityWithStatus, app::execute, layers::Layers, overlay::Overlay, painter::Painter, - top::Top, + activity::ActivityWithStatus, app::execute, layers::Layers, menu_bar::MenuBar, + overlay::Overlay, painter::Painter, }; pub struct Viewport { @@ -41,7 +41,7 @@ impl Viewport { pub fn update( &mut self, ctx: &egui::Context, - top: &Top, + top: &MenuBar, maybe_invoker: &mut Option>, maybe_activity: &mut Option, maybe_overlay: &mut Option, @@ -66,7 +66,7 @@ impl Viewport { pub fn paint( &mut self, ctx: &egui::Context, - top: &Top, + top: &MenuBar, maybe_invoker: &mut Option>, maybe_activity: &mut Option, maybe_overlay: &mut Option,