Skip to main content

i_slint_backend_winit/
event_loop.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4#![warn(missing_docs)]
5/*!
6    This module contains the event loop implementation using winit, as well as the
7    [WindowAdapter] trait used by the generated code and the run-time to change
8    aspects of windows on the screen.
9*/
10use crate::EventResult;
11use crate::drag_resize_window::{handle_cursor_move_for_resize, handle_resize};
12use crate::winitwindowadapter::WindowVisibility;
13use crate::{SharedBackendData, SlintEvent};
14use corelib::SharedString;
15use corelib::graphics::euclid;
16use corelib::input::{InternalKeyEvent, KeyEvent, KeyEventType, MouseEvent, TouchPhase};
17use corelib::items::{ColorScheme, PointerEventButton};
18use corelib::lengths::LogicalPoint;
19use corelib::platform::PlatformError;
20use corelib::window::*;
21use i_slint_core as corelib;
22
23#[allow(unused_imports)]
24use std::cell::{RefCell, RefMut};
25use std::rc::Rc;
26use winit::event::WindowEvent;
27use winit::event_loop::ActiveEventLoop;
28use winit::keyboard::Key;
29
30fn winit_touch_phase(phase: winit::event::TouchPhase) -> corelib::input::TouchPhase {
31    match phase {
32        winit::event::TouchPhase::Started => corelib::input::TouchPhase::Started,
33        winit::event::TouchPhase::Moved => corelib::input::TouchPhase::Moved,
34        winit::event::TouchPhase::Ended => corelib::input::TouchPhase::Ended,
35        winit::event::TouchPhase::Cancelled => corelib::input::TouchPhase::Cancelled,
36    }
37}
38use winit::event_loop::ControlFlow;
39use winit::window::ResizeDirection;
40
41/// This enum captures run-time specific events that can be dispatched to the event loop in
42/// addition to the winit events.
43pub enum CustomEvent {
44    /// On wasm request_redraw doesn't wake the event loop, so we need to manually send an event
45    /// so that the event loop can run
46    #[cfg(target_arch = "wasm32")]
47    WakeEventLoopWorkaround,
48    /// Slint internal: Invoke the
49    UserEvent(Box<dyn FnOnce() + Send>),
50    /// Emitted from quit_event_loop with the current event loop generation
51    Exit(usize),
52    #[cfg(enable_accesskit)]
53    Accesskit(accesskit_winit::Event),
54    #[cfg(muda)]
55    Muda(muda::MenuEvent),
56}
57
58impl std::fmt::Debug for CustomEvent {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        match self {
61            #[cfg(target_arch = "wasm32")]
62            Self::WakeEventLoopWorkaround => write!(f, "WakeEventLoopWorkaround"),
63            Self::UserEvent(_) => write!(f, "UserEvent"),
64            Self::Exit(_) => write!(f, "Exit"),
65            #[cfg(enable_accesskit)]
66            Self::Accesskit(a) => write!(f, "AccessKit({a:?})"),
67            #[cfg(muda)]
68            Self::Muda(e) => write!(f, "Muda({e:?})"),
69        }
70    }
71}
72
73pub struct EventLoopState {
74    shared_backend_data: Rc<SharedBackendData>,
75    // last seen cursor position
76    cursor_pos: LogicalPoint,
77    /// Whether a *mouse* button is currently pressed. Touch input is handled
78    /// separately via `process_touch_input` and does not affect this flag.
79    pressed: bool,
80
81    loop_error: Option<PlatformError>,
82    current_resize_direction: Option<ResizeDirection>,
83
84    /// Buffered mouse move event pending dispatch. Consecutive `CursorMoved`
85    /// events are coalesced. Otherwise winit sends events so frequently that it can cause performance
86    /// issues (see #9038 and #10912).
87    pending_mouse_move: Option<(winit::window::WindowId, LogicalPoint)>,
88
89    /// Set to true when pumping events for the shortest amount of time possible.
90    pumping_events_instantly: bool,
91
92    /// Allocates small i32 finger ids for iOS's pointer-valued touch ids.
93    #[cfg(target_os = "ios")]
94    touch_finger_ids: crate::ios::TouchFingerIdAllocator,
95
96    custom_application_handler: Option<Box<dyn crate::CustomApplicationHandler>>,
97}
98
99impl EventLoopState {
100    pub fn new(
101        shared_backend_data: Rc<SharedBackendData>,
102        custom_application_handler: Option<Box<dyn crate::CustomApplicationHandler>>,
103    ) -> Self {
104        Self {
105            shared_backend_data,
106            cursor_pos: Default::default(),
107            pressed: Default::default(),
108            loop_error: Default::default(),
109            current_resize_direction: Default::default(),
110            pending_mouse_move: Default::default(),
111            pumping_events_instantly: Default::default(),
112            #[cfg(target_os = "ios")]
113            touch_finger_ids: Default::default(),
114            custom_application_handler,
115        }
116    }
117
118    /// Free graphics resources for any hidden windows. Called when quitting the event loop, to work
119    /// around #8795.
120    fn suspend_all_hidden_windows(&self) {
121        let windows_to_suspend = self
122            .shared_backend_data
123            .active_windows
124            .borrow()
125            .values()
126            .filter_map(|w| w.upgrade())
127            .filter(|w| matches!(w.visibility(), WindowVisibility::Hidden))
128            .collect::<Vec<_>>();
129        for window in windows_to_suspend.into_iter() {
130            let _ = window.suspend();
131        }
132    }
133
134    /// Dispatch the buffered mouse move event, if any.
135    fn flush_pending_mouse_move(&mut self) {
136        if let Some((window_id, position)) = self.pending_mouse_move.take()
137            && let Some(window) = self.shared_backend_data.window_by_id(window_id)
138        {
139            let runtime_window = WindowInner::from_pub(window.window());
140            runtime_window.process_mouse_input(MouseEvent::Moved { position, touch_finger_id: 0 });
141        }
142    }
143}
144
145impl winit::application::ApplicationHandler<SlintEvent> for EventLoopState {
146    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
147        if matches!(
148            self.custom_application_handler
149                .as_mut()
150                .map_or(EventResult::Propagate, |handler| { handler.resumed(event_loop) }),
151            EventResult::PreventDefault
152        ) {
153            return;
154        }
155        if let Err(err) = self.shared_backend_data.create_inactive_windows(event_loop) {
156            self.loop_error = Some(err);
157            event_loop.exit();
158        }
159    }
160
161    #[allow(clippy::collapsible_match)]
162    fn window_event(
163        &mut self,
164        event_loop: &ActiveEventLoop,
165        window_id: winit::window::WindowId,
166        event: WindowEvent,
167    ) {
168        let Some(window) = self.shared_backend_data.window_by_id(window_id) else {
169            if let Some(handler) = self.custom_application_handler.as_mut() {
170                handler.window_event(event_loop, window_id, None, None, &event);
171            }
172            return;
173        };
174
175        if let Some(winit_window) = window.winit_window() {
176            if matches!(
177                self.custom_application_handler.as_mut().map_or(
178                    EventResult::Propagate,
179                    |handler| handler.window_event(
180                        event_loop,
181                        window_id,
182                        Some(&*winit_window),
183                        Some(window.window()),
184                        &event
185                    )
186                ),
187                EventResult::PreventDefault
188            ) {
189                return;
190            }
191
192            if let Some(mut window_event_filter) = window.window_event_filter.take() {
193                let event_result = window_event_filter(window.window(), &event);
194                window.window_event_filter.set(Some(window_event_filter));
195
196                match event_result {
197                    EventResult::PreventDefault => return,
198                    EventResult::Propagate => (),
199                }
200            }
201
202            #[cfg(enable_accesskit)]
203            window
204                .accesskit_adapter()
205                .expect("internal error: accesskit adapter must exist when window exists")
206                .borrow_mut()
207                .process_event(&winit_window, &event);
208        } else {
209            return;
210        }
211
212        let runtime_window = WindowInner::from_pub(window.window());
213        if !matches!(event, WindowEvent::CursorMoved { .. } | WindowEvent::AxisMotion { .. }) {
214            self.flush_pending_mouse_move();
215        }
216
217        match event {
218            WindowEvent::RedrawRequested => {
219                self.loop_error = window.draw().err();
220            }
221            WindowEvent::Resized(size) => {
222                self.loop_error = window.resize_event(size).err();
223
224                // Entering fullscreen, maximizing or minimizing the window will
225                // trigger a resize event. We need to update the internal window
226                // state to match the actual window state. We simulate a "window
227                // state event" since there is not an official event for it yet.
228                // See: https://github.com/rust-windowing/winit/issues/2334
229                window.window_state_event();
230
231                // Some platforms (e.g., Windows) may not emit an Occluded event when minimized,
232                // so manually mark the window as occluded if its size is zero.
233                #[cfg(target_os = "windows")]
234                {
235                    if size.width == 0 || size.height == 0 {
236                        window.renderer.occluded(true);
237                    }
238                }
239            }
240            WindowEvent::CloseRequested => {
241                self.loop_error = window
242                    .window()
243                    .try_dispatch_event(corelib::platform::WindowEvent::CloseRequested)
244                    .err();
245            }
246            WindowEvent::Focused(have_focus) => {
247                // Work around https://github.com/rust-windowing/winit/issues/4371
248                let have_focus = if cfg!(target_os = "macos") {
249                    window.winit_window().map_or(have_focus, |w| w.has_focus())
250                } else {
251                    have_focus
252                };
253                self.loop_error = window.activation_changed(have_focus).err();
254            }
255
256            WindowEvent::KeyboardInput { event, is_synthetic, .. } => {
257                let key_code = event.logical_key.clone();
258                // For now: Match Qt's behavior of mapping command to control and control to meta (LWin/RWin).
259                let swap_cmd_ctrl = i_slint_core::is_apple_platform();
260
261                let key_code = if swap_cmd_ctrl {
262                    #[cfg_attr(slint_nightly_test, allow(non_exhaustive_omitted_patterns))]
263                    match key_code {
264                        winit::keyboard::Key::Named(winit::keyboard::NamedKey::Control) => {
265                            winit::keyboard::Key::Named(winit::keyboard::NamedKey::Super)
266                        }
267                        winit::keyboard::Key::Named(winit::keyboard::NamedKey::Super) => {
268                            winit::keyboard::Key::Named(winit::keyboard::NamedKey::Control)
269                        }
270                        code => code,
271                    }
272                } else {
273                    key_code
274                };
275
276                fn to_slint_key(event: &winit::event::KeyEvent, key_code: &Key) -> SharedString {
277                    macro_rules! winit_key_to_char {
278                        ($($char:literal # $name:ident # $($shifted:ident)? $(=> $($_muda:ident)? # $($_qt:ident)|* # $($winit:ident $(($pos:ident))?)|* # $($_xkb:ident)|* )? ;)*) => {
279                            #[cfg_attr(slint_nightly_test, allow(non_exhaustive_omitted_patterns))]
280                            match key_code {
281                                $( $( $(
282                                            winit::keyboard::Key::Named(winit::keyboard::NamedKey::$winit)
283                                            $(if event.location == winit::keyboard::KeyLocation::$pos)?
284                                            => $char.into(),
285                                )* )? )*
286                                    winit::keyboard::Key::Character(str) => str.as_str().into(),
287                                _ => {
288                                    if let Some(text) = &event.text {
289                                        text.as_str().into()
290                                    } else {
291                                        "".into()
292                                    }
293                                }
294                            }
295                        }
296                    }
297                    i_slint_common::for_each_keys!(winit_key_to_char)
298                }
299                #[allow(unused_mut)]
300                let mut text = to_slint_key(&event, &key_code);
301
302                #[cfg(target_os = "windows")]
303                let text_without_modifiers = {
304                    use winit::platform::modifier_supplement::KeyEventExtModifierSupplement;
305
306                    // On Windows, if Ctrl+Alt is pressed with a key that does not use
307                    // AltGr for remapping, we need to fall back to the
308                    // key_without_modifiers.
309                    //
310                    // See: https://github.com/rust-windowing/winit/issues/2945
311                    //
312                    // The text_without_modifiers also let's us disambiguate between a Ctrl+Alt
313                    // combination used to imply AltGr or not.
314                    // The latter case should be treated as a shortcut, the former should not.
315                    let text_without_modifiers =
316                        to_slint_key(&event, &event.key_without_modifiers());
317                    if text.is_empty() && !text_without_modifiers.is_empty() {
318                        text = text_without_modifiers.clone();
319                    }
320                    text_without_modifiers
321                };
322
323                if text.is_empty() {
324                    // Failed to translate the key event
325                    return;
326                }
327
328                if is_synthetic {
329                    // Synthetic event are sent when the focus is acquired, for all the keys currently pressed.
330                    // Don't forward these keys other than modifiers to the app
331                    use winit::keyboard::{Key::Named, NamedKey as N};
332                    if !matches!(
333                        key_code,
334                        Named(N::Control | N::Shift | N::Super | N::Alt | N::AltGraph),
335                    ) {
336                        return;
337                    }
338                }
339
340                let event_type = match event.state {
341                    winit::event::ElementState::Pressed => corelib::input::KeyEventType::KeyPressed,
342                    winit::event::ElementState::Released => {
343                        corelib::input::KeyEventType::KeyReleased
344                    }
345                };
346                let mut key_event = KeyEvent::default();
347                key_event.text = text;
348
349                let event = corelib::input::InternalKeyEvent {
350                    key_event,
351                    event_type,
352                    #[cfg(target_os = "windows")]
353                    text_without_modifiers,
354                    ..Default::default()
355                };
356
357                runtime_window.process_key_input(event);
358            }
359            WindowEvent::Ime(winit::event::Ime::Preedit(string, preedit_selection)) => {
360                let event = InternalKeyEvent {
361                    event_type: KeyEventType::UpdateComposition,
362                    preedit_text: string.into(),
363                    preedit_selection: preedit_selection.map(|e| e.0 as i32..e.1 as i32),
364                    ..Default::default()
365                };
366                runtime_window.process_key_input(event);
367            }
368            WindowEvent::Ime(winit::event::Ime::Commit(string)) => {
369                let mut key_event = KeyEvent::default();
370                key_event.text = string.into();
371                let event = InternalKeyEvent {
372                    event_type: KeyEventType::CommitComposition,
373                    key_event,
374                    ..Default::default()
375                };
376                runtime_window.process_key_input(event);
377            }
378            WindowEvent::CursorMoved { position, .. } => {
379                self.current_resize_direction = handle_cursor_move_for_resize(
380                    &window.winit_window().unwrap(),
381                    position,
382                    self.current_resize_direction,
383                    runtime_window
384                        .window_item()
385                        .map_or(0_f64, |w| w.as_pin_ref().resize_border_width().get().into()),
386                );
387                let position = position.to_logical(runtime_window.scale_factor() as f64);
388                self.cursor_pos = euclid::point2(position.x, position.y);
389                // winit sends this event at a very high frequency. So, bunch up consecutive
390                // cursor moved events and dispatch them as soon as any other kind of event
391                // arrives.
392                self.pending_mouse_move = Some((window_id, self.cursor_pos));
393            }
394            WindowEvent::CursorLeft { .. } => {
395                // On the html canvas, we don't get the mouse move or release event when outside the canvas. So we have no choice but canceling the event
396                if cfg!(target_arch = "wasm32") || !self.pressed {
397                    self.pressed = false;
398                    runtime_window.process_mouse_input(MouseEvent::Exit);
399                }
400            }
401            WindowEvent::MouseWheel { delta, phase, .. } => {
402                let (delta_x, delta_y) = match delta {
403                    winit::event::MouseScrollDelta::LineDelta(lx, ly) => (lx * 60., ly * 60.),
404                    winit::event::MouseScrollDelta::PixelDelta(d) => {
405                        let d = d.to_logical(runtime_window.scale_factor() as f64);
406                        (d.x, d.y)
407                    }
408                };
409                let phase = match phase {
410                    winit::event::TouchPhase::Started => TouchPhase::Started,
411                    winit::event::TouchPhase::Moved => TouchPhase::Moved,
412                    winit::event::TouchPhase::Ended => TouchPhase::Ended,
413                    winit::event::TouchPhase::Cancelled => TouchPhase::Cancelled,
414                };
415                runtime_window.process_mouse_input(MouseEvent::Wheel {
416                    position: self.cursor_pos,
417                    delta_x,
418                    delta_y,
419                    phase,
420                });
421            }
422            WindowEvent::MouseInput { state, button, .. } => {
423                let button = match button {
424                    winit::event::MouseButton::Left => PointerEventButton::Left,
425                    winit::event::MouseButton::Right => PointerEventButton::Right,
426                    winit::event::MouseButton::Middle => PointerEventButton::Middle,
427                    winit::event::MouseButton::Back => PointerEventButton::Back,
428                    winit::event::MouseButton::Forward => PointerEventButton::Forward,
429                    winit::event::MouseButton::Other(_) => PointerEventButton::Other,
430                };
431                let ev = match state {
432                    winit::event::ElementState::Pressed => {
433                        if button == PointerEventButton::Left
434                            && self.current_resize_direction.is_some()
435                        {
436                            handle_resize(
437                                &window.winit_window().unwrap(),
438                                self.current_resize_direction,
439                            );
440                            return;
441                        }
442
443                        self.pressed = true;
444                        MouseEvent::Pressed {
445                            position: self.cursor_pos,
446                            button,
447                            click_count: 0,
448                            touch_finger_id: 0,
449                        }
450                    }
451                    winit::event::ElementState::Released => {
452                        self.pressed = false;
453                        MouseEvent::Released {
454                            position: self.cursor_pos,
455                            button,
456                            click_count: 0,
457                            touch_finger_id: 0,
458                        }
459                    }
460                };
461                runtime_window.process_mouse_input(ev);
462            }
463            WindowEvent::Touch(touch) => {
464                let location = touch.location.to_logical(runtime_window.scale_factor() as f64);
465                let position = euclid::point2(location.x, location.y);
466                // winit types the touch id as u64, but on all platforms except
467                // iOS it is in fact a small integer that fits in i32. Only iOS
468                // stores a UITouch pointer address in it, which
469                // TouchFingerIdAllocator maps to a small id instead.
470                #[cfg(not(target_os = "ios"))]
471                let finger_id =
472                    Some(i32::try_from(touch.id).expect("winit touch id out of i32 range"));
473                #[cfg(target_os = "ios")]
474                let finger_id = match touch.phase {
475                    winit::event::TouchPhase::Started | winit::event::TouchPhase::Moved => {
476                        self.touch_finger_ids.id_for(touch.id)
477                    }
478                    winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
479                        self.touch_finger_ids.take(touch.id)
480                    }
481                };
482                if let Some(finger_id) = finger_id {
483                    runtime_window.process_touch_input(
484                        finger_id,
485                        position,
486                        winit_touch_phase(touch.phase),
487                    );
488                }
489            }
490            WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: _ } => {
491                if std::env::var("SLINT_SCALE_FACTOR").is_err() {
492                    self.loop_error = window
493                        .window()
494                        .try_dispatch_event(corelib::platform::WindowEvent::ScaleFactorChanged {
495                            scale_factor: scale_factor as f32,
496                        })
497                        .err();
498                    // TODO: send a resize event or try to keep the logical size the same.
499                    //window.resize_event(inner_size_writer.???)?;
500                }
501            }
502            WindowEvent::ThemeChanged(theme) => {
503                window.set_color_scheme(match theme {
504                    winit::window::Theme::Dark => ColorScheme::Dark,
505                    winit::window::Theme::Light => ColorScheme::Light,
506                });
507                window.update_accent_color();
508            }
509            WindowEvent::Occluded(x) => {
510                window.renderer.occluded(x);
511
512                // In addition to the hack done for WindowEvent::Resize, also do it for Occluded so we handle Minimized change
513                window.window_state_event();
514            }
515            // Note: winit's PinchGesture does not carry a position; we use the last
516            // known cursor position as the best available approximation. On macOS
517            // trackpads, CursorMoved events typically precede gesture events.
518            WindowEvent::PinchGesture { delta, phase, .. } => {
519                runtime_window.process_mouse_input(corelib::input::MouseEvent::PinchGesture {
520                    position: self.cursor_pos,
521                    delta: delta as f32,
522                    phase: winit_touch_phase(phase),
523                });
524            }
525            WindowEvent::RotationGesture { delta, phase, .. } => {
526                // macOS/winit: positive = counterclockwise. Negate to match
527                // Slint convention (positive = clockwise).
528                runtime_window.process_mouse_input(corelib::input::MouseEvent::RotationGesture {
529                    position: self.cursor_pos,
530                    delta: -delta,
531                    phase: winit_touch_phase(phase),
532                });
533            }
534
535            WindowEvent::AxisMotion { .. } => {
536                // Ignored, but happens often and is also ignored for the purpose of bundling CursorMoved.
537            }
538            _ => {}
539        }
540
541        if self.loop_error.is_some() {
542            event_loop.exit();
543        }
544    }
545
546    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: SlintEvent) {
547        match event.0 {
548            CustomEvent::UserEvent(user_callback) => user_callback(),
549            CustomEvent::Exit(generation) => {
550                if self
551                    .shared_backend_data
552                    .event_loop_generation
553                    .load(std::sync::atomic::Ordering::Relaxed)
554                    == generation
555                {
556                    self.suspend_all_hidden_windows();
557                    event_loop.exit()
558                }
559                // else ignore the event, since it's from a previous run of the event loop
560            }
561            #[cfg(enable_accesskit)]
562            CustomEvent::Accesskit(accesskit_winit::Event { window_id, window_event }) => {
563                if let Some(window) = self.shared_backend_data.window_by_id(window_id) {
564                    let deferred_action = window
565                        .accesskit_adapter()
566                        .expect("internal error: accesskit adapter must exist when window exists")
567                        .borrow_mut()
568                        .process_accesskit_event(window_event);
569                    // access kit adapter not borrowed anymore, now invoke the deferred action
570                    if let Some(deferred_action) = deferred_action {
571                        deferred_action.invoke(window.window());
572                    }
573                }
574            }
575            #[cfg(target_arch = "wasm32")]
576            CustomEvent::WakeEventLoopWorkaround => {
577                event_loop.set_control_flow(ControlFlow::Poll);
578            }
579            #[cfg(muda)]
580            CustomEvent::Muda(event) => {
581                if let Some((window, eid, muda_type)) =
582                    event.id().0.split_once('|').and_then(|(w, e)| {
583                        let (e, muda_type) = e.split_once('|')?;
584                        Some((
585                            self.shared_backend_data.window_by_id(
586                                winit::window::WindowId::from(w.parse::<u64>().ok()?),
587                            )?,
588                            e.parse::<usize>().ok()?,
589                            muda_type.parse::<crate::muda::MudaType>().ok()?,
590                        ))
591                    })
592                {
593                    window.muda_event(eid, muda_type);
594                };
595            }
596        }
597    }
598
599    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
600        if matches!(
601            self.custom_application_handler.as_mut().map_or(EventResult::Propagate, |handler| {
602                handler.new_events(event_loop, cause)
603            }),
604            EventResult::PreventDefault
605        ) {
606            return;
607        }
608
609        event_loop.set_control_flow(ControlFlow::Wait);
610
611        corelib::platform::update_timers_and_animations();
612    }
613
614    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
615        self.flush_pending_mouse_move();
616
617        if matches!(
618            self.custom_application_handler
619                .as_mut()
620                .map_or(EventResult::Propagate, |handler| { handler.about_to_wait(event_loop) }),
621            EventResult::PreventDefault
622        ) {
623            return;
624        }
625
626        if let Err(err) = self.shared_backend_data.create_inactive_windows(event_loop) {
627            self.loop_error = Some(err);
628        }
629
630        if !event_loop.exiting() {
631            for w in self
632                .shared_backend_data
633                .active_windows
634                .borrow()
635                .values()
636                .filter_map(|w| w.upgrade())
637            {
638                if w.window().has_active_animations() {
639                    w.request_redraw();
640                }
641            }
642        }
643
644        if event_loop.control_flow() == ControlFlow::Wait
645            && let Some(next_timer) = corelib::platform::duration_until_next_timer_update()
646        {
647            event_loop.set_control_flow(ControlFlow::wait_duration(next_timer));
648        }
649
650        if self.pumping_events_instantly {
651            event_loop.set_control_flow(ControlFlow::Poll);
652        }
653    }
654
655    fn device_event(
656        &mut self,
657        event_loop: &ActiveEventLoop,
658        device_id: winit::event::DeviceId,
659        event: winit::event::DeviceEvent,
660    ) {
661        if let Some(handler) = self.custom_application_handler.as_mut() {
662            handler.device_event(event_loop, device_id, event);
663        }
664    }
665
666    fn suspended(&mut self, event_loop: &ActiveEventLoop) {
667        if let Some(handler) = self.custom_application_handler.as_mut() {
668            handler.suspended(event_loop);
669        }
670    }
671
672    fn exiting(&mut self, event_loop: &ActiveEventLoop) {
673        if let Some(handler) = self.custom_application_handler.as_mut() {
674            handler.exiting(event_loop);
675        }
676    }
677
678    fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
679        if let Some(handler) = self.custom_application_handler.as_mut() {
680            handler.memory_warning(event_loop);
681        }
682    }
683}
684
685impl EventLoopState {
686    /// Runs the event loop and renders the items in the provided `component` in its
687    /// own window.
688    #[allow(unused_mut)] // mut need changes for wasm
689    pub fn run(mut self) -> Result<Self, corelib::platform::PlatformError> {
690        let not_running_loop_instance = self
691            .shared_backend_data
692            .not_running_event_loop
693            .take()
694            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
695        let mut winit_loop = not_running_loop_instance;
696
697        cfg_if::cfg_if! {
698            if #[cfg(any(target_arch = "wasm32", ios_and_friends))] {
699                winit_loop
700                    .run_app(&mut self)
701                    .map_err(|e| format!("Error running winit event loop: {e}"))?;
702                // This can't really happen, as run() doesn't return
703                Ok(Self::new(self.shared_backend_data.clone(), None))
704            } else {
705                use winit::platform::run_on_demand::EventLoopExtRunOnDemand as _;
706                winit_loop
707                    .run_app_on_demand(&mut self)
708                    .map_err(|e| format!("Error running winit event loop: {e}"))?;
709
710                // Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
711                // Winit does not support creating multiple instances of the event loop.
712                self.shared_backend_data.not_running_event_loop.replace(Some(winit_loop));
713
714                if let Some(error) = self.loop_error {
715                    return Err(error);
716                }
717                Ok(self)
718            }
719        }
720    }
721
722    /// Runs the event loop and renders the items in the provided `component` in its
723    /// own window.
724    #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
725    pub fn pump_events(
726        mut self,
727        timeout: Option<std::time::Duration>,
728    ) -> Result<(Self, winit::platform::pump_events::PumpStatus), corelib::platform::PlatformError>
729    {
730        use winit::platform::pump_events::EventLoopExtPumpEvents;
731
732        let not_running_loop_instance = self
733            .shared_backend_data
734            .not_running_event_loop
735            .take()
736            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
737        let mut winit_loop = not_running_loop_instance;
738
739        self.pumping_events_instantly = timeout.is_some_and(|duration| duration.is_zero());
740
741        let result = winit_loop.pump_app_events(timeout, &mut self);
742
743        self.pumping_events_instantly = false;
744
745        // Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
746        // Winit does not support creating multiple instances of the event loop.
747        self.shared_backend_data.not_running_event_loop.replace(Some(winit_loop));
748
749        if let Some(error) = self.loop_error {
750            return Err(error);
751        }
752        Ok((self, result))
753    }
754
755    #[cfg(target_arch = "wasm32")]
756    pub fn spawn(self) -> Result<(), corelib::platform::PlatformError> {
757        use winit::platform::web::EventLoopExtWebSys;
758        let not_running_loop_instance = self
759            .shared_backend_data
760            .not_running_event_loop
761            .take()
762            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
763
764        not_running_loop_instance.spawn_app(self);
765
766        Ok(())
767    }
768}