use eframe::{egui_glow, glow}; use egui::{mutex::Mutex, ScrollArea}; use std::sync::Arc; use crate::backend::{self, Backends}; pub mod debug_plot; use debug_plot::DebugPlots; mod waterfall; use waterfall::Waterfall; mod fft; use fft::Fft; pub mod turbo_colormap; const FFT_SIZE: usize = 1024; pub struct TemplateApp { plots: DebugPlots, /// Behind an `Arc>` so we can pass it to [`egui::PaintCallback`] and paint later. waterfall: Arc>, fft: Fft, backends: backend::Backends, selected_backend: usize, open_device: Option>, device_window_open: bool, side_panel_open: bool, } impl TemplateApp { /// Called once before the first frame. pub fn new(cc: &eframe::CreationContext<'_>) -> Self { // This is also where you can customize the look and feel of egui using // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`. // Load previous app state (if any). // Note that you must enable the `persistence` feature for this to work. let plots = DebugPlots::new(); let (fft, rx) = Fft::new(FFT_SIZE, plots.get_sender()).unwrap(); let wf_size = fft.output_len; let gl = cc .gl .as_ref() .expect("Could not get gl context from glow backend"); Self { plots, waterfall: Arc::new(Mutex::new(Waterfall::new(gl, wf_size, wf_size, rx))), fft, backends: Backends::default(), selected_backend: 0, open_device: None, device_window_open: true, side_panel_open: false, } } } impl eframe::App for TemplateApp { /// Called by the frame work to save state before shutdown. /// Currently does nothing fn save(&mut self, _storage: &mut dyn eframe::Storage) {} /// Called once on shutdown, after [`Self::save`]. fn on_exit(&mut self, gl: Option<&glow::Context>) { if let Some(gl) = gl { self.waterfall.lock().destroy(gl); } } /// Called each time the UI needs repainting, which may be many times per second. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { ctx.request_repaint(); self.plots.update_plots(); // Menu bar panel egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::menu::bar(ui, |ui| { // NOTE: no File->Quit on web pages! let is_web = cfg!(target_arch = "wasm32"); ui.menu_button("File", |ui| { if ui.button("Open Device").clicked() { self.device_window_open = true; } if self.open_device.is_some() { if ui.button("Close Device").clicked() { if let Some(dev) = self.open_device.take() { dev.close(); } } } if !is_web { if ui.button("Quit").clicked() { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } } }); ui.menu_button("View", |ui| { ui.checkbox(&mut self.side_panel_open, "Side Panel"); }); self.plots.render_menu_buttons(ui); ui.add_space(16.0); egui::widgets::global_theme_preference_buttons(ui); }); }); // Side panel egui::SidePanel::right("Sid panel").show_animated(ctx, self.side_panel_open, |ui| { if let Some(d) = &mut self.open_device { d.show_settings(ui) } }); // Central panel egui::CentralPanel::default().show(ctx, |ui| { egui::TopBottomPanel::top("Plot") .resizable(true) .show_inside(ui, |_ui| { // TODO: Add plot }); egui::CentralPanel::default().show_inside(ui, |ui| { ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { powered_by_egui_and_eframe(ui); egui::warn_if_debug_build(ui); egui::Frame::canvas(ui.style()).show(ui, |ui| { let available_space = ui.available_size(); let (rect, response) = ui.allocate_exact_size(available_space, egui::Sense::drag()); let _angle = response.drag_motion().x * 0.01; // Clone locals so we can move them into the paint callback: let waterfall = self.waterfall.clone(); let callback = egui::PaintCallback { rect, callback: std::sync::Arc::new(egui_glow::CallbackFn::new( move |_info, painter| { waterfall.lock().paint(painter.gl(), _angle); }, )), }; ui.painter().add(callback); }); }); }); }); // Update debug plot windows self.plots.render_plot_windows(ctx); // Update device selection window let mut device_window = egui::Window::new("Select Device") .default_width(600.0) .default_height(400.0) .vscroll(false) .resizable(true) .collapsible(false); if self.open_device.is_some() { device_window = device_window.open(&mut self.device_window_open); } else { device_window = device_window.anchor(egui::Align2::CENTER_CENTER, [0., 0.]); } let mut close_device_window = false; device_window.show(ctx, |ui| { egui::SidePanel::left("Select Driver") .resizable(true) .default_width(150.0) .width_range(80.0..=200.0) .show_inside(ui, |ui| { ScrollArea::vertical().show(ui, |ui| { ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| { for (i, b) in self.backends.0.iter().enumerate() { ui.selectable_value( &mut self.selected_backend, i, b.display_text(), ); } }); }); }); ui.vertical_centered(|ui| { egui::ScrollArea::vertical().show(ui, |ui| { //if self._selected_backend < self._backends.0.len() { if let Some(b) = self.backends.0.get_mut(self.selected_backend) { //let mut b = &self._backends.0[self._selected_backend]; b.show_device_selection(ui); if ui.add(egui::Button::new("Apply")).clicked() { if let Some(dev) = self.open_device.take() { dev.close() }; if let Ok(device) = b.build_device(self.fft.tx.clone(), self.plots.get_sender()) { self.open_device = Some(device); close_device_window = true; } } } else { ui.add(egui::Label::new("Select a Device Driver")); } }); }); }); if close_device_window { self.device_window_open = false; } } } fn powered_by_egui_and_eframe(ui: &mut egui::Ui) { ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; ui.label("Powered by "); ui.hyperlink_to("egui", "https://github.com/emilk/egui"); ui.label(", "); ui.hyperlink_to( "eframe", "https://github.com/emilk/egui/tree/master/crates/eframe", ); ui.label(" and "); ui.hyperlink_to("glow", "https://github.com/grovesNL/glow"); ui.label("."); }); }