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