use core::time::Duration; use std::time::Instant; use winit::{ application::ApplicationHandler, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, window::WindowId, }; use ahash::HashMap; use super::winit_integration::{UserEvent, WinitApp}; use crate::{ Result, epi, native::{ event_loop_context, winit_integration::{EventResult, is_invisible_or_minimized}, }, }; /// Minimum interval between repaints for invisible windows. /// /// On Windows, invisible windows don't receive `RedrawRequested` events, /// so we throttle their repaints to avoid busy-looping while still /// processing viewport commands like `Visible(true)`. /// See . const INVISIBLE_WINDOW_REPAINT_INTERVAL: Duration = Duration::from_millis(100); // ---------------------------------------------------------------------------- fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result> { #[cfg(target_os = "android")] use winit::platform::android::EventLoopBuilderExtAndroid as _; profiling::function_scope!(); let mut builder = winit::event_loop::EventLoop::with_user_event(); #[cfg(target_os = "android")] let mut builder = builder.with_android_app(native_options.android_app.take().ok_or_else(|| { crate::Error::AppCreation(Box::from( "`NativeOptions` is missing required `android_app`", )) })?); if let Some(hook) = core::mem::take(&mut native_options.event_loop_builder) { hook(&mut builder); } profiling::scope!("EventLoopBuilder::build"); Ok(builder.build()?) } /// Access a thread-local event loop. /// /// We reuse the event-loop so we can support closing and opening an eframe window /// multiple times. This is just a limitation of winit. #[cfg(not(target_os = "ios"))] fn with_event_loop( mut native_options: epi::NativeOptions, f: impl FnOnce(&mut EventLoop, epi::NativeOptions) -> R, ) -> Result { thread_local!(static EVENT_LOOP: core::cell::RefCell>> = const { core::cell::RefCell::new(None) }); EVENT_LOOP.with(|event_loop| { // Since we want to reference NativeOptions when creating the EventLoop we can't // do that as part of the lazy thread local storage initialization and so we instead // create the event loop lazily here let mut event_loop_lock = event_loop.borrow_mut(); let event_loop = if let Some(event_loop) = &mut *event_loop_lock { event_loop } else { event_loop_lock.insert(create_event_loop(&mut native_options)?) }; Ok(f(event_loop, native_options)) }) } /// Wraps a [`WinitApp`] to implement [`ApplicationHandler`]. This handles redrawing, exit states, and /// some events, but otherwise forwards events to the [`WinitApp`]. struct WinitAppWrapper { windows_next_repaint_times: HashMap, winit_app: T, return_result: Result<(), crate::Error>, run_and_return: bool, } impl WinitAppWrapper { fn new(winit_app: T, run_and_return: bool) -> Self { Self { windows_next_repaint_times: HashMap::default(), winit_app, return_result: Ok(()), run_and_return, } } fn handle_event_result( &mut self, event_loop: &ActiveEventLoop, event_result: Result, ) { let mut exit = false; let mut save = false; log::trace!("event_result: {event_result:?}"); let mut event_result = event_result; if cfg!(target_os = "windows") && let Ok(EventResult::RepaintNow(window_id)) = event_result { log::trace!("RepaintNow of {window_id:?}"); self.windows_next_repaint_times .insert(window_id, Instant::now()); // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 event_result = self.winit_app.run_ui_and_paint(event_loop, window_id); } let combined_result = event_result.map(|event_result| match event_result { EventResult::Wait => { event_loop.set_control_flow(ControlFlow::Wait); event_result } EventResult::RepaintNow(window_id) => { log::trace!("RepaintNow of {window_id:?}"); self.windows_next_repaint_times .insert(window_id, Instant::now()); event_result } EventResult::RepaintNext(window_id) => { log::trace!("RepaintNext of {window_id:?}"); self.windows_next_repaint_times .insert(window_id, Instant::now()); event_result } EventResult::RepaintAt(window_id, repaint_time) => { self.windows_next_repaint_times.insert( window_id, self.windows_next_repaint_times .get(&window_id) .map_or(repaint_time, |last| (*last).min(repaint_time)), ); event_result } EventResult::Save => { save = true; event_result } EventResult::Exit => { exit = true; event_result } EventResult::CloseRequested => { // The windows need to be dropped whilst the event loop is running to allow for proper cleanup. self.winit_app.save_and_destroy(); event_result } }); if let Err(err) = combined_result { log::error!("Exiting because of error: {err}"); exit = true; self.return_result = Err(err); } if save { log::debug!("Received an EventResult::Save - saving app state"); self.winit_app.save(); } if exit { if self.run_and_return { log::debug!("Asking to exit event loop…"); event_loop.exit(); } else { log::debug!("Quitting - saving app state…"); self.winit_app.save_and_destroy(); log::debug!("Exiting with return code 0"); std::process::exit(0); } } self.check_redraw_requests(event_loop); } fn check_redraw_requests(&mut self, event_loop: &ActiveEventLoop) { let now = Instant::now(); let mut invisible_window_ids = Vec::new(); self.windows_next_repaint_times .retain(|window_id, repaint_time| { if now < *repaint_time { return true; // not yet ready } if let Some(window) = self.winit_app.window(*window_id) { // On Windows, invisible windows don't receive RedrawRequested // events, so pending viewport commands (e.g. Visible(true)) would // never be processed. We collect these windows to paint them // directly below. // See: https://github.com/emilk/egui/issues/5229 if is_invisible_or_minimized(&window) { invisible_window_ids.push(*window_id); } else { log::trace!("request_redraw for {window_id:?}"); // Don't switch to `ControlFlow::Poll` here. `request_redraw` // is enough to wake the event loop, and on Wayland the // `RedrawRequested` event is only delivered once the // compositor sends a frame callback. Polling in the meantime // busy-loops a whole CPU core. // See https://github.com/emilk/egui/issues/8326. window.request_redraw(); } } else { log::trace!("No window found for {window_id:?}"); } false }); // Paint invisible windows directly, since they won't receive // RedrawRequested events on Windows. This ensures that viewport // commands like Visible(true) are still processed. for window_id in &invisible_window_ids { let event_result = self.winit_app.run_ui_and_paint(event_loop, *window_id); self.handle_event_result(event_loop, event_result); } // Throttle any already-scheduled repaints for invisible windows // to avoid busy-looping. If no repaint was requested by the app, // the window will simply sleep. // See: https://github.com/emilk/egui/issues/7776 if !invisible_window_ids.is_empty() { let next_paint = Instant::now() + INVISIBLE_WINDOW_REPAINT_INTERVAL; for window_id in &invisible_window_ids { self.windows_next_repaint_times .entry(*window_id) .and_modify(|t| *t = (*t).min(next_paint)); } } // Always set an explicit, sleeping control flow. Previously we only set // `WaitUntil` when a repaint was already scheduled, which meant that a // `ControlFlow::Poll` set earlier was never undone once the last timed // repaint had been consumed, leaving the loop spinning. // See https://github.com/emilk/egui/issues/8326. let next_repaint_time = self.windows_next_repaint_times.values().min().copied(); event_loop.set_control_flow(match next_repaint_time { Some(next_repaint_time) => ControlFlow::WaitUntil(next_repaint_time), None => ControlFlow::Wait, }); } } impl ApplicationHandler for WinitAppWrapper { fn suspended(&mut self, event_loop: &ActiveEventLoop) { profiling::scope!("Event::Suspended"); event_loop_context::with_event_loop_context(event_loop, move || { let event_result = self.winit_app.suspended(event_loop); self.handle_event_result(event_loop, event_result); }); } fn resumed(&mut self, event_loop: &ActiveEventLoop) { profiling::scope!("Event::Resumed"); // Nb: Make sure this guard is dropped after this function returns. event_loop_context::with_event_loop_context(event_loop, move || { let event_result = self.winit_app.resumed(event_loop); self.handle_event_result(event_loop, event_result); }); } fn exiting(&mut self, event_loop: &ActiveEventLoop) { // On Mac, Cmd-Q we get here and then `run_app_on_demand` doesn't return (despite its name), // so we need to save state now: log::debug!("Received Event::LoopExiting - saving app state…"); event_loop_context::with_event_loop_context(event_loop, move || { self.winit_app.save_and_destroy(); }); } fn device_event( &mut self, event_loop: &ActiveEventLoop, device_id: winit::event::DeviceId, event: winit::event::DeviceEvent, ) { profiling::function_scope!(egui_winit::short_device_event_description(&event)); // Nb: Make sure this guard is dropped after this function returns. event_loop_context::with_event_loop_context(event_loop, move || { let event_result = self.winit_app.device_event(event_loop, device_id, event); self.handle_event_result(event_loop, event_result); }); } fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { profiling::function_scope!(match &event { UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint", #[cfg(feature = "accesskit")] UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest", }); event_loop_context::with_event_loop_context(event_loop, move || { let event_result = match event { UserEvent::RequestRepaint { when, cumulative_pass_nr, viewport_id, } => { let current_pass_nr = self .winit_app .egui_ctx() .map_or(0, |ctx| ctx.cumulative_pass_nr_for(viewport_id)); if current_pass_nr == cumulative_pass_nr || current_pass_nr == cumulative_pass_nr + 1 { log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}"); if let Some(window_id) = self.winit_app.window_id_from_viewport_id(viewport_id) { // Throttle repaints for invisible windows to prevent // high CPU usage on Windows. // See: https://github.com/emilk/egui/issues/7776 let when = if let Some(window) = self.winit_app.window(window_id) && is_invisible_or_minimized(&window) { when.max(Instant::now() + INVISIBLE_WINDOW_REPAINT_INTERVAL) } else { when }; Ok(EventResult::RepaintAt(window_id, when)) } else { Ok(EventResult::Wait) } } else { log::trace!("Got outdated UserEvent::RequestRepaint"); Ok(EventResult::Wait) // old request - we've already repainted } } #[cfg(feature = "accesskit")] UserEvent::AccessKitActionRequest(request) => { self.winit_app.on_accesskit_event(request) } }; self.handle_event_result(event_loop, event_result); }); } fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) { if let winit::event::StartCause::ResumeTimeReached { .. } = cause { log::trace!("Woke up to check next_repaint_time"); } self.check_redraw_requests(event_loop); } fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: winit::event::WindowEvent, ) { profiling::function_scope!(egui_winit::short_window_event_description(&event)); // Nb: Make sure this guard is dropped after this function returns. event_loop_context::with_event_loop_context(event_loop, move || { let event_result = match event { winit::event::WindowEvent::RedrawRequested => { self.winit_app.run_ui_and_paint(event_loop, window_id) } _ => self.winit_app.window_event(event_loop, window_id, event), }; self.handle_event_result(event_loop, event_result); }); } } #[cfg(not(target_os = "ios"))] fn run_and_return(event_loop: &mut EventLoop, winit_app: impl WinitApp) -> Result { use winit::platform::run_on_demand::EventLoopExtRunOnDemand as _; log::trace!("Entering the winit event loop (run_app_on_demand)…"); let mut app = WinitAppWrapper::new(winit_app, true); event_loop.run_app_on_demand(&mut app)?; log::debug!("eframe window closed"); app.return_result } fn run_and_exit(event_loop: EventLoop, winit_app: impl WinitApp) -> Result { log::trace!("Entering the winit event loop (run_app)…"); // When to repaint what window let mut app = WinitAppWrapper::new(winit_app, false); event_loop.run_app(&mut app)?; log::debug!("winit event loop unexpectedly returned"); Ok(()) } // ---------------------------------------------------------------------------- #[cfg(feature = "glow")] pub fn run_glow( app_name: &str, mut native_options: epi::NativeOptions, egui_ctx: Option, app_creator: epi::AppCreator<'_>, ) -> Result { use super::glow_integration::GlowWinitApp; #[cfg(not(target_os = "ios"))] if native_options.run_and_return { return with_event_loop(native_options, |event_loop, native_options| { let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, egui_ctx, app_creator); run_and_return(event_loop, glow_eframe) })?; } let event_loop = create_event_loop(&mut native_options)?; let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, egui_ctx, app_creator); run_and_exit(event_loop, glow_eframe) } #[cfg(feature = "glow")] pub fn create_glow<'a>( app_name: &str, native_options: epi::NativeOptions, app_creator: epi::AppCreator<'a>, event_loop: &EventLoop, ) -> impl ApplicationHandler + 'a { use super::glow_integration::GlowWinitApp; let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, None, app_creator); WinitAppWrapper::new(glow_eframe, true) } // ---------------------------------------------------------------------------- #[cfg(feature = "wgpu_no_default_features")] pub fn run_wgpu( app_name: &str, mut native_options: epi::NativeOptions, egui_ctx: Option, app_creator: epi::AppCreator<'_>, ) -> Result { use super::wgpu_integration::WgpuWinitApp; #[cfg(not(target_os = "ios"))] if native_options.run_and_return { return with_event_loop(native_options, |event_loop, native_options| { let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, egui_ctx, app_creator); run_and_return(event_loop, wgpu_eframe) })?; } let event_loop = create_event_loop(&mut native_options)?; let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, egui_ctx, app_creator); run_and_exit(event_loop, wgpu_eframe) } #[cfg(feature = "wgpu_no_default_features")] pub fn create_wgpu<'a>( app_name: &str, native_options: epi::NativeOptions, app_creator: epi::AppCreator<'a>, event_loop: &EventLoop, ) -> impl ApplicationHandler + 'a { use super::wgpu_integration::WgpuWinitApp; let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, None, app_creator); WinitAppWrapper::new(wgpu_eframe, true) } // ---------------------------------------------------------------------------- /// A proxy to the eframe application that implements [`ApplicationHandler`]. /// /// This can be run directly on your own [`EventLoop`] by itself or with other /// windows you manage outside of eframe. pub struct EframeWinitApplication<'a> { wrapper: Box + 'a>, control_flow: ControlFlow, } impl ApplicationHandler for EframeWinitApplication<'_> { fn resumed(&mut self, event_loop: &ActiveEventLoop) { self.wrapper.resumed(event_loop); } fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: winit::window::WindowId, event: winit::event::WindowEvent, ) { self.wrapper.window_event(event_loop, window_id, event); } fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) { self.wrapper.new_events(event_loop, cause); } fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { self.wrapper.user_event(event_loop, event); } fn device_event( &mut self, event_loop: &ActiveEventLoop, device_id: winit::event::DeviceId, event: winit::event::DeviceEvent, ) { self.wrapper.device_event(event_loop, device_id, event); } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { self.wrapper.about_to_wait(event_loop); self.control_flow = event_loop.control_flow(); } fn suspended(&mut self, event_loop: &ActiveEventLoop) { self.wrapper.suspended(event_loop); } fn exiting(&mut self, event_loop: &ActiveEventLoop) { self.wrapper.exiting(event_loop); } fn memory_warning(&mut self, event_loop: &ActiveEventLoop) { self.wrapper.memory_warning(event_loop); } } impl<'a> EframeWinitApplication<'a> { pub(crate) fn new + 'a>(app: T) -> Self { Self { wrapper: Box::new(app), control_flow: ControlFlow::default(), } } /// Pump the `EventLoop` to check for and dispatch pending events to this application. /// /// Returns either the exit code for the application or the final state of the [`ControlFlow`] /// after all events have been dispatched in this iteration. /// /// This is useful when your [`EventLoop`] is not the main event loop for your application. /// See the `external_eventloop_async` example. #[cfg(not(target_os = "ios"))] pub fn pump_eframe_app( &mut self, event_loop: &mut EventLoop, timeout: Option, ) -> EframePumpStatus { use winit::platform::pump_events::{EventLoopExtPumpEvents as _, PumpStatus}; match event_loop.pump_app_events(timeout, self) { PumpStatus::Continue => EframePumpStatus::Continue(self.control_flow), PumpStatus::Exit(code) => EframePumpStatus::Exit(code), } } } /// Either an exit code or a [`ControlFlow`] from the [`ActiveEventLoop`]. /// /// The result of [`EframeWinitApplication::pump_eframe_app`]. #[cfg(not(target_os = "ios"))] pub enum EframePumpStatus { /// The final state of the [`ControlFlow`] after all events have been dispatched /// /// Callers should perform the action that is appropriate for the [`ControlFlow`] value. Continue(ControlFlow), /// The exit code for the application Exit(i32), }