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 epi,
14 native::{event_loop_context, winit_integration::EventResult},
15 Result,
16};
17
18fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
20 #[cfg(target_os = "android")]
21 use winit::platform::android::EventLoopBuilderExtAndroid as _;
22
23 profiling::function_scope!();
24 let mut builder = winit::event_loop::EventLoop::with_user_event();
25
26 #[cfg(target_os = "android")]
27 let mut builder =
28 builder.with_android_app(native_options.android_app.take().ok_or_else(|| {
29 crate::Error::AppCreation(Box::from(
30 "`NativeOptions` is missing required `android_app`",
31 ))
32 })?);
33
34 if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
35 hook(&mut builder);
36 }
37
38 profiling::scope!("EventLoopBuilder::build");
39 Ok(builder.build()?)
40}
41
42#[cfg(not(target_os = "ios"))]
47fn with_event_loop<R>(
48 mut native_options: epi::NativeOptions,
49 f: impl FnOnce(&mut EventLoop<UserEvent>, epi::NativeOptions) -> R,
50) -> Result<R> {
51 thread_local!(static EVENT_LOOP: std::cell::RefCell<Option<EventLoop<UserEvent>>> = const { std::cell::RefCell::new(None) });
52
53 EVENT_LOOP.with(|event_loop| {
54 let mut event_loop_lock = event_loop.borrow_mut();
58 let event_loop = if let Some(event_loop) = &mut *event_loop_lock {
59 event_loop
60 } else {
61 event_loop_lock.insert(create_event_loop(&mut native_options)?)
62 };
63 Ok(f(event_loop, native_options))
64 })
65}
66
67struct WinitAppWrapper<T: WinitApp> {
70 windows_next_repaint_times: HashMap<WindowId, Instant>,
71 winit_app: T,
72 return_result: Result<(), crate::Error>,
73 run_and_return: bool,
74}
75
76impl<T: WinitApp> WinitAppWrapper<T> {
77 fn new(winit_app: T, run_and_return: bool) -> Self {
78 Self {
79 windows_next_repaint_times: HashMap::default(),
80 winit_app,
81 return_result: Ok(()),
82 run_and_return,
83 }
84 }
85
86 fn handle_event_result(
87 &mut self,
88 event_loop: &ActiveEventLoop,
89 event_result: Result<EventResult>,
90 ) {
91 let mut exit = false;
92
93 log::trace!("event_result: {event_result:?}");
94
95 let combined_result = event_result.and_then(|event_result| {
96 match event_result {
97 EventResult::Wait => {
98 event_loop.set_control_flow(ControlFlow::Wait);
99 Ok(event_result)
100 }
101 EventResult::RepaintNow(window_id) => {
102 log::trace!("RepaintNow of {window_id:?}",);
103
104 if cfg!(target_os = "windows") {
105 self.winit_app.run_ui_and_paint(event_loop, window_id)
107 } else {
108 self.windows_next_repaint_times
110 .insert(window_id, Instant::now());
111 Ok(event_result)
112 }
113 }
114 EventResult::RepaintNext(window_id) => {
115 log::trace!("RepaintNext of {window_id:?}",);
116 self.windows_next_repaint_times
117 .insert(window_id, Instant::now());
118 Ok(event_result)
119 }
120 EventResult::RepaintAt(window_id, repaint_time) => {
121 self.windows_next_repaint_times.insert(
122 window_id,
123 self.windows_next_repaint_times
124 .get(&window_id)
125 .map_or(repaint_time, |last| (*last).min(repaint_time)),
126 );
127 Ok(event_result)
128 }
129 EventResult::Exit => {
130 exit = true;
131 Ok(event_result)
132 }
133 }
134 });
135
136 if let Err(err) = combined_result {
137 log::error!("Exiting because of error: {err}");
138 exit = true;
139 self.return_result = Err(err);
140 };
141
142 if exit {
143 if self.run_and_return {
144 log::debug!("Asking to exit event loop…");
145 event_loop.exit();
146 } else {
147 log::debug!("Quitting - saving app state…");
148 self.winit_app.save_and_destroy();
149
150 log::debug!("Exiting with return code 0");
151
152 #[allow(clippy::exit)]
153 std::process::exit(0);
154 }
155 }
156
157 self.check_redraw_requests(event_loop);
158 }
159
160 fn check_redraw_requests(&mut self, event_loop: &ActiveEventLoop) {
161 let now = Instant::now();
162
163 self.windows_next_repaint_times
164 .retain(|window_id, repaint_time| {
165 if now < *repaint_time {
166 return true; };
168
169 event_loop.set_control_flow(ControlFlow::Poll);
170
171 if let Some(window) = self.winit_app.window(*window_id) {
172 log::trace!("request_redraw for {window_id:?}");
173 window.request_redraw();
174 } else {
175 log::trace!("No window found for {window_id:?}");
176 }
177 false
178 });
179
180 let next_repaint_time = self.windows_next_repaint_times.values().min().copied();
181 if let Some(next_repaint_time) = next_repaint_time {
182 event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
183 };
184 }
185}
186
187impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
188 fn suspended(&mut self, event_loop: &ActiveEventLoop) {
189 profiling::scope!("Event::Suspended");
190
191 event_loop_context::with_event_loop_context(event_loop, move || {
192 let event_result = self.winit_app.suspended(event_loop);
193 self.handle_event_result(event_loop, event_result);
194 });
195 }
196
197 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
198 profiling::scope!("Event::Resumed");
199
200 event_loop_context::with_event_loop_context(event_loop, move || {
202 let event_result = self.winit_app.resumed(event_loop);
203 self.handle_event_result(event_loop, event_result);
204 });
205 }
206
207 fn exiting(&mut self, event_loop: &ActiveEventLoop) {
208 log::debug!("Received Event::LoopExiting - saving app state…");
211 event_loop_context::with_event_loop_context(event_loop, move || {
212 self.winit_app.save_and_destroy();
213 });
214 }
215
216 fn device_event(
217 &mut self,
218 event_loop: &ActiveEventLoop,
219 device_id: winit::event::DeviceId,
220 event: winit::event::DeviceEvent,
221 ) {
222 profiling::function_scope!(egui_winit::short_device_event_description(&event));
223
224 event_loop_context::with_event_loop_context(event_loop, move || {
226 let event_result = self.winit_app.device_event(event_loop, device_id, event);
227 self.handle_event_result(event_loop, event_result);
228 });
229 }
230
231 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
232 profiling::function_scope!(match &event {
233 UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
234 #[cfg(feature = "accesskit")]
235 UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",
236 });
237
238 event_loop_context::with_event_loop_context(event_loop, move || {
239 let event_result = match event {
240 UserEvent::RequestRepaint {
241 when,
242 cumulative_pass_nr,
243 viewport_id,
244 } => {
245 let current_pass_nr = self
246 .winit_app
247 .egui_ctx()
248 .map_or(0, |ctx| ctx.cumulative_pass_nr_for(viewport_id));
249 if current_pass_nr == cumulative_pass_nr
250 || current_pass_nr == cumulative_pass_nr + 1
251 {
252 log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}");
253 if let Some(window_id) =
254 self.winit_app.window_id_from_viewport_id(viewport_id)
255 {
256 Ok(EventResult::RepaintAt(window_id, when))
257 } else {
258 Ok(EventResult::Wait)
259 }
260 } else {
261 log::trace!("Got outdated UserEvent::RequestRepaint");
262 Ok(EventResult::Wait) }
264 }
265 #[cfg(feature = "accesskit")]
266 UserEvent::AccessKitActionRequest(request) => {
267 self.winit_app.on_accesskit_event(request)
268 }
269 };
270 self.handle_event_result(event_loop, event_result);
271 });
272 }
273
274 fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
275 if let winit::event::StartCause::ResumeTimeReached { .. } = cause {
276 log::trace!("Woke up to check next_repaint_time");
277 }
278
279 self.check_redraw_requests(event_loop);
280 }
281
282 fn window_event(
283 &mut self,
284 event_loop: &ActiveEventLoop,
285 window_id: WindowId,
286 event: winit::event::WindowEvent,
287 ) {
288 profiling::function_scope!(egui_winit::short_window_event_description(&event));
289
290 event_loop_context::with_event_loop_context(event_loop, move || {
292 let event_result = match event {
293 winit::event::WindowEvent::RedrawRequested => {
294 self.winit_app.run_ui_and_paint(event_loop, window_id)
295 }
296 _ => self.winit_app.window_event(event_loop, window_id, event),
297 };
298
299 self.handle_event_result(event_loop, event_result);
300 });
301 }
302}
303
304#[cfg(not(target_os = "ios"))]
305fn run_and_return(event_loop: &mut EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
306 use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
307
308 log::trace!("Entering the winit event loop (run_app_on_demand)…");
309
310 let mut app = WinitAppWrapper::new(winit_app, true);
311 event_loop.run_app_on_demand(&mut app)?;
312 log::debug!("eframe window closed");
313 app.return_result
314}
315
316fn run_and_exit(event_loop: EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
317 log::trace!("Entering the winit event loop (run_app)…");
318
319 let mut app = WinitAppWrapper::new(winit_app, false);
321 event_loop.run_app(&mut app)?;
322
323 log::debug!("winit event loop unexpectedly returned");
324 Ok(())
325}
326
327#[cfg(feature = "glow")]
330pub fn run_glow(
331 app_name: &str,
332 mut native_options: epi::NativeOptions,
333 app_creator: epi::AppCreator<'_>,
334) -> Result {
335 #![allow(clippy::needless_return_with_question_mark)] use super::glow_integration::GlowWinitApp;
338
339 #[cfg(not(target_os = "ios"))]
340 if native_options.run_and_return {
341 return with_event_loop(native_options, |event_loop, native_options| {
342 let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
343 run_and_return(event_loop, glow_eframe)
344 })?;
345 }
346
347 let event_loop = create_event_loop(&mut native_options)?;
348 let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
349 run_and_exit(event_loop, glow_eframe)
350}
351
352#[cfg(feature = "wgpu")]
355pub fn run_wgpu(
356 app_name: &str,
357 mut native_options: epi::NativeOptions,
358 app_creator: epi::AppCreator<'_>,
359) -> Result {
360 #![allow(clippy::needless_return_with_question_mark)] use super::wgpu_integration::WgpuWinitApp;
363
364 #[cfg(not(target_os = "ios"))]
365 if native_options.run_and_return {
366 return with_event_loop(native_options, |event_loop, native_options| {
367 let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
368 run_and_return(event_loop, wgpu_eframe)
369 })?;
370 }
371
372 let event_loop = create_event_loop(&mut native_options)?;
373 let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
374 run_and_exit(event_loop, wgpu_eframe)
375}