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::drag_resize_window::{handle_cursor_move_for_resize, handle_resize};
11use crate::winitwindowadapter::WindowVisibility;
12use crate::EventResult;
13use crate::{SharedBackendData, SlintEvent};
14use corelib::graphics::euclid;
15use corelib::input::{KeyEvent, KeyEventType, MouseEvent};
16use corelib::items::{ColorScheme, PointerEventButton};
17use corelib::lengths::LogicalPoint;
18use corelib::platform::PlatformError;
19use corelib::window::*;
20use i_slint_core as corelib;
21
22#[allow(unused_imports)]
23use std::cell::{RefCell, RefMut};
24use std::rc::Rc;
25use winit::event::WindowEvent;
26use winit::event_loop::ActiveEventLoop;
27use winit::event_loop::ControlFlow;
28use winit::window::ResizeDirection;
29
30/// This enum captures run-time specific events that can be dispatched to the event loop in
31/// addition to the winit events.
32pub enum CustomEvent {
33    /// On wasm request_redraw doesn't wake the event loop, so we need to manually send an event
34    /// so that the event loop can run
35    #[cfg(target_arch = "wasm32")]
36    WakeEventLoopWorkaround,
37    /// Slint internal: Invoke the
38    UserEvent(Box<dyn FnOnce() + Send>),
39    Exit,
40    #[cfg(enable_accesskit)]
41    Accesskit(accesskit_winit::Event),
42    #[cfg(muda)]
43    Muda(muda::MenuEvent),
44}
45
46impl std::fmt::Debug for CustomEvent {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        match self {
49            #[cfg(target_arch = "wasm32")]
50            Self::WakeEventLoopWorkaround => write!(f, "WakeEventLoopWorkaround"),
51            Self::UserEvent(_) => write!(f, "UserEvent"),
52            Self::Exit => write!(f, "Exit"),
53            #[cfg(enable_accesskit)]
54            Self::Accesskit(a) => write!(f, "AccessKit({a:?})"),
55            #[cfg(muda)]
56            Self::Muda(e) => write!(f, "Muda({e:?})"),
57        }
58    }
59}
60
61pub struct EventLoopState {
62    shared_backend_data: Rc<SharedBackendData>,
63    // last seen cursor position
64    cursor_pos: LogicalPoint,
65    pressed: bool,
66    current_touch_id: Option<u64>,
67
68    loop_error: Option<PlatformError>,
69    current_resize_direction: Option<ResizeDirection>,
70
71    /// Set to true when pumping events for the shortest amount of time possible.
72    pumping_events_instantly: bool,
73
74    custom_application_handler: Option<Box<dyn crate::CustomApplicationHandler>>,
75}
76
77impl EventLoopState {
78    pub fn new(
79        shared_backend_data: Rc<SharedBackendData>,
80        custom_application_handler: Option<Box<dyn crate::CustomApplicationHandler>>,
81    ) -> Self {
82        Self {
83            shared_backend_data,
84            cursor_pos: Default::default(),
85            pressed: Default::default(),
86            current_touch_id: Default::default(),
87            loop_error: Default::default(),
88            current_resize_direction: Default::default(),
89            pumping_events_instantly: Default::default(),
90            custom_application_handler,
91        }
92    }
93
94    /// Free graphics resources for any hidden windows. Called when quitting the event loop, to work
95    /// around #8795.
96    fn suspend_all_hidden_windows(&self) {
97        let windows_to_suspend = self
98            .shared_backend_data
99            .active_windows
100            .borrow()
101            .values()
102            .filter_map(|w| w.upgrade())
103            .filter(|w| matches!(w.visibility(), WindowVisibility::Hidden))
104            .collect::<Vec<_>>();
105        for window in windows_to_suspend.into_iter() {
106            let _ = window.suspend();
107        }
108    }
109}
110
111impl winit::application::ApplicationHandler<SlintEvent> for EventLoopState {
112    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
113        if matches!(
114            self.custom_application_handler
115                .as_mut()
116                .map_or(EventResult::Propagate, |handler| { handler.resumed(event_loop) }),
117            EventResult::PreventDefault
118        ) {
119            return;
120        }
121        if let Err(err) = self.shared_backend_data.create_inactive_windows(event_loop) {
122            self.loop_error = Some(err);
123            event_loop.exit();
124        }
125    }
126
127    fn window_event(
128        &mut self,
129        event_loop: &ActiveEventLoop,
130        window_id: winit::window::WindowId,
131        event: WindowEvent,
132    ) {
133        let Some(window) = self.shared_backend_data.window_by_id(window_id) else {
134            if let Some(handler) = self.custom_application_handler.as_mut() {
135                handler.window_event(event_loop, window_id, None, None, &event);
136            }
137            return;
138        };
139
140        if let Some(winit_window) = window.winit_window() {
141            if matches!(
142                self.custom_application_handler.as_mut().map_or(
143                    EventResult::Propagate,
144                    |handler| handler.window_event(
145                        event_loop,
146                        window_id,
147                        Some(&*winit_window),
148                        Some(window.window()),
149                        &event
150                    )
151                ),
152                EventResult::PreventDefault
153            ) {
154                return;
155            }
156
157            if let Some(mut window_event_filter) = window.window_event_filter.take() {
158                let event_result = window_event_filter(window.window(), &event);
159                window.window_event_filter.set(Some(window_event_filter));
160
161                match event_result {
162                    EventResult::PreventDefault => return,
163                    EventResult::Propagate => (),
164                }
165            }
166
167            #[cfg(enable_accesskit)]
168            window
169                .accesskit_adapter()
170                .expect("internal error: accesskit adapter must exist when window exists")
171                .borrow_mut()
172                .process_event(&winit_window, &event);
173        } else {
174            return;
175        }
176
177        let runtime_window = WindowInner::from_pub(window.window());
178        match event {
179            WindowEvent::RedrawRequested => {
180                self.loop_error = window.draw().err();
181            }
182            WindowEvent::Resized(size) => {
183                self.loop_error = window.resize_event(size).err();
184
185                // Entering fullscreen, maximizing or minimizing the window will
186                // trigger a resize event. We need to update the internal window
187                // state to match the actual window state. We simulate a "window
188                // state event" since there is not an official event for it yet.
189                // Because we don't always get a Resized event (eg, minimized), also handle Occluded
190                // See: https://github.com/rust-windowing/winit/issues/2334
191                window.window_state_event();
192            }
193            WindowEvent::CloseRequested => {
194                self.loop_error = window
195                    .window()
196                    .try_dispatch_event(corelib::platform::WindowEvent::CloseRequested)
197                    .err();
198            }
199            WindowEvent::Focused(have_focus) => {
200                self.loop_error = window.activation_changed(have_focus).err();
201            }
202
203            WindowEvent::KeyboardInput { event, is_synthetic, .. } => {
204                let key_code = event.logical_key;
205                // For now: Match Qt's behavior of mapping command to control and control to meta (LWin/RWin).
206                cfg_if::cfg_if!(
207                    if #[cfg(target_vendor = "apple")] {
208                        let swap_cmd_ctrl = true;
209                    } else if #[cfg(target_family = "wasm")] {
210                        let swap_cmd_ctrl = web_sys::window()
211                            .and_then(|window| window.navigator().platform().ok())
212                            .is_some_and(|platform| {
213                                let platform = platform.to_ascii_lowercase();
214                                platform.contains("mac")
215                                    || platform.contains("iphone")
216                                    || platform.contains("ipad")
217                            });
218                    } else {
219                        let swap_cmd_ctrl = false;
220                    }
221                );
222
223                let key_code = if swap_cmd_ctrl {
224                    match key_code {
225                        winit::keyboard::Key::Named(winit::keyboard::NamedKey::Control) => {
226                            winit::keyboard::Key::Named(winit::keyboard::NamedKey::Super)
227                        }
228                        winit::keyboard::Key::Named(winit::keyboard::NamedKey::Super) => {
229                            winit::keyboard::Key::Named(winit::keyboard::NamedKey::Control)
230                        }
231                        code => code,
232                    }
233                } else {
234                    key_code
235                };
236
237                macro_rules! winit_key_to_char {
238                ($($char:literal # $name:ident # $($_qt:ident)|* # $($winit:ident $(($pos:ident))?)|* # $($_xkb:ident)|*;)*) => {
239                    match &key_code {
240                        $($(winit::keyboard::Key::Named(winit::keyboard::NamedKey::$winit) $(if event.location == winit::keyboard::KeyLocation::$pos)? => $char.into(),)*)*
241                        winit::keyboard::Key::Character(str) => str.as_str().into(),
242                        _ => {
243                            if let Some(text) = &event.text {
244                                text.as_str().into()
245                            } else {
246                                return;
247                            }
248                        }
249                    }
250                }
251            }
252                let text = i_slint_common::for_each_special_keys!(winit_key_to_char);
253
254                self.loop_error = window
255                    .window()
256                    .try_dispatch_event(match event.state {
257                        winit::event::ElementState::Pressed if event.repeat => {
258                            corelib::platform::WindowEvent::KeyPressRepeated { text }
259                        }
260                        winit::event::ElementState::Pressed => {
261                            if is_synthetic {
262                                // Synthetic event are sent when the focus is acquired, for all the keys currently pressed.
263                                // Don't forward these keys other than modifiers to the app
264                                use winit::keyboard::{Key::Named, NamedKey as N};
265                                if !matches!(
266                                    key_code,
267                                    Named(N::Control | N::Shift | N::Super | N::Alt | N::AltGraph),
268                                ) {
269                                    return;
270                                }
271                            }
272                            corelib::platform::WindowEvent::KeyPressed { text }
273                        }
274                        winit::event::ElementState::Released => {
275                            corelib::platform::WindowEvent::KeyReleased { text }
276                        }
277                    })
278                    .err();
279            }
280            WindowEvent::Ime(winit::event::Ime::Preedit(string, preedit_selection)) => {
281                let event = KeyEvent {
282                    event_type: KeyEventType::UpdateComposition,
283                    preedit_text: string.into(),
284                    preedit_selection: preedit_selection.map(|e| e.0 as i32..e.1 as i32),
285                    ..Default::default()
286                };
287                runtime_window.process_key_input(event);
288            }
289            WindowEvent::Ime(winit::event::Ime::Commit(string)) => {
290                let event = KeyEvent {
291                    event_type: KeyEventType::CommitComposition,
292                    text: string.into(),
293                    ..Default::default()
294                };
295                runtime_window.process_key_input(event);
296            }
297            WindowEvent::CursorMoved { position, .. } => {
298                self.current_resize_direction = handle_cursor_move_for_resize(
299                    &window.winit_window().unwrap(),
300                    position,
301                    self.current_resize_direction,
302                    runtime_window
303                        .window_item()
304                        .map_or(0_f64, |w| w.as_pin_ref().resize_border_width().get().into()),
305                );
306                let position = position.to_logical(runtime_window.scale_factor() as f64);
307                self.cursor_pos = euclid::point2(position.x, position.y);
308                runtime_window.process_mouse_input(MouseEvent::Moved { position: self.cursor_pos });
309            }
310            WindowEvent::CursorLeft { .. } => {
311                // 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
312                if cfg!(target_arch = "wasm32") || !self.pressed {
313                    self.pressed = false;
314                    runtime_window.process_mouse_input(MouseEvent::Exit);
315                }
316            }
317            WindowEvent::MouseWheel { delta, .. } => {
318                let (delta_x, delta_y) = match delta {
319                    winit::event::MouseScrollDelta::LineDelta(lx, ly) => (lx * 60., ly * 60.),
320                    winit::event::MouseScrollDelta::PixelDelta(d) => {
321                        let d = d.to_logical(runtime_window.scale_factor() as f64);
322                        (d.x, d.y)
323                    }
324                };
325                runtime_window.process_mouse_input(MouseEvent::Wheel {
326                    position: self.cursor_pos,
327                    delta_x,
328                    delta_y,
329                });
330            }
331            WindowEvent::MouseInput { state, button, .. } => {
332                let button = match button {
333                    winit::event::MouseButton::Left => PointerEventButton::Left,
334                    winit::event::MouseButton::Right => PointerEventButton::Right,
335                    winit::event::MouseButton::Middle => PointerEventButton::Middle,
336                    winit::event::MouseButton::Back => PointerEventButton::Back,
337                    winit::event::MouseButton::Forward => PointerEventButton::Forward,
338                    winit::event::MouseButton::Other(_) => PointerEventButton::Other,
339                };
340                let ev = match state {
341                    winit::event::ElementState::Pressed => {
342                        if button == PointerEventButton::Left
343                            && self.current_resize_direction.is_some()
344                        {
345                            handle_resize(
346                                &window.winit_window().unwrap(),
347                                self.current_resize_direction,
348                            );
349                            return;
350                        }
351
352                        self.pressed = true;
353                        MouseEvent::Pressed { position: self.cursor_pos, button, click_count: 0 }
354                    }
355                    winit::event::ElementState::Released => {
356                        self.pressed = false;
357                        MouseEvent::Released { position: self.cursor_pos, button, click_count: 0 }
358                    }
359                };
360                runtime_window.process_mouse_input(ev);
361            }
362            WindowEvent::Touch(touch) => {
363                if Some(touch.id) == self.current_touch_id || self.current_touch_id.is_none() {
364                    let location = touch.location.to_logical(runtime_window.scale_factor() as f64);
365                    let position = euclid::point2(location.x, location.y);
366                    let ev = match touch.phase {
367                        winit::event::TouchPhase::Started => {
368                            self.pressed = true;
369                            if self.current_touch_id.is_none() {
370                                self.current_touch_id = Some(touch.id);
371                            }
372                            MouseEvent::Pressed {
373                                position,
374                                button: PointerEventButton::Left,
375                                click_count: 0,
376                            }
377                        }
378                        winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
379                            self.pressed = false;
380                            self.current_touch_id = None;
381                            MouseEvent::Released {
382                                position,
383                                button: PointerEventButton::Left,
384                                click_count: 0,
385                            }
386                        }
387                        winit::event::TouchPhase::Moved => MouseEvent::Moved { position },
388                    };
389                    runtime_window.process_mouse_input(ev);
390                }
391            }
392            WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: _ } => {
393                if std::env::var("SLINT_SCALE_FACTOR").is_err() {
394                    self.loop_error = window
395                        .window()
396                        .try_dispatch_event(corelib::platform::WindowEvent::ScaleFactorChanged {
397                            scale_factor: scale_factor as f32,
398                        })
399                        .err();
400                    // TODO: send a resize event or try to keep the logical size the same.
401                    //window.resize_event(inner_size_writer.???)?;
402                }
403            }
404            WindowEvent::ThemeChanged(theme) => window.set_color_scheme(match theme {
405                winit::window::Theme::Dark => ColorScheme::Dark,
406                winit::window::Theme::Light => ColorScheme::Light,
407            }),
408            WindowEvent::Occluded(x) => {
409                window.renderer.occluded(x);
410
411                // In addition to the hack done for WindowEvent::Resize, also do it for Occluded so we handle Minimized change
412                window.window_state_event();
413            }
414            _ => {}
415        }
416
417        if self.loop_error.is_some() {
418            event_loop.exit();
419        }
420    }
421
422    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: SlintEvent) {
423        match event.0 {
424            CustomEvent::UserEvent(user_callback) => user_callback(),
425            CustomEvent::Exit => {
426                self.suspend_all_hidden_windows();
427                event_loop.exit()
428            }
429            #[cfg(enable_accesskit)]
430            CustomEvent::Accesskit(accesskit_winit::Event { window_id, window_event }) => {
431                if let Some(window) = self.shared_backend_data.window_by_id(window_id) {
432                    let deferred_action = window
433                        .accesskit_adapter()
434                        .expect("internal error: accesskit adapter must exist when window exists")
435                        .borrow_mut()
436                        .process_accesskit_event(window_event);
437                    // access kit adapter not borrowed anymore, now invoke the deferred action
438                    if let Some(deferred_action) = deferred_action {
439                        deferred_action.invoke(window.window());
440                    }
441                }
442            }
443            #[cfg(target_arch = "wasm32")]
444            CustomEvent::WakeEventLoopWorkaround => {
445                event_loop.set_control_flow(ControlFlow::Poll);
446            }
447            #[cfg(muda)]
448            CustomEvent::Muda(event) => {
449                if let Some((window, eid, muda_type)) =
450                    event.id().0.split_once('|').and_then(|(w, e)| {
451                        let (e, muda_type) = e.split_once('|')?;
452                        Some((
453                            self.shared_backend_data.window_by_id(
454                                winit::window::WindowId::from(w.parse::<u64>().ok()?),
455                            )?,
456                            e.parse::<usize>().ok()?,
457                            muda_type.parse::<crate::muda::MudaType>().ok()?,
458                        ))
459                    })
460                {
461                    window.muda_event(eid, muda_type);
462                };
463            }
464        }
465    }
466
467    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
468        if matches!(
469            self.custom_application_handler.as_mut().map_or(EventResult::Propagate, |handler| {
470                handler.new_events(event_loop, cause)
471            }),
472            EventResult::PreventDefault
473        ) {
474            return;
475        }
476
477        event_loop.set_control_flow(ControlFlow::Wait);
478
479        corelib::platform::update_timers_and_animations();
480    }
481
482    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
483        if matches!(
484            self.custom_application_handler
485                .as_mut()
486                .map_or(EventResult::Propagate, |handler| { handler.about_to_wait(event_loop) }),
487            EventResult::PreventDefault
488        ) {
489            return;
490        }
491
492        if let Err(err) = self.shared_backend_data.create_inactive_windows(event_loop) {
493            self.loop_error = Some(err);
494        }
495
496        if !event_loop.exiting() {
497            for w in self
498                .shared_backend_data
499                .active_windows
500                .borrow()
501                .iter()
502                .filter_map(|(_, w)| w.upgrade())
503            {
504                if w.window().has_active_animations() {
505                    w.request_redraw();
506                }
507            }
508        }
509
510        if event_loop.control_flow() == ControlFlow::Wait {
511            if let Some(next_timer) = corelib::platform::duration_until_next_timer_update() {
512                event_loop.set_control_flow(ControlFlow::wait_duration(next_timer));
513            }
514        }
515
516        if self.pumping_events_instantly {
517            event_loop.set_control_flow(ControlFlow::Poll);
518        }
519    }
520
521    fn device_event(
522        &mut self,
523        event_loop: &ActiveEventLoop,
524        device_id: winit::event::DeviceId,
525        event: winit::event::DeviceEvent,
526    ) {
527        if let Some(handler) = self.custom_application_handler.as_mut() {
528            handler.device_event(event_loop, device_id, event);
529        }
530    }
531
532    fn suspended(&mut self, event_loop: &ActiveEventLoop) {
533        if let Some(handler) = self.custom_application_handler.as_mut() {
534            handler.suspended(event_loop);
535        }
536    }
537
538    fn exiting(&mut self, event_loop: &ActiveEventLoop) {
539        if let Some(handler) = self.custom_application_handler.as_mut() {
540            handler.exiting(event_loop);
541        }
542    }
543
544    fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
545        if let Some(handler) = self.custom_application_handler.as_mut() {
546            handler.memory_warning(event_loop);
547        }
548    }
549}
550
551impl EventLoopState {
552    /// Runs the event loop and renders the items in the provided `component` in its
553    /// own window.
554    #[allow(unused_mut)] // mut need changes for wasm
555    pub fn run(mut self) -> Result<Self, corelib::platform::PlatformError> {
556        let not_running_loop_instance = self
557            .shared_backend_data
558            .not_running_event_loop
559            .take()
560            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
561        let mut winit_loop = not_running_loop_instance;
562
563        cfg_if::cfg_if! {
564            if #[cfg(any(target_arch = "wasm32", ios_and_friends))] {
565                winit_loop
566                    .run_app(&mut self)
567                    .map_err(|e| format!("Error running winit event loop: {e}"))?;
568                // This can't really happen, as run() doesn't return
569                Ok(Self::new(self.shared_backend_data.clone(), None))
570            } else {
571                use winit::platform::run_on_demand::EventLoopExtRunOnDemand as _;
572                winit_loop
573                    .run_app_on_demand(&mut self)
574                    .map_err(|e| format!("Error running winit event loop: {e}"))?;
575
576                // Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
577                // Winit does not support creating multiple instances of the event loop.
578                self.shared_backend_data.not_running_event_loop.replace(Some(winit_loop));
579
580                if let Some(error) = self.loop_error {
581                    return Err(error);
582                }
583                Ok(self)
584            }
585        }
586    }
587
588    /// Runs the event loop and renders the items in the provided `component` in its
589    /// own window.
590    #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
591    pub fn pump_events(
592        mut self,
593        timeout: Option<std::time::Duration>,
594    ) -> Result<(Self, winit::platform::pump_events::PumpStatus), corelib::platform::PlatformError>
595    {
596        use winit::platform::pump_events::EventLoopExtPumpEvents;
597
598        let not_running_loop_instance = self
599            .shared_backend_data
600            .not_running_event_loop
601            .take()
602            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
603        let mut winit_loop = not_running_loop_instance;
604
605        self.pumping_events_instantly = timeout.is_some_and(|duration| duration.is_zero());
606
607        let result = winit_loop.pump_app_events(timeout, &mut self);
608
609        self.pumping_events_instantly = false;
610
611        // Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
612        // Winit does not support creating multiple instances of the event loop.
613        self.shared_backend_data.not_running_event_loop.replace(Some(winit_loop));
614
615        if let Some(error) = self.loop_error {
616            return Err(error);
617        }
618        Ok((self, result))
619    }
620
621    #[cfg(target_arch = "wasm32")]
622    pub fn spawn(self) -> Result<(), corelib::platform::PlatformError> {
623        use winit::platform::web::EventLoopExtWebSys;
624        let not_running_loop_instance = self
625            .shared_backend_data
626            .not_running_event_loop
627            .take()
628            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
629
630        not_running_loop_instance.spawn_app(self);
631
632        Ok(())
633    }
634}