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::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                // See: https://github.com/rust-windowing/winit/issues/2334
190                window.window_state_event();
191
192                // Some platforms (e.g., Windows) may not emit an Occluded event when minimized,
193                // so manually mark the window as occluded if its size is zero.
194                #[cfg(target_os = "windows")]
195                {
196                    if size.width == 0 || size.height == 0 {
197                        window.renderer.occluded(true);
198                    }
199                }
200            }
201            WindowEvent::CloseRequested => {
202                self.loop_error = window
203                    .window()
204                    .try_dispatch_event(corelib::platform::WindowEvent::CloseRequested)
205                    .err();
206            }
207            WindowEvent::Focused(have_focus) => {
208                // Work around https://github.com/rust-windowing/winit/issues/4371
209                let have_focus = if cfg!(target_os = "macos") {
210                    window.winit_window().map_or(have_focus, |w| w.has_focus())
211                } else {
212                    have_focus
213                };
214                self.loop_error = window.activation_changed(have_focus).err();
215            }
216
217            WindowEvent::KeyboardInput { event, is_synthetic, .. } => {
218                let key_code = event.logical_key;
219                // For now: Match Qt's behavior of mapping command to control and control to meta (LWin/RWin).
220                let swap_cmd_ctrl = i_slint_core::is_apple_platform();
221
222                let key_code = if swap_cmd_ctrl {
223                    #[cfg_attr(slint_nightly_test, allow(non_exhaustive_omitted_patterns))]
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                #[cfg_attr(slint_nightly_test, allow(non_exhaustive_omitted_patterns))]
253                let text = i_slint_common::for_each_special_keys!(winit_key_to_char);
254
255                self.loop_error = window
256                    .window()
257                    .try_dispatch_event(match event.state {
258                        winit::event::ElementState::Pressed if event.repeat => {
259                            corelib::platform::WindowEvent::KeyPressRepeated { text }
260                        }
261                        winit::event::ElementState::Pressed => {
262                            if is_synthetic {
263                                // Synthetic event are sent when the focus is acquired, for all the keys currently pressed.
264                                // Don't forward these keys other than modifiers to the app
265                                use winit::keyboard::{Key::Named, NamedKey as N};
266                                if !matches!(
267                                    key_code,
268                                    Named(N::Control | N::Shift | N::Super | N::Alt | N::AltGraph),
269                                ) {
270                                    return;
271                                }
272                            }
273                            corelib::platform::WindowEvent::KeyPressed { text }
274                        }
275                        winit::event::ElementState::Released => {
276                            corelib::platform::WindowEvent::KeyReleased { text }
277                        }
278                    })
279                    .err();
280            }
281            WindowEvent::Ime(winit::event::Ime::Preedit(string, preedit_selection)) => {
282                let event = KeyEvent {
283                    event_type: KeyEventType::UpdateComposition,
284                    preedit_text: string.into(),
285                    preedit_selection: preedit_selection.map(|e| e.0 as i32..e.1 as i32),
286                    ..Default::default()
287                };
288                runtime_window.process_key_input(event);
289            }
290            WindowEvent::Ime(winit::event::Ime::Commit(string)) => {
291                let event = KeyEvent {
292                    event_type: KeyEventType::CommitComposition,
293                    text: string.into(),
294                    ..Default::default()
295                };
296                runtime_window.process_key_input(event);
297            }
298            WindowEvent::CursorMoved { position, .. } => {
299                self.current_resize_direction = handle_cursor_move_for_resize(
300                    &window.winit_window().unwrap(),
301                    position,
302                    self.current_resize_direction,
303                    runtime_window
304                        .window_item()
305                        .map_or(0_f64, |w| w.as_pin_ref().resize_border_width().get().into()),
306                );
307                let position = position.to_logical(runtime_window.scale_factor() as f64);
308                self.cursor_pos = euclid::point2(position.x, position.y);
309                runtime_window.process_mouse_input(MouseEvent::Moved {
310                    position: self.cursor_pos,
311                    is_touch: false,
312                });
313            }
314            WindowEvent::CursorLeft { .. } => {
315                // 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
316                if cfg!(target_arch = "wasm32") || !self.pressed {
317                    self.pressed = false;
318                    runtime_window.process_mouse_input(MouseEvent::Exit);
319                }
320            }
321            WindowEvent::MouseWheel { delta, .. } => {
322                let (delta_x, delta_y) = match delta {
323                    winit::event::MouseScrollDelta::LineDelta(lx, ly) => (lx * 60., ly * 60.),
324                    winit::event::MouseScrollDelta::PixelDelta(d) => {
325                        let d = d.to_logical(runtime_window.scale_factor() as f64);
326                        (d.x, d.y)
327                    }
328                };
329                runtime_window.process_mouse_input(MouseEvent::Wheel {
330                    position: self.cursor_pos,
331                    delta_x,
332                    delta_y,
333                });
334            }
335            WindowEvent::MouseInput { state, button, .. } => {
336                let button = match button {
337                    winit::event::MouseButton::Left => PointerEventButton::Left,
338                    winit::event::MouseButton::Right => PointerEventButton::Right,
339                    winit::event::MouseButton::Middle => PointerEventButton::Middle,
340                    winit::event::MouseButton::Back => PointerEventButton::Back,
341                    winit::event::MouseButton::Forward => PointerEventButton::Forward,
342                    winit::event::MouseButton::Other(_) => PointerEventButton::Other,
343                };
344                let ev = match state {
345                    winit::event::ElementState::Pressed => {
346                        if button == PointerEventButton::Left
347                            && self.current_resize_direction.is_some()
348                        {
349                            handle_resize(
350                                &window.winit_window().unwrap(),
351                                self.current_resize_direction,
352                            );
353                            return;
354                        }
355
356                        self.pressed = true;
357                        MouseEvent::Pressed {
358                            position: self.cursor_pos,
359                            button,
360                            click_count: 0,
361                            is_touch: false,
362                        }
363                    }
364                    winit::event::ElementState::Released => {
365                        self.pressed = false;
366                        MouseEvent::Released {
367                            position: self.cursor_pos,
368                            button,
369                            click_count: 0,
370                            is_touch: false,
371                        }
372                    }
373                };
374                runtime_window.process_mouse_input(ev);
375            }
376            WindowEvent::Touch(touch) => {
377                if Some(touch.id) == self.current_touch_id || self.current_touch_id.is_none() {
378                    let location = touch.location.to_logical(runtime_window.scale_factor() as f64);
379                    let position = euclid::point2(location.x, location.y);
380                    let ev = match touch.phase {
381                        winit::event::TouchPhase::Started => {
382                            self.pressed = true;
383                            if self.current_touch_id.is_none() {
384                                self.current_touch_id = Some(touch.id);
385                            }
386                            MouseEvent::Pressed {
387                                position,
388                                button: PointerEventButton::Left,
389                                click_count: 0,
390                                is_touch: true,
391                            }
392                        }
393                        winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
394                            self.pressed = false;
395                            self.current_touch_id = None;
396                            MouseEvent::Released {
397                                position,
398                                button: PointerEventButton::Left,
399                                click_count: 0,
400                                is_touch: true,
401                            }
402                        }
403                        winit::event::TouchPhase::Moved => {
404                            MouseEvent::Moved { position, is_touch: true }
405                        }
406                    };
407                    runtime_window.process_mouse_input(ev);
408                }
409            }
410            WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: _ } => {
411                if std::env::var("SLINT_SCALE_FACTOR").is_err() {
412                    self.loop_error = window
413                        .window()
414                        .try_dispatch_event(corelib::platform::WindowEvent::ScaleFactorChanged {
415                            scale_factor: scale_factor as f32,
416                        })
417                        .err();
418                    // TODO: send a resize event or try to keep the logical size the same.
419                    //window.resize_event(inner_size_writer.???)?;
420                }
421            }
422            WindowEvent::ThemeChanged(theme) => window.set_color_scheme(match theme {
423                winit::window::Theme::Dark => ColorScheme::Dark,
424                winit::window::Theme::Light => ColorScheme::Light,
425            }),
426            WindowEvent::Occluded(x) => {
427                window.renderer.occluded(x);
428
429                // In addition to the hack done for WindowEvent::Resize, also do it for Occluded so we handle Minimized change
430                window.window_state_event();
431            }
432            _ => {}
433        }
434
435        if self.loop_error.is_some() {
436            event_loop.exit();
437        }
438    }
439
440    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: SlintEvent) {
441        match event.0 {
442            CustomEvent::UserEvent(user_callback) => user_callback(),
443            CustomEvent::Exit => {
444                self.suspend_all_hidden_windows();
445                event_loop.exit()
446            }
447            #[cfg(enable_accesskit)]
448            CustomEvent::Accesskit(accesskit_winit::Event { window_id, window_event }) => {
449                if let Some(window) = self.shared_backend_data.window_by_id(window_id) {
450                    let deferred_action = window
451                        .accesskit_adapter()
452                        .expect("internal error: accesskit adapter must exist when window exists")
453                        .borrow_mut()
454                        .process_accesskit_event(window_event);
455                    // access kit adapter not borrowed anymore, now invoke the deferred action
456                    if let Some(deferred_action) = deferred_action {
457                        deferred_action.invoke(window.window());
458                    }
459                }
460            }
461            #[cfg(target_arch = "wasm32")]
462            CustomEvent::WakeEventLoopWorkaround => {
463                event_loop.set_control_flow(ControlFlow::Poll);
464            }
465            #[cfg(muda)]
466            CustomEvent::Muda(event) => {
467                if let Some((window, eid, muda_type)) =
468                    event.id().0.split_once('|').and_then(|(w, e)| {
469                        let (e, muda_type) = e.split_once('|')?;
470                        Some((
471                            self.shared_backend_data.window_by_id(
472                                winit::window::WindowId::from(w.parse::<u64>().ok()?),
473                            )?,
474                            e.parse::<usize>().ok()?,
475                            muda_type.parse::<crate::muda::MudaType>().ok()?,
476                        ))
477                    })
478                {
479                    window.muda_event(eid, muda_type);
480                };
481            }
482        }
483    }
484
485    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
486        if matches!(
487            self.custom_application_handler.as_mut().map_or(EventResult::Propagate, |handler| {
488                handler.new_events(event_loop, cause)
489            }),
490            EventResult::PreventDefault
491        ) {
492            return;
493        }
494
495        event_loop.set_control_flow(ControlFlow::Wait);
496
497        corelib::platform::update_timers_and_animations();
498    }
499
500    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
501        if matches!(
502            self.custom_application_handler
503                .as_mut()
504                .map_or(EventResult::Propagate, |handler| { handler.about_to_wait(event_loop) }),
505            EventResult::PreventDefault
506        ) {
507            return;
508        }
509
510        if let Err(err) = self.shared_backend_data.create_inactive_windows(event_loop) {
511            self.loop_error = Some(err);
512        }
513
514        if !event_loop.exiting() {
515            for w in self
516                .shared_backend_data
517                .active_windows
518                .borrow()
519                .iter()
520                .filter_map(|(_, w)| w.upgrade())
521            {
522                if w.window().has_active_animations() {
523                    w.request_redraw();
524                }
525            }
526        }
527
528        if event_loop.control_flow() == ControlFlow::Wait {
529            if let Some(next_timer) = corelib::platform::duration_until_next_timer_update() {
530                event_loop.set_control_flow(ControlFlow::wait_duration(next_timer));
531            }
532        }
533
534        if self.pumping_events_instantly {
535            event_loop.set_control_flow(ControlFlow::Poll);
536        }
537    }
538
539    fn device_event(
540        &mut self,
541        event_loop: &ActiveEventLoop,
542        device_id: winit::event::DeviceId,
543        event: winit::event::DeviceEvent,
544    ) {
545        if let Some(handler) = self.custom_application_handler.as_mut() {
546            handler.device_event(event_loop, device_id, event);
547        }
548    }
549
550    fn suspended(&mut self, event_loop: &ActiveEventLoop) {
551        if let Some(handler) = self.custom_application_handler.as_mut() {
552            handler.suspended(event_loop);
553        }
554    }
555
556    fn exiting(&mut self, event_loop: &ActiveEventLoop) {
557        if let Some(handler) = self.custom_application_handler.as_mut() {
558            handler.exiting(event_loop);
559        }
560    }
561
562    fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
563        if let Some(handler) = self.custom_application_handler.as_mut() {
564            handler.memory_warning(event_loop);
565        }
566    }
567}
568
569impl EventLoopState {
570    /// Runs the event loop and renders the items in the provided `component` in its
571    /// own window.
572    #[allow(unused_mut)] // mut need changes for wasm
573    pub fn run(mut self) -> Result<Self, corelib::platform::PlatformError> {
574        let not_running_loop_instance = self
575            .shared_backend_data
576            .not_running_event_loop
577            .take()
578            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
579        let mut winit_loop = not_running_loop_instance;
580
581        cfg_if::cfg_if! {
582            if #[cfg(any(target_arch = "wasm32", ios_and_friends))] {
583                winit_loop
584                    .run_app(&mut self)
585                    .map_err(|e| format!("Error running winit event loop: {e}"))?;
586                // This can't really happen, as run() doesn't return
587                Ok(Self::new(self.shared_backend_data.clone(), None))
588            } else {
589                use winit::platform::run_on_demand::EventLoopExtRunOnDemand as _;
590                winit_loop
591                    .run_app_on_demand(&mut self)
592                    .map_err(|e| format!("Error running winit event loop: {e}"))?;
593
594                // Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
595                // Winit does not support creating multiple instances of the event loop.
596                self.shared_backend_data.not_running_event_loop.replace(Some(winit_loop));
597
598                if let Some(error) = self.loop_error {
599                    return Err(error);
600                }
601                Ok(self)
602            }
603        }
604    }
605
606    /// Runs the event loop and renders the items in the provided `component` in its
607    /// own window.
608    #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
609    pub fn pump_events(
610        mut self,
611        timeout: Option<std::time::Duration>,
612    ) -> Result<(Self, winit::platform::pump_events::PumpStatus), corelib::platform::PlatformError>
613    {
614        use winit::platform::pump_events::EventLoopExtPumpEvents;
615
616        let not_running_loop_instance = self
617            .shared_backend_data
618            .not_running_event_loop
619            .take()
620            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
621        let mut winit_loop = not_running_loop_instance;
622
623        self.pumping_events_instantly = timeout.is_some_and(|duration| duration.is_zero());
624
625        let result = winit_loop.pump_app_events(timeout, &mut self);
626
627        self.pumping_events_instantly = false;
628
629        // Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
630        // Winit does not support creating multiple instances of the event loop.
631        self.shared_backend_data.not_running_event_loop.replace(Some(winit_loop));
632
633        if let Some(error) = self.loop_error {
634            return Err(error);
635        }
636        Ok((self, result))
637    }
638
639    #[cfg(target_arch = "wasm32")]
640    pub fn spawn(self) -> Result<(), corelib::platform::PlatformError> {
641        use winit::platform::web::EventLoopExtWebSys;
642        let not_running_loop_instance = self
643            .shared_backend_data
644            .not_running_event_loop
645            .take()
646            .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
647
648        not_running_loop_instance.spawn_app(self);
649
650        Ok(())
651    }
652}