diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index e56e7695772..d017c2fb88d 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -8,6 +8,7 @@ Changes since the last release can be found by running the `scripts/generate_cha ## Unreleased * `NativeOptions::fullsize_content` has been replaced with four settings: `ViewportBuilder::with_fullsize_content_view`, `with_title_shown`, `with_titlebar_shown`, `with_titlebar_buttons_shown` +* `App::on_close_event` has been replaced with `ctx.input(|i| i.viewport().close_requested())` and `ctx.send_viewport_cmd(ViewportCommand::CancelClose)`. ## 0.23.0 - 2023-09-27 * Update MSRV to Rust 1.70.0 [#3310](https://github.com/emilk/egui/pull/3310) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index b84a5925f6e..982eb54814d 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -149,25 +149,10 @@ pub trait App { /// On native the path is picked using [`crate::storage_dir`]. fn save(&mut self, _storage: &mut dyn Storage) {} - /// Called when the user attempts to close the desktop window and/or quit the application. - /// - /// By returning `false` the closing will be aborted. To continue the closing return `true`. - /// - /// A scenario where this method will be run is after pressing the close button on a native - /// window, which allows you to ask the user whether they want to do something before exiting. - /// See the example at for practical usage. - /// - /// It will _not_ be called on the web or when the window is forcefully closed. - #[cfg(not(target_arch = "wasm32"))] - #[doc(alias = "exit")] - #[doc(alias = "quit")] - fn on_close_event(&mut self) -> bool { - true - } - /// Called once on shutdown, after [`Self::save`]. /// - /// If you need to abort an exit use [`Self::on_close_event`]. + /// If you need to abort an exit check `ctx.input(|i| i.viewport().close_requested())` + /// and respond with [`egui::ViewportCommand::CancelClose`]. /// /// To get a [`glow`] context you need to compile with the `glow` feature flag, /// and run eframe with the glow backend. diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 7e36a2742e1..40e756c0846 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -229,22 +229,14 @@ impl EpiIntegration { pub fn on_window_event( &mut self, - app: &mut dyn epi::App, event: &winit::event::WindowEvent<'_>, egui_winit: &mut egui_winit::State, - viewport_id: ViewportId, ) -> EventResponse { crate::profile_function!(egui_winit::short_window_event_description(event)); use winit::event::{ElementState, MouseButton, WindowEvent}; match event { - WindowEvent::CloseRequested => { - if viewport_id == ViewportId::ROOT { - self.close = app.on_close_event(); - log::debug!("App::on_close_event returned {}", self.close); - } - } WindowEvent::Destroyed => { log::debug!("Received WindowEvent::Destroyed"); self.close = true; @@ -281,23 +273,32 @@ impl EpiIntegration { ) -> egui::FullOutput { raw_input.time = Some(self.beginning.elapsed().as_secs_f64()); + let close_requested = raw_input.viewport().close_requested(); + let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { if let Some(viewport_ui_cb) = viewport_ui_cb { // Child viewport crate::profile_scope!("viewport_callback"); viewport_ui_cb(egui_ctx); } else { - // Root viewport - if egui_ctx.input(|i| i.viewport().close_requested()) { - self.close = app.on_close_event(); - log::debug!("App::on_close_event returned {}", self.close); - } - crate::profile_scope!("App::update"); app.update(egui_ctx, &mut self.frame); } }); + let is_root_viewport = viewport_ui_cb.is_none(); + if is_root_viewport && close_requested { + let canceled = full_output.viewport_output[&ViewportId::ROOT] + .commands + .contains(&egui::ViewportCommand::CancelClose); + if canceled { + log::debug!("Closing of root viewport canceled with ViewportCommand::CancelClose"); + } else { + log::debug!("Closing root viewport (ViewportCommand::CancelClose was not sent)"); + self.close = true; + } + } + self.pending_full_output.append(full_output); std::mem::take(&mut self.pending_full_output) } diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index c3e8850e1aa..bd9bdac2d8b 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -568,6 +568,7 @@ impl GlowWinitRunning { } = &mut *glutin; let viewport = viewports.get_mut(&viewport_id).unwrap(); + viewport.info.events.clear(); // they should have been processed let window = viewport.window.as_ref().unwrap(); let gl_surface = viewport.gl_surface.as_ref().unwrap(); let egui_winit = viewport.egui_winit.as_mut().unwrap(); @@ -748,12 +749,9 @@ impl GlowWinitRunning { }; if let Some(viewport_id) = viewport_id { if let Some(viewport) = glutin.viewports.get_mut(&viewport_id) { - event_response = self.integration.on_window_event( - self.app.as_mut(), - event, - viewport.egui_winit.as_mut().unwrap(), - viewport.ids.this, - ); + event_response = self + .integration + .on_window_event(event, viewport.egui_winit.as_mut().unwrap()); } } @@ -1340,6 +1338,7 @@ fn render_immediate_viewport( let Some(viewport) = viewports.get_mut(&ids.this) else { return; }; + viewport.info.events.clear(); // they should have been processed let Some(winit_state) = &mut viewport.egui_winit else { return; diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index a149b396547..8570d3b6729 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -565,6 +565,8 @@ impl WgpuWinitRunning { return EventResult::Wait; }; + viewport.info.events.clear(); // they should have been processed + let Viewport { window: Some(window), egui_winit: Some(egui_winit), @@ -657,8 +659,8 @@ impl WgpuWinitRunning { let Self { integration, - app, shared, + .. } = self; let mut shared = shared.borrow_mut(); @@ -742,9 +744,10 @@ impl WgpuWinitRunning { let event_response = viewport_id .and_then(|viewport_id| { shared.viewports.get_mut(&viewport_id).and_then(|viewport| { - viewport.egui_winit.as_mut().map(|egui_winit| { - integration.on_window_event(app.as_mut(), event, egui_winit, viewport_id) - }) + viewport + .egui_winit + .as_mut() + .map(|egui_winit| integration.on_window_event(event, egui_winit)) }) }) .unwrap_or_default(); @@ -923,6 +926,7 @@ fn render_immediate_viewport( let Some(viewport) = viewports.get_mut(&ids.this) else { return; }; + viewport.info.events.clear(); // they should have been processed let Some(winit_state) = &mut viewport.egui_winit else { return; }; diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 8bcd1dc8cfa..9c43ecb1305 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1089,6 +1089,9 @@ fn process_viewport_command( ViewportCommand::Close => { info.events.push(egui::ViewportEvent::Close); } + ViewportCommand::CancelClose => { + // Need to be handled elsewhere + } ViewportCommand::StartDrag => { // If `is_viewport_focused` is not checked on x11 the input will be permanently taken until the app is killed! diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 152d4999e42..52239865e2c 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1392,11 +1392,16 @@ impl Context { /// Sets zoom factor of the UI. /// Will become active at the start of the next frame. /// + /// Note that calling this will not update [`Self::zoom_factor`] until the end of the frame. + /// /// This is used to calculate the `pixels_per_point` /// for the UI as `pixels_per_point = zoom_fator * native_pixels_per_point`. /// /// The default is 1.0. /// Make larger to make everything larger. + /// + /// It is better to call this than modifying + /// [`Options::zoom_factor`]. #[inline(always)] pub fn set_zoom_factor(&self, zoom_factor: f32) { self.write(|ctx| { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 598fa9caf91..50073338b3c 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -156,7 +156,12 @@ impl RawInput { pub enum ViewportEvent { /// The user clicked the close-button on the window, or similar. /// - /// It is up to the user to react to this by _not_ showing the viewport in the next frame in the parent viewport. + /// If this is the root viewport, the application will exit + /// after this frame unless you send a + /// [`crate::ViewportCommand::CancelClose`] command. + /// + /// If this is not the root viewport, + /// it is up to the user to hide this viewport the next frame. /// /// This even will wake up both the child and parent viewport. Close, @@ -216,6 +221,14 @@ pub struct ViewportInfo { } impl ViewportInfo { + /// This viewport has been told to close. + /// + /// If this is the root viewport, the application will exit + /// after this frame unless you send a + /// [`crate::ViewportCommand::CancelClose`] command. + /// + /// If this is not the root viewport, + /// it is up to the user to hide this viewport the next frame. pub fn close_requested(&self) -> bool { self.events .iter() diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index b9a9b46e076..45b7f5e8253 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -178,6 +178,9 @@ pub struct Options { /// /// The default is 1.0. /// Make larger to make everything larger. + /// + /// Please call [`crate::Context::set_zoom_factor`] + /// instead of modifying this directly! pub zoom_factor: f32, /// If `true`, egui will change the scale of the ui ([`crate::Context::zoom_factor`]) when the user diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 1ee09f57fae..740b604d2ec 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -831,6 +831,9 @@ pub enum ViewportCommand { /// For other viewports, the [`crate::ViewportInfo::close_requested`] flag will be set. Close, + /// Calcel the closing that was signaled by [`crate::ViewportInfo::close_requested`]. + CancelClose, + /// Set the window title. Title(String), diff --git a/examples/confirm_exit/src/main.rs b/examples/confirm_exit/src/main.rs index f0769e0e33e..3a09da59d91 100644 --- a/examples/confirm_exit/src/main.rs +++ b/examples/confirm_exit/src/main.rs @@ -17,33 +17,38 @@ fn main() -> Result<(), eframe::Error> { #[derive(Default)] struct MyApp { - allowed_to_close: bool, show_confirmation_dialog: bool, + allowed_to_close: bool, } impl eframe::App for MyApp { - fn on_close_event(&mut self) -> bool { - self.show_confirmation_dialog = true; - self.allowed_to_close - } - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Try to close the window"); }); + if ctx.input(|i| i.viewport().close_requested()) { + if self.allowed_to_close { + // do nothing - we will close + } else { + ctx.send_viewport_cmd(egui::ViewportCommand::CancelClose); + self.show_confirmation_dialog = true; + } + } + if self.show_confirmation_dialog { - // Show confirmation dialog: egui::Window::new("Do you want to quit?") .collapsible(false) .resizable(false) .show(ctx, |ui| { ui.horizontal(|ui| { - if ui.button("Cancel").clicked() { + if ui.button("No").clicked() { self.show_confirmation_dialog = false; + self.allowed_to_close = false; } - if ui.button("Yes!").clicked() { + if ui.button("Yes").clicked() { + self.show_confirmation_dialog = false; self.allowed_to_close = true; ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); } diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index d9ae7dc0815..4077c6de4a2 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -6,7 +6,7 @@ fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() - .with_inner_size([320.0, 240.0]) + .with_inner_size([640.0, 240.0]) // wide enough for the drag-drop overlay text .with_drag_and_drop(true), ..Default::default() }; diff --git a/examples/images/src/main.rs b/examples/images/src/main.rs index 9527c4d0411..efb088b6727 100644 --- a/examples/images/src/main.rs +++ b/examples/images/src/main.rs @@ -26,11 +26,11 @@ impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { egui::ScrollArea::both().show(ui, |ui| { - ui.image(egui::include_image!("ferris.svg")); - ui.add( egui::Image::new("https://picsum.photos/seed/1.759706314/1024").rounding(10.0), ); + + ui.image(egui::include_image!("ferris.svg")); }); }); } diff --git a/examples/puffin_profiler/src/main.rs b/examples/puffin_profiler/src/main.rs index 60d6efbf77d..ff9e58f4f01 100644 --- a/examples/puffin_profiler/src/main.rs +++ b/examples/puffin_profiler/src/main.rs @@ -13,8 +13,17 @@ fn main() -> Result<(), eframe::Error> { ) } -#[derive(Default)] -struct MyApp {} +struct MyApp { + keep_repainting: bool, +} + +impl Default for MyApp { + fn default() -> Self { + Self { + keep_repainting: true, + } + } +} impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { @@ -34,7 +43,15 @@ impl eframe::App for MyApp { ui.separator(); - ui.label("Note that this app runs in 'reactive' mode, so you must interact with the app for new profile events to be sent. Waving the mouse over this window is enough."); + ui.horizontal(|ui| { + ui.checkbox(&mut self.keep_repainting, "Keep repainting"); + if self.keep_repainting { + ui.spinner(); + ui.ctx().request_repaint(); + } else { + ui.label("Repainting on events (e.g. mouse movement)"); + } + }); if ui .button( @@ -42,9 +59,15 @@ impl eframe::App for MyApp { ) .clicked() { - puffin::profile_scope!("sleep"); + puffin::profile_scope!("long_sleep"); std::thread::sleep(std::time::Duration::from_millis(50)); } + + { + // Sleep a bit to emulate some work: + puffin::profile_scope!("small_sleep"); + std::thread::sleep(std::time::Duration::from_millis(10)); + } }); } } diff --git a/examples/run_all.sh b/examples/run_all.sh new file mode 100755 index 00000000000..5513dde4e49 --- /dev/null +++ b/examples/run_all.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -eu +script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +cd "$script_path/" +set -x + +for example_name in *; do + if [ -d "$example_name" ]; then + cargo run --quiet -p $example_name + fi +done diff --git a/examples/serial_windows/src/main.rs b/examples/serial_windows/src/main.rs index 9e07ca0557c..57dccaa8b0e 100644 --- a/examples/serial_windows/src/main.rs +++ b/examples/serial_windows/src/main.rs @@ -6,7 +6,10 @@ fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). if cfg!(target_os = "macos") { - eprintln!("WARNING: this example does not work on Mac! See https://github.com/emilk/egui/issues/1918"); + eprintln!( + "This example does not work on Mac! See https://github.com/emilk/egui/issues/1918" + ); + return Ok(()); } let options = eframe::NativeOptions { @@ -54,6 +57,11 @@ impl eframe::App for MyApp { "This is the last window. Program will end when closed" }; ui.label(label_text); + + if ctx.os() == egui::os::OperatingSystem::Mac { + ui.label("This example doesn't work on Mac!"); + } + if ui.button("Close").clicked() { eprintln!("Pressed Close button"); ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); diff --git a/examples/test_viewports/src/main.rs b/examples/test_viewports/src/main.rs index 86f1d1356c9..94bc46c2a61 100644 --- a/examples/test_viewports/src/main.rs +++ b/examples/test_viewports/src/main.rs @@ -237,29 +237,6 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc>]) { )); } - let tmp_pixels_per_point = ctx.pixels_per_point(); - let mut pixels_per_point = ui.data_mut(|data| { - *data.get_temp_mut_or(container_id.with("pixels_per_point"), tmp_pixels_per_point) - }); - let res = ui.add( - egui::DragValue::new(&mut pixels_per_point) - .prefix("Pixels per Point: ") - .speed(0.1) - .clamp_range(0.5..=4.0), - ); - if res.drag_released() { - ctx.set_pixels_per_point(pixels_per_point); - } - if res.dragged() { - ui.data_mut(|data| { - data.insert_temp(container_id.with("pixels_per_point"), pixels_per_point); - }); - } else { - ui.data_mut(|data| { - data.insert_temp(container_id.with("pixels_per_point"), tmp_pixels_per_point); - }); - } - if ctx.viewport_id() != ctx.parent_viewport_id() { let parent = ctx.parent_viewport_id(); if ui.button("Set parent pos 0,0").clicked() {