1use std::time::Instant;
2
3use winit::{
4 application::ApplicationHandler,
5 event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
6 window::WindowId,
7};
8
9use ahash::HashMap;
10
11use super::winit_integration::{UserEvent, WinitApp};
12use crate::{
13 Result, epi,
14 native::{event_loop_context, winit_integration::EventResult},
15};
16
17fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
19 #[cfg(target_os = "android")]
20 use winit::platform::android::EventLoopBuilderExtAndroid as _;
21
22 profiling::function_scope!();
23 let mut builder = winit::event_loop::EventLoop::with_user_event();
24
25 #[cfg(target_os = "android")]
26 let mut builder =
27 builder.with_android_app(native_options.android_app.take().ok_or_else(|| {
28 crate::Error::AppCreation(Box::from(
29 "`NativeOptions` is missing required `android_app`",
30 ))
31 })?);
32
33 if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
34 hook(&mut builder);
35 }
36
37 profiling::scope!("EventLoopBuilder::build");
38 Ok(builder.build()?)
39}
40
41#[cfg(not(target_os = "ios"))]
46fn with_event_loop<R>(
47 mut native_options: epi::NativeOptions,
48 f: impl FnOnce(&mut EventLoop<UserEvent>, epi::NativeOptions) -> R,
49) -> Result<R> {
50 thread_local!(static EVENT_LOOP: std::cell::RefCell<Option<EventLoop<UserEvent>>> = const { std::cell::RefCell::new(None) });
51
52 EVENT_LOOP.with(|event_loop| {
53 let mut event_loop_lock = event_loop.borrow_mut();
57 let event_loop = if let Some(event_loop) = &mut *event_loop_lock {
58 event_loop
59 } else {
60 event_loop_lock.insert(create_event_loop(&mut native_options)?)
61 };
62 Ok(f(event_loop, native_options))
63 })
64}
65
66struct WinitAppWrapper<T: WinitApp> {
69 windows_next_repaint_times: HashMap<WindowId, Instant>,
70 winit_app: T,
71 return_result: Result<(), crate::Error>,
72 run_and_return: bool,
73}
74
75impl<T: WinitApp> WinitAppWrapper<T> {
76 fn new(winit_app: T, run_and_return: bool) -> Self {
77 Self {
78 windows_next_repaint_times: HashMap::default(),
79 winit_app,
80 return_result: Ok(()),
81 run_and_return,
82 }
83 }
84
85 fn handle_event_result(
86 &mut self,
87 event_loop: &ActiveEventLoop,
88 event_result: Result<EventResult>,
89 ) {
90 let mut exit = false;
91 let mut save = false;
92
93 log::trace!("event_result: {event_result:?}");
94
95 let mut event_result = event_result;
96
97 if cfg!(target_os = "windows")
98 && let Ok(EventResult::RepaintNow(window_id)) = event_result
99 {
100 log::trace!("RepaintNow of {window_id:?}");
101 self.windows_next_repaint_times
102 .insert(window_id, Instant::now());
103
104 event_result = self.winit_app.run_ui_and_paint(event_loop, window_id);
106 }
107
108 let combined_result = event_result.map(|event_result| match event_result {
109 EventResult::Wait => {
110 event_loop.set_control_flow(ControlFlow::Wait);
111 event_result
112 }
113 EventResult::RepaintNow(window_id) => {
114 log::trace!("RepaintNow of {window_id:?}",);
115 self.windows_next_repaint_times
116 .insert(window_id, Instant::now());
117 event_result
118 }
119 EventResult::RepaintNext(window_id) => {
120 log::trace!("RepaintNext of {window_id:?}",);
121 self.windows_next_repaint_times
122 .insert(window_id, Instant::now());
123 event_result
124 }
125 EventResult::RepaintAt(window_id, repaint_time) => {
126 self.windows_next_repaint_times.insert(
127 window_id,
128 self.windows_next_repaint_times
129 .get(&window_id)
130 .map_or(repaint_time, |last| (*last).min(repaint_time)),
131 );
132 event_result
133 }
134 EventResult::Save => {
135 save = true;
136 event_result
137 }
138 EventResult::Exit => {
139 exit = true;
140 event_result
141 }
142 EventResult::CloseRequested => {
143 self.winit_app.save_and_destroy();
145 event_result
146 }
147 });
148
149 if let Err(err) = combined_result {
150 log::error!("Exiting because of error: {err}");
151 exit = true;
152 self.return_result = Err(err);
153 }
154
155 if save {
156 log::debug!("Received an EventResult::Save - saving app state");
157 self.winit_app.save();
158 }
159
160 if exit {
161 if self.run_and_return {
162 log::debug!("Asking to exit event loop…");
163 event_loop.exit();
164 } else {
165 log::debug!("Quitting - saving app state…");
166 self.winit_app.save_and_destroy();
167
168 log::debug!("Exiting with return code 0");
169
170 std::process::exit(0);
171 }
172 }
173
174 self.check_redraw_requests(event_loop);
175 }
176
177 fn check_redraw_requests(&mut self, event_loop: &ActiveEventLoop) {
178 let now = Instant::now();
179
180 self.windows_next_repaint_times
181 .retain(|window_id, repaint_time| {
182 if now < *repaint_time {
183 return true; }
185
186 event_loop.set_control_flow(ControlFlow::Poll);
187
188 if let Some(window) = self.winit_app.window(*window_id) {
189 log::trace!("request_redraw for {window_id:?}");
190 window.request_redraw();
191 } else {
192 log::trace!("No window found for {window_id:?}");
193 }
194 false
195 });
196
197 let next_repaint_time = self.windows_next_repaint_times.values().min().copied();
198 if let Some(next_repaint_time) = next_repaint_time {
199 event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
200 }
201 }
202}
203
204impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
205 fn suspended(&mut self, event_loop: &ActiveEventLoop) {
206 profiling::scope!("Event::Suspended");
207
208 event_loop_context::with_event_loop_context(event_loop, move || {
209 let event_result = self.winit_app.suspended(event_loop);
210 self.handle_event_result(event_loop, event_result);
211 });
212 }
213
214 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
215 profiling::scope!("Event::Resumed");
216
217 event_loop_context::with_event_loop_context(event_loop, move || {
219 let event_result = self.winit_app.resumed(event_loop);
220 self.handle_event_result(event_loop, event_result);
221 });
222 }
223
224 fn exiting(&mut self, event_loop: &ActiveEventLoop) {
225 log::debug!("Received Event::LoopExiting - saving app state…");
228 event_loop_context::with_event_loop_context(event_loop, move || {
229 self.winit_app.save_and_destroy();
230 });
231 }
232
233 fn device_event(
234 &mut self,
235 event_loop: &ActiveEventLoop,
236 device_id: winit::event::DeviceId,
237 event: winit::event::DeviceEvent,
238 ) {
239 profiling::function_scope!(egui_winit::short_device_event_description(&event));
240
241 event_loop_context::with_event_loop_context(event_loop, move || {
243 let event_result = self.winit_app.device_event(event_loop, device_id, event);
244 self.handle_event_result(event_loop, event_result);
245 });
246 }
247
248 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
249 profiling::function_scope!(match &event {
250 UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
251 #[cfg(feature = "accesskit")]
252 UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",
253 });
254
255 event_loop_context::with_event_loop_context(event_loop, move || {
256 let event_result = match event {
257 UserEvent::RequestRepaint {
258 when,
259 cumulative_pass_nr,
260 viewport_id,
261 } => {
262 let current_pass_nr = self
263 .winit_app
264 .egui_ctx()
265 .map_or(0, |ctx| ctx.cumulative_pass_nr_for(viewport_id));
266 if current_pass_nr == cumulative_pass_nr
267 || current_pass_nr == cumulative_pass_nr + 1
268 {
269 log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}");
270 if let Some(window_id) =
271 self.winit_app.window_id_from_viewport_id(viewport_id)
272 {
273 Ok(EventResult::RepaintAt(window_id, when))
274 } else {
275 Ok(EventResult::Wait)
276 }
277 } else {
278 log::trace!("Got outdated UserEvent::RequestRepaint");
279 Ok(EventResult::Wait) }
281 }
282 #[cfg(feature = "accesskit")]
283 UserEvent::AccessKitActionRequest(request) => {
284 self.winit_app.on_accesskit_event(request)
285 }
286 };
287 self.handle_event_result(event_loop, event_result);
288 });
289 }
290
291 fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
292 if let winit::event::StartCause::ResumeTimeReached { .. } = cause {
293 log::trace!("Woke up to check next_repaint_time");
294 }
295
296 self.check_redraw_requests(event_loop);
297 }
298
299 fn window_event(
300 &mut self,
301 event_loop: &ActiveEventLoop,
302 window_id: WindowId,
303 event: winit::event::WindowEvent,
304 ) {
305 profiling::function_scope!(egui_winit::short_window_event_description(&event));
306
307 event_loop_context::with_event_loop_context(event_loop, move || {
309 let event_result = match event {
310 winit::event::WindowEvent::RedrawRequested => {
311 self.winit_app.run_ui_and_paint(event_loop, window_id)
312 }
313 _ => self.winit_app.window_event(event_loop, window_id, event),
314 };
315
316 self.handle_event_result(event_loop, event_result);
317 });
318 }
319}
320
321#[cfg(not(target_os = "ios"))]
322fn run_and_return(event_loop: &mut EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
323 use winit::platform::run_on_demand::EventLoopExtRunOnDemand as _;
324
325 log::trace!("Entering the winit event loop (run_app_on_demand)…");
326
327 let mut app = WinitAppWrapper::new(winit_app, true);
328 event_loop.run_app_on_demand(&mut app)?;
329 log::debug!("eframe window closed");
330 app.return_result
331}
332
333fn run_and_exit(event_loop: EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
334 log::trace!("Entering the winit event loop (run_app)…");
335
336 let mut app = WinitAppWrapper::new(winit_app, false);
338 event_loop.run_app(&mut app)?;
339
340 log::debug!("winit event loop unexpectedly returned");
341 Ok(())
342}
343
344#[cfg(feature = "glow")]
347pub fn run_glow(
348 app_name: &str,
349 mut native_options: epi::NativeOptions,
350 app_creator: epi::AppCreator<'_>,
351) -> Result {
352 #![allow(clippy::needless_return_with_question_mark)] use super::glow_integration::GlowWinitApp;
355
356 #[cfg(not(target_os = "ios"))]
357 if native_options.run_and_return {
358 return with_event_loop(native_options, |event_loop, native_options| {
359 let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
360 run_and_return(event_loop, glow_eframe)
361 })?;
362 }
363
364 let event_loop = create_event_loop(&mut native_options)?;
365 let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
366 run_and_exit(event_loop, glow_eframe)
367}
368
369#[cfg(feature = "glow")]
370pub fn create_glow<'a>(
371 app_name: &str,
372 native_options: epi::NativeOptions,
373 app_creator: epi::AppCreator<'a>,
374 event_loop: &EventLoop<UserEvent>,
375) -> impl ApplicationHandler<UserEvent> + 'a {
376 use super::glow_integration::GlowWinitApp;
377
378 let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
379 WinitAppWrapper::new(glow_eframe, true)
380}
381
382#[cfg(feature = "wgpu")]
385pub fn run_wgpu(
386 app_name: &str,
387 mut native_options: epi::NativeOptions,
388 app_creator: epi::AppCreator<'_>,
389) -> Result {
390 #![allow(clippy::needless_return_with_question_mark)] use super::wgpu_integration::WgpuWinitApp;
393
394 #[cfg(not(target_os = "ios"))]
395 if native_options.run_and_return {
396 return with_event_loop(native_options, |event_loop, native_options| {
397 let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
398 run_and_return(event_loop, wgpu_eframe)
399 })?;
400 }
401
402 let event_loop = create_event_loop(&mut native_options)?;
403 let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
404 run_and_exit(event_loop, wgpu_eframe)
405}
406
407#[cfg(feature = "wgpu")]
408pub fn create_wgpu<'a>(
409 app_name: &str,
410 native_options: epi::NativeOptions,
411 app_creator: epi::AppCreator<'a>,
412 event_loop: &EventLoop<UserEvent>,
413) -> impl ApplicationHandler<UserEvent> + 'a {
414 use super::wgpu_integration::WgpuWinitApp;
415
416 let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
417 WinitAppWrapper::new(wgpu_eframe, true)
418}
419
420pub struct EframeWinitApplication<'a> {
427 wrapper: Box<dyn ApplicationHandler<UserEvent> + 'a>,
428 control_flow: ControlFlow,
429}
430
431impl ApplicationHandler<UserEvent> for EframeWinitApplication<'_> {
432 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
433 self.wrapper.resumed(event_loop);
434 }
435
436 fn window_event(
437 &mut self,
438 event_loop: &ActiveEventLoop,
439 window_id: winit::window::WindowId,
440 event: winit::event::WindowEvent,
441 ) {
442 self.wrapper.window_event(event_loop, window_id, event);
443 }
444
445 fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
446 self.wrapper.new_events(event_loop, cause);
447 }
448
449 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
450 self.wrapper.user_event(event_loop, event);
451 }
452
453 fn device_event(
454 &mut self,
455 event_loop: &ActiveEventLoop,
456 device_id: winit::event::DeviceId,
457 event: winit::event::DeviceEvent,
458 ) {
459 self.wrapper.device_event(event_loop, device_id, event);
460 }
461
462 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
463 self.wrapper.about_to_wait(event_loop);
464 self.control_flow = event_loop.control_flow();
465 }
466
467 fn suspended(&mut self, event_loop: &ActiveEventLoop) {
468 self.wrapper.suspended(event_loop);
469 }
470
471 fn exiting(&mut self, event_loop: &ActiveEventLoop) {
472 self.wrapper.exiting(event_loop);
473 }
474
475 fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
476 self.wrapper.memory_warning(event_loop);
477 }
478}
479
480impl<'a> EframeWinitApplication<'a> {
481 pub(crate) fn new<T: ApplicationHandler<UserEvent> + 'a>(app: T) -> Self {
482 Self {
483 wrapper: Box::new(app),
484 control_flow: ControlFlow::default(),
485 }
486 }
487
488 #[cfg(not(target_os = "ios"))]
496 pub fn pump_eframe_app(
497 &mut self,
498 event_loop: &mut EventLoop<UserEvent>,
499 timeout: Option<std::time::Duration>,
500 ) -> EframePumpStatus {
501 use winit::platform::pump_events::{EventLoopExtPumpEvents as _, PumpStatus};
502
503 match event_loop.pump_app_events(timeout, self) {
504 PumpStatus::Continue => EframePumpStatus::Continue(self.control_flow),
505 PumpStatus::Exit(code) => EframePumpStatus::Exit(code),
506 }
507 }
508}
509
510#[cfg(not(target_os = "ios"))]
514pub enum EframePumpStatus {
515 Continue(ControlFlow),
519
520 Exit(i32),
522}