1#![warn(missing_docs)]
7
8use crate::item_tree::ItemTreeRc;
9use crate::item_tree::{ItemRc, ItemWeak, VisitChildrenResult};
10use crate::items::{DropEvent, ItemRef, MouseCursor, OperatingSystemType, TextCursorDirection};
11pub use crate::items::{FocusReason, KeyEvent, KeyboardModifiers, PointerEventButton};
12use crate::lengths::{ItemTransform, LogicalPoint, LogicalVector};
13use crate::timers::Timer;
14use crate::window::{WindowAdapter, WindowInner};
15use crate::{Coord, Property, SharedString};
16use alloc::rc::Rc;
17use alloc::vec::Vec;
18use const_field_offset::FieldOffsets;
19use core::cell::Cell;
20use core::fmt::Display;
21use core::pin::Pin;
22use core::time::Duration;
23
24#[repr(C)]
29#[derive(Debug, Clone, PartialEq)]
30pub enum MouseEvent {
31 Pressed {
33 position: LogicalPoint,
35 button: PointerEventButton,
37 click_count: u8,
39 is_touch: bool,
41 },
42 Released {
44 position: LogicalPoint,
46 button: PointerEventButton,
48 click_count: u8,
50 is_touch: bool,
52 },
53 Moved {
55 position: LogicalPoint,
57 is_touch: bool,
59 },
60 Wheel {
62 position: LogicalPoint,
64 delta_x: Coord,
66 delta_y: Coord,
68 phase: TouchPhase,
70 },
71 DragMove(DropEvent),
75 Drop(DropEvent),
77 PinchGesture {
79 position: LogicalPoint,
81 delta: f32,
83 phase: TouchPhase,
85 },
86 RotationGesture {
88 position: LogicalPoint,
90 delta: f32,
92 phase: TouchPhase,
94 },
95 Exit,
97}
98
99impl MouseEvent {
100 pub fn is_touch(&self) -> Option<bool> {
102 match self {
103 MouseEvent::Pressed { is_touch, .. } => Some(*is_touch),
104 MouseEvent::Released { is_touch, .. } => Some(*is_touch),
105 MouseEvent::Moved { is_touch, .. } => Some(*is_touch),
106 MouseEvent::Wheel { .. } => None,
107 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => Some(true),
108 MouseEvent::DragMove(..) | MouseEvent::Drop(..) => None,
109 MouseEvent::Exit => None,
110 }
111 }
112
113 pub fn position(&self) -> Option<LogicalPoint> {
115 match self {
116 MouseEvent::Pressed { position, .. } => Some(*position),
117 MouseEvent::Released { position, .. } => Some(*position),
118 MouseEvent::Moved { position, .. } => Some(*position),
119 MouseEvent::Wheel { position, .. } => Some(*position),
120 MouseEvent::PinchGesture { position, .. } => Some(*position),
121 MouseEvent::RotationGesture { position, .. } => Some(*position),
122 MouseEvent::DragMove(e) | MouseEvent::Drop(e) => {
123 Some(crate::lengths::logical_point_from_api(e.position))
124 }
125 MouseEvent::Exit => None,
126 }
127 }
128
129 pub fn translate(&mut self, vec: LogicalVector) {
131 let pos = match self {
132 MouseEvent::Pressed { position, .. } => Some(position),
133 MouseEvent::Released { position, .. } => Some(position),
134 MouseEvent::Moved { position, .. } => Some(position),
135 MouseEvent::Wheel { position, .. } => Some(position),
136 MouseEvent::PinchGesture { position, .. } => Some(position),
137 MouseEvent::RotationGesture { position, .. } => Some(position),
138 MouseEvent::DragMove(e) | MouseEvent::Drop(e) => {
139 e.position = crate::api::LogicalPosition::from_euclid(
140 crate::lengths::logical_point_from_api(e.position) + vec,
141 );
142 None
143 }
144 MouseEvent::Exit => None,
145 };
146 if let Some(pos) = pos {
147 *pos += vec;
148 }
149 }
150
151 pub fn transform(&mut self, transform: ItemTransform) {
153 let pos = match self {
154 MouseEvent::Pressed { position, .. } => Some(position),
155 MouseEvent::Released { position, .. } => Some(position),
156 MouseEvent::Moved { position, .. } => Some(position),
157 MouseEvent::Wheel { position, .. } => Some(position),
158 MouseEvent::PinchGesture { position, .. } => Some(position),
159 MouseEvent::RotationGesture { position, .. } => Some(position),
160 MouseEvent::DragMove(e) | MouseEvent::Drop(e) => {
161 e.position = crate::api::LogicalPosition::from_euclid(
162 transform
163 .transform_point(crate::lengths::logical_point_from_api(e.position).cast())
164 .cast(),
165 );
166 None
167 }
168 MouseEvent::Exit => None,
169 };
170 if let Some(pos) = pos {
171 *pos = transform.transform_point(pos.cast()).cast();
172 }
173 }
174
175 fn set_click_count(&mut self, count: u8) {
177 match self {
178 MouseEvent::Pressed { click_count, .. } | MouseEvent::Released { click_count, .. } => {
179 *click_count = count
180 }
181 _ => (),
182 }
183 }
184}
185
186#[repr(u8)]
190#[derive(Debug, Clone, Copy, PartialEq)]
191pub enum TouchPhase {
192 Started,
194 Moved,
196 Ended,
198 Cancelled,
200}
201
202#[repr(u8)]
207#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
208pub enum InputEventResult {
209 EventAccepted,
212 #[default]
214 EventIgnored,
215 GrabMouse,
217 StartDrag,
219}
220
221#[repr(C)]
225#[derive(Debug, Copy, Clone, PartialEq, Default)]
226pub enum InputEventFilterResult {
227 #[default]
230 ForwardEvent,
231 ForwardAndIgnore,
234 ForwardAndInterceptGrab,
237 Intercept,
240 DelayForwarding(u64),
248}
249
250#[allow(missing_docs, non_upper_case_globals)]
252pub mod key_codes {
253 macro_rules! declare_consts_for_special_keys {
254 ($($char:literal # $name:ident # $($shifted:ident)? # $($_muda:ident)? $(=> $($_qt:ident)|* # $($_winit:ident $(($_pos:ident))?)|* # $($_xkb:ident)|* )? ;)*) => {
255 $(pub const $name : char = $char;)*
256
257 #[allow(missing_docs)]
258 #[derive(Debug, Copy, Clone, PartialEq)]
259 #[non_exhaustive]
260 pub enum Key {
275 $($name,)*
276 }
277
278 impl From<Key> for char {
279 fn from(k: Key) -> Self {
280 match k {
281 $(Key::$name => $name,)*
282 }
283 }
284 }
285
286 impl From<Key> for crate::SharedString {
287 fn from(k: Key) -> Self {
288 char::from(k).into()
289 }
290 }
291 };
292 }
293
294 i_slint_common::for_each_keys!(declare_consts_for_special_keys);
295}
296
297#[derive(Clone, Copy, Default, Debug)]
300pub(crate) struct InternalKeyboardModifierState {
301 left_alt: bool,
302 right_alt: bool,
303 altgr: bool,
304 left_control: bool,
305 right_control: bool,
306 left_meta: bool,
307 right_meta: bool,
308 left_shift: bool,
309 right_shift: bool,
310}
311
312impl InternalKeyboardModifierState {
313 pub(crate) fn state_update(mut self, pressed: bool, text: &SharedString) -> Option<Self> {
316 if let Some(key_code) = text.chars().next() {
317 match key_code {
318 key_codes::Alt => self.left_alt = pressed,
319 key_codes::AltGr => self.altgr = pressed,
320 key_codes::Control => self.left_control = pressed,
321 key_codes::ControlR => self.right_control = pressed,
322 key_codes::Shift => self.left_shift = pressed,
323 key_codes::ShiftR => self.right_shift = pressed,
324 key_codes::Meta => self.left_meta = pressed,
325 key_codes::MetaR => self.right_meta = pressed,
326 _ => return None,
327 };
328
329 debug_assert_eq!(key_code.len_utf8(), text.len());
333 }
334
335 Some(self)
336 }
337
338 pub fn shift(&self) -> bool {
339 self.right_shift || self.left_shift
340 }
341 pub fn alt(&self) -> bool {
342 self.right_alt || self.left_alt
343 }
344 pub fn meta(&self) -> bool {
345 self.right_meta || self.left_meta
346 }
347 pub fn control(&self) -> bool {
348 self.right_control || self.left_control
349 }
350
351 pub fn modifiers_for(&self, _event: &InternalKeyEvent) -> KeyboardModifiers {
352 #[allow(unused_mut)]
353 let mut alt = self.alt();
354 #[allow(unused_mut)]
355 let mut control = self.control();
356
357 #[cfg(target_os = "windows")]
376 {
377 if !self.altgr && self.control() && self.alt() {
379 let implies_altgr = if _event.text_without_modifiers.is_empty() {
386 _event.key_event.text.chars().any(|c| !c.is_ascii_alphanumeric())
387 } else {
388 _event.text_without_modifiers.to_lowercase()
389 != _event.key_event.text.to_lowercase()
390 };
391 if implies_altgr {
392 alt = false;
393 control = false;
394 }
395 }
396 }
397 #[cfg(target_family = "wasm")]
398 if crate::detect_operating_system() == OperatingSystemType::Windows {
399 let is_altgr = self.altgr
403 || (self.control()
404 && self.alt()
405 && _event.key_event.text.chars().any(|c| !c.is_ascii_alphanumeric()));
406 if is_altgr {
407 alt = false;
408 control = false;
409 }
410 }
411
412 KeyboardModifiers { alt, control, meta: self.meta(), shift: self.shift() }
413 }
414}
415
416impl From<InternalKeyboardModifierState> for KeyboardModifiers {
417 fn from(internal_state: InternalKeyboardModifierState) -> Self {
418 Self {
419 alt: internal_state.alt(),
420 control: internal_state.control(),
421 meta: internal_state.meta(),
422 shift: internal_state.shift(),
423 }
424 }
425}
426
427#[i_slint_core_macros::slint_doc]
428#[derive(Clone, Eq, PartialEq, Default)]
434#[repr(C)]
435pub struct Keys {
436 inner: KeysInner,
437}
438
439pub fn make_keys(
441 key: SharedString,
442 modifiers: KeyboardModifiers,
443 ignore_shift: bool,
444 ignore_alt: bool,
445) -> Keys {
446 Keys {
447 inner: KeysInner { key: key.to_lowercase().into(), modifiers, ignore_shift, ignore_alt },
448 }
449}
450
451#[cfg(feature = "ffi")]
452#[allow(unsafe_code)]
453pub(crate) mod ffi {
454 use crate::api::ToSharedString as _;
455
456 use super::*;
457
458 #[unsafe(no_mangle)]
459 pub unsafe extern "C" fn slint_keys(
460 key: &SharedString,
461 alt: bool,
462 control: bool,
463 shift: bool,
464 meta: bool,
465 ignore_shift: bool,
466 ignore_alt: bool,
467 out: &mut Keys,
468 ) {
469 *out = make_keys(
470 key.clone(),
471 KeyboardModifiers { alt, control, shift, meta },
472 ignore_shift,
473 ignore_alt,
474 );
475 }
476
477 #[unsafe(no_mangle)]
478 pub unsafe extern "C" fn slint_keys_debug_string(shortcut: &Keys, out: &mut SharedString) {
479 *out = crate::format!("{shortcut:?}");
480 }
481
482 #[unsafe(no_mangle)]
483 pub unsafe extern "C" fn slint_keys_to_string(shortcut: &Keys, out: &mut SharedString) {
484 *out = shortcut.to_shared_string();
485 }
486}
487
488#[derive(PartialEq, Eq, Clone, Default)]
491#[repr(C)]
492pub struct KeysInner {
493 pub key: SharedString,
497 pub modifiers: KeyboardModifiers,
499 pub ignore_shift: bool,
501 pub ignore_alt: bool,
503}
504
505impl KeysInner {
506 pub fn from_pub(keys: &Keys) -> &Self {
508 &keys.inner
509 }
510}
511
512impl Keys {
513 pub(crate) fn matches(&self, key_event: &KeyEvent) -> bool {
515 let inner = &self.inner;
516 if inner.key.is_empty() {
518 return false;
519 }
520
521 let mut expected_modifiers = inner.modifiers;
523 if inner.ignore_shift {
524 expected_modifiers.shift = key_event.modifiers.shift;
525 }
526 if inner.ignore_alt {
527 expected_modifiers.alt = key_event.modifiers.alt;
528 }
529 let event_text = key_event.text.chars().flat_map(|character| character.to_lowercase());
537
538 event_text.eq(inner.key.chars()) && key_event.modifiers == expected_modifiers
539 }
540
541 fn format_key_for_display(&self) -> crate::SharedString {
542 let key_str = self.inner.key.as_str();
543 let first_char = key_str.chars().next();
544
545 if let Some(first_char) = first_char {
546 macro_rules! check_special_key {
547 ($($char:literal # $name:ident # $($shifted:ident)? # $($_muda:ident)? $(=> $($qt:ident)|* # $($winit:ident $(($_pos:ident))?)|* # $($xkb:ident)|*)? ;)*) => {
548 match first_char {
549 $($(
550 $char => {
552 let _ = stringify!($($qt)|*); return stringify!($name).into();
554 }
555 )?)*
556 _ => ()
557 }
558 };
559 }
560 i_slint_common::for_each_keys!(check_special_key);
561 }
562
563 if key_str.chars().count() == 1 {
564 return key_str.to_uppercase().into();
565 }
566
567 key_str.into()
568 }
569}
570
571impl Display for Keys {
572 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
588 let inner = &self.inner;
589 if inner.key.is_empty() {
590 return Ok(());
591 }
592
593 if crate::is_apple_platform() {
594 if inner.modifiers.meta {
601 f.write_str("⌃")?;
602 }
603 if !inner.ignore_alt && inner.modifiers.alt {
604 f.write_str("⌥")?;
605 }
606 if !inner.ignore_shift && inner.modifiers.shift {
607 f.write_str("⇧")?;
608 }
609 if inner.modifiers.control {
610 f.write_str("⌘")?;
611 }
612 } else {
613 let separator = "+";
614
615 let (ctrl_str, alt_str, shift_str, meta_str) =
618 if crate::detect_operating_system() == OperatingSystemType::Windows {
619 ("Ctrl", "Alt", "Shift", "Win")
620 } else {
621 ("Ctrl", "Alt", "Shift", "Super")
622 };
623
624 if inner.modifiers.meta {
625 f.write_str(meta_str)?;
626 f.write_str(separator)?;
627 }
628 if inner.modifiers.control {
629 f.write_str(ctrl_str)?;
630 f.write_str(separator)?;
631 }
632 if !inner.ignore_alt && inner.modifiers.alt {
633 f.write_str(alt_str)?;
634 f.write_str(separator)?;
635 }
636 if !inner.ignore_shift && inner.modifiers.shift {
637 f.write_str(shift_str)?;
638 f.write_str(separator)?;
639 }
640 }
641 f.write_str(&self.format_key_for_display())
642 }
643}
644
645impl core::fmt::Debug for Keys {
646 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
648 let inner = &self.inner;
649 if inner.key.is_empty() {
651 write!(f, "")
652 } else {
653 let alt = inner
654 .ignore_alt
655 .then_some("Alt?+")
656 .or(inner.modifiers.alt.then_some("Alt+"))
657 .unwrap_or_default();
658 let ctrl = if inner.modifiers.control { "Control+" } else { "" };
659 let meta = if inner.modifiers.meta { "Meta+" } else { "" };
660 let shift = inner
661 .ignore_shift
662 .then_some("Shift?+")
663 .or(inner.modifiers.shift.then_some("Shift+"))
664 .unwrap_or_default();
665 let keycode: SharedString = inner
666 .key
667 .chars()
668 .flat_map(|character| {
669 let mut escaped = alloc::vec![];
670 if character.is_control() {
671 escaped.extend(character.escape_unicode());
672 } else {
673 escaped.push(character);
674 }
675 escaped
676 })
677 .collect();
678 write!(f, "{meta}{ctrl}{alt}{shift}\"{keycode}\"")
679 }
680 }
681}
682
683#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
685#[repr(u8)]
686pub enum KeyEventType {
687 #[default]
689 KeyPressed = 0,
690 KeyReleased = 1,
692 UpdateComposition = 2,
695 CommitComposition = 3,
697}
698
699#[derive(Default)]
700pub struct InternalKeyEvent {
702 pub key_event: KeyEvent,
704 pub event_type: KeyEventType,
706 #[cfg(target_os = "windows")]
712 pub text_without_modifiers: SharedString,
713 pub replacement_range: Option<core::ops::Range<i32>>,
717 pub preedit_text: SharedString,
719 pub preedit_selection: Option<core::ops::Range<i32>>,
721 pub cursor_position: Option<i32>,
723 pub anchor_position: Option<i32>,
725}
726
727impl InternalKeyEvent {
728 pub fn shortcut(&self) -> Option<StandardShortcut> {
731 if self.key_event.modifiers.control && !self.key_event.modifiers.shift {
732 match self.key_event.text.as_str() {
733 #[cfg(not(target_arch = "wasm32"))]
734 "c" => Some(StandardShortcut::Copy),
735 #[cfg(not(target_arch = "wasm32"))]
736 "x" => Some(StandardShortcut::Cut),
737 #[cfg(not(target_arch = "wasm32"))]
738 "v" => Some(StandardShortcut::Paste),
739 "a" => Some(StandardShortcut::SelectAll),
740 "f" => Some(StandardShortcut::Find),
741 "s" => Some(StandardShortcut::Save),
742 "p" => Some(StandardShortcut::Print),
743 "z" => Some(StandardShortcut::Undo),
744 #[cfg(target_os = "windows")]
745 "y" => Some(StandardShortcut::Redo),
746 "r" => Some(StandardShortcut::Refresh),
747 _ => None,
748 }
749 } else if self.key_event.modifiers.control && self.key_event.modifiers.shift {
750 match self.key_event.text.as_str() {
751 #[cfg(not(target_os = "windows"))]
752 "z" | "Z" => Some(StandardShortcut::Redo),
753 _ => None,
754 }
755 } else {
756 None
757 }
758 }
759
760 pub fn text_shortcut(&self) -> Option<TextShortcut> {
763 let ke = &self.key_event;
764 let keycode = ke.text.chars().next()?;
765
766 let is_apple = crate::is_apple_platform();
767
768 let move_mod = if is_apple {
769 ke.modifiers.alt && !ke.modifiers.control && !ke.modifiers.meta
770 } else {
771 ke.modifiers.control && !ke.modifiers.alt && !ke.modifiers.meta
772 };
773
774 if move_mod {
775 match keycode {
776 key_codes::LeftArrow => {
777 return Some(TextShortcut::Move(TextCursorDirection::BackwardByWord));
778 }
779 key_codes::RightArrow => {
780 return Some(TextShortcut::Move(TextCursorDirection::ForwardByWord));
781 }
782 key_codes::UpArrow => {
783 return Some(TextShortcut::Move(TextCursorDirection::StartOfParagraph));
784 }
785 key_codes::DownArrow => {
786 return Some(TextShortcut::Move(TextCursorDirection::EndOfParagraph));
787 }
788 key_codes::Backspace => {
789 return Some(TextShortcut::DeleteWordBackward);
790 }
791 key_codes::Delete => {
792 return Some(TextShortcut::DeleteWordForward);
793 }
794 _ => (),
795 };
796 }
797
798 #[cfg(not(target_os = "macos"))]
799 {
800 if ke.modifiers.control && !ke.modifiers.alt && !ke.modifiers.meta {
801 match keycode {
802 key_codes::Home => {
803 return Some(TextShortcut::Move(TextCursorDirection::StartOfText));
804 }
805 key_codes::End => {
806 return Some(TextShortcut::Move(TextCursorDirection::EndOfText));
807 }
808 _ => (),
809 };
810 }
811 }
812
813 if is_apple && ke.modifiers.control {
814 match keycode {
815 key_codes::LeftArrow => {
816 return Some(TextShortcut::Move(TextCursorDirection::StartOfLine));
817 }
818 key_codes::RightArrow => {
819 return Some(TextShortcut::Move(TextCursorDirection::EndOfLine));
820 }
821 key_codes::UpArrow => {
822 return Some(TextShortcut::Move(TextCursorDirection::StartOfText));
823 }
824 key_codes::DownArrow => {
825 return Some(TextShortcut::Move(TextCursorDirection::EndOfText));
826 }
827 key_codes::Backspace => {
828 return Some(TextShortcut::DeleteToStartOfLine);
829 }
830 _ => (),
831 };
832 }
833
834 if let Ok(direction) = TextCursorDirection::try_from(keycode) {
835 Some(TextShortcut::Move(direction))
836 } else {
837 match keycode {
838 key_codes::Backspace => Some(TextShortcut::DeleteBackward),
839 key_codes::Delete => Some(TextShortcut::DeleteForward),
840 _ => None,
841 }
842 }
843 }
844}
845
846pub enum StandardShortcut {
848 Copy,
850 Cut,
852 Paste,
854 SelectAll,
856 Find,
858 Save,
860 Print,
862 Undo,
864 Redo,
866 Refresh,
868}
869
870pub enum TextShortcut {
872 Move(TextCursorDirection),
874 DeleteForward,
876 DeleteBackward,
878 DeleteWordForward,
880 DeleteWordBackward,
882 DeleteToStartOfLine,
884}
885
886#[repr(u8)]
889#[derive(Debug, Clone, Copy, PartialEq, Default)]
890pub enum KeyEventResult {
891 EventAccepted,
893 #[default]
895 EventIgnored,
896}
897
898#[repr(u8)]
901#[derive(Debug, Clone, Copy, PartialEq, Default)]
902pub enum FocusEventResult {
903 FocusAccepted,
905 #[default]
907 FocusIgnored,
908}
909
910#[derive(Debug, Clone, Copy, PartialEq)]
913#[repr(u8)]
914pub enum FocusEvent {
915 FocusIn(FocusReason),
917 FocusOut(FocusReason),
919}
920
921#[derive(Default)]
923pub struct ClickState {
924 click_count_time_stamp: Cell<Option<crate::animations::Instant>>,
925 click_count: Cell<u8>,
926 click_position: Cell<LogicalPoint>,
927 click_button: Cell<PointerEventButton>,
928}
929
930impl ClickState {
931 fn restart(&self, position: LogicalPoint, button: PointerEventButton) {
933 self.click_count.set(0);
934 self.click_count_time_stamp.set(Some(crate::animations::Instant::now()));
935 self.click_position.set(position);
936 self.click_button.set(button);
937 }
938
939 pub fn reset(&self) {
941 self.click_count.set(0);
942 self.click_count_time_stamp.replace(None);
943 }
944
945 pub fn check_repeat(&self, mouse_event: MouseEvent, click_interval: Duration) -> MouseEvent {
947 match mouse_event {
948 MouseEvent::Pressed { position, button, is_touch, .. } => {
949 let instant_now = crate::animations::Instant::now();
950
951 if let Some(click_count_time_stamp) = self.click_count_time_stamp.get() {
952 if instant_now - click_count_time_stamp < click_interval
953 && button == self.click_button.get()
954 && (position - self.click_position.get()).square_length() < 100 as _
955 {
956 self.click_count.set(self.click_count.get().wrapping_add(1));
957 self.click_count_time_stamp.set(Some(instant_now));
958 } else {
959 self.restart(position, button);
960 }
961 } else {
962 self.restart(position, button);
963 }
964
965 return MouseEvent::Pressed {
966 position,
967 button,
968 click_count: self.click_count.get(),
969 is_touch,
970 };
971 }
972 MouseEvent::Released { position, button, is_touch, .. } => {
973 return MouseEvent::Released {
974 position,
975 button,
976 click_count: self.click_count.get(),
977 is_touch,
978 };
979 }
980 _ => {}
981 };
982
983 mouse_event
984 }
985}
986
987#[derive(Default)]
989pub struct MouseInputState {
990 item_stack: Vec<(ItemWeak, InputEventFilterResult)>,
993 pub(crate) offset: LogicalPoint,
995 grabbed: bool,
997 pub(crate) drag_data: Option<DropEvent>,
1000 delayed: Option<(crate::timers::Timer, MouseEvent)>,
1001 delayed_exit_items: Vec<ItemWeak>,
1002 pub(crate) cursor: MouseCursor,
1003}
1004
1005impl MouseInputState {
1006 fn top_item(&self) -> Option<ItemRc> {
1008 self.item_stack.last().and_then(|x| x.0.upgrade())
1009 }
1010
1011 pub fn top_item_including_delayed(&self) -> Option<ItemRc> {
1013 self.delayed_exit_items.last().and_then(|x| x.upgrade()).or_else(|| self.top_item())
1014 }
1015}
1016
1017pub(crate) fn handle_mouse_grab(
1020 mouse_event: &MouseEvent,
1021 window_adapter: &Rc<dyn WindowAdapter>,
1022 mouse_input_state: &mut MouseInputState,
1023) -> Option<MouseEvent> {
1024 if !mouse_input_state.grabbed || mouse_input_state.item_stack.is_empty() {
1025 return Some(mouse_event.clone());
1026 };
1027
1028 let mut event = mouse_event.clone();
1029 let mut intercept = false;
1030 let mut invalid = false;
1031
1032 event.translate(-mouse_input_state.offset.to_vector());
1033
1034 mouse_input_state.item_stack.retain(|it| {
1035 if invalid {
1036 return false;
1037 }
1038 let item = if let Some(item) = it.0.upgrade() {
1039 item
1040 } else {
1041 invalid = true;
1042 return false;
1043 };
1044 if intercept {
1045 item.borrow().as_ref().input_event(
1046 &MouseEvent::Exit,
1047 window_adapter,
1048 &item,
1049 &mut mouse_input_state.cursor,
1050 );
1051 return false;
1052 }
1053 let g = item.geometry();
1054 event.translate(-g.origin.to_vector());
1055 if window_adapter.renderer().supports_transformations()
1056 && let Some(inverse_transform) = item.inverse_children_transform()
1057 {
1058 event.transform(inverse_transform);
1059 }
1060
1061 let interested = matches!(
1062 it.1,
1063 InputEventFilterResult::ForwardAndInterceptGrab
1064 | InputEventFilterResult::DelayForwarding(_)
1065 );
1066
1067 if interested
1068 && item.borrow().as_ref().input_event_filter_before_children(
1069 &event,
1070 window_adapter,
1071 &item,
1072 &mut mouse_input_state.cursor,
1073 ) == InputEventFilterResult::Intercept
1074 {
1075 intercept = true;
1076 }
1077 true
1078 });
1079 if invalid {
1080 return Some(mouse_event.clone());
1081 }
1082
1083 let grabber = mouse_input_state.top_item().unwrap();
1084 let input_result = grabber.borrow().as_ref().input_event(
1085 &event,
1086 window_adapter,
1087 &grabber,
1088 &mut mouse_input_state.cursor,
1089 );
1090 match input_result {
1091 InputEventResult::GrabMouse => None,
1092 InputEventResult::StartDrag => {
1093 mouse_input_state.grabbed = false;
1094 let drag_area_item = grabber.downcast::<crate::items::DragArea>().unwrap();
1095 mouse_input_state.drag_data = Some(DropEvent {
1096 mime_type: drag_area_item.as_pin_ref().mime_type(),
1097 data: drag_area_item.as_pin_ref().data(),
1098 position: Default::default(),
1099 });
1100 None
1101 }
1102 _ => {
1103 mouse_input_state.grabbed = false;
1104 Some(mouse_event.position().map_or(MouseEvent::Exit, |position| MouseEvent::Moved {
1106 position,
1107 is_touch: mouse_event.is_touch().unwrap_or(false),
1108 }))
1109 }
1110 }
1111}
1112
1113pub(crate) fn send_exit_events(
1114 old_input_state: &MouseInputState,
1115 new_input_state: &mut MouseInputState,
1116 mut pos: Option<LogicalPoint>,
1117 window_adapter: &Rc<dyn WindowAdapter>,
1118) {
1119 let cursor = &mut MouseCursor::Default;
1121
1122 for it in core::mem::take(&mut new_input_state.delayed_exit_items) {
1123 let Some(item) = it.upgrade() else { continue };
1124 item.borrow().as_ref().input_event(&MouseEvent::Exit, window_adapter, &item, cursor);
1125 }
1126
1127 let mut clipped = false;
1128 for (idx, it) in old_input_state.item_stack.iter().enumerate() {
1129 let Some(item) = it.0.upgrade() else { break };
1130 let g = item.geometry();
1131 let contains = pos.is_some_and(|p| g.contains(p));
1132 if let Some(p) = pos.as_mut() {
1133 *p -= g.origin.to_vector();
1134 if window_adapter.renderer().supports_transformations()
1135 && let Some(inverse_transform) = item.inverse_children_transform()
1136 {
1137 *p = inverse_transform.transform_point(p.cast()).cast();
1138 }
1139 }
1140 if !contains || clipped {
1141 if item.borrow().as_ref().clips_children() {
1142 clipped = true;
1143 }
1144 item.borrow().as_ref().input_event(&MouseEvent::Exit, window_adapter, &item, cursor);
1145 } else if new_input_state.item_stack.get(idx).is_none_or(|(x, _)| *x != it.0) {
1146 if new_input_state.delayed.is_some() {
1148 new_input_state.delayed_exit_items.push(it.0.clone());
1149 } else {
1150 item.borrow().as_ref().input_event(
1151 &MouseEvent::Exit,
1152 window_adapter,
1153 &item,
1154 cursor,
1155 );
1156 }
1157 }
1158 }
1159}
1160
1161pub fn process_mouse_input(
1165 root: ItemRc,
1166 mouse_event: &MouseEvent,
1167 window_adapter: &Rc<dyn WindowAdapter>,
1168 mouse_input_state: MouseInputState,
1169) -> MouseInputState {
1170 let mut result = MouseInputState {
1171 drag_data: mouse_input_state.drag_data.clone(),
1172 cursor: mouse_input_state.cursor,
1173 ..Default::default()
1174 };
1175 let r = send_mouse_event_to_item(
1176 mouse_event,
1177 root.clone(),
1178 window_adapter,
1179 &mut result,
1180 mouse_input_state.top_item().as_ref(),
1181 false,
1182 );
1183 if mouse_input_state.delayed.is_some()
1184 && (!r.has_aborted()
1185 || Option::zip(result.item_stack.last(), mouse_input_state.item_stack.last())
1186 .is_none_or(|(a, b)| a.0 != b.0))
1187 {
1188 return mouse_input_state;
1190 }
1191 send_exit_events(&mouse_input_state, &mut result, mouse_event.position(), window_adapter);
1192
1193 if let MouseEvent::Wheel { position, .. } = mouse_event
1194 && r.has_aborted()
1195 {
1196 return process_mouse_input(
1198 root,
1199 &MouseEvent::Moved { position: *position, is_touch: false },
1200 window_adapter,
1201 result,
1202 );
1203 }
1204
1205 result
1206}
1207
1208pub(crate) fn process_delayed_event(
1209 window_adapter: &Rc<dyn WindowAdapter>,
1210 mut mouse_input_state: MouseInputState,
1211) -> MouseInputState {
1212 let event = match mouse_input_state.delayed.take() {
1214 Some(e) => e.1,
1215 None => return mouse_input_state,
1216 };
1217
1218 let top_item = match mouse_input_state.top_item() {
1219 Some(i) => i,
1220 None => return MouseInputState::default(),
1221 };
1222
1223 let mut actual_visitor =
1224 |component: &ItemTreeRc, index: u32, _: Pin<ItemRef>| -> VisitChildrenResult {
1225 send_mouse_event_to_item(
1226 &event,
1227 ItemRc::new(component.clone(), index),
1228 window_adapter,
1229 &mut mouse_input_state,
1230 Some(&top_item),
1231 true,
1232 )
1233 };
1234 vtable::new_vref!(let mut actual_visitor : VRefMut<crate::item_tree::ItemVisitorVTable> for crate::item_tree::ItemVisitor = &mut actual_visitor);
1235 vtable::VRc::borrow_pin(top_item.item_tree()).as_ref().visit_children_item(
1236 top_item.index() as isize,
1237 crate::item_tree::TraversalOrder::FrontToBack,
1238 actual_visitor,
1239 );
1240 mouse_input_state
1241}
1242
1243fn send_mouse_event_to_item(
1244 mouse_event: &MouseEvent,
1245 item_rc: ItemRc,
1246 window_adapter: &Rc<dyn WindowAdapter>,
1247 result: &mut MouseInputState,
1248 last_top_item: Option<&ItemRc>,
1249 ignore_delays: bool,
1250) -> VisitChildrenResult {
1251 let item = item_rc.borrow();
1252 let geom = item_rc.geometry();
1253 let mut event_for_children = mouse_event.clone();
1255 event_for_children.translate(-geom.origin.to_vector());
1257 if window_adapter.renderer().supports_transformations() {
1258 if let Some(inverse_transform) = item_rc.inverse_children_transform() {
1260 event_for_children.transform(inverse_transform);
1261 }
1262 }
1263
1264 let filter_result = if mouse_event.position().is_some_and(|p| geom.contains(p))
1265 || item.as_ref().clips_children()
1266 {
1267 item.as_ref().input_event_filter_before_children(
1268 &event_for_children,
1269 window_adapter,
1270 &item_rc,
1271 &mut result.cursor,
1272 )
1273 } else {
1274 InputEventFilterResult::ForwardAndIgnore
1275 };
1276
1277 let (forward_to_children, ignore) = match filter_result {
1278 InputEventFilterResult::ForwardEvent => (true, false),
1279 InputEventFilterResult::ForwardAndIgnore => (true, true),
1280 InputEventFilterResult::ForwardAndInterceptGrab => (true, false),
1281 InputEventFilterResult::Intercept => (false, false),
1282 InputEventFilterResult::DelayForwarding(_) if ignore_delays => (true, false),
1283 InputEventFilterResult::DelayForwarding(duration) => {
1284 let timer = Timer::default();
1285 let w = Rc::downgrade(window_adapter);
1286 timer.start(
1287 crate::timers::TimerMode::SingleShot,
1288 Duration::from_millis(duration),
1289 move || {
1290 if let Some(w) = w.upgrade() {
1291 WindowInner::from_pub(w.window()).process_delayed_event();
1292 }
1293 },
1294 );
1295 result.delayed = Some((timer, event_for_children));
1296 result
1297 .item_stack
1298 .push((item_rc.downgrade(), InputEventFilterResult::DelayForwarding(duration)));
1299 return VisitChildrenResult::abort(item_rc.index(), 0);
1300 }
1301 };
1302
1303 result.item_stack.push((item_rc.downgrade(), filter_result));
1304 if forward_to_children {
1305 let mut actual_visitor =
1306 |component: &ItemTreeRc, index: u32, _: Pin<ItemRef>| -> VisitChildrenResult {
1307 send_mouse_event_to_item(
1308 &event_for_children,
1309 ItemRc::new(component.clone(), index),
1310 window_adapter,
1311 result,
1312 last_top_item,
1313 ignore_delays,
1314 )
1315 };
1316 vtable::new_vref!(let mut actual_visitor : VRefMut<crate::item_tree::ItemVisitorVTable> for crate::item_tree::ItemVisitor = &mut actual_visitor);
1317 let r = vtable::VRc::borrow_pin(item_rc.item_tree()).as_ref().visit_children_item(
1318 item_rc.index() as isize,
1319 crate::item_tree::TraversalOrder::FrontToBack,
1320 actual_visitor,
1321 );
1322 if r.has_aborted() {
1323 return r;
1324 }
1325 };
1326
1327 let r = if ignore {
1328 InputEventResult::EventIgnored
1329 } else {
1330 let mut event = mouse_event.clone();
1331 event.translate(-geom.origin.to_vector());
1332 if last_top_item.is_none_or(|x| *x != item_rc) {
1333 event.set_click_count(0);
1334 }
1335 item.as_ref().input_event(&event, window_adapter, &item_rc, &mut result.cursor)
1336 };
1337 match r {
1338 InputEventResult::EventAccepted => VisitChildrenResult::abort(item_rc.index(), 0),
1339 InputEventResult::EventIgnored => {
1340 let _pop = result.item_stack.pop();
1341 debug_assert_eq!(
1342 _pop.map(|x| (x.0.upgrade().unwrap().index(), x.1)).unwrap(),
1343 (item_rc.index(), filter_result)
1344 );
1345 VisitChildrenResult::CONTINUE
1346 }
1347 InputEventResult::GrabMouse => {
1348 result.item_stack.last_mut().unwrap().1 =
1349 InputEventFilterResult::ForwardAndInterceptGrab;
1350 result.grabbed = true;
1351 VisitChildrenResult::abort(item_rc.index(), 0)
1352 }
1353 InputEventResult::StartDrag => {
1354 result.item_stack.last_mut().unwrap().1 =
1355 InputEventFilterResult::ForwardAndInterceptGrab;
1356 result.grabbed = false;
1357 let drag_area_item = item_rc.downcast::<crate::items::DragArea>().unwrap();
1358 result.drag_data = Some(DropEvent {
1359 mime_type: drag_area_item.as_pin_ref().mime_type(),
1360 data: drag_area_item.as_pin_ref().data(),
1361 position: Default::default(),
1362 });
1363 VisitChildrenResult::abort(item_rc.index(), 0)
1364 }
1365 }
1366}
1367
1368#[derive(FieldOffsets)]
1375#[repr(C)]
1376#[pin]
1377pub(crate) struct TextCursorBlinker {
1378 cursor_visible: Property<bool>,
1379 cursor_blink_timer: crate::timers::Timer,
1380}
1381
1382impl TextCursorBlinker {
1383 pub fn new() -> Pin<Rc<Self>> {
1386 Rc::pin(Self {
1387 cursor_visible: Property::new(true),
1388 cursor_blink_timer: Default::default(),
1389 })
1390 }
1391
1392 pub fn set_binding(
1395 instance: Pin<Rc<TextCursorBlinker>>,
1396 prop: &Property<bool>,
1397 cycle_duration: Duration,
1398 ) {
1399 instance.as_ref().cursor_visible.set(true);
1400 Self::start(&instance, cycle_duration);
1402 prop.set_binding(move || {
1403 TextCursorBlinker::FIELD_OFFSETS.cursor_visible().apply_pin(instance.as_ref()).get()
1404 });
1405 }
1406
1407 pub fn start(self: &Pin<Rc<Self>>, cycle_duration: Duration) {
1410 if self.cursor_blink_timer.running() {
1411 self.cursor_blink_timer.restart();
1412 } else {
1413 let toggle_cursor = {
1414 let weak_blinker = pin_weak::rc::PinWeak::downgrade(self.clone());
1415 move || {
1416 if let Some(blinker) = weak_blinker.upgrade() {
1417 let visible = TextCursorBlinker::FIELD_OFFSETS
1418 .cursor_visible()
1419 .apply_pin(blinker.as_ref())
1420 .get();
1421 blinker.cursor_visible.set(!visible);
1422 }
1423 }
1424 };
1425 if !cycle_duration.is_zero() {
1426 self.cursor_blink_timer.start(
1427 crate::timers::TimerMode::Repeated,
1428 cycle_duration / 2,
1429 toggle_cursor,
1430 );
1431 }
1432 }
1433 }
1434
1435 pub fn stop(&self) {
1438 self.cursor_blink_timer.stop()
1439 }
1440}
1441
1442#[derive(Clone, Copy, Default)]
1444struct TouchPoint {
1445 id: u64,
1446 position: LogicalPoint,
1447}
1448
1449const MAX_TRACKED_TOUCHES: usize = 5;
1455
1456#[derive(Clone)]
1457struct TouchMap {
1458 entries: [TouchPoint; MAX_TRACKED_TOUCHES],
1459 len: usize,
1460}
1461
1462impl Default for TouchMap {
1463 fn default() -> Self {
1464 Self { entries: [TouchPoint::default(); MAX_TRACKED_TOUCHES], len: 0 }
1465 }
1466}
1467
1468impl TouchMap {
1469 fn get(&self, id: u64) -> Option<&TouchPoint> {
1470 self.entries[..self.len].iter().find(|tp| tp.id == id)
1471 }
1472
1473 fn get_mut(&mut self, id: u64) -> Option<&mut TouchPoint> {
1474 self.entries[..self.len].iter_mut().find(|tp| tp.id == id)
1475 }
1476
1477 fn insert(&mut self, point: TouchPoint) {
1478 if let Some(existing) = self.entries[..self.len].iter_mut().find(|tp| tp.id == point.id) {
1479 *existing = point;
1480 } else if self.len < MAX_TRACKED_TOUCHES {
1481 self.entries[self.len] = point;
1482 self.len += 1;
1483 }
1484 }
1485
1486 fn remove(&mut self, id: u64) {
1487 if let Some(idx) = self.entries[..self.len].iter().position(|tp| tp.id == id) {
1488 self.len -= 1;
1489 self.entries[idx] = self.entries[self.len];
1490 }
1491 }
1492
1493 fn len(&self) -> usize {
1494 self.len
1495 }
1496
1497 fn first_two_ids(&self) -> Option<(u64, u64)> {
1499 if self.len >= 2 { Some((self.entries[0].id, self.entries[1].id)) } else { None }
1500 }
1501
1502 fn first(&self) -> Option<&TouchPoint> {
1504 if self.len > 0 { Some(&self.entries[0]) } else { None }
1505 }
1506}
1507
1508const MAX_TOUCH_EVENTS: usize = 4;
1514
1515#[derive(Clone)]
1516pub(crate) struct TouchEventBuffer {
1517 events: [Option<MouseEvent>; MAX_TOUCH_EVENTS],
1518 len: usize,
1519}
1520
1521impl TouchEventBuffer {
1522 fn new() -> Self {
1523 Self { events: [None, None, None, None], len: 0 }
1524 }
1525
1526 fn push(&mut self, event: MouseEvent) {
1527 debug_assert!(self.len < MAX_TOUCH_EVENTS, "TouchEventBuffer overflow");
1528 if self.len < MAX_TOUCH_EVENTS {
1529 self.events[self.len] = Some(event);
1530 self.len += 1;
1531 }
1532 }
1533
1534 pub(crate) fn into_iter(self) -> impl Iterator<Item = MouseEvent> {
1536 let len = self.len;
1537 self.events.into_iter().take(len).flatten()
1538 }
1539}
1540
1541#[derive(Default, Debug, Clone, Copy)]
1543enum GestureRecognitionState {
1544 #[default]
1546 Idle,
1547 TwoFingersDown { finger_ids: (u64, u64), initial_distance: f32, last_angle: euclid::Angle<f32> },
1549 Pinching {
1551 finger_ids: (u64, u64),
1552 initial_distance: f32,
1553 last_scale: f32,
1554 last_angle: euclid::Angle<f32>,
1555 },
1556}
1557
1558pub(crate) struct TouchState {
1565 active_touches: TouchMap,
1566 primary_touch_id: Option<u64>,
1568 gesture_state: GestureRecognitionState,
1569}
1570
1571impl Default for TouchState {
1572 fn default() -> Self {
1573 Self {
1574 active_touches: TouchMap::default(),
1575 primary_touch_id: None,
1576 gesture_state: GestureRecognitionState::Idle,
1577 }
1578 }
1579}
1580
1581impl TouchState {
1582 const PINCH_THRESHOLD: f32 = 8.0;
1584
1585 const ROTATION_THRESHOLD: f32 = 5.0;
1587
1588 fn gesture_finger_ids(&self) -> Option<(u64, u64)> {
1590 match self.gesture_state {
1591 GestureRecognitionState::TwoFingersDown { finger_ids, .. }
1592 | GestureRecognitionState::Pinching { finger_ids, .. } => Some(finger_ids),
1593 GestureRecognitionState::Idle => None,
1594 }
1595 }
1596
1597 fn geometry_for(&self, (id_a, id_b): (u64, u64)) -> Option<(f32, euclid::Angle<f32>)> {
1599 let a = self.active_touches.get(id_a)?;
1600 let b = self.active_touches.get(id_b)?;
1601 let delta = (b.position - a.position).cast::<f32>();
1602 Some((delta.length(), delta.angle_from_x_axis()))
1603 }
1604
1605 fn gesture_finger_positions(&self) -> Option<(&TouchPoint, &TouchPoint)> {
1607 let (id_a, id_b) = self.gesture_finger_ids()?;
1608 let a = self.active_touches.get(id_a)?;
1609 let b = self.active_touches.get(id_b)?;
1610 Some((a, b))
1611 }
1612
1613 fn gesture_midpoint(&self) -> Option<LogicalPoint> {
1615 let (a, b) = self.gesture_finger_positions()?;
1616 let mid = a.position.cast::<f32>().lerp(b.position.cast::<f32>(), 0.5);
1617 Some(mid.cast())
1618 }
1619
1620 fn gesture_geometry(&self) -> Option<(f32, euclid::Angle<f32>)> {
1622 let (a, b) = self.gesture_finger_positions()?;
1623 let delta = (b.position - a.position).cast::<f32>();
1624 Some((delta.length(), delta.angle_from_x_axis()))
1625 }
1626
1627 fn is_gesture_finger(&self, id: u64) -> bool {
1629 self.gesture_finger_ids().is_some_and(|(a, b)| id == a || id == b)
1630 }
1631
1632 pub(crate) fn process(
1639 &mut self,
1640 id: u64,
1641 position: LogicalPoint,
1642 phase: TouchPhase,
1643 ) -> TouchEventBuffer {
1644 let mut events = TouchEventBuffer::new();
1645 match phase {
1646 TouchPhase::Started => self.process_started(id, position, &mut events),
1647 TouchPhase::Moved => self.process_moved(id, position, &mut events),
1648 TouchPhase::Ended => self.process_ended(id, position, false, &mut events),
1649 TouchPhase::Cancelled => self.process_ended(id, position, true, &mut events),
1650 }
1651 events
1652 }
1653
1654 fn process_started(&mut self, id: u64, position: LogicalPoint, events: &mut TouchEventBuffer) {
1655 self.active_touches.insert(TouchPoint { id, position });
1656
1657 let total = self.active_touches.len();
1658 if total == 1 {
1659 self.primary_touch_id = Some(id);
1661 self.gesture_state = GestureRecognitionState::Idle;
1662 events.push(MouseEvent::Pressed {
1663 position,
1664 button: PointerEventButton::Left,
1665 click_count: 0,
1666 is_touch: true,
1667 });
1668 } else if total == 2 {
1669 let finger_ids = self.active_touches.first_two_ids().unwrap_or((0, 0));
1671
1672 let primary_pos = self
1675 .primary_touch_id
1676 .and_then(|pid| self.active_touches.get(pid))
1677 .map(|tp| tp.position)
1678 .unwrap_or(position);
1679
1680 let (initial_distance, last_angle) =
1682 self.geometry_for(finger_ids).unwrap_or((0.0, euclid::Angle::zero()));
1683 self.gesture_state = GestureRecognitionState::TwoFingersDown {
1684 finger_ids,
1685 initial_distance,
1686 last_angle,
1687 };
1688
1689 events.push(MouseEvent::Released {
1690 position: primary_pos,
1691 button: PointerEventButton::Left,
1692 click_count: 0,
1693 is_touch: true,
1694 });
1695 }
1696 }
1698
1699 fn process_moved(&mut self, id: u64, position: LogicalPoint, events: &mut TouchEventBuffer) {
1700 if let Some(tp) = self.active_touches.get_mut(id) {
1701 tp.position = position;
1702 }
1703
1704 let is_gesture_finger = self.is_gesture_finger(id);
1705
1706 match self.gesture_state {
1707 GestureRecognitionState::Idle => {
1708 if self.primary_touch_id == Some(id) {
1709 events.push(MouseEvent::Moved { position, is_touch: true });
1710 }
1711 }
1712 GestureRecognitionState::TwoFingersDown {
1713 finger_ids,
1714 initial_distance,
1715 last_angle,
1716 } if is_gesture_finger => {
1717 if let Some((dist, angle)) = self.gesture_geometry() {
1718 let delta_dist = (dist - initial_distance).abs();
1719 let delta_angle = (angle - last_angle).signed().to_degrees().abs();
1720 if delta_dist > Self::PINCH_THRESHOLD || delta_angle > Self::ROTATION_THRESHOLD
1721 {
1722 self.gesture_state = GestureRecognitionState::Pinching {
1726 finger_ids,
1727 initial_distance: dist,
1728 last_scale: 1.0,
1729 last_angle: angle,
1730 };
1731
1732 let midpoint = self.gesture_midpoint().unwrap_or(position);
1733
1734 events.push(MouseEvent::PinchGesture {
1735 position: midpoint,
1736 delta: 0.0,
1737 phase: TouchPhase::Started,
1738 });
1739 events.push(MouseEvent::RotationGesture {
1740 position: midpoint,
1741 delta: 0.0,
1742 phase: TouchPhase::Started,
1743 });
1744 }
1745 }
1746 }
1747 GestureRecognitionState::Pinching {
1748 initial_distance, last_scale, last_angle, ..
1749 } if is_gesture_finger => {
1750 if let Some((dist, angle)) = self.gesture_geometry() {
1751 let midpoint = self.gesture_midpoint().unwrap_or(position);
1752
1753 let current_scale =
1754 if initial_distance > 0.0 { dist / initial_distance } else { 1.0 };
1755 let scale_delta = current_scale - last_scale;
1756
1757 let rotation_delta = (angle - last_angle).signed().to_degrees();
1760
1761 if let GestureRecognitionState::Pinching {
1763 last_scale: ref mut ls,
1764 last_angle: ref mut la,
1765 ..
1766 } = self.gesture_state
1767 {
1768 *ls = current_scale;
1769 *la = angle;
1770 }
1771
1772 events.push(MouseEvent::PinchGesture {
1773 position: midpoint,
1774 delta: scale_delta,
1775 phase: TouchPhase::Moved,
1776 });
1777 events.push(MouseEvent::RotationGesture {
1778 position: midpoint,
1779 delta: rotation_delta,
1780 phase: TouchPhase::Moved,
1781 });
1782 }
1783 }
1784 _ => {}
1785 }
1786 }
1787
1788 fn process_ended(
1789 &mut self,
1790 id: u64,
1791 position: LogicalPoint,
1792 is_cancelled: bool,
1793 events: &mut TouchEventBuffer,
1794 ) {
1795 let is_gesture_finger = self.is_gesture_finger(id);
1797 let midpoint = self.gesture_midpoint().unwrap_or(position);
1798 self.active_touches.remove(id);
1799
1800 match self.gesture_state {
1801 GestureRecognitionState::Idle => {
1802 if self.primary_touch_id == Some(id) {
1803 self.primary_touch_id = None;
1804 events.push(MouseEvent::Released {
1805 position,
1806 button: PointerEventButton::Left,
1807 click_count: 0,
1808 is_touch: true,
1809 });
1810 events.push(MouseEvent::Exit);
1811 }
1812 }
1813 GestureRecognitionState::TwoFingersDown { .. } if is_gesture_finger => {
1814 self.gesture_state = GestureRecognitionState::Idle;
1815 if !is_cancelled {
1816 if let Some(remaining) = self.active_touches.first() {
1817 let remaining_pos = remaining.position;
1818 self.primary_touch_id = Some(remaining.id);
1819 events.push(MouseEvent::Pressed {
1820 position: remaining_pos,
1821 button: PointerEventButton::Left,
1822 click_count: 0,
1823 is_touch: true,
1824 });
1825 } else {
1826 self.primary_touch_id = None;
1827 events.push(MouseEvent::Exit);
1828 }
1829 } else {
1830 self.primary_touch_id = None;
1831 events.push(MouseEvent::Exit);
1832 }
1833 }
1834 GestureRecognitionState::Pinching { .. } if is_gesture_finger => {
1835 self.gesture_state = GestureRecognitionState::Idle;
1836
1837 let gesture_phase =
1838 if is_cancelled { TouchPhase::Cancelled } else { TouchPhase::Ended };
1839
1840 let remaining = if !is_cancelled {
1841 self.active_touches.first().map(|tp| (tp.id, tp.position))
1842 } else {
1843 None
1844 };
1845 if let Some((rid, _)) = remaining {
1846 self.primary_touch_id = Some(rid);
1847 } else {
1848 self.primary_touch_id = None;
1849 }
1850
1851 events.push(MouseEvent::PinchGesture {
1852 position: midpoint,
1853 delta: 0.0,
1854 phase: gesture_phase,
1855 });
1856 events.push(MouseEvent::RotationGesture {
1857 position: midpoint,
1858 delta: 0.0,
1859 phase: gesture_phase,
1860 });
1861
1862 if let Some((_, rpos)) = remaining {
1863 events.push(MouseEvent::Pressed {
1864 position: rpos,
1865 button: PointerEventButton::Left,
1866 click_count: 0,
1867 is_touch: true,
1868 });
1869 } else {
1870 events.push(MouseEvent::Exit);
1871 }
1872 }
1873 _ => {}
1874 }
1875 }
1876}
1877
1878#[cfg(test)]
1879mod touch_tests {
1880 extern crate alloc;
1881 use alloc::vec;
1882 use alloc::vec::Vec;
1883
1884 use super::*;
1885 use crate::lengths::LogicalPoint;
1886
1887 fn pt(x: f32, y: f32) -> LogicalPoint {
1888 euclid::point2(x, y)
1889 }
1890
1891 #[test]
1896 fn touch_map_insert_and_get() {
1897 let mut map = TouchMap::default();
1898 assert_eq!(map.len(), 0);
1899 map.insert(TouchPoint { id: 1, position: pt(10.0, 20.0) });
1900 assert_eq!(map.len(), 1);
1901 assert!(map.get(1).is_some());
1902 assert!((map.get(1).unwrap().position.x - 10.0).abs() < f32::EPSILON);
1903 assert!(map.get(2).is_none());
1904 }
1905
1906 #[test]
1907 fn touch_map_update_existing() {
1908 let mut map = TouchMap::default();
1909 map.insert(TouchPoint { id: 1, position: pt(10.0, 20.0) });
1910 map.insert(TouchPoint { id: 1, position: pt(30.0, 40.0) });
1911 assert_eq!(map.len(), 1);
1912 assert!((map.get(1).unwrap().position.x - 30.0).abs() < f32::EPSILON);
1913 }
1914
1915 #[test]
1916 fn touch_map_remove() {
1917 let mut map = TouchMap::default();
1918 map.insert(TouchPoint { id: 1, position: pt(10.0, 20.0) });
1919 map.insert(TouchPoint { id: 2, position: pt(30.0, 40.0) });
1920 assert_eq!(map.len(), 2);
1921 map.remove(1);
1922 assert_eq!(map.len(), 1);
1923 assert!(map.get(1).is_none());
1924 assert!(map.get(2).is_some());
1925 }
1926
1927 #[test]
1928 fn touch_map_remove_nonexistent() {
1929 let mut map = TouchMap::default();
1930 map.insert(TouchPoint { id: 1, position: pt(10.0, 20.0) });
1931 map.remove(99);
1932 assert_eq!(map.len(), 1);
1933 }
1934
1935 #[test]
1936 fn touch_map_capacity() {
1937 let mut map = TouchMap::default();
1938 for i in 0..MAX_TRACKED_TOUCHES {
1939 map.insert(TouchPoint { id: i as u64, position: pt(i as f32, 0.0) });
1940 }
1941 assert_eq!(map.len(), MAX_TRACKED_TOUCHES);
1942 map.insert(TouchPoint { id: 99, position: pt(99.0, 0.0) });
1944 assert_eq!(map.len(), MAX_TRACKED_TOUCHES);
1945 assert!(map.get(99).is_none());
1946 }
1947
1948 #[test]
1949 fn touch_map_first_two_ids() {
1950 let mut map = TouchMap::default();
1951 assert!(map.first_two_ids().is_none());
1952 map.insert(TouchPoint { id: 5, position: pt(0.0, 0.0) });
1953 assert!(map.first_two_ids().is_none());
1954 map.insert(TouchPoint { id: 10, position: pt(0.0, 0.0) });
1955 assert_eq!(map.first_two_ids(), Some((5, 10)));
1956 }
1957
1958 #[test]
1959 fn touch_map_first() {
1960 let mut map = TouchMap::default();
1961 assert!(map.first().is_none());
1962 map.insert(TouchPoint { id: 7, position: pt(1.0, 2.0) });
1963 let tp = map.first().unwrap();
1964 assert_eq!(tp.id, 7);
1965 assert!((tp.position.x - 1.0).abs() < f32::EPSILON);
1966 }
1967
1968 #[test]
1969 fn touch_map_get_mut() {
1970 let mut map = TouchMap::default();
1971 map.insert(TouchPoint { id: 1, position: pt(0.0, 0.0) });
1972 map.get_mut(1).unwrap().position = pt(5.0, 6.0);
1973 assert!((map.get(1).unwrap().position.x - 5.0).abs() < f32::EPSILON);
1974 }
1975
1976 #[derive(Debug, PartialEq)]
1981 enum Ev {
1982 Pressed(f32, f32),
1983 Released(f32, f32),
1984 Moved(f32, f32),
1985 Exit,
1986 PinchStarted,
1987 PinchMoved(f32),
1988 PinchEnded,
1989 PinchCancelled,
1990 RotationStarted,
1991 RotationMoved(f32),
1992 RotationEnded,
1993 RotationCancelled,
1994 }
1995
1996 fn classify(events: &TouchEventBuffer) -> Vec<Ev> {
1997 events
1998 .clone()
1999 .into_iter()
2000 .map(|e| match e {
2001 MouseEvent::Pressed { position, .. } => Ev::Pressed(position.x, position.y),
2002 MouseEvent::Released { position, .. } => Ev::Released(position.x, position.y),
2003 MouseEvent::Moved { position, .. } => Ev::Moved(position.x, position.y),
2004 MouseEvent::Exit => Ev::Exit,
2005 MouseEvent::PinchGesture { delta, phase, .. } => match phase {
2006 TouchPhase::Started => Ev::PinchStarted,
2007 TouchPhase::Moved => Ev::PinchMoved(delta),
2008 TouchPhase::Ended => Ev::PinchEnded,
2009 TouchPhase::Cancelled => Ev::PinchCancelled,
2010 },
2011 MouseEvent::RotationGesture { delta, phase, .. } => match phase {
2012 TouchPhase::Started => Ev::RotationStarted,
2013 TouchPhase::Moved => Ev::RotationMoved(delta),
2014 TouchPhase::Ended => Ev::RotationEnded,
2015 TouchPhase::Cancelled => Ev::RotationCancelled,
2016 },
2017 _ => panic!("unexpected event: {:?}", e),
2018 })
2019 .collect()
2020 }
2021
2022 #[test]
2027 fn single_finger_press_move_release() {
2028 let mut state = TouchState::default();
2029
2030 let evs = state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2031 assert_eq!(classify(&evs), vec![Ev::Pressed(100.0, 200.0)]);
2032
2033 let evs = state.process(1, pt(110.0, 200.0), TouchPhase::Moved);
2034 assert_eq!(classify(&evs), vec![Ev::Moved(110.0, 200.0)]);
2035
2036 let evs = state.process(1, pt(110.0, 200.0), TouchPhase::Ended);
2037 assert_eq!(classify(&evs), vec![Ev::Released(110.0, 200.0), Ev::Exit]);
2038 }
2039
2040 #[test]
2041 fn single_finger_cancel() {
2042 let mut state = TouchState::default();
2043
2044 state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2045
2046 let evs = state.process(1, pt(100.0, 200.0), TouchPhase::Cancelled);
2047 assert_eq!(classify(&evs), vec![Ev::Released(100.0, 200.0), Ev::Exit]);
2048 }
2049
2050 #[test]
2051 fn non_primary_move_ignored() {
2052 let mut state = TouchState::default();
2053 state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2055
2056 let evs = state.process(99, pt(50.0, 50.0), TouchPhase::Moved);
2058 assert!(classify(&evs).is_empty());
2059 }
2060
2061 #[test]
2066 fn two_fingers_synthesize_release_then_gesture() {
2067 let mut state = TouchState::default();
2068
2069 let evs = state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2071 assert_eq!(classify(&evs), vec![Ev::Pressed(100.0, 200.0)]);
2072
2073 let evs = state.process(2, pt(200.0, 200.0), TouchPhase::Started);
2075 assert_eq!(classify(&evs), vec![Ev::Released(100.0, 200.0)]);
2076 assert!(matches!(state.gesture_state, GestureRecognitionState::TwoFingersDown { .. }));
2077
2078 let evs = state.process(2, pt(220.0, 200.0), TouchPhase::Moved);
2080 assert_eq!(classify(&evs), vec![Ev::PinchStarted, Ev::RotationStarted]);
2081 assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2082 }
2083
2084 #[test]
2085 fn two_fingers_below_threshold_no_gesture() {
2086 let mut state = TouchState::default();
2087
2088 state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2089 state.process(2, pt(200.0, 200.0), TouchPhase::Started);
2090
2091 let evs = state.process(2, pt(202.0, 200.0), TouchPhase::Moved);
2093 assert!(classify(&evs).is_empty());
2094 assert!(matches!(state.gesture_state, GestureRecognitionState::TwoFingersDown { .. }));
2095 }
2096
2097 #[test]
2098 fn pinch_produces_scale_deltas() {
2099 let mut state = TouchState::default();
2100
2101 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2103 state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2104
2105 state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2107 assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2108
2109 let evs = state.process(2, pt(180.0, 0.0), TouchPhase::Moved);
2113 let classified = classify(&evs);
2114 assert_eq!(classified.len(), 2);
2115 if let Ev::PinchMoved(delta) = classified[0] {
2116 assert!((delta - 0.5).abs() < 0.01, "expected ~0.5, got {}", delta);
2117 } else {
2118 panic!("expected PinchMoved, got {:?}", classified[0]);
2119 }
2120 }
2121
2122 #[test]
2123 fn rotation_produces_correct_deltas() {
2124 let mut state = TouchState::default();
2125
2126 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2129 state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2130
2131 state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2133 assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2134
2135 let evs = state.process(2, pt(70.7, 70.7), TouchPhase::Moved);
2140 let classified = classify(&evs);
2141 assert_eq!(classified.len(), 2);
2142 if let Ev::RotationMoved(delta) = classified[1] {
2143 assert!((delta - 45.0).abs() < 1.0, "expected ~45.0 (clockwise), got {}", delta);
2144 } else {
2145 panic!("expected RotationMoved, got {:?}", classified[1]);
2146 }
2147 }
2148
2149 #[test]
2150 fn rotation_across_180_degree_boundary() {
2151 let mut state = TouchState::default();
2152
2153 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2156 state.process(2, pt(-100.0, -10.0), TouchPhase::Started);
2157
2158 state.process(2, pt(-120.0, -10.0), TouchPhase::Moved);
2160 assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2161
2162 let evs = state.process(2, pt(-100.0, 10.0), TouchPhase::Moved);
2167 let classified = classify(&evs);
2168 if let Ev::RotationMoved(delta) = classified[1] {
2169 assert!(
2170 delta.abs() < 20.0,
2171 "rotation should be a small delta (~11°), got {} (discontinuity!)",
2172 delta
2173 );
2174 } else {
2175 panic!("expected RotationMoved, got {:?}", classified[1]);
2176 }
2177 }
2178
2179 #[test]
2184 fn pinch_end_with_remaining_finger() {
2185 let mut state = TouchState::default();
2186
2187 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2188 state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2189 state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2191
2192 let evs = state.process(2, pt(120.0, 0.0), TouchPhase::Ended);
2194 let classified = classify(&evs);
2195 assert_eq!(classified, vec![Ev::PinchEnded, Ev::RotationEnded, Ev::Pressed(0.0, 0.0)]);
2196 assert!(matches!(state.gesture_state, GestureRecognitionState::Idle));
2197 assert_eq!(state.primary_touch_id, Some(1));
2198 }
2199
2200 #[test]
2201 fn pinch_cancel_emits_cancelled_and_exit() {
2202 let mut state = TouchState::default();
2203
2204 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2205 state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2206 state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2207
2208 let evs = state.process(2, pt(120.0, 0.0), TouchPhase::Cancelled);
2210 let classified = classify(&evs);
2211 assert_eq!(classified, vec![Ev::PinchCancelled, Ev::RotationCancelled, Ev::Exit]);
2212 assert!(state.primary_touch_id.is_none());
2213 }
2214
2215 #[test]
2216 fn two_fingers_down_lift_before_threshold_returns_to_idle() {
2217 let mut state = TouchState::default();
2218
2219 state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2220 state.process(2, pt(200.0, 200.0), TouchPhase::Started);
2221 assert!(matches!(state.gesture_state, GestureRecognitionState::TwoFingersDown { .. }));
2222
2223 let evs = state.process(2, pt(200.0, 200.0), TouchPhase::Ended);
2225 let classified = classify(&evs);
2226 assert_eq!(classified, vec![Ev::Pressed(100.0, 200.0)]);
2228 assert!(matches!(state.gesture_state, GestureRecognitionState::Idle));
2229 assert_eq!(state.primary_touch_id, Some(1));
2230 }
2231
2232 #[test]
2233 fn two_fingers_down_cancel_both_emits_exit() {
2234 let mut state = TouchState::default();
2235
2236 state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2237 state.process(2, pt(200.0, 200.0), TouchPhase::Started);
2238
2239 let evs = state.process(2, pt(200.0, 200.0), TouchPhase::Cancelled);
2241 assert_eq!(classify(&evs), vec![Ev::Exit]);
2242
2243 let evs = state.process(1, pt(100.0, 200.0), TouchPhase::Cancelled);
2245 assert!(classify(&evs).is_empty());
2246 }
2247
2248 #[test]
2253 fn third_finger_ignored_for_gesture() {
2254 let mut state = TouchState::default();
2255
2256 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2257 state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2258
2259 let evs = state.process(3, pt(50.0, 50.0), TouchPhase::Started);
2261 assert!(classify(&evs).is_empty());
2262 assert_eq!(state.active_touches.len(), 3);
2263 }
2264
2265 #[test]
2270 fn euclid_angle_signed_wrapping() {
2271 use euclid::Angle;
2272 let wrap = |deg: f32| Angle::degrees(deg).signed().to_degrees();
2273 assert!(wrap(0.0).abs() < f32::EPSILON);
2274 assert!((wrap(180.0) - 180.0).abs() < 0.01);
2275 assert!((wrap(181.0) - (-179.0)).abs() < 0.01);
2276 assert!((wrap(-181.0) - 179.0).abs() < 0.01);
2277 assert!(wrap(360.0).abs() < 0.01);
2278 }
2279
2280 #[test]
2281 fn zero_distance_fingers_no_division_by_zero() {
2282 let mut state = TouchState::default();
2283
2284 state.process(1, pt(100.0, 100.0), TouchPhase::Started);
2286 state.process(2, pt(100.0, 100.0), TouchPhase::Started);
2287 assert!(matches!(state.gesture_state, GestureRecognitionState::TwoFingersDown { .. }));
2288
2289 let evs = state.process(2, pt(120.0, 100.0), TouchPhase::Moved);
2291 assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2292 let classified = classify(&evs);
2293 assert_eq!(classified.len(), 2);
2294 assert_eq!(classified[0], Ev::PinchStarted);
2295
2296 let evs = state.process(2, pt(140.0, 100.0), TouchPhase::Moved);
2299 let classified = classify(&evs);
2300 if let Ev::PinchMoved(delta) = classified[0] {
2301 assert!(delta.is_finite(), "scale delta should be finite, got {}", delta);
2302 } else {
2303 panic!("expected PinchMoved, got {:?}", classified[0]);
2304 }
2305 }
2306}
2307
2308#[cfg(test)]
2309mod tests {
2310 use super::*;
2311 extern crate alloc;
2312
2313 #[test]
2314 fn test_to_string() {
2315 let test_cases = [
2316 (
2317 "a",
2318 KeyboardModifiers { alt: false, control: true, shift: false, meta: false },
2319 false,
2320 false,
2321 "⌘A",
2322 "Ctrl+A",
2323 "Ctrl+A",
2324 ),
2325 (
2326 "a",
2327 KeyboardModifiers { alt: true, control: true, shift: true, meta: true },
2328 false,
2329 false,
2330 "⌃⌥⇧⌘A",
2331 "Win+Ctrl+Alt+Shift+A",
2332 "Super+Ctrl+Alt+Shift+A",
2333 ),
2334 (
2335 "\u{001b}",
2336 KeyboardModifiers { alt: false, control: true, shift: true, meta: false },
2337 false,
2338 false,
2339 "⇧⌘Escape",
2340 "Ctrl+Shift+Escape",
2341 "Ctrl+Shift+Escape",
2342 ),
2343 (
2344 "+",
2345 KeyboardModifiers { alt: false, control: true, shift: false, meta: false },
2346 true,
2347 false,
2348 "⌘+",
2349 "Ctrl++",
2350 "Ctrl++",
2351 ),
2352 (
2353 "a",
2354 KeyboardModifiers { alt: true, control: true, shift: false, meta: false },
2355 false,
2356 true,
2357 "⌘A",
2358 "Ctrl+A",
2359 "Ctrl+A",
2360 ),
2361 (
2362 "",
2363 KeyboardModifiers { alt: false, control: true, shift: false, meta: false },
2364 false,
2365 false,
2366 "",
2367 "",
2368 "",
2369 ),
2370 (
2371 "\u{000a}",
2372 KeyboardModifiers { alt: false, control: false, shift: false, meta: false },
2373 false,
2374 false,
2375 "Return",
2376 "Return",
2377 "Return",
2378 ),
2379 (
2380 "\u{0009}",
2381 KeyboardModifiers { alt: false, control: false, shift: false, meta: false },
2382 false,
2383 false,
2384 "Tab",
2385 "Tab",
2386 "Tab",
2387 ),
2388 (
2389 "\u{0020}",
2390 KeyboardModifiers { alt: false, control: false, shift: false, meta: false },
2391 false,
2392 false,
2393 "Space",
2394 "Space",
2395 "Space",
2396 ),
2397 (
2398 "\u{0008}",
2399 KeyboardModifiers { alt: false, control: false, shift: false, meta: false },
2400 false,
2401 false,
2402 "Backspace",
2403 "Backspace",
2404 "Backspace",
2405 ),
2406 ];
2407
2408 for (
2409 key,
2410 modifiers,
2411 ignore_shift,
2412 ignore_alt,
2413 _expected_macos,
2414 _expected_windows,
2415 _expected_linux,
2416 ) in test_cases
2417 {
2418 let shortcut = make_keys(key.into(), modifiers, ignore_shift, ignore_alt);
2419
2420 use crate::alloc::string::ToString;
2421 let result = shortcut.to_string();
2422
2423 #[cfg(target_os = "macos")]
2424 assert_eq!(result.as_str(), _expected_macos, "Failed for key: {:?}", key);
2425
2426 #[cfg(target_os = "windows")]
2427 assert_eq!(result.as_str(), _expected_windows, "Failed for key: {:?}", key);
2428
2429 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
2430 assert_eq!(result.as_str(), _expected_linux, "Failed for key: {:?}", key);
2431 }
2432 }
2433}