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() {