From ab770997818212fd84a6b858f45ae222e55edbe5 Mon Sep 17 00:00:00 2001 From: Erlend Walstad <96946613+lampsitter@users.noreply.github.com> Date: Mon, 17 Jan 2022 18:45:09 +0100 Subject: [PATCH] eframe: allow aborting an exit event (#1038) --- eframe/CHANGELOG.md | 1 + eframe/examples/confirm_exit.rs | 49 +++++++++++++++++++++++++++++++++ egui-winit/src/epi.rs | 18 ++++++++++-- epi/src/lib.rs | 15 +++++++++- 4 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 eframe/examples/confirm_exit.rs diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index b704213e..d236aac5 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -9,6 +9,7 @@ NOTE: [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.m * The default native backend is now `egui_glow` (instead of `egui_glium`) ([#1020](https://github.com/emilk/egui/pull/1020)). * The default web painter is now `egui_glow` (instead of WebGL) ([#1020](https://github.com/emilk/egui/pull/1020)). * Fix horizontal scrolling direction on Linux. +* Added `App::on_exit_event` ([#1038](https://github.com/emilk/egui/pull/1038)) ## 0.16.0 - 2021-12-29 diff --git a/eframe/examples/confirm_exit.rs b/eframe/examples/confirm_exit.rs new file mode 100644 index 00000000..f1e8a359 --- /dev/null +++ b/eframe/examples/confirm_exit.rs @@ -0,0 +1,49 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +use eframe::{egui, epi}; + +#[derive(Default)] +struct MyApp { + can_exit: bool, + is_exiting: bool, +} + +impl epi::App for MyApp { + fn name(&self) -> &str { + "Confirm exit" + } + + fn on_exit_event(&mut self) -> bool { + self.is_exiting = true; + self.can_exit + } + + fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("Try to close the window"); + }); + + if self.is_exiting { + egui::Window::new("Do you want to quit?") + .collapsible(false) + .resizable(false) + .show(ctx, |ui| { + ui.horizontal(|ui| { + if ui.button("Yes!").clicked() { + self.can_exit = true; + frame.quit(); + } + + if ui.button("Not yet").clicked() { + self.is_exiting = false; + } + }); + }); + } + } +} + +fn main() { + let options = eframe::NativeOptions::default(); + eframe::run_native(Box::new(MyApp::default()), options); +} diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index 64ce7a9d..869921ab 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -240,7 +240,11 @@ impl EpiIntegration { self.app .setup(&self.egui_ctx, &self.frame, self.persistence.storage()); let app_output = self.frame.take_app_output(); - self.quit |= app_output.quit; + + if app_output.quit { + self.quit = self.app.on_exit_event(); + } + crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); } @@ -260,7 +264,12 @@ impl EpiIntegration { pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) { use winit::event::WindowEvent; - self.quit |= matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed); + if *event == WindowEvent::CloseRequested { + self.quit = self.app.on_exit_event(); + } else if *event == WindowEvent::Destroyed { + self.quit = true; + } + self.egui_winit.on_event(&self.egui_ctx, event); } @@ -282,7 +291,10 @@ impl EpiIntegration { .handle_output(window, &self.egui_ctx, egui_output); let app_output = self.frame.take_app_output(); - self.quit |= app_output.quit; + + if app_output.quit { + self.quit = self.app.on_exit_event(); + } crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 3b46494d..95fce001 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -144,7 +144,20 @@ pub trait App { /// where `APPNAME` is what is returned by [`Self::name()`]. fn save(&mut self, _storage: &mut dyn Storage) {} - /// Called once on shutdown (before or after [`Self::save`]) + /// Called before an exit that can be aborted. + /// By returning `false` the exit will be aborted. To continue the exit 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 `eframe/examples/confirm_exit.rs` for practical usage. + /// + /// It will _not_ be called on the web or when the window is forcefully closed. + fn on_exit_event(&mut self) -> bool { + true + } + + /// Called once on shutdown (before or after [`Self::save`]). If you need to abort an exit use + /// [`Self::on_exit_event`] fn on_exit(&mut self) {} // ---------