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::{ epi, native::{event_loop_context, winit_integration::EventResult}, Result, }; // ---------------------------------------------------------------------------- fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result> { #[cfg(target_os = "android")] use winit::platform::android::EventLoopBuilderExtAndroid as _; crate::profile_function!(); 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) = std::mem::take(&mut native_options.event_loop_builder) { hook(&mut builder); } crate::profile_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: std::cell::RefCell>> = const { std::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; log::trace!("event_result: {event_result:?}"); let combined_result = event_result.and_then(|event_result| { match event_result { EventResult::Wait => { event_loop.set_control_flow(ControlFlow::Wait); Ok(event_result) } EventResult::RepaintNow(window_id) => { log::trace!("RepaintNow of {window_id:?}",); if cfg!(target_os = "windows") { // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 self.winit_app.run_ui_and_paint(event_loop, window_id) } else { // Fix for https://github.com/emilk/egui/issues/2425 self.windows_next_repaint_times .insert(window_id, Instant::now()); Ok(event_result) } } EventResult::RepaintNext(window_id) => { log::trace!("RepaintNext of {window_id:?}",); self.windows_next_repaint_times .insert(window_id, Instant::now()); Ok(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)), ); Ok(event_result) } EventResult::Exit => { exit = true; Ok(event_result) } } }); if let Err(err) = combined_result { log::error!("Exiting because of error: {err}"); exit = true; self.return_result = Err(err); }; 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"); #[allow(clippy::exit)] std::process::exit(0); } } self.check_redraw_requests(event_loop); } fn check_redraw_requests(&mut self, event_loop: &ActiveEventLoop) { let now = Instant::now(); self.windows_next_repaint_times .retain(|window_id, repaint_time| { if now < *repaint_time { return true; // not yet ready }; event_loop.set_control_flow(ControlFlow::Poll); if let Some(window) = self.winit_app.window(*window_id) { log::trace!("request_redraw for {window_id:?}"); window.request_redraw(); } else { log::trace!("No window found for {window_id:?}"); } false }); let next_repaint_time = self.windows_next_repaint_times.values().min().copied(); if let Some(next_repaint_time) = next_repaint_time { event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time)); }; } } impl ApplicationHandler for WinitAppWrapper { fn suspended(&mut self, event_loop: &ActiveEventLoop) { crate::profile_function!("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) { crate::profile_function!("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, ) { crate::profile_function!(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) { crate::profile_function!(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) { 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, ) { crate::profile_function!(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; 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, app_creator: epi::AppCreator<'_>, ) -> Result { #![allow(clippy::needless_return_with_question_mark)] // False positive 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, 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, app_creator); run_and_exit(event_loop, glow_eframe) } // ---------------------------------------------------------------------------- #[cfg(feature = "wgpu")] pub fn run_wgpu( app_name: &str, mut native_options: epi::NativeOptions, app_creator: epi::AppCreator<'_>, ) -> Result { #![allow(clippy::needless_return_with_question_mark)] // False positive 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, 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, app_creator); run_and_exit(event_loop, wgpu_eframe) }