use egui::Widget; /// How often we repaint the demo app by default #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum RunMode { /// This is the default for the demo. /// /// If this is selected, egui is only updated if are input events /// (like mouse movements) or there are some animations in the GUI. /// /// Reactive mode saves CPU. /// /// The downside is that the UI can become out-of-date if something it is supposed to monitor changes. /// For instance, a GUI for a thermostat need to repaint each time the temperature changes. /// To ensure the UI is up to date you need to call `egui::Context::request_repaint()` each /// time such an event happens. You can also chose to call `request_repaint()` once every second /// or after every single frame - this is called [`Continuous`](RunMode::Continuous) mode, /// and for games and interactive tools that need repainting every frame anyway, this should be the default. Reactive, /// This will call `egui::Context::request_repaint()` at the end of each frame /// to request the backend to repaint as soon as possible. /// /// On most platforms this will mean that egui will run at the display refresh rate of e.g. 60 Hz. /// /// For this demo it is not any reason to do so except to /// demonstrate how quickly egui runs. /// /// For games or other interactive apps, this is probably what you want to do. /// It will guarantee that egui is always up-to-date. Continuous, } /// Default for demo is Reactive since /// 1) We want to use minimal CPU /// 2) There are no external events that could invalidate the UI /// so there are no events to miss. impl Default for RunMode { fn default() -> Self { RunMode::Reactive } } // ---------------------------------------------------------------------------- #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct BackendPanel { pub open: bool, #[cfg_attr(feature = "serde", serde(skip))] // go back to [`RunMode::Reactive`] mode each time we start run_mode: RunMode, #[cfg_attr(feature = "serde", serde(skip))] repaint_after_seconds: f32, /// current slider value for current gui scale #[cfg_attr(feature = "serde", serde(skip))] pixels_per_point: Option, #[cfg_attr(feature = "serde", serde(skip))] frame_history: crate::frame_history::FrameHistory, egui_windows: EguiWindows, } impl Default for BackendPanel { fn default() -> Self { Self { open: false, run_mode: Default::default(), repaint_after_seconds: 1.0, pixels_per_point: None, frame_history: Default::default(), egui_windows: Default::default(), } } } impl BackendPanel { pub fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { self.frame_history .on_new_frame(ctx.input().time, frame.info().cpu_usage); match self.run_mode { RunMode::Continuous => { // Tell the backend to repaint as soon as possible ctx.request_repaint(); } RunMode::Reactive => { // let the computer rest for a bit ctx.request_repaint_after(std::time::Duration::from_secs_f32( self.repaint_after_seconds, )); } } } pub fn end_of_frame(&mut self, ctx: &egui::Context) { self.egui_windows.windows(ctx); } pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { egui::trace!(ui); self.integration_ui(ui, frame); ui.separator(); self.run_mode_ui(ui); ui.separator(); self.frame_history.ui(ui); ui.separator(); ui.label("egui windows:"); self.egui_windows.checkboxes(ui); ui.separator(); { let mut debug_on_hover = ui.ctx().debug_on_hover(); ui.checkbox(&mut debug_on_hover, "🐛 Debug on hover") .on_hover_text("Show structure of the ui when you hover with the mouse"); ui.ctx().set_debug_on_hover(debug_on_hover); } ui.separator(); { let mut screen_reader = ui.ctx().options().screen_reader; ui.checkbox(&mut screen_reader, "🔈 Screen reader").on_hover_text("Experimental feature: checking this will turn on the screen reader on supported platforms"); ui.ctx().options().screen_reader = screen_reader; } #[cfg(not(target_arch = "wasm32"))] { ui.separator(); if ui.button("Quit").clicked() { frame.quit(); } if ui .button("Drag me to drag window") .is_pointer_button_down_on() { frame.drag_window(); } } } fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; ui.label("egui running inside "); ui.hyperlink_to("eframe", "https://github.com/emilk/egui/tree/master/eframe"); ui.label("."); }); #[cfg(target_arch = "wasm32")] ui.collapsing("Web info (location)", |ui| { ui.monospace(format!("{:#?}", frame.info().web_info.location)); }); // For instance: `eframe` web sets `pixels_per_point` every frame to force // egui to use the same scale as the web zoom factor. let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some(); if !integration_controls_pixels_per_point { if let Some(new_pixels_per_point) = self.pixels_per_point_ui(ui, &frame.info()) { ui.ctx().set_pixels_per_point(new_pixels_per_point); } } #[cfg(not(target_arch = "wasm32"))] { ui.horizontal(|ui| { { let mut fullscreen = frame.info().window_info.fullscreen; ui.checkbox(&mut fullscreen, "🗖 Fullscreen") .on_hover_text("Fullscreen the window"); frame.set_fullscreen(fullscreen); } if ui .button("📱 Phone Size") .on_hover_text("Resize the window to be small like a phone.") .clicked() { // frame.set_window_size(egui::vec2(375.0, 812.0)); // iPhone 12 mini frame.set_window_size(egui::vec2(375.0, 667.0)); // iPhone SE 2nd gen ui.close_menu(); } }); } } fn pixels_per_point_ui( &mut self, ui: &mut egui::Ui, info: &eframe::IntegrationInfo, ) -> Option { let pixels_per_point = self.pixels_per_point.get_or_insert_with(|| { info.native_pixels_per_point .unwrap_or_else(|| ui.ctx().pixels_per_point()) }); ui.horizontal(|ui| { ui.spacing_mut().slider_width = 90.0; ui.add( egui::Slider::new(pixels_per_point, 0.5..=5.0) .logarithmic(true) .clamp_to_range(true) .text("Scale"), ) .on_hover_text("Physical pixels per point."); if let Some(native_pixels_per_point) = info.native_pixels_per_point { let enabled = *pixels_per_point != native_pixels_per_point; if ui .add_enabled(enabled, egui::Button::new("Reset")) .on_hover_text(format!( "Reset scale to native value ({:.1})", native_pixels_per_point )) .clicked() { *pixels_per_point = native_pixels_per_point; } } }); // We wait until mouse release to activate: if ui.ctx().is_using_pointer() { None } else { Some(*pixels_per_point) } } fn run_mode_ui(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { let run_mode = &mut self.run_mode; ui.label("Mode:"); ui.radio_value(run_mode, RunMode::Reactive, "Reactive") .on_hover_text("Repaint when there are animations or input (e.g. mouse movement)"); ui.radio_value(run_mode, RunMode::Continuous, "Continuous") .on_hover_text("Repaint everything each frame"); }); if self.run_mode == RunMode::Continuous { ui.label(format!( "Repainting the UI each frame. FPS: {:.1}", self.frame_history.fps() )); } else { ui.label("Only running UI code when there are animations or input."); ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; ui.label("(but at least every "); egui::DragValue::new(&mut self.repaint_after_seconds) .clamp_range(0.1..=10.0) .speed(0.1) .suffix(" s") .ui(ui) .on_hover_text("Repaint this often, even if there is no input."); ui.label(")"); }); } } } // ---------------------------------------------------------------------------- #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] struct EguiWindows { // egui stuff: settings: bool, inspection: bool, memory: bool, output_events: bool, #[cfg_attr(feature = "serde", serde(skip))] output_event_history: std::collections::VecDeque, } impl Default for EguiWindows { fn default() -> Self { EguiWindows::none() } } impl EguiWindows { fn none() -> Self { Self { settings: false, inspection: false, memory: false, output_events: false, output_event_history: Default::default(), } } fn checkboxes(&mut self, ui: &mut egui::Ui) { let Self { settings, inspection, memory, output_events, output_event_history: _, } = self; ui.checkbox(settings, "🔧 Settings"); ui.checkbox(inspection, "🔍 Inspection"); ui.checkbox(memory, "📝 Memory"); ui.checkbox(output_events, "📤 Output Events"); } fn windows(&mut self, ctx: &egui::Context) { let Self { settings, inspection, memory, output_events, output_event_history, } = self; for event in &ctx.output().events { output_event_history.push_back(event.clone()); } while output_event_history.len() > 1000 { output_event_history.pop_front(); } egui::Window::new("🔧 Settings") .open(settings) .vscroll(true) .show(ctx, |ui| { ctx.settings_ui(ui); }); egui::Window::new("🔍 Inspection") .open(inspection) .vscroll(true) .show(ctx, |ui| { ctx.inspection_ui(ui); }); egui::Window::new("📝 Memory") .open(memory) .resizable(false) .show(ctx, |ui| { ctx.memory_ui(ui); }); egui::Window::new("📤 Output Events") .open(output_events) .resizable(true) .default_width(520.0) .show(ctx, |ui| { ui.label( "Recent output events from egui. \ These are emitted when you interact with widgets, or move focus between them with TAB. \ They can be hooked up to a screen reader on supported platforms.", ); ui.separator(); egui::ScrollArea::vertical() .stick_to_bottom() .show(ui, |ui| { for event in output_event_history { ui.label(format!("{:?}", event)); } }); }); } }