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