1#![warn(missing_docs)]
5use 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
41pub enum CustomEvent {
44 #[cfg(target_arch = "wasm32")]
47 WakeEventLoopWorkaround,
48 UserEvent(Box<dyn FnOnce() + Send>),
50 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 cursor_pos: LogicalPoint,
77 pressed: bool,
80
81 loop_error: Option<PlatformError>,
82 current_resize_direction: Option<ResizeDirection>,
83
84 pending_mouse_move: Option<(winit::window::WindowId, LogicalPoint)>,
88
89 pumping_events_instantly: bool,
91
92 #[cfg(target_os = "ios")]
94 touch_finger_ids: crate::ios::TouchFingerIdAllocator,
95
96 custom_application_handler: Option<Box<dyn crate::CustomApplicationHandler>>,
97}
98
99impl EventLoopState {
100 pub fn new(
101 shared_backend_data: Rc<SharedBackendData>,
102 custom_application_handler: Option<Box<dyn crate::CustomApplicationHandler>>,
103 ) -> Self {
104 Self {
105 shared_backend_data,
106 cursor_pos: Default::default(),
107 pressed: Default::default(),
108 loop_error: Default::default(),
109 current_resize_direction: Default::default(),
110 pending_mouse_move: Default::default(),
111 pumping_events_instantly: Default::default(),
112 #[cfg(target_os = "ios")]
113 touch_finger_ids: Default::default(),
114 custom_application_handler,
115 }
116 }
117
118 fn suspend_all_hidden_windows(&self) {
121 let windows_to_suspend = self
122 .shared_backend_data
123 .active_windows
124 .borrow()
125 .values()
126 .filter_map(|w| w.upgrade())
127 .filter(|w| matches!(w.visibility(), WindowVisibility::Hidden))
128 .collect::<Vec<_>>();
129 for window in windows_to_suspend.into_iter() {
130 let _ = window.suspend();
131 }
132 }
133
134 fn flush_pending_mouse_move(&mut self) {
136 if let Some((window_id, position)) = self.pending_mouse_move.take()
137 && let Some(window) = self.shared_backend_data.window_by_id(window_id)
138 {
139 let runtime_window = WindowInner::from_pub(window.window());
140 runtime_window.process_mouse_input(MouseEvent::Moved { position, touch_finger_id: 0 });
141 }
142 }
143}
144
145impl winit::application::ApplicationHandler<SlintEvent> for EventLoopState {
146 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
147 if matches!(
148 self.custom_application_handler
149 .as_mut()
150 .map_or(EventResult::Propagate, |handler| { handler.resumed(event_loop) }),
151 EventResult::PreventDefault
152 ) {
153 return;
154 }
155 if let Err(err) = self.shared_backend_data.create_inactive_windows(event_loop) {
156 self.loop_error = Some(err);
157 event_loop.exit();
158 }
159 }
160
161 #[allow(clippy::collapsible_match)]
162 fn window_event(
163 &mut self,
164 event_loop: &ActiveEventLoop,
165 window_id: winit::window::WindowId,
166 event: WindowEvent,
167 ) {
168 let Some(window) = self.shared_backend_data.window_by_id(window_id) else {
169 if let Some(handler) = self.custom_application_handler.as_mut() {
170 handler.window_event(event_loop, window_id, None, None, &event);
171 }
172 return;
173 };
174
175 if let Some(winit_window) = window.winit_window() {
176 if matches!(
177 self.custom_application_handler.as_mut().map_or(
178 EventResult::Propagate,
179 |handler| handler.window_event(
180 event_loop,
181 window_id,
182 Some(&*winit_window),
183 Some(window.window()),
184 &event
185 )
186 ),
187 EventResult::PreventDefault
188 ) {
189 return;
190 }
191
192 if let Some(mut window_event_filter) = window.window_event_filter.take() {
193 let event_result = window_event_filter(window.window(), &event);
194 window.window_event_filter.set(Some(window_event_filter));
195
196 match event_result {
197 EventResult::PreventDefault => return,
198 EventResult::Propagate => (),
199 }
200 }
201
202 #[cfg(enable_accesskit)]
203 window
204 .accesskit_adapter()
205 .expect("internal error: accesskit adapter must exist when window exists")
206 .borrow_mut()
207 .process_event(&winit_window, &event);
208 } else {
209 return;
210 }
211
212 let runtime_window = WindowInner::from_pub(window.window());
213 if !matches!(event, WindowEvent::CursorMoved { .. } | WindowEvent::AxisMotion { .. }) {
214 self.flush_pending_mouse_move();
215 }
216
217 match event {
218 WindowEvent::RedrawRequested => {
219 self.loop_error = window.draw().err();
220 }
221 WindowEvent::Resized(size) => {
222 self.loop_error = window.resize_event(size).err();
223
224 window.window_state_event();
230
231 #[cfg(target_os = "windows")]
234 {
235 if size.width == 0 || size.height == 0 {
236 window.renderer.occluded(true);
237 }
238 }
239 }
240 WindowEvent::CloseRequested => {
241 self.loop_error = window
242 .window()
243 .try_dispatch_event(corelib::platform::WindowEvent::CloseRequested)
244 .err();
245 }
246 WindowEvent::Focused(have_focus) => {
247 let have_focus = if cfg!(target_os = "macos") {
249 window.winit_window().map_or(have_focus, |w| w.has_focus())
250 } else {
251 have_focus
252 };
253 self.loop_error = window.activation_changed(have_focus).err();
254 }
255
256 WindowEvent::KeyboardInput { event, is_synthetic, .. } => {
257 let key_code = event.logical_key.clone();
258 let swap_cmd_ctrl = i_slint_core::is_apple_platform();
260
261 let key_code = if swap_cmd_ctrl {
262 #[cfg_attr(slint_nightly_test, allow(non_exhaustive_omitted_patterns))]
263 match key_code {
264 winit::keyboard::Key::Named(winit::keyboard::NamedKey::Control) => {
265 winit::keyboard::Key::Named(winit::keyboard::NamedKey::Super)
266 }
267 winit::keyboard::Key::Named(winit::keyboard::NamedKey::Super) => {
268 winit::keyboard::Key::Named(winit::keyboard::NamedKey::Control)
269 }
270 code => code,
271 }
272 } else {
273 key_code
274 };
275
276 fn to_slint_key(event: &winit::event::KeyEvent, key_code: &Key) -> SharedString {
277 macro_rules! winit_key_to_char {
278 ($($char:literal # $name:ident # $($shifted:ident)? $(=> $($_muda:ident)? # $($_qt:ident)|* # $($winit:ident $(($pos:ident))?)|* # $($_xkb:ident)|* )? ;)*) => {
279 #[cfg_attr(slint_nightly_test, allow(non_exhaustive_omitted_patterns))]
280 match key_code {
281 $( $( $(
282 winit::keyboard::Key::Named(winit::keyboard::NamedKey::$winit)
283 $(if event.location == winit::keyboard::KeyLocation::$pos)?
284 => $char.into(),
285 )* )? )*
286 winit::keyboard::Key::Character(str) => str.as_str().into(),
287 _ => {
288 if let Some(text) = &event.text {
289 text.as_str().into()
290 } else {
291 "".into()
292 }
293 }
294 }
295 }
296 }
297 i_slint_common::for_each_keys!(winit_key_to_char)
298 }
299 #[allow(unused_mut)]
300 let mut text = to_slint_key(&event, &key_code);
301
302 #[cfg(target_os = "windows")]
303 let text_without_modifiers = {
304 use winit::platform::modifier_supplement::KeyEventExtModifierSupplement;
305
306 let text_without_modifiers =
316 to_slint_key(&event, &event.key_without_modifiers());
317 if text.is_empty() && !text_without_modifiers.is_empty() {
318 text = text_without_modifiers.clone();
319 }
320 text_without_modifiers
321 };
322
323 if text.is_empty() {
324 return;
326 }
327
328 if is_synthetic {
329 use winit::keyboard::{Key::Named, NamedKey as N};
332 if !matches!(
333 key_code,
334 Named(N::Control | N::Shift | N::Super | N::Alt | N::AltGraph),
335 ) {
336 return;
337 }
338 }
339
340 let event_type = match event.state {
341 winit::event::ElementState::Pressed => corelib::input::KeyEventType::KeyPressed,
342 winit::event::ElementState::Released => {
343 corelib::input::KeyEventType::KeyReleased
344 }
345 };
346 let mut key_event = KeyEvent::default();
347 key_event.text = text;
348
349 let event = corelib::input::InternalKeyEvent {
350 key_event,
351 event_type,
352 #[cfg(target_os = "windows")]
353 text_without_modifiers,
354 ..Default::default()
355 };
356
357 runtime_window.process_key_input(event);
358 }
359 WindowEvent::Ime(winit::event::Ime::Preedit(string, preedit_selection)) => {
360 let event = InternalKeyEvent {
361 event_type: KeyEventType::UpdateComposition,
362 preedit_text: string.into(),
363 preedit_selection: preedit_selection.map(|e| e.0 as i32..e.1 as i32),
364 ..Default::default()
365 };
366 runtime_window.process_key_input(event);
367 }
368 WindowEvent::Ime(winit::event::Ime::Commit(string)) => {
369 let mut key_event = KeyEvent::default();
370 key_event.text = string.into();
371 let event = InternalKeyEvent {
372 event_type: KeyEventType::CommitComposition,
373 key_event,
374 ..Default::default()
375 };
376 runtime_window.process_key_input(event);
377 }
378 WindowEvent::CursorMoved { position, .. } => {
379 self.current_resize_direction = handle_cursor_move_for_resize(
380 &window.winit_window().unwrap(),
381 position,
382 self.current_resize_direction,
383 runtime_window
384 .window_item()
385 .map_or(0_f64, |w| w.as_pin_ref().resize_border_width().get().into()),
386 );
387 let position = position.to_logical(runtime_window.scale_factor() as f64);
388 self.cursor_pos = euclid::point2(position.x, position.y);
389 self.pending_mouse_move = Some((window_id, self.cursor_pos));
393 }
394 WindowEvent::CursorLeft { .. } => {
395 if cfg!(target_arch = "wasm32") || !self.pressed {
397 self.pressed = false;
398 runtime_window.process_mouse_input(MouseEvent::Exit);
399 }
400 }
401 WindowEvent::MouseWheel { delta, phase, .. } => {
402 let (delta_x, delta_y) = match delta {
403 winit::event::MouseScrollDelta::LineDelta(lx, ly) => (lx * 60., ly * 60.),
404 winit::event::MouseScrollDelta::PixelDelta(d) => {
405 let d = d.to_logical(runtime_window.scale_factor() as f64);
406 (d.x, d.y)
407 }
408 };
409 let phase = match phase {
410 winit::event::TouchPhase::Started => TouchPhase::Started,
411 winit::event::TouchPhase::Moved => TouchPhase::Moved,
412 winit::event::TouchPhase::Ended => TouchPhase::Ended,
413 winit::event::TouchPhase::Cancelled => TouchPhase::Cancelled,
414 };
415 runtime_window.process_mouse_input(MouseEvent::Wheel {
416 position: self.cursor_pos,
417 delta_x,
418 delta_y,
419 phase,
420 });
421 }
422 WindowEvent::MouseInput { state, button, .. } => {
423 let button = match button {
424 winit::event::MouseButton::Left => PointerEventButton::Left,
425 winit::event::MouseButton::Right => PointerEventButton::Right,
426 winit::event::MouseButton::Middle => PointerEventButton::Middle,
427 winit::event::MouseButton::Back => PointerEventButton::Back,
428 winit::event::MouseButton::Forward => PointerEventButton::Forward,
429 winit::event::MouseButton::Other(_) => PointerEventButton::Other,
430 };
431 let ev = match state {
432 winit::event::ElementState::Pressed => {
433 if button == PointerEventButton::Left
434 && self.current_resize_direction.is_some()
435 {
436 handle_resize(
437 &window.winit_window().unwrap(),
438 self.current_resize_direction,
439 );
440 return;
441 }
442
443 self.pressed = true;
444 MouseEvent::Pressed {
445 position: self.cursor_pos,
446 button,
447 click_count: 0,
448 touch_finger_id: 0,
449 }
450 }
451 winit::event::ElementState::Released => {
452 self.pressed = false;
453 MouseEvent::Released {
454 position: self.cursor_pos,
455 button,
456 click_count: 0,
457 touch_finger_id: 0,
458 }
459 }
460 };
461 runtime_window.process_mouse_input(ev);
462 }
463 WindowEvent::Touch(touch) => {
464 let location = touch.location.to_logical(runtime_window.scale_factor() as f64);
465 let position = euclid::point2(location.x, location.y);
466 #[cfg(not(target_os = "ios"))]
471 let finger_id =
472 Some(i32::try_from(touch.id).expect("winit touch id out of i32 range"));
473 #[cfg(target_os = "ios")]
474 let finger_id = match touch.phase {
475 winit::event::TouchPhase::Started | winit::event::TouchPhase::Moved => {
476 self.touch_finger_ids.id_for(touch.id)
477 }
478 winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
479 self.touch_finger_ids.take(touch.id)
480 }
481 };
482 if let Some(finger_id) = finger_id {
483 runtime_window.process_touch_input(
484 finger_id,
485 position,
486 winit_touch_phase(touch.phase),
487 );
488 }
489 }
490 WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: _ } => {
491 if std::env::var("SLINT_SCALE_FACTOR").is_err() {
492 self.loop_error = window
493 .window()
494 .try_dispatch_event(corelib::platform::WindowEvent::ScaleFactorChanged {
495 scale_factor: scale_factor as f32,
496 })
497 .err();
498 }
501 }
502 WindowEvent::ThemeChanged(theme) => {
503 window.set_color_scheme(match theme {
504 winit::window::Theme::Dark => ColorScheme::Dark,
505 winit::window::Theme::Light => ColorScheme::Light,
506 });
507 window.update_accent_color();
508 }
509 WindowEvent::Occluded(x) => {
510 window.renderer.occluded(x);
511
512 window.window_state_event();
514 }
515 WindowEvent::PinchGesture { delta, phase, .. } => {
519 runtime_window.process_mouse_input(corelib::input::MouseEvent::PinchGesture {
520 position: self.cursor_pos,
521 delta: delta as f32,
522 phase: winit_touch_phase(phase),
523 });
524 }
525 WindowEvent::RotationGesture { delta, phase, .. } => {
526 runtime_window.process_mouse_input(corelib::input::MouseEvent::RotationGesture {
529 position: self.cursor_pos,
530 delta: -delta,
531 phase: winit_touch_phase(phase),
532 });
533 }
534
535 WindowEvent::AxisMotion { .. } => {
536 }
538 _ => {}
539 }
540
541 if self.loop_error.is_some() {
542 event_loop.exit();
543 }
544 }
545
546 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: SlintEvent) {
547 match event.0 {
548 CustomEvent::UserEvent(user_callback) => user_callback(),
549 CustomEvent::Exit(generation) => {
550 if self
551 .shared_backend_data
552 .event_loop_generation
553 .load(std::sync::atomic::Ordering::Relaxed)
554 == generation
555 {
556 self.suspend_all_hidden_windows();
557 event_loop.exit()
558 }
559 }
561 #[cfg(enable_accesskit)]
562 CustomEvent::Accesskit(accesskit_winit::Event { window_id, window_event }) => {
563 if let Some(window) = self.shared_backend_data.window_by_id(window_id) {
564 let deferred_action = window
565 .accesskit_adapter()
566 .expect("internal error: accesskit adapter must exist when window exists")
567 .borrow_mut()
568 .process_accesskit_event(window_event);
569 if let Some(deferred_action) = deferred_action {
571 deferred_action.invoke(window.window());
572 }
573 }
574 }
575 #[cfg(target_arch = "wasm32")]
576 CustomEvent::WakeEventLoopWorkaround => {
577 event_loop.set_control_flow(ControlFlow::Poll);
578 }
579 #[cfg(muda)]
580 CustomEvent::Muda(event) => {
581 if let Some((window, eid, muda_type)) =
582 event.id().0.split_once('|').and_then(|(w, e)| {
583 let (e, muda_type) = e.split_once('|')?;
584 Some((
585 self.shared_backend_data.window_by_id(
586 winit::window::WindowId::from(w.parse::<u64>().ok()?),
587 )?,
588 e.parse::<usize>().ok()?,
589 muda_type.parse::<crate::muda::MudaType>().ok()?,
590 ))
591 })
592 {
593 window.muda_event(eid, muda_type);
594 };
595 }
596 }
597 }
598
599 fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
600 if matches!(
601 self.custom_application_handler.as_mut().map_or(EventResult::Propagate, |handler| {
602 handler.new_events(event_loop, cause)
603 }),
604 EventResult::PreventDefault
605 ) {
606 return;
607 }
608
609 event_loop.set_control_flow(ControlFlow::Wait);
610
611 corelib::platform::update_timers_and_animations();
612 }
613
614 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
615 self.flush_pending_mouse_move();
616
617 if matches!(
618 self.custom_application_handler
619 .as_mut()
620 .map_or(EventResult::Propagate, |handler| { handler.about_to_wait(event_loop) }),
621 EventResult::PreventDefault
622 ) {
623 return;
624 }
625
626 if let Err(err) = self.shared_backend_data.create_inactive_windows(event_loop) {
627 self.loop_error = Some(err);
628 }
629
630 if !event_loop.exiting() {
631 for w in self
632 .shared_backend_data
633 .active_windows
634 .borrow()
635 .values()
636 .filter_map(|w| w.upgrade())
637 {
638 if w.window().has_active_animations() {
639 w.request_redraw();
640 }
641 }
642 }
643
644 if event_loop.control_flow() == ControlFlow::Wait
645 && let Some(next_timer) = corelib::platform::duration_until_next_timer_update()
646 {
647 event_loop.set_control_flow(ControlFlow::wait_duration(next_timer));
648 }
649
650 if self.pumping_events_instantly {
651 event_loop.set_control_flow(ControlFlow::Poll);
652 }
653 }
654
655 fn device_event(
656 &mut self,
657 event_loop: &ActiveEventLoop,
658 device_id: winit::event::DeviceId,
659 event: winit::event::DeviceEvent,
660 ) {
661 if let Some(handler) = self.custom_application_handler.as_mut() {
662 handler.device_event(event_loop, device_id, event);
663 }
664 }
665
666 fn suspended(&mut self, event_loop: &ActiveEventLoop) {
667 if let Some(handler) = self.custom_application_handler.as_mut() {
668 handler.suspended(event_loop);
669 }
670 }
671
672 fn exiting(&mut self, event_loop: &ActiveEventLoop) {
673 if let Some(handler) = self.custom_application_handler.as_mut() {
674 handler.exiting(event_loop);
675 }
676 }
677
678 fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
679 if let Some(handler) = self.custom_application_handler.as_mut() {
680 handler.memory_warning(event_loop);
681 }
682 }
683}
684
685impl EventLoopState {
686 #[allow(unused_mut)] pub fn run(mut self) -> Result<Self, corelib::platform::PlatformError> {
690 let not_running_loop_instance = self
691 .shared_backend_data
692 .not_running_event_loop
693 .take()
694 .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
695 let mut winit_loop = not_running_loop_instance;
696
697 cfg_if::cfg_if! {
698 if #[cfg(any(target_arch = "wasm32", ios_and_friends))] {
699 winit_loop
700 .run_app(&mut self)
701 .map_err(|e| format!("Error running winit event loop: {e}"))?;
702 Ok(Self::new(self.shared_backend_data.clone(), None))
704 } else {
705 use winit::platform::run_on_demand::EventLoopExtRunOnDemand as _;
706 winit_loop
707 .run_app_on_demand(&mut self)
708 .map_err(|e| format!("Error running winit event loop: {e}"))?;
709
710 self.shared_backend_data.not_running_event_loop.replace(Some(winit_loop));
713
714 if let Some(error) = self.loop_error {
715 return Err(error);
716 }
717 Ok(self)
718 }
719 }
720 }
721
722 #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
725 pub fn pump_events(
726 mut self,
727 timeout: Option<std::time::Duration>,
728 ) -> Result<(Self, winit::platform::pump_events::PumpStatus), corelib::platform::PlatformError>
729 {
730 use winit::platform::pump_events::EventLoopExtPumpEvents;
731
732 let not_running_loop_instance = self
733 .shared_backend_data
734 .not_running_event_loop
735 .take()
736 .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
737 let mut winit_loop = not_running_loop_instance;
738
739 self.pumping_events_instantly = timeout.is_some_and(|duration| duration.is_zero());
740
741 let result = winit_loop.pump_app_events(timeout, &mut self);
742
743 self.pumping_events_instantly = false;
744
745 self.shared_backend_data.not_running_event_loop.replace(Some(winit_loop));
748
749 if let Some(error) = self.loop_error {
750 return Err(error);
751 }
752 Ok((self, result))
753 }
754
755 #[cfg(target_arch = "wasm32")]
756 pub fn spawn(self) -> Result<(), corelib::platform::PlatformError> {
757 use winit::platform::web::EventLoopExtWebSys;
758 let not_running_loop_instance = self
759 .shared_backend_data
760 .not_running_event_loop
761 .take()
762 .ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
763
764 not_running_loop_instance.spawn_app(self);
765
766 Ok(())
767 }
768}