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}, }; use crate::{ activity::{ActivityStatus, ActivityWithStatus}, error_dialog::ErrorDialog, file_receiver::FileReceiver, layers::Layers, menu_bar::MenuBar, overlay::Overlay, status_bar::StatusBar, translator::Translator, viewport::Viewport, }; /// Deserialize/Serialize is needed to persist app state between restarts. #[derive(Serialize, Deserialize)] #[serde(default)] pub struct App { translator: Translator, #[serde(skip)] maybe_overlay: Option, #[serde(skip)] arc_mutex_maybe_invoker: Arc>>>, #[serde(skip)] maybe_activity: Option, #[serde(skip)] content_channel: (Sender, Receiver), #[serde(skip)] history_channel: (Sender, Receiver), #[serde(skip)] viewport: Viewport, #[serde(skip)] top: MenuBar, #[serde(skip)] bottom: StatusBar, #[serde(skip)] error_dialog: ErrorDialog, #[serde(skip)] maybe_layers: Option, #[serde(skip)] maybe_design: Option, #[serde(skip)] update_counter: f32, } impl Default for App { fn default() -> Self { Self { 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(), top: MenuBar::new(), bottom: 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 { // Load previous app state if one exists. if let Some(storage) = cc.storage { let this = Self { translator: Translator::new(langid), ..eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default() }; return this; } Self { translator: Translator::new(langid), ..Default::default() } } fn advance_state_by_dt(&mut self, dt: f32) { self.update_counter += dt; while self.update_counter >= self.top.frame_timestep { self.update_counter -= self.top.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("specctra-dsn-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( "history-loader", format!("{}; {}", tr.text("error-file-history-parse"), err), ); } }, Err(err) => { self.error_dialog.push_error( "history-loader", format!("{}; {}", tr.text("error-file-load"), 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("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("error-file-load"), err))?; let design = SpecctraDesign::load(bufread) .map_err(|err| format!("{}; {}", tr.text("error-file-specctra-dsn-parse"), err))?; let board = design.make_board(); let overlay = Overlay::new(&board) .map_err(|err| format!("{}; {}", tr.text("error-overlay-init"), err))?; let layers = Layers::new(&board); let autorouter = Autorouter::new(board) .map_err(|err| format!("{}; {}", tr.text("error-autorouter-init"), 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); } /// Called each time the UI has to be repainted. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.top.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.bottom .update(ctx, &self.translator, &self.viewport, &self.maybe_activity); if self.top.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.top, &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); }