use serde::{Deserialize, Serialize}; use std::{ future::Future, sync::{ mpsc::{channel, Receiver, Sender}, Arc, Mutex, }, }; use unic_langid::{langid, LanguageIdentifier}; use topola::{ autorouter::{invoker::Invoker, Autorouter}, specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata}, stepper::Step, }; use crate::{ activity::{ActivityStatus, ActivityStepperWithStatus}, config::Config, error_dialog::ErrorDialog, file_receiver::FileReceiver, layers::Layers, menu_bar::MenuBar, overlay::Overlay, status_bar::StatusBar, translator::Translator, viewport::Viewport, }; pub struct App { config: Config, translator: Translator, maybe_overlay: Option, arc_mutex_maybe_invoker: Arc>>>, maybe_activity: Option, content_channel: (Sender, Receiver), history_channel: (Sender, Receiver), viewport: Viewport, menu_bar: MenuBar, status_bar: StatusBar, error_dialog: ErrorDialog, maybe_layers: Option, maybe_design: Option, update_counter: f32, } impl Default for App { fn default() -> Self { Self { config: Config::default(), translator: Translator::new(langid!("en-US")), maybe_overlay: None, arc_mutex_maybe_invoker: Arc::new(Mutex::new(None)), maybe_activity: None, content_channel: channel(), history_channel: channel(), viewport: Viewport::new(), menu_bar: MenuBar::new(), status_bar: StatusBar::new(), error_dialog: ErrorDialog::new(), maybe_layers: None, maybe_design: None, update_counter: 0.0, } } } impl App { /// Called once on start. pub fn new(cc: &eframe::CreationContext<'_>, langid: LanguageIdentifier) -> Self { let mut this = Self { translator: Translator::new(langid), ..Default::default() }; // Load previous app state if one exists. if let Some(storage) = cc.storage { this.config = eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default() } this } fn advance_state_by_dt(&mut self, dt: f32) { self.update_counter += dt; while self.update_counter >= self.menu_bar.frame_timestep { self.update_counter -= self.menu_bar.frame_timestep; if !self.update_state() { return; } } } fn update_state(&mut self) -> bool { let mut content_file_receiver = FileReceiver::new(&self.content_channel.1); if let Some(input) = content_file_receiver.try_recv() { match self.load_specctra_dsn(input) { Ok(()) => {} Err(err) => { self.error_dialog.push_error("tr-module-specctra-dsn-file-loader", err); } } } if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_mut() { let mut history_file_receiver = FileReceiver::new(&self.history_channel.1); if let Some(input) = history_file_receiver.try_recv() { let tr = &self.translator; match input { Ok(bufread) => match serde_json::from_reader(bufread) { Ok(res) => invoker.replay(res), 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(invoker) { Ok(ActivityStatus::Running) => true, Ok(ActivityStatus::Finished(..)) => false, Err(err) => { self.error_dialog.push_error("tr-module-invoker", format!("{}", err)); false } }; } } false } fn load_specctra_dsn( &mut self, input: std::io::Result, ) -> Result<(), String> { let tr = &self.translator; let bufread = input.map_err(|err| format!("{}; {}", tr.text("tr-error_unable-to-read-file"), err))?; let design = SpecctraDesign::load(bufread) .map_err(|err| format!("{}; {}", tr.text("tr-error_failed-to-parse-as-specctra-dsn"), err))?; 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 { /// Called to save state before shutdown. fn save(&mut self, storage: &mut dyn eframe::Storage) { eframe::set_value(storage, eframe::APP_KEY, &self.config); } /// Called each time the UI has to be repainted. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.menu_bar.update( ctx, &self.translator, 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.maybe_overlay, &self.maybe_design, ); self.advance_state_by_dt(ctx.input(|i| i.stable_dt)); self.status_bar .update(ctx, &self.translator, &self.viewport, &self.maybe_activity); if self.menu_bar.show_layer_manager { if let Some(ref mut layers) = self.maybe_layers { if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_ref() { layers.update(ctx, invoker.autorouter().board()); } } } self.error_dialog.update(ctx, &self.translator); let _viewport_rect = self.viewport.update( ctx, &self.menu_bar, &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)) { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } } } #[cfg(not(target_arch = "wasm32"))] pub fn execute + Send + 'static>(f: F) { std::thread::spawn(move || futures_lite::future::block_on(f)); } #[cfg(target_arch = "wasm32")] pub fn execute + 'static>(f: F) { wasm_bindgen_futures::spawn_local(f); }