From 66c601f775ef751f7274e66bc5e81936a4d8b151 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 5 Aug 2022 08:20:31 +0200 Subject: [PATCH] Continue execution after closing native eframe window (#1889) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds `NativeOptions::run_and_return` with default value `true`. If `true`, execution will continue after the eframe window is closed. This is a new behavior introduced in this PR. If `false`, the app will close once the eframe window is closed. The is the old behavior. This is `true` by default, and the `false` option is only there so we can revert if we find any bugs. When `true`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`](https://docs.rs/winit/latest/winit/platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return) is used. The winit docs warns of its usage, recommending `EventLoop::run`, but 🤷 When `false`, [`winit::event_loop::EventLoop::run`](https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html#method.run) is used. This is a really useful feature. You can now use `eframe` to quickly open up a window and show some data or options, and then continue your program after the `eframe` window is closed My previous attempt at this caused some problems, but my new attempt seems to be working much better, at least on my Mac. --- eframe/src/epi.rs | 15 + eframe/src/lib.rs | 6 +- eframe/src/native/run.rs | 745 ++++++++++++++++++++++++++------------- 3 files changed, 524 insertions(+), 242 deletions(-) diff --git a/eframe/src/epi.rs b/eframe/src/epi.rs index a5b32d7d6ab..ab4ee0ef04b 100644 --- a/eframe/src/epi.rs +++ b/eframe/src/epi.rs @@ -269,6 +269,20 @@ pub struct NativeOptions { /// /// Default: `Theme::Dark`. pub default_theme: Theme, + + /// This controls what happens when you close the main eframe window. + /// + /// If `true`, execution will continue after the eframe window is closed. + /// If `false`, the app will close once the eframe window is closed. + /// + /// This is `true` by default, and the `false` option is only there + /// so we can revert if we find any bugs. + /// + /// This feature was introduced in . + /// + /// When `true`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used. + /// When `false`, [`winit::event_loop::EventLoop::run`] is used. + pub run_and_return: bool, } #[cfg(not(target_arch = "wasm32"))] @@ -295,6 +309,7 @@ impl Default for NativeOptions { renderer: Renderer::default(), follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"), default_theme: Theme::Dark, + run_and_return: true, } } } diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index 1175c2af94f..7558163d329 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -161,20 +161,20 @@ mod native; /// ``` #[cfg(not(target_arch = "wasm32"))] #[allow(clippy::needless_pass_by_value)] -pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! { +pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) { let renderer = native_options.renderer; match renderer { #[cfg(feature = "glow")] Renderer::Glow => { tracing::debug!("Using the glow renderer"); - native::run::run_glow(app_name, &native_options, app_creator) + native::run::run_glow(app_name, &native_options, app_creator); } #[cfg(feature = "wgpu")] Renderer::Wgpu => { tracing::debug!("Using the wgpu renderer"); - native::run::run_wgpu(app_name, &native_options, app_creator) + native::run::run_wgpu(app_name, &native_options, app_creator); } } } diff --git a/eframe/src/native/run.rs b/eframe/src/native/run.rs index 0387cb5aee9..0087aafa908 100644 --- a/eframe/src/native/run.rs +++ b/eframe/src/native/run.rs @@ -1,7 +1,16 @@ -use super::epi_integration; -use crate::epi; +//! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`]. +//! When making changes to one you often also want to apply it to the other. + +use std::time::Instant; +use std::{sync::Arc, time::Duration}; + use egui_winit::winit; +use winit::event_loop::{ControlFlow, EventLoop}; +use super::epi_integration::{self, EpiIntegration}; +use crate::epi; + +#[derive(Debug)] struct RequestRepaintEvent; #[cfg(feature = "glow")] @@ -9,7 +18,7 @@ struct RequestRepaintEvent; fn create_display( native_options: &NativeOptions, window_builder: winit::window::WindowBuilder, - event_loop: &winit::event_loop::EventLoop, + event_loop: &EventLoop, ) -> ( glutin::WindowedContext, glow::Context, @@ -47,73 +56,261 @@ fn create_display( pub use epi::NativeOptions; +enum EventResult { + Wait, + RepaintAsap, + RepaintAt(Instant), + Exit, +} + +trait WinitApp { + fn is_focused(&self) -> bool; + fn integration(&self) -> &EpiIntegration; + fn window(&self) -> &winit::window::Window; + fn save_and_destroy(&mut self); + fn paint(&mut self) -> EventResult; + fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult; +} + +fn run_and_return(mut event_loop: EventLoop, mut winit_app: impl WinitApp) { + use winit::platform::run_return::EventLoopExtRunReturn as _; + + tracing::debug!("event_loop.run_return"); + + let mut next_repaint_time = Instant::now(); + + event_loop.run_return(|event, _, control_flow| { + let event_result = match event { + winit::event::Event::LoopDestroyed => EventResult::Exit, + + // Platform-dependent event handlers to workaround a winit bug + // See: https://github.com/rust-windowing/winit/issues/987 + // See: https://github.com/rust-windowing/winit/issues/1619 + winit::event::Event::RedrawEventsCleared if cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } + + winit::event::Event::UserEvent(RequestRepaintEvent) + | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { + .. + }) => EventResult::RepaintAsap, + + event => winit_app.on_event(event), + }; + + match event_result { + EventResult::Wait => {} + EventResult::RepaintAsap => { + next_repaint_time = Instant::now(); + } + EventResult::RepaintAt(repaint_time) => { + next_repaint_time = next_repaint_time.min(repaint_time); + } + EventResult::Exit => { + *control_flow = ControlFlow::Exit; + return; + } + } + + *control_flow = match next_repaint_time.checked_duration_since(Instant::now()) { + None => { + winit_app.window().request_redraw(); + ControlFlow::Poll + } + Some(time_until_next_repaint) => { + ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint) + } + } + }); + + tracing::debug!("eframe window closed"); + + winit_app.save_and_destroy(); +} + +fn run_and_exit( + event_loop: EventLoop, + mut winit_app: impl WinitApp + 'static, +) -> ! { + tracing::debug!("event_loop.run"); + + let mut next_repaint_time = Instant::now(); + + event_loop.run(move |event, _, control_flow| { + let event_result = match event { + winit::event::Event::LoopDestroyed => EventResult::Exit, + + // Platform-dependent event handlers to workaround a winit bug + // See: https://github.com/rust-windowing/winit/issues/987 + // See: https://github.com/rust-windowing/winit/issues/1619 + winit::event::Event::RedrawEventsCleared if cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint() + } + + winit::event::Event::UserEvent(RequestRepaintEvent) + | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { + .. + }) => EventResult::RepaintAsap, + + event => winit_app.on_event(event), + }; + + match event_result { + EventResult::Wait => {} + EventResult::RepaintAsap => { + next_repaint_time = Instant::now(); + } + EventResult::RepaintAt(repaint_time) => { + next_repaint_time = next_repaint_time.min(repaint_time); + } + EventResult::Exit => { + tracing::debug!("Quitting…"); + winit_app.save_and_destroy(); + #[allow(clippy::exit)] + std::process::exit(0); + } + } + + *control_flow = match next_repaint_time.checked_duration_since(Instant::now()) { + None => { + winit_app.window().request_redraw(); + ControlFlow::Poll + } + Some(time_until_next_repaint) => { + ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint) + } + } + }) +} + +// ---------------------------------------------------------------------------- + /// Run an egui app #[cfg(feature = "glow")] -pub fn run_glow( - app_name: &str, - native_options: &epi::NativeOptions, - app_creator: epi::AppCreator, -) -> ! { - let storage = epi_integration::create_storage(app_name); - let window_settings = epi_integration::load_window_settings(storage.as_deref()); - let event_loop = winit::event_loop::EventLoop::with_user_event(); - - let window_builder = - epi_integration::window_builder(native_options, &window_settings).with_title(app_name); - let (gl_window, gl) = create_display(native_options, window_builder, &event_loop); - let gl = std::sync::Arc::new(gl); - - let mut painter = egui_glow::Painter::new(gl.clone(), None, "") - .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); - - let system_theme = native_options.system_theme(); - let mut integration = epi_integration::EpiIntegration::new( - &event_loop, - painter.max_texture_side(), - gl_window.window(), - system_theme, - storage, - Some(gl.clone()), - #[cfg(feature = "wgpu")] - None, - ); - let theme = system_theme.unwrap_or(native_options.default_theme); - integration.egui_ctx.set_visuals(theme.egui_visuals()); - - { - let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); - integration.egui_ctx.set_request_repaint_callback(move || { - event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); - }); +mod glow_integration { + use super::*; + + struct GlowWinitApp { + gl_window: glutin::WindowedContext, + gl: Arc, + painter: egui_glow::Painter, + integration: epi_integration::EpiIntegration, + app: Box, + is_focused: bool, } - let mut app = app_creator(&epi::CreationContext { - egui_ctx: integration.egui_ctx.clone(), - integration_info: integration.frame.info(), - storage: integration.frame.storage(), - gl: Some(gl.clone()), - #[cfg(feature = "wgpu")] - render_state: None, - }); + impl GlowWinitApp { + fn new( + event_loop: &EventLoop, + app_name: &str, + native_options: &epi::NativeOptions, + app_creator: epi::AppCreator, + ) -> Self { + let storage = epi_integration::create_storage(app_name); + let window_settings = epi_integration::load_window_settings(storage.as_deref()); + + let window_builder = epi_integration::window_builder(native_options, &window_settings) + .with_title(app_name); + let (gl_window, gl) = create_display(native_options, window_builder, event_loop); + let gl = Arc::new(gl); + + let painter = egui_glow::Painter::new(gl.clone(), None, "") + .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); + + let system_theme = native_options.system_theme(); + let mut integration = epi_integration::EpiIntegration::new( + event_loop, + painter.max_texture_side(), + gl_window.window(), + system_theme, + storage, + Some(gl.clone()), + #[cfg(feature = "wgpu")] + None, + ); + let theme = system_theme.unwrap_or(native_options.default_theme); + integration.egui_ctx.set_visuals(theme.egui_visuals()); + + { + let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); + integration.egui_ctx.set_request_repaint_callback(move || { + event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); + }); + } - if app.warm_up_enabled() { - integration.warm_up(app.as_mut(), gl_window.window()); + let mut app = app_creator(&epi::CreationContext { + egui_ctx: integration.egui_ctx.clone(), + integration_info: integration.frame.info(), + storage: integration.frame.storage(), + gl: Some(gl.clone()), + #[cfg(feature = "wgpu")] + render_state: None, + }); + + if app.warm_up_enabled() { + integration.warm_up(app.as_mut(), gl_window.window()); + } + + Self { + gl_window, + gl, + painter, + integration, + app, + is_focused: true, + } + } } - let mut is_focused = true; + impl WinitApp for GlowWinitApp { + fn is_focused(&self) -> bool { + self.is_focused + } - event_loop.run(move |event, _, control_flow| { - let window = gl_window.window(); + fn integration(&self) -> &EpiIntegration { + &self.integration + } + + fn window(&self) -> &winit::window::Window { + self.gl_window.window() + } + + fn save_and_destroy(&mut self) { + self.integration + .save(&mut *self.app, self.gl_window.window()); + self.app.on_exit(Some(&self.gl)); + self.painter.destroy(); + } - let mut redraw = || { + fn paint(&mut self) -> EventResult { #[cfg(feature = "puffin")] puffin::GlobalProfiler::lock().new_frame(); crate::profile_scope!("frame"); + let Self { + gl_window, + gl, + app, + integration, + painter, + .. + } = self; + let window = gl_window.window(); + let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); egui_glow::painter::clear( - &gl, + gl, screen_size_in_pixels, app.clear_color(&integration.egui_ctx.style().visuals), ); @@ -146,11 +343,10 @@ pub fn run_glow( gl_window.swap_buffers().unwrap(); } - *control_flow = if integration.should_quit() { - winit::event_loop::ControlFlow::Exit + let control_flow = if integration.should_quit() { + EventResult::Exit } else if repaint_after.is_zero() { - window.request_redraw(); - winit::event_loop::ControlFlow::Poll + EventResult::RepaintAsap } else if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_after) { @@ -159,14 +355,14 @@ pub fn run_glow( // technically, this might lead to some weird corner cases where the user *WANTS* // winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own // egui backend impl i guess. - winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant) + EventResult::RepaintAt(repaint_after_instant) } else { - winit::event_loop::ControlFlow::Wait + EventResult::Wait }; integration.maybe_autosave(app.as_mut(), window); - if !is_focused { + if !self.is_focused { // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 // But we know if we are focused (in foreground). When minimized, we are not focused. @@ -175,140 +371,203 @@ pub fn run_glow( crate::profile_scope!("bg_sleep"); std::thread::sleep(std::time::Duration::from_millis(10)); } - }; - match event { - // Platform-dependent event handlers to workaround a winit bug - // See: https://github.com/rust-windowing/winit/issues/987 - // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), - winit::event::Event::WindowEvent { event, .. } => { - match &event { - winit::event::WindowEvent::Focused(new_focused) => { - is_focused = *new_focused; - } - winit::event::WindowEvent::Resized(physical_size) => { - // Resize with 0 width and height is used by winit to signal a minimize event on Windows. - // See: https://github.com/rust-windowing/winit/issues/208 - // This solves an issue where the app would panic when minimizing on Windows. - if physical_size.width > 0 && physical_size.height > 0 { - gl_window.resize(*physical_size); + control_flow + } + + fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult { + match event { + winit::event::Event::WindowEvent { event, .. } => { + match &event { + winit::event::WindowEvent::Focused(new_focused) => { + self.is_focused = *new_focused; } + winit::event::WindowEvent::Resized(physical_size) => { + // Resize with 0 width and height is used by winit to signal a minimize event on Windows. + // See: https://github.com/rust-windowing/winit/issues/208 + // This solves an issue where the app would panic when minimizing on Windows. + if physical_size.width > 0 && physical_size.height > 0 { + self.gl_window.resize(*physical_size); + } + } + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, .. + } => { + self.gl_window.resize(**new_inner_size); + } + winit::event::WindowEvent::CloseRequested + if self.integration.should_quit() => + { + return EventResult::Exit + } + _ => {} } - winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - gl_window.resize(**new_inner_size); - } - winit::event::WindowEvent::CloseRequested if integration.should_quit() => { - *control_flow = winit::event_loop::ControlFlow::Exit; - } - _ => {} - } - integration.on_event(app.as_mut(), &event); - if integration.should_quit() { - *control_flow = winit::event_loop::ControlFlow::Exit; + self.integration.on_event(self.app.as_mut(), &event); + + if self.integration.should_quit() { + EventResult::Exit + } else { + // TODO(emilk): ask egui if the event warrants a repaint + EventResult::RepaintAsap + } } - window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead - } - winit::event::Event::LoopDestroyed => { - integration.save(&mut *app, window); - app.on_exit(Some(&gl)); - painter.destroy(); - } - winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(), - winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { - .. - }) => { - window.request_redraw(); + _ => EventResult::Wait, } - _ => {} } - }); + } + + pub fn run_glow( + app_name: &str, + native_options: &epi::NativeOptions, + app_creator: epi::AppCreator, + ) { + let event_loop = EventLoop::with_user_event(); + let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator); + + if native_options.run_and_return { + run_and_return(event_loop, glow_eframe); + } else { + run_and_exit(event_loop, glow_eframe); + } + } } -// TODO(emilk): merge with with the clone above -/// Run an egui app -#[cfg(feature = "wgpu")] -pub fn run_wgpu( - app_name: &str, - native_options: &epi::NativeOptions, - app_creator: epi::AppCreator, -) -> ! { - let storage = epi_integration::create_storage(app_name); - let window_settings = epi_integration::load_window_settings(storage.as_deref()); - let event_loop = winit::event_loop::EventLoop::with_user_event(); - - let window = epi_integration::window_builder(native_options, &window_settings) - .with_title(app_name) - .build(&event_loop) - .unwrap(); - - // SAFETY: `window` must outlive `painter`. - #[allow(unsafe_code)] - let mut painter = unsafe { - let mut painter = egui_wgpu::winit::Painter::new( - wgpu::Backends::PRIMARY | wgpu::Backends::GL, - wgpu::PowerPreference::HighPerformance, - wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::default(), - limits: wgpu::Limits::default(), - }, - wgpu::PresentMode::Fifo, - native_options.multisampling.max(1) as _, - ); - #[cfg(not(target_os = "android"))] - painter.set_window(Some(&window)); - painter - }; +#[cfg(feature = "glow")] +pub use glow_integration::run_glow; - let render_state = painter.get_render_state().expect("Uninitialized"); - - let system_theme = native_options.system_theme(); - let mut integration = epi_integration::EpiIntegration::new( - &event_loop, - painter.max_texture_side().unwrap_or(2048), - &window, - system_theme, - storage, - #[cfg(feature = "glow")] - None, - Some(render_state.clone()), - ); - let theme = system_theme.unwrap_or(native_options.default_theme); - integration.egui_ctx.set_visuals(theme.egui_visuals()); - - { - let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); - integration.egui_ctx.set_request_repaint_callback(move || { - event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); - }); +// ---------------------------------------------------------------------------- + +#[cfg(feature = "wgpu")] +mod wgpu_integration { + use super::*; + + struct WgpuWinitApp { + window: winit::window::Window, + painter: egui_wgpu::winit::Painter<'static>, + integration: epi_integration::EpiIntegration, + app: Box, + is_focused: bool, } - let mut app = app_creator(&epi::CreationContext { - egui_ctx: integration.egui_ctx.clone(), - integration_info: integration.frame.info(), - storage: integration.frame.storage(), - #[cfg(feature = "glow")] - gl: None, - render_state: Some(render_state), - }); + impl WgpuWinitApp { + fn new( + event_loop: &EventLoop, + app_name: &str, + native_options: &epi::NativeOptions, + app_creator: epi::AppCreator, + ) -> Self { + let storage = epi_integration::create_storage(app_name); + let window_settings = epi_integration::load_window_settings(storage.as_deref()); + + let window = epi_integration::window_builder(native_options, &window_settings) + .with_title(app_name) + .build(event_loop) + .unwrap(); + + // SAFETY: `window` must outlive `painter`. + #[allow(unsafe_code)] + let painter = unsafe { + let mut painter = egui_wgpu::winit::Painter::new( + wgpu::Backends::PRIMARY | wgpu::Backends::GL, + wgpu::PowerPreference::HighPerformance, + wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::default(), + limits: wgpu::Limits::default(), + }, + wgpu::PresentMode::Fifo, + native_options.multisampling.max(1) as _, + ); + #[cfg(not(target_os = "android"))] + painter.set_window(Some(&window)); + painter + }; + + let render_state = painter.get_render_state().expect("Uninitialized"); + + let system_theme = native_options.system_theme(); + let mut integration = epi_integration::EpiIntegration::new( + event_loop, + painter.max_texture_side().unwrap_or(2048), + &window, + system_theme, + storage, + #[cfg(feature = "glow")] + None, + Some(render_state.clone()), + ); + let theme = system_theme.unwrap_or(native_options.default_theme); + integration.egui_ctx.set_visuals(theme.egui_visuals()); + + { + let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); + integration.egui_ctx.set_request_repaint_callback(move || { + event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); + }); + } + + let mut app = app_creator(&epi::CreationContext { + egui_ctx: integration.egui_ctx.clone(), + integration_info: integration.frame.info(), + storage: integration.frame.storage(), + #[cfg(feature = "glow")] + gl: None, + render_state: Some(render_state), + }); + + if app.warm_up_enabled() { + integration.warm_up(app.as_mut(), &window); + } - if app.warm_up_enabled() { - integration.warm_up(app.as_mut(), &window); + Self { + window, + painter, + integration, + app, + is_focused: true, + } + } } - let mut is_focused = true; + impl WinitApp for WgpuWinitApp { + fn is_focused(&self) -> bool { + self.is_focused + } - event_loop.run(move |event, _, control_flow| { - let window = &window; + fn integration(&self) -> &EpiIntegration { + &self.integration + } + + fn window(&self) -> &winit::window::Window { + &self.window + } + + fn save_and_destroy(&mut self) { + self.integration.save(&mut *self.app, &self.window); + + #[cfg(feature = "glow")] + self.app.on_exit(None); + + #[cfg(not(feature = "glow"))] + self.app.on_exit(); - let mut redraw = || { + self.painter.destroy(); + } + + fn paint(&mut self) -> EventResult { #[cfg(feature = "puffin")] puffin::GlobalProfiler::lock().new_frame(); crate::profile_scope!("frame"); + let Self { + window, + app, + integration, + painter, + .. + } = self; + let egui::FullOutput { platform_output, repaint_after, @@ -330,11 +589,10 @@ pub fn run_wgpu( &textures_delta, ); - *control_flow = if integration.should_quit() { - winit::event_loop::ControlFlow::Exit + let control_flow = if integration.should_quit() { + EventResult::Exit } else if repaint_after.is_zero() { - window.request_redraw(); - winit::event_loop::ControlFlow::Poll + EventResult::RepaintAsap } else if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_after) { @@ -343,14 +601,14 @@ pub fn run_wgpu( // technically, this might lead to some weird corner cases where the user *WANTS* // winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own // egui backend impl i guess. - winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant) + EventResult::RepaintAt(repaint_after_instant) } else { - winit::event_loop::ControlFlow::Wait + EventResult::Wait }; integration.maybe_autosave(app.as_mut(), window); - if !is_focused { + if !self.is_focused { // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 // But we know if we are focused (in foreground). When minimized, we are not focused. @@ -359,70 +617,79 @@ pub fn run_wgpu( crate::profile_scope!("bg_sleep"); std::thread::sleep(std::time::Duration::from_millis(10)); } - }; - match event { - // Platform-dependent event handlers to workaround a winit bug - // See: https://github.com/rust-windowing/winit/issues/987 - // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), + control_flow + } - #[cfg(target_os = "android")] - winit::event::Event::Resumed => unsafe { - painter.set_window(Some(&window)); - }, - #[cfg(target_os = "android")] - winit::event::Event::Paused => unsafe { - painter.set_window(None); - }, - - winit::event::Event::WindowEvent { event, .. } => { - match &event { - winit::event::WindowEvent::Focused(new_focused) => { - is_focused = *new_focused; - } - winit::event::WindowEvent::Resized(physical_size) => { - // Resize with 0 width and height is used by winit to signal a minimize event on Windows. - // See: https://github.com/rust-windowing/winit/issues/208 - // This solves an issue where the app would panic when minimizing on Windows. - if physical_size.width > 0 && physical_size.height > 0 { - painter.on_window_resized(physical_size.width, physical_size.height); + fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult { + match event { + #[cfg(target_os = "android")] + winit::event::Event::Resumed => unsafe { + painter.set_window(Some(&window)); + }, + #[cfg(target_os = "android")] + winit::event::Event::Paused => unsafe { + painter.set_window(None); + }, + + winit::event::Event::WindowEvent { event, .. } => { + match &event { + winit::event::WindowEvent::Focused(new_focused) => { + self.is_focused = *new_focused; } + winit::event::WindowEvent::Resized(physical_size) => { + // Resize with 0 width and height is used by winit to signal a minimize event on Windows. + // See: https://github.com/rust-windowing/winit/issues/208 + // This solves an issue where the app would panic when minimizing on Windows. + if physical_size.width > 0 && physical_size.height > 0 { + self.painter + .on_window_resized(physical_size.width, physical_size.height); + } + } + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, .. + } => { + self.painter + .on_window_resized(new_inner_size.width, new_inner_size.height); + } + winit::event::WindowEvent::CloseRequested + if self.integration.should_quit() => + { + return EventResult::Exit + } + _ => {} + }; + + self.integration.on_event(self.app.as_mut(), &event); + if self.integration.should_quit() { + EventResult::Exit + } else { + // TODO(emilk): ask egui if the event warrants a repaint + EventResult::RepaintAsap } - winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - painter.on_window_resized(new_inner_size.width, new_inner_size.height); - } - winit::event::WindowEvent::CloseRequested if integration.should_quit() => { - *control_flow = winit::event_loop::ControlFlow::Exit; - } - _ => {} - }; - - integration.on_event(app.as_mut(), &event); - if integration.should_quit() { - *control_flow = winit::event_loop::ControlFlow::Exit; } - window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead + _ => EventResult::Wait, } - winit::event::Event::LoopDestroyed => { - integration.save(&mut *app, window); - - #[cfg(feature = "glow")] - app.on_exit(None); - - #[cfg(not(feature = "glow"))] - app.on_exit(); + } + } - painter.destroy(); - } - winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(), - winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { - .. - }) => { - window.request_redraw(); - } - _ => (), + pub fn run_wgpu( + app_name: &str, + native_options: &epi::NativeOptions, + app_creator: epi::AppCreator, + ) { + let event_loop = EventLoop::with_user_event(); + let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator); + + if native_options.run_and_return { + run_and_return(event_loop, wgpu_eframe); + } else { + run_and_exit(event_loop, wgpu_eframe); } - }); + } } + +// ---------------------------------------------------------------------------- + +#[cfg(feature = "wgpu")] +pub use wgpu_integration::run_wgpu;