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::winitwindowadapter::WindowVisibility;
12use crate::{SharedBackendData, SlintEvent};
13use corelib::platform::PlatformError;
14use corelib::window::*;
15use i_slint_core as corelib;
16
17#[allow(unused_imports)]
18use std::cell::{RefCell, RefMut};
19use std::rc::Rc;
20use winit::event::WindowEvent;
21use winit::event_loop::{ActiveEventLoop, ControlFlow};
22
23/// This enum captures run-time specific events that can be dispatched to the event loop in
24/// addition to the winit events.
25pub enum CustomEvent {
26    /// On wasm request_redraw doesn't wake the event loop, so we need to manually send an event
27    /// so that the event loop can run
28    #[cfg(target_arch = "wasm32")]
29    WakeEventLoopWorkaround,
30    /// Slint internal: Invoke the
31    UserEvent(Box<dyn FnOnce() + Send>),
32    /// Invoke the callback with the [`ActiveEventLoop`], for [`crate::invoke_from_active_event_loop`]
33    UserEventWithEventLoop(Box<dyn FnOnce(&ActiveEventLoop) + Send>),
34    /// Emitted from quit_event_loop with the current event loop generation
35    Exit(usize),
36    #[cfg(enable_accesskit)]
37    Accesskit(accesskit_winit::Event),
38    #[cfg(muda)]
39    Muda(muda::MenuEvent),
40}
41
42impl std::fmt::Debug for CustomEvent {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        match self {
45            #[cfg(target_arch = "wasm32")]
46            Self::WakeEventLoopWorkaround => write!(f, "WakeEventLoopWorkaround"),
47            Self::UserEvent(_) => write!(f, "UserEvent"),
48            Self::UserEventWithEventLoop(_) => write!(f, "UserEventWithEventLoop"),
49            Self::Exit(_) => write!(f, "Exit"),
50            #[cfg(enable_accesskit)]
51            Self::Accesskit(a) => write!(f, "AccessKit({a:?})"),
52            #[cfg(muda)]
53            Self::Muda(e) => write!(f, "Muda({e:?})"),
54        }
55    }
56}
57
58pub struct EventLoopState {
59    shared_backend_data: Rc<SharedBackendData>,
60
61    loop_error: Option<PlatformError>,
62
63    /// Set to true when pumping events for the shortest amount of time possible.
64    pumping_events_instantly: bool,
65
66    custom_application_handler: Option<Box<dyn crate::CustomApplicationHandler>>,
67}
68
69impl EventLoopState {
70    pub fn new(
71        shared_backend_data: Rc<SharedBackendData>,
72        custom_application_handler: Option<Box<dyn crate::CustomApplicationHandler>>,
73    ) -> Self {
74        Self {
75            shared_backend_data,
76            loop_error: Default::default(),
77            pumping_events_instantly: Default::default(),
78            custom_application_handler,
79        }
80    }
81
82    /// Free graphics resources for any hidden windows. Called when quitting the event loop, to work
83    /// around #8795.
84    fn suspend_all_hidden_windows(&self) {
85        let windows_to_suspend = self
86            .shared_backend_data
87            .active_windows
88            .borrow()
89            .values()
90            .filter_map(|w| w.upgrade())
91            .filter(|w| matches!(w.visibility(), WindowVisibility::Hidden))
92            .collect::<Vec<_>>();
93        for window in windows_to_suspend.into_iter() {
94            let _ = window.suspend();
95        }
96    }
97}
98
99impl winit::application::ApplicationHandler<SlintEvent> for EventLoopState {
100    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
101        if matches!(
102            self.custom_application_handler
103                .as_mut()
104                .map_or(EventResult::Propagate, |handler| { handler.resumed(event_loop) }),
105            EventResult::PreventDefault
106        ) {
107            return;
108        }
109        if let Err(err) = self.shared_backend_data.create_inactive_windows(event_loop) {
110            self.loop_error = Some(err);
111            event_loop.exit();
112        }
113    }
114
115    fn window_event(
116        &mut self,
117        event_loop: &ActiveEventLoop,
118        window_id: winit::window::WindowId,
119        event: WindowEvent,
120    ) {
121        let Some(window) = self.shared_backend_data.window_by_id(window_id) else {
122            if let Some(handler) = self.custom_application_handler.as_mut() {
123                handler.window_event(event_loop, window_id, None, None, &event);
124            }
125            return;
126        };
127
128        let Some(winit_window) = window.winit_window() else {
129            return;
130        };
131
132        if matches!(
133            self.custom_application_handler.as_mut().map_or(EventResult::Propagate, |handler| {
134                handler.window_event(
135                    event_loop,
136                    window_id,
137                    Some(&*winit_window),
138                    Some(window.window()),
139                    &event,
140                )
141            }),
142            EventResult::PreventDefault
143        ) {
144            return;
145        }
146
147        if let Err(err) = window.dispatch_winit_window_event(event_loop, &winit_window, event) {
148            self.loop_error = Some(err);
149            event_loop.exit();
150        }
151    }
152
153    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: SlintEvent) {
154        match event.0 {
155            CustomEvent::UserEvent(user_callback) => user_callback(),
156            CustomEvent::UserEventWithEventLoop(user_callback) => user_callback(event_loop),
157            CustomEvent::Exit(generation) => {
158                if self
159                    .shared_backend_data
160                    .event_loop_generation
161                    .load(std::sync::atomic::Ordering::Relaxed)
162                    == generation
163                {
164                    self.suspend_all_hidden_windows();
165                    event_loop.exit()
166                }
167                // else ignore the event, since it's from a previous run of the event loop
168            }
169            #[cfg(enable_accesskit)]
170            CustomEvent::Accesskit(accesskit_winit::Event { window_id, window_event }) => {
171                if let Some(window) = self.shared_backend_data.window_by_id(window_id) {
172                    let deferred_action = window
173                        .accesskit_adapter()
174                        .expect("internal error: accesskit adapter must exist when window exists")
175                        .borrow_mut()
176                        .process_accesskit_event(window_event);
177                    // access kit adapter not borrowed anymore, now invoke the deferred action
178                    if let Some(deferred_action) = deferred_action {
179                        deferred_action.invoke(window.window());
180                    }
181                }
182            }
183            #[cfg(target_arch = "wasm32")]
184            CustomEvent::WakeEventLoopWorkaround => {
185                event_loop.set_control_flow(ControlFlow::Poll);
186            }
187            #[cfg(muda)]
188            CustomEvent::Muda(event) => {
189                if let Some((window, eid, muda_type)) =
190                    event.id().0.split_once('|').and_then(|(w, e)| {
191                        let (e, muda_type) = e.split_once('|')?;
192                        Some((
193                            self.shared_backend_data.window_by_id(
194                                winit::window::WindowId::from(w.parse::<u64>().ok()?),
195                            )?,
196                            e.parse::<usize>().ok()?,
197                            muda_type.parse::<crate::muda::MudaType>().ok()?,
198                        ))
199                    })
200                {
201                    window.muda_event(eid, muda_type);
202                };
203            }
204        }
205    }
206
207    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
208        if matches!(
209            self.custom_application_handler.as_mut().map_or(EventResult::Propagate, |handler| {
210                handler.new_events(event_loop, cause)
211            }),
212            EventResult::PreventDefault
213        ) {
214            return;
215        }
216
217        event_loop.set_control_flow(ControlFlow::Wait);
218
219        self.shared_backend_data.context().update_timers_and_animations();
220    }
221
222    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
223        self.shared_backend_data.flush_pending_mouse_move();
224
225        if matches!(
226            self.custom_application_handler
227                .as_mut()
228                .map_or(EventResult::Propagate, |handler| { handler.about_to_wait(event_loop) }),
229            EventResult::PreventDefault
230        ) {
231            return;
232        }
233
234        if let Err(err) = self.shared_backend_data.create_inactive_windows(event_loop) {
235            self.loop_error = Some(err);
236        }
237
238        if !event_loop.exiting() {
239            for w in self
240                .shared_backend_data
241                .active_windows
242                .borrow()
243                .values()
244                .filter_map(|w| w.upgrade())
245            {
246                if w.window().has_active_animations() {
247                    w.request_redraw();
248                }
249            }
250        }
251
252        if event_loop.control_flow() == ControlFlow::Wait
253            && let Some(next_timer) =
254                self.shared_backend_data.context().duration_until_next_timer_update()
255        {
256            event_loop.set_control_flow(ControlFlow::wait_duration(next_timer));
257        }
258
259        if self.pumping_events_instantly {
260            event_loop.set_control_flow(ControlFlow::Poll);
261        }
262    }
263
264    fn device_event(
265        &mut self,
266        event_loop: &ActiveEventLoop,
267        device_id: winit::event::DeviceId,
268        event: winit::event::DeviceEvent,
269    ) {
270        if let Some(handler) = self.custom_application_handler.as_mut() {
271            handler.device_event(event_loop, device_id, event);
272        }
273    }
274
275    fn suspended(&mut self, event_loop: &ActiveEventLoop) {
276        if let Some(handler) = self.custom_application_handler.as_mut() {
277            handler.suspended(event_loop);
278        }
279    }
280
281    fn exiting(&mut self, event_loop: &ActiveEventLoop) {
282        if let Some(handler) = self.custom_application_handler.as_mut() {
283            handler.exiting(event_loop);
284        }
285    }
286
287    fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
288        if let Some(handler) = self.custom_application_handler.as_mut() {
289            handler.memory_warning(event_loop);
290        }
291    }
292}
293
294impl EventLoopState {
295    /// Runs the event loop and renders the items in the provided `component` in its
296    /// own window.
297    #[allow(unused_mut)] // mut need changes for wasm
298    pub fn run(mut self) -> Result<Self, corelib::platform::PlatformError> {
299        let not_running_loop_instance = self
300            .shared_backend_data
301            .not_running_event_loop
302            .take()
303            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
304        let mut winit_loop = not_running_loop_instance;
305
306        cfg_if::cfg_if! {
307            if #[cfg(any(target_arch = "wasm32", ios_and_friends))] {
308                winit_loop
309                    .run_app(&mut self)
310                    .map_err(|e| format!("Error running winit event loop: {e}"))?;
311                // This can't really happen, as run() doesn't return
312                Ok(Self::new(self.shared_backend_data.clone(), None))
313            } else {
314                use winit::platform::run_on_demand::EventLoopExtRunOnDemand as _;
315                winit_loop
316                    .run_app_on_demand(&mut self)
317                    .map_err(|e| format!("Error running winit event loop: {e}"))?;
318
319                // Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
320                // Winit does not support creating multiple instances of the event loop.
321                self.shared_backend_data.not_running_event_loop.replace(Some(winit_loop));
322
323                if let Some(error) = self.loop_error {
324                    return Err(error);
325                }
326                Ok(self)
327            }
328        }
329    }
330
331    /// Runs the event loop and renders the items in the provided `component` in its
332    /// own window.
333    #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
334    pub fn pump_events(
335        mut self,
336        timeout: Option<std::time::Duration>,
337    ) -> Result<(Self, winit::platform::pump_events::PumpStatus), corelib::platform::PlatformError>
338    {
339        use winit::platform::pump_events::EventLoopExtPumpEvents;
340
341        let not_running_loop_instance = self
342            .shared_backend_data
343            .not_running_event_loop
344            .take()
345            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
346        let mut winit_loop = not_running_loop_instance;
347
348        self.pumping_events_instantly = timeout.is_some_and(|duration| duration.is_zero());
349
350        let result = winit_loop.pump_app_events(timeout, &mut self);
351
352        self.pumping_events_instantly = false;
353
354        // Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
355        // Winit does not support creating multiple instances of the event loop.
356        self.shared_backend_data.not_running_event_loop.replace(Some(winit_loop));
357
358        if let Some(error) = self.loop_error {
359            return Err(error);
360        }
361        Ok((self, result))
362    }
363
364    #[cfg(target_arch = "wasm32")]
365    pub fn spawn(self) -> Result<(), corelib::platform::PlatformError> {
366        use winit::platform::web::EventLoopExtWebSys;
367        let not_running_loop_instance = self
368            .shared_backend_data
369            .not_running_event_loop
370            .take()
371            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
372
373        not_running_loop_instance.spawn_app(self);
374
375        Ok(())
376    }
377}