1#![warn(missing_docs)]
8
9use crate::item_tree::ItemTreeRc;
10use crate::item_tree::{ItemRc, ItemWeak, VisitChildrenResult};
11use crate::items::{
12 AllowedDragActions, DropEvent, ItemRef, MouseCursor, OperatingSystemType, TextCursorDirection,
13};
14pub use crate::items::{FocusReason, KeyEvent, KeyboardModifiers, PointerEventButton};
15use crate::lengths::{ItemTransform, LogicalPoint, LogicalVector};
16use crate::timers::Timer;
17use crate::window::{WindowAdapter, WindowInner};
18use crate::{Coord, Property, SharedString};
19use alloc::rc::Rc;
20use alloc::vec::Vec;
21use const_field_offset::FieldOffsets;
22use core::cell::Cell;
23use core::fmt::Display;
24use core::pin::Pin;
25use core::time::Duration;
26
27#[repr(C)]
32#[derive(Debug, Clone, PartialEq)]
33pub enum MouseEvent {
34 Pressed {
36 position: LogicalPoint,
38 button: PointerEventButton,
40 click_count: u8,
42 touch_finger_id: i32,
44 },
45 Released {
47 position: LogicalPoint,
49 button: PointerEventButton,
51 click_count: u8,
53 touch_finger_id: i32,
55 },
56 Moved {
58 position: LogicalPoint,
60 touch_finger_id: i32,
62 },
63 Wheel {
65 position: LogicalPoint,
67 delta_x: Coord,
69 delta_y: Coord,
71 phase: TouchPhase,
73 },
74 DragMove {
78 event: DropEvent,
80 allowed: AllowedDragActions,
82 },
83 Drop {
85 event: DropEvent,
87 allowed: AllowedDragActions,
89 },
90 PinchGesture {
92 position: LogicalPoint,
94 delta: f32,
96 phase: TouchPhase,
98 },
99 RotationGesture {
101 position: LogicalPoint,
103 delta: f32,
105 phase: TouchPhase,
107 },
108 Exit,
110}
111
112impl MouseEvent {
113 pub fn touch_finger_id(&self) -> i32 {
115 match self {
116 MouseEvent::Pressed { touch_finger_id, .. } => *touch_finger_id,
117 MouseEvent::Released { touch_finger_id, .. } => *touch_finger_id,
118 MouseEvent::Moved { touch_finger_id, .. } => *touch_finger_id,
119 _ => 0,
120 }
121 }
122
123 pub fn position(&self) -> Option<LogicalPoint> {
125 match self {
126 MouseEvent::Pressed { position, .. } => Some(*position),
127 MouseEvent::Released { position, .. } => Some(*position),
128 MouseEvent::Moved { position, .. } => Some(*position),
129 MouseEvent::Wheel { position, .. } => Some(*position),
130 MouseEvent::PinchGesture { position, .. } => Some(*position),
131 MouseEvent::RotationGesture { position, .. } => Some(*position),
132 MouseEvent::DragMove { event: e, .. } | MouseEvent::Drop { event: e, .. } => {
133 Some(crate::lengths::logical_point_from_api(e.position))
134 }
135 MouseEvent::Exit => None,
136 }
137 }
138
139 pub fn translate(&mut self, vec: LogicalVector) {
141 let pos = match self {
142 MouseEvent::Pressed { position, .. } => Some(position),
143 MouseEvent::Released { position, .. } => Some(position),
144 MouseEvent::Moved { position, .. } => Some(position),
145 MouseEvent::Wheel { position, .. } => Some(position),
146 MouseEvent::PinchGesture { position, .. } => Some(position),
147 MouseEvent::RotationGesture { position, .. } => Some(position),
148 MouseEvent::DragMove { event: e, .. } | MouseEvent::Drop { event: e, .. } => {
149 e.position = crate::api::LogicalPosition::from_euclid(
150 crate::lengths::logical_point_from_api(e.position) + vec,
151 );
152 None
153 }
154 MouseEvent::Exit => None,
155 };
156 if let Some(pos) = pos {
157 *pos += vec;
158 }
159 }
160
161 pub fn transform(&mut self, transform: ItemTransform) {
163 let pos = match self {
164 MouseEvent::Pressed { position, .. } => Some(position),
165 MouseEvent::Released { position, .. } => Some(position),
166 MouseEvent::Moved { position, .. } => Some(position),
167 MouseEvent::Wheel { position, .. } => Some(position),
168 MouseEvent::PinchGesture { position, .. } => Some(position),
169 MouseEvent::RotationGesture { position, .. } => Some(position),
170 MouseEvent::DragMove { event: e, .. } | MouseEvent::Drop { event: e, .. } => {
171 e.position = crate::api::LogicalPosition::from_euclid(
172 transform
173 .transform_point(crate::lengths::logical_point_from_api(e.position).cast())
174 .cast(),
175 );
176 None
177 }
178 MouseEvent::Exit => None,
179 };
180 if let Some(pos) = pos {
181 *pos = transform.transform_point(pos.cast()).cast();
182 }
183 }
184
185 fn set_click_count(&mut self, count: u8) {
187 match self {
188 MouseEvent::Pressed { click_count, .. } | MouseEvent::Released { click_count, .. } => {
189 *click_count = count
190 }
191 _ => (),
192 }
193 }
194}
195
196#[repr(u8)]
200#[derive(Debug, Clone, Copy, PartialEq)]
201pub enum TouchPhase {
202 Started,
204 Moved,
206 Ended,
208 Cancelled,
210}
211
212#[repr(u8)]
217#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
218pub enum InputEventResult {
219 EventAccepted,
222 #[default]
224 EventIgnored,
225 GrabMouse,
227 StartDrag,
229}
230
231#[repr(C)]
235#[derive(Debug, Copy, Clone, PartialEq, Default)]
236pub enum InputEventFilterResult {
237 #[default]
240 ForwardEvent,
241 ForwardAndIgnore,
244 ForwardAndInterceptGrab,
247 Intercept,
250 DelayForwarding(u64),
258 ForwardAndObserve,
261}
262
263#[allow(missing_docs, non_upper_case_globals)]
265pub mod key_codes {
266 macro_rules! declare_consts_for_special_keys {
267 ($($char:literal # $name:ident # $($shifted:ident)? $(=> $($_muda:ident)? # $($_qt:ident)|* # $($_winit:ident $(($_pos:ident))?)|* # $($_xkb:ident)|* )? ;)*) => {
268 $(pub const $name : char = $char;)*
269
270 #[allow(missing_docs)]
271 #[derive(Debug, Copy, Clone, PartialEq)]
272 #[non_exhaustive]
273 pub enum Key {
288 $($name,)*
289 }
290
291 impl From<Key> for char {
292 fn from(k: Key) -> Self {
293 match k {
294 $(Key::$name => $name,)*
295 }
296 }
297 }
298
299 impl From<Key> for crate::SharedString {
300 fn from(k: Key) -> Self {
301 char::from(k).into()
302 }
303 }
304 };
305 }
306
307 i_slint_common::for_each_keys!(declare_consts_for_special_keys);
308}
309
310#[derive(Clone, Copy, Default, Debug)]
313pub(crate) struct InternalKeyboardModifierState {
314 left_alt: bool,
315 right_alt: bool,
316 altgr: bool,
317 left_control: bool,
318 right_control: bool,
319 left_meta: bool,
320 right_meta: bool,
321 left_shift: bool,
322 right_shift: bool,
323}
324
325impl InternalKeyboardModifierState {
326 pub(crate) fn state_update(mut self, pressed: bool, text: &SharedString) -> Option<Self> {
329 if let Some(key_code) = text.chars().next() {
330 match key_code {
331 key_codes::Alt => self.left_alt = pressed,
332 key_codes::AltGr => self.altgr = pressed,
333 key_codes::Control => self.left_control = pressed,
334 key_codes::ControlR => self.right_control = pressed,
335 key_codes::Shift => self.left_shift = pressed,
336 key_codes::ShiftR => self.right_shift = pressed,
337 key_codes::Meta => self.left_meta = pressed,
338 key_codes::MetaR => self.right_meta = pressed,
339 _ => return None,
340 };
341
342 debug_assert_eq!(key_code.len_utf8(), text.len());
346 }
347
348 Some(self)
349 }
350
351 pub fn shift(&self) -> bool {
352 self.right_shift || self.left_shift
353 }
354 pub fn alt(&self) -> bool {
355 self.right_alt || self.left_alt
356 }
357 pub fn meta(&self) -> bool {
358 self.right_meta || self.left_meta
359 }
360 pub fn control(&self) -> bool {
361 self.right_control || self.left_control
362 }
363
364 pub fn modifiers_for(&self, _event: &InternalKeyEvent) -> KeyboardModifiers {
365 #[allow(unused_mut)]
366 let mut alt = self.alt();
367 #[allow(unused_mut)]
368 let mut control = self.control();
369
370 #[cfg(target_os = "windows")]
389 {
390 if !self.altgr && self.control() && self.alt() {
392 let implies_altgr = if _event.text_without_modifiers.is_empty() {
399 _event.key_event.text.chars().any(|c| !c.is_ascii_alphanumeric())
400 } else {
401 _event.text_without_modifiers.to_lowercase()
402 != _event.key_event.text.to_lowercase()
403 };
404 if implies_altgr {
405 alt = false;
406 control = false;
407 }
408 }
409 }
410 #[cfg(target_family = "wasm")]
411 if crate::detect_operating_system() == OperatingSystemType::Windows {
412 let is_altgr = self.altgr
416 || (self.control()
417 && self.alt()
418 && _event.key_event.text.chars().any(|c| !c.is_ascii_alphanumeric()));
419 if is_altgr {
420 alt = false;
421 control = false;
422 }
423 }
424
425 KeyboardModifiers { alt, control, meta: self.meta(), shift: self.shift() }
426 }
427}
428
429impl From<InternalKeyboardModifierState> for KeyboardModifiers {
430 fn from(internal_state: InternalKeyboardModifierState) -> Self {
431 Self {
432 alt: internal_state.alt(),
433 control: internal_state.control(),
434 meta: internal_state.meta(),
435 shift: internal_state.shift(),
436 }
437 }
438}
439
440#[i_slint_core_macros::slint_doc]
441#[derive(Clone, Eq, PartialEq, Default)]
474#[repr(C)]
475pub struct Keys {
476 inner: KeysInner,
477}
478
479#[derive(Debug, Clone, PartialEq, Eq)]
481enum KeysParseErrorInner {
482 NoKey,
484 MultipleKeys,
486 MultipleGraphemeClusters(SharedString),
489 NotLowercase(SharedString),
492 IncompatibleModifiers(SharedString),
495}
496
497#[derive(Debug, Clone, PartialEq, Eq)]
502pub struct KeysParseError(KeysParseErrorInner);
503
504impl core::fmt::Display for KeysParseError {
505 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
506 match &self.0 {
507 KeysParseErrorInner::NoKey => write!(f, "no key found (only modifiers)"),
508 KeysParseErrorInner::MultipleKeys => {
509 write!(f, "multiple non-modifier keys found")
510 }
511 KeysParseErrorInner::MultipleGraphemeClusters(s) => {
512 write!(f, "key string must be a single grapheme cluster, got: {s}")
513 }
514 KeysParseErrorInner::NotLowercase(s) => {
515 let lower = s.to_lowercase();
516 write!(f, "key string must be lowercase, use \"{lower}\" instead")
517 }
518 KeysParseErrorInner::IncompatibleModifiers(msg) => write!(f, "{msg}"),
519 }
520 }
521}
522
523impl core::error::Error for KeysParseError {}
524
525use i_slint_common::key_codes::{ShiftBehavior, lookup_key_name};
526
527pub fn make_keys(
529 key: SharedString,
530 modifiers: KeyboardModifiers,
531 ignore_shift: bool,
532 ignore_alt: bool,
533) -> Keys {
534 Keys {
535 inner: KeysInner { key: key.to_lowercase().into(), modifiers, ignore_shift, ignore_alt },
536 }
537}
538
539#[cfg(feature = "ffi")]
540#[allow(unsafe_code)]
541pub(crate) mod ffi {
542 use crate::api::ToSharedString as _;
543
544 use super::*;
545
546 #[unsafe(no_mangle)]
547 pub unsafe extern "C" fn slint_keys(
548 key: &SharedString,
549 alt: bool,
550 control: bool,
551 shift: bool,
552 meta: bool,
553 ignore_shift: bool,
554 ignore_alt: bool,
555 out: &mut Keys,
556 ) {
557 *out = make_keys(
558 key.clone(),
559 KeyboardModifiers { alt, control, shift, meta },
560 ignore_shift,
561 ignore_alt,
562 );
563 }
564
565 #[unsafe(no_mangle)]
566 pub unsafe extern "C" fn slint_keys_debug_string(shortcut: &Keys, out: &mut SharedString) {
567 *out = crate::format!("{shortcut:?}");
568 }
569
570 #[unsafe(no_mangle)]
571 pub unsafe extern "C" fn slint_keys_to_string(shortcut: &Keys, out: &mut SharedString) {
572 *out = shortcut.to_shared_string();
573 }
574
575 #[unsafe(no_mangle)]
576 pub unsafe extern "C" fn slint_keys_from_parts(
577 parts: crate::slice::Slice<'_, SharedString>,
578 out: &mut Keys,
579 ) -> bool {
580 match keys_from_parts(parts.as_slice().iter().map(|s| s.as_str())) {
581 Ok(keys) => {
582 *out = keys;
583 true
584 }
585 Err(_) => false,
586 }
587 }
588}
589
590fn normalize_key(key: &str) -> SharedString {
592 let lowered = key.to_lowercase();
593 cfg_if::cfg_if! {
594 if #[cfg(feature = "shared-parley")] {
595 let normalizer = icu_normalizer::ComposingNormalizer::new_nfc();
596 let normalized = normalizer.normalize(&lowered);
597 SharedString::from(normalized.as_ref())
598 } else {
599 SharedString::from(lowered.as_str())
600 }
601 }
602}
603
604fn keys_from_parts<'a>(parts: impl Iterator<Item = &'a str>) -> Result<Keys, KeysParseError> {
605 keys_from_parts_inner(parts).map_err(KeysParseError)
606}
607
608fn keys_from_parts_inner<'a>(
609 parts: impl Iterator<Item = &'a str>,
610) -> Result<Keys, KeysParseErrorInner> {
611 use unicode_segmentation::UnicodeSegmentation;
612
613 let mut modifiers = KeyboardModifiers::default();
614 let mut ignore_shift = false;
615 let mut ignore_alt = false;
616 let mut key_part: Option<&str> = None;
617
618 for part in parts {
619 let part = part.trim();
620 if part.is_empty() {
621 continue;
622 }
623 match part {
624 "Control" => modifiers.control = true,
625 "Alt" => {
626 if ignore_alt {
627 return Err(KeysParseErrorInner::IncompatibleModifiers(
628 "Alt and Alt? cannot be combined".into(),
629 ));
630 }
631 modifiers.alt = true;
632 }
633 "Shift" => {
634 if ignore_shift {
635 return Err(KeysParseErrorInner::IncompatibleModifiers(
636 "Shift and Shift? cannot be combined".into(),
637 ));
638 }
639 modifiers.shift = true;
640 }
641 "Meta" => modifiers.meta = true,
642 "Shift?" => {
643 if modifiers.shift {
644 return Err(KeysParseErrorInner::IncompatibleModifiers(
645 "Shift and Shift? cannot be combined".into(),
646 ));
647 }
648 ignore_shift = true;
649 }
650 "Alt?" => {
651 if modifiers.alt {
652 return Err(KeysParseErrorInner::IncompatibleModifiers(
653 "Alt and Alt? cannot be combined".into(),
654 ));
655 }
656 ignore_alt = true;
657 }
658 _ => {
659 if key_part.is_some() {
660 return Err(KeysParseErrorInner::MultipleKeys);
661 }
662 key_part = Some(part);
663 }
664 }
665 }
666
667 let key_name = match key_part {
668 Some(k) => k,
669 None if modifiers == KeyboardModifiers::default() && !ignore_shift && !ignore_alt => {
670 return Ok(Keys::default());
672 }
673 None => return Err(KeysParseErrorInner::NoKey),
674 };
675
676 if let Some((key_char, shift_behavior)) = lookup_key_name(key_name) {
678 if matches!(shift_behavior, ShiftBehavior::LocalizedShiftable { .. }) {
680 if modifiers.shift {
681 return Err(KeysParseErrorInner::IncompatibleModifiers(
682 alloc::format!(
683 "Key bindings involving {key_name} ignore Shift to support different keyboard layouts; remove Shift"
684 ).into(),
685 ));
686 }
687 ignore_shift = true;
688 }
689 let key: SharedString = key_char.to_lowercase().collect::<alloc::string::String>().into();
691 return Ok(Keys { inner: KeysInner { key, modifiers, ignore_shift, ignore_alt } });
692 }
693
694 let grapheme_count = key_name.graphemes(true).count();
697 if grapheme_count > 1 {
698 return Err(KeysParseErrorInner::MultipleGraphemeClusters(key_name.into()));
699 }
700
701 let lowered = key_name.to_lowercase();
703 if lowered != key_name {
704 return Err(KeysParseErrorInner::NotLowercase(key_name.into()));
705 }
706
707 let key = normalize_key(key_name);
708 Ok(Keys { inner: KeysInner { key, modifiers, ignore_shift, ignore_alt } })
709}
710
711#[derive(PartialEq, Eq, Clone, Default)]
714#[repr(C)]
715pub struct KeysInner {
716 pub key: SharedString,
720 pub modifiers: KeyboardModifiers,
722 pub ignore_shift: bool,
724 pub ignore_alt: bool,
726}
727
728impl KeysInner {
729 pub fn from_pub(keys: &Keys) -> &Self {
731 &keys.inner
732 }
733}
734
735impl Keys {
736 #[i_slint_core_macros::slint_doc]
737 pub fn from_parts<'a>(
750 parts: impl IntoIterator<Item = &'a str>,
751 ) -> Result<Keys, KeysParseError> {
752 keys_from_parts(parts.into_iter())
753 }
754
755 pub(crate) fn matches(&self, key_event: &KeyEvent) -> bool {
757 let inner = &self.inner;
758 if inner.key.is_empty() {
760 return false;
761 }
762
763 let mut expected_modifiers = inner.modifiers;
765 if inner.ignore_shift {
766 expected_modifiers.shift = key_event.modifiers.shift;
767 }
768 if inner.ignore_alt {
769 expected_modifiers.alt = key_event.modifiers.alt;
770 }
771 let event_text = key_event.text.chars().flat_map(|character| character.to_lowercase());
779
780 event_text.eq(inner.key.chars()) && key_event.modifiers == expected_modifiers
781 }
782
783 fn format_key_for_display(&self) -> crate::SharedString {
784 let key_str = self.inner.key.as_str();
785 let first_char = key_str.chars().next();
786
787 if let Some(first_char) = first_char {
788 macro_rules! check_special_key {
789 ($($char:literal # $name:ident # $($shifted:ident)? $(=> $($_muda:ident)? # $($qt:ident)|* # $($winit:ident $(($_pos:ident))?)|* # $($xkb:ident)|*)? ;)*) => {
790 match first_char {
791 $($(
792 $char => {
794 let _ = stringify!($($qt)|*); return stringify!($name).into();
796 }
797 )?)*
798 _ => ()
799 }
800 };
801 }
802 i_slint_common::for_each_keys!(check_special_key);
803 }
804
805 if key_str.chars().count() == 1 {
806 return key_str.to_uppercase().into();
807 }
808
809 key_str.into()
810 }
811}
812
813impl Display for Keys {
814 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
830 let inner = &self.inner;
831 if inner.key.is_empty() {
832 return Ok(());
833 }
834
835 if crate::is_apple_platform() {
836 if inner.modifiers.meta {
843 f.write_str("⌃")?;
844 }
845 if !inner.ignore_alt && inner.modifiers.alt {
846 f.write_str("⌥")?;
847 }
848 if !inner.ignore_shift && inner.modifiers.shift {
849 f.write_str("⇧")?;
850 }
851 if inner.modifiers.control {
852 f.write_str("⌘")?;
853 }
854 } else {
855 let separator = "+";
856
857 let (ctrl_str, alt_str, shift_str, meta_str) =
860 if crate::detect_operating_system() == OperatingSystemType::Windows {
861 ("Ctrl", "Alt", "Shift", "Win")
862 } else {
863 ("Ctrl", "Alt", "Shift", "Super")
864 };
865
866 if inner.modifiers.meta {
867 f.write_str(meta_str)?;
868 f.write_str(separator)?;
869 }
870 if inner.modifiers.control {
871 f.write_str(ctrl_str)?;
872 f.write_str(separator)?;
873 }
874 if !inner.ignore_alt && inner.modifiers.alt {
875 f.write_str(alt_str)?;
876 f.write_str(separator)?;
877 }
878 if !inner.ignore_shift && inner.modifiers.shift {
879 f.write_str(shift_str)?;
880 f.write_str(separator)?;
881 }
882 }
883 f.write_str(&self.format_key_for_display())
884 }
885}
886
887impl core::fmt::Debug for Keys {
888 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
890 let inner = &self.inner;
891 if inner.key.is_empty() {
893 write!(f, "")
894 } else {
895 let alt = inner
896 .ignore_alt
897 .then_some("Alt?+")
898 .or(inner.modifiers.alt.then_some("Alt+"))
899 .unwrap_or_default();
900 let ctrl = if inner.modifiers.control { "Control+" } else { "" };
901 let meta = if inner.modifiers.meta { "Meta+" } else { "" };
902 let shift = inner
903 .ignore_shift
904 .then_some("Shift?+")
905 .or(inner.modifiers.shift.then_some("Shift+"))
906 .unwrap_or_default();
907 let keycode: SharedString = inner
908 .key
909 .chars()
910 .flat_map(|character| {
911 let mut escaped = alloc::vec![];
912 if character.is_control() {
913 escaped.extend(character.escape_unicode());
914 } else {
915 escaped.push(character);
916 }
917 escaped
918 })
919 .collect();
920 write!(f, "{meta}{ctrl}{alt}{shift}\"{keycode}\"")
921 }
922 }
923}
924
925#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
927#[repr(u8)]
928pub enum KeyEventType {
929 #[default]
931 KeyPressed = 0,
932 KeyReleased = 1,
934 UpdateComposition = 2,
937 CommitComposition = 3,
939}
940
941#[derive(Default)]
942pub struct InternalKeyEvent {
944 pub key_event: KeyEvent,
946 pub event_type: KeyEventType,
948 #[cfg(target_os = "windows")]
954 pub text_without_modifiers: SharedString,
955 pub replacement_range: Option<core::ops::Range<i32>>,
959 pub preedit_text: SharedString,
961 pub preedit_selection: Option<core::ops::Range<i32>>,
963 pub cursor_position: Option<i32>,
965 pub anchor_position: Option<i32>,
967}
968
969impl InternalKeyEvent {
970 pub fn shortcut(&self) -> Option<StandardShortcut> {
973 if self.key_event.modifiers.control && !self.key_event.modifiers.shift {
974 match self.key_event.text.as_str() {
975 #[cfg(not(target_arch = "wasm32"))]
976 "c" => Some(StandardShortcut::Copy),
977 #[cfg(not(target_arch = "wasm32"))]
978 "x" => Some(StandardShortcut::Cut),
979 #[cfg(not(target_arch = "wasm32"))]
980 "v" => Some(StandardShortcut::Paste),
981 "a" => Some(StandardShortcut::SelectAll),
982 "f" => Some(StandardShortcut::Find),
983 "s" => Some(StandardShortcut::Save),
984 "p" => Some(StandardShortcut::Print),
985 "z" => Some(StandardShortcut::Undo),
986 #[cfg(target_os = "windows")]
987 "y" => Some(StandardShortcut::Redo),
988 "r" => Some(StandardShortcut::Refresh),
989 _ => None,
990 }
991 } else if self.key_event.modifiers.control && self.key_event.modifiers.shift {
992 match self.key_event.text.as_str() {
993 #[cfg(not(target_os = "windows"))]
994 "z" | "Z" => Some(StandardShortcut::Redo),
995 _ => None,
996 }
997 } else {
998 None
999 }
1000 }
1001
1002 pub fn text_shortcut(&self) -> Option<TextShortcut> {
1005 let ke = &self.key_event;
1006 let keycode = ke.text.chars().next()?;
1007
1008 let is_apple = crate::is_apple_platform();
1009
1010 let move_mod = if is_apple {
1011 ke.modifiers.alt && !ke.modifiers.control && !ke.modifiers.meta
1012 } else {
1013 ke.modifiers.control && !ke.modifiers.alt && !ke.modifiers.meta
1014 };
1015
1016 if move_mod {
1017 match keycode {
1018 key_codes::LeftArrow => {
1019 return Some(TextShortcut::Move(TextCursorDirection::BackwardByWord));
1020 }
1021 key_codes::RightArrow => {
1022 return Some(TextShortcut::Move(TextCursorDirection::ForwardByWord));
1023 }
1024 key_codes::UpArrow => {
1025 return Some(TextShortcut::Move(TextCursorDirection::StartOfParagraph));
1026 }
1027 key_codes::DownArrow => {
1028 return Some(TextShortcut::Move(TextCursorDirection::EndOfParagraph));
1029 }
1030 key_codes::Backspace => {
1031 return Some(TextShortcut::DeleteWordBackward);
1032 }
1033 key_codes::Delete => {
1034 return Some(TextShortcut::DeleteWordForward);
1035 }
1036 _ => (),
1037 };
1038 }
1039
1040 #[cfg(not(target_os = "macos"))]
1041 {
1042 if ke.modifiers.control && !ke.modifiers.alt && !ke.modifiers.meta {
1043 match keycode {
1044 key_codes::Home => {
1045 return Some(TextShortcut::Move(TextCursorDirection::StartOfText));
1046 }
1047 key_codes::End => {
1048 return Some(TextShortcut::Move(TextCursorDirection::EndOfText));
1049 }
1050 _ => (),
1051 };
1052 }
1053 }
1054
1055 if is_apple && ke.modifiers.control {
1056 match keycode {
1057 key_codes::LeftArrow => {
1058 return Some(TextShortcut::Move(TextCursorDirection::StartOfLine));
1059 }
1060 key_codes::RightArrow => {
1061 return Some(TextShortcut::Move(TextCursorDirection::EndOfLine));
1062 }
1063 key_codes::UpArrow => {
1064 return Some(TextShortcut::Move(TextCursorDirection::StartOfText));
1065 }
1066 key_codes::DownArrow => {
1067 return Some(TextShortcut::Move(TextCursorDirection::EndOfText));
1068 }
1069 key_codes::Backspace => {
1070 return Some(TextShortcut::DeleteToStartOfLine);
1071 }
1072 _ => (),
1073 };
1074 }
1075
1076 if let Ok(direction) = TextCursorDirection::try_from(keycode) {
1077 Some(TextShortcut::Move(direction))
1078 } else {
1079 match keycode {
1080 key_codes::Backspace => Some(TextShortcut::DeleteBackward),
1081 key_codes::Delete => Some(TextShortcut::DeleteForward),
1082 _ => None,
1083 }
1084 }
1085 }
1086}
1087
1088pub enum StandardShortcut {
1090 Copy,
1092 Cut,
1094 Paste,
1096 SelectAll,
1098 Find,
1100 Save,
1102 Print,
1104 Undo,
1106 Redo,
1108 Refresh,
1110}
1111
1112pub enum TextShortcut {
1114 Move(TextCursorDirection),
1116 DeleteForward,
1118 DeleteBackward,
1120 DeleteWordForward,
1122 DeleteWordBackward,
1124 DeleteToStartOfLine,
1126}
1127
1128#[repr(u8)]
1131#[derive(Debug, Clone, Copy, PartialEq, Default)]
1132pub enum KeyEventResult {
1133 EventAccepted,
1135 #[default]
1137 EventIgnored,
1138}
1139
1140#[repr(u8)]
1143#[derive(Debug, Clone, Copy, PartialEq, Default)]
1144pub enum FocusEventResult {
1145 FocusAccepted,
1147 #[default]
1149 FocusIgnored,
1150}
1151
1152#[derive(Debug, Clone, Copy, PartialEq)]
1155#[repr(u8)]
1156pub enum FocusEvent {
1157 FocusIn(FocusReason),
1159 FocusOut(FocusReason),
1161}
1162
1163#[derive(Default)]
1165pub struct ClickState {
1166 click_count_time_stamp: Cell<Option<crate::animations::Instant>>,
1167 click_count: Cell<u8>,
1168 click_position: Cell<LogicalPoint>,
1169 click_button: Cell<PointerEventButton>,
1170}
1171
1172impl ClickState {
1173 fn restart(&self, position: LogicalPoint, button: PointerEventButton) {
1175 self.click_count.set(0);
1176 self.click_count_time_stamp.set(Some(crate::animations::Instant::now()));
1177 self.click_position.set(position);
1178 self.click_button.set(button);
1179 }
1180
1181 pub fn reset(&self) {
1183 self.click_count.set(0);
1184 self.click_count_time_stamp.replace(None);
1185 }
1186
1187 pub fn check_repeat(&self, mouse_event: MouseEvent, click_interval: Duration) -> MouseEvent {
1189 match mouse_event {
1190 MouseEvent::Pressed { position, button, touch_finger_id, .. } => {
1191 let instant_now = crate::animations::Instant::now();
1192
1193 if let Some(click_count_time_stamp) = self.click_count_time_stamp.get() {
1194 if instant_now - click_count_time_stamp < click_interval
1195 && button == self.click_button.get()
1196 && (position - self.click_position.get()).square_length() < 100 as _
1197 {
1198 self.click_count.set(self.click_count.get().wrapping_add(1));
1199 self.click_count_time_stamp.set(Some(instant_now));
1200 } else {
1201 self.restart(position, button);
1202 }
1203 } else {
1204 self.restart(position, button);
1205 }
1206
1207 return MouseEvent::Pressed {
1208 position,
1209 button,
1210 click_count: self.click_count.get(),
1211 touch_finger_id,
1212 };
1213 }
1214 MouseEvent::Released { position, button, touch_finger_id, .. } => {
1215 return MouseEvent::Released {
1216 position,
1217 button,
1218 click_count: self.click_count.get(),
1219 touch_finger_id,
1220 };
1221 }
1222 _ => {}
1223 };
1224
1225 mouse_event
1226 }
1227}
1228
1229#[derive(Clone)]
1231pub(crate) struct DragData {
1232 pub(crate) event: DropEvent,
1235 pub(crate) allowed: AllowedDragActions,
1237}
1238
1239#[derive(Default)]
1241pub struct MouseInputState {
1242 item_stack: Vec<(ItemWeak, InputEventFilterResult)>,
1245 observers: Vec<ItemWeak>,
1250 pub(crate) offset: LogicalPoint,
1252 grabbed: bool,
1254 pub(crate) drag_data: Option<DragData>,
1257 pub(crate) drag_source: Option<ItemWeak>,
1260 pub(crate) drop_target: Option<ItemWeak>,
1264 delayed: Option<(crate::timers::Timer, MouseEvent)>,
1265 delayed_exit_items: Vec<ItemWeak>,
1266 pub(crate) cursor: MouseCursor,
1267}
1268
1269impl MouseInputState {
1270 fn top_item(&self) -> Option<ItemRc> {
1272 self.item_stack.last().and_then(|x| x.0.upgrade())
1273 }
1274
1275 pub fn top_item_including_delayed(&self) -> Option<ItemRc> {
1277 self.delayed_exit_items.last().and_then(|x| x.upgrade()).or_else(|| self.top_item())
1278 }
1279
1280 pub fn has_delayed_event(&self) -> bool {
1282 self.delayed.is_some()
1283 }
1284
1285 pub fn drop_target_action(&self) -> Option<crate::items::DragAction> {
1288 let action = self
1289 .drop_target
1290 .as_ref()
1291 .and_then(|t| t.upgrade())
1292 .and_then(|i| i.downcast::<crate::items::DropArea>())
1293 .map(|d| d.as_pin_ref().current_action())?;
1294 (action != crate::items::DragAction::None).then_some(action)
1295 }
1296}
1297
1298pub(crate) struct MouseGrabResult {
1299 pub event: Option<MouseEvent>,
1302 pub accepted: bool,
1305}
1306
1307pub(crate) fn handle_mouse_grab(
1309 mouse_event: &MouseEvent,
1310 window_adapter: &Rc<dyn WindowAdapter>,
1311 mouse_input_state: &mut MouseInputState,
1312) -> MouseGrabResult {
1313 if !mouse_input_state.grabbed || mouse_input_state.item_stack.is_empty() {
1314 return MouseGrabResult { event: Some(mouse_event.clone()), accepted: false };
1315 };
1316
1317 let mut event = mouse_event.clone();
1318 let mut intercept = false;
1319 let mut invalid = false;
1320
1321 event.translate(-mouse_input_state.offset.to_vector());
1322
1323 mouse_input_state.item_stack.retain(|it| {
1324 if invalid {
1325 return false;
1326 }
1327 let item = if let Some(item) = it.0.upgrade() {
1328 item
1329 } else {
1330 invalid = true;
1331 return false;
1332 };
1333 if intercept {
1334 item.borrow().as_ref().input_event(
1335 &MouseEvent::Exit,
1336 window_adapter,
1337 &item,
1338 &mut mouse_input_state.cursor,
1339 );
1340 return false;
1341 }
1342 let g = item.geometry();
1343 event.translate(-g.origin.to_vector());
1344 if window_adapter.renderer().supports_transformations()
1345 && let Some(inverse_transform) = item.inverse_children_transform()
1346 {
1347 event.transform(inverse_transform);
1348 }
1349
1350 let interested = matches!(
1351 it.1,
1352 InputEventFilterResult::ForwardAndInterceptGrab
1353 | InputEventFilterResult::DelayForwarding(_)
1354 );
1355
1356 if interested
1357 && item.borrow().as_ref().input_event_filter_before_children(
1358 &event,
1359 window_adapter,
1360 &item,
1361 &mut mouse_input_state.cursor,
1362 ) == InputEventFilterResult::Intercept
1363 {
1364 intercept = true;
1365 }
1366 true
1367 });
1368 if invalid {
1369 return MouseGrabResult { event: Some(mouse_event.clone()), accepted: false };
1370 }
1371
1372 let grabber = mouse_input_state.top_item().unwrap();
1373 let input_result = grabber.borrow().as_ref().input_event(
1374 &event,
1375 window_adapter,
1376 &grabber,
1377 &mut mouse_input_state.cursor,
1378 );
1379 match input_result {
1380 InputEventResult::GrabMouse => MouseGrabResult { event: None, accepted: true },
1381 InputEventResult::StartDrag => {
1382 mouse_input_state.grabbed = false;
1383 let drag_area_item = grabber.downcast::<crate::items::DragArea>().unwrap();
1384 let drag_area = drag_area_item.as_pin_ref();
1385 let (mut drop_event, allowed) = drag_area.initial_drop_event();
1386 drop_event.position = mouse_event
1389 .position()
1390 .map(crate::lengths::logical_position_to_api)
1391 .unwrap_or_default();
1392 mouse_input_state.drag_data = Some(DragData { event: drop_event, allowed });
1393 mouse_input_state.drag_source = Some(grabber.downgrade());
1394 drag_area.dragging.set(true);
1395 MouseGrabResult { event: None, accepted: true }
1396 }
1397 InputEventResult::EventAccepted | InputEventResult::EventIgnored => {
1398 mouse_input_state.grabbed = false;
1399 MouseGrabResult {
1401 event: Some(mouse_event.position().map_or(MouseEvent::Exit, |position| {
1402 MouseEvent::Moved { position, touch_finger_id: mouse_event.touch_finger_id() }
1403 })),
1404 accepted: input_result == InputEventResult::EventAccepted,
1405 }
1406 }
1407 }
1408}
1409
1410pub(crate) fn send_exit_events(
1411 old_input_state: &MouseInputState,
1412 new_input_state: &mut MouseInputState,
1413 mut pos: Option<LogicalPoint>,
1414 window_adapter: &Rc<dyn WindowAdapter>,
1415) {
1416 let cursor = &mut MouseCursor::Default;
1418
1419 for it in core::mem::take(&mut new_input_state.delayed_exit_items) {
1420 let Some(item) = it.upgrade() else { continue };
1421 item.borrow().as_ref().input_event(&MouseEvent::Exit, window_adapter, &item, cursor);
1422 }
1423
1424 let mut clipped = false;
1425 for (idx, it) in old_input_state.item_stack.iter().enumerate() {
1426 let Some(item) = it.0.upgrade() else { break };
1427 let g = item.geometry();
1428 let contains = pos.is_some_and(|p| g.contains(p));
1429 if let Some(p) = pos.as_mut() {
1430 *p -= g.origin.to_vector();
1431 if window_adapter.renderer().supports_transformations()
1432 && let Some(inverse_transform) = item.inverse_children_transform()
1433 {
1434 *p = inverse_transform.transform_point(p.cast()).cast();
1435 }
1436 }
1437 if !contains || clipped {
1438 if item.borrow().as_ref().clips_children() {
1439 clipped = true;
1440 }
1441 item.borrow().as_ref().input_event(&MouseEvent::Exit, window_adapter, &item, cursor);
1442 } else if new_input_state.item_stack.get(idx).is_none_or(|(x, _)| *x != it.0) {
1443 if new_input_state.delayed.is_some() {
1445 new_input_state.delayed_exit_items.push(it.0.clone());
1446 } else {
1447 item.borrow().as_ref().input_event(
1448 &MouseEvent::Exit,
1449 window_adapter,
1450 &item,
1451 cursor,
1452 );
1453 }
1454 }
1455 }
1456
1457 for obs in &old_input_state.observers {
1463 if new_input_state.observers.iter().any(|x| x == obs)
1464 || new_input_state.item_stack.iter().any(|(x, _)| x == obs)
1465 {
1466 continue;
1467 }
1468 let Some(item) = obs.upgrade() else { continue };
1469 item.borrow().as_ref().input_event(&MouseEvent::Exit, window_adapter, &item, cursor);
1470 }
1471}
1472
1473pub struct MouseInputResult {
1475 pub state: MouseInputState,
1477 pub accepted: bool,
1480}
1481
1482pub fn process_mouse_input(
1486 root: ItemRc,
1487 mouse_event: &MouseEvent,
1488 window_adapter: &Rc<dyn WindowAdapter>,
1489 mut mouse_input_state: MouseInputState,
1490) -> MouseInputResult {
1491 let mut result = MouseInputState {
1492 drag_data: mouse_input_state.drag_data.clone(),
1493 drag_source: mouse_input_state.drag_source.clone(),
1494 drop_target: mouse_input_state.drop_target.clone(),
1495 cursor: mouse_input_state.cursor,
1496 ..Default::default()
1497 };
1498 let r = send_mouse_event_to_item(
1499 mouse_event,
1500 root.clone(),
1501 window_adapter,
1502 &mut result,
1503 mouse_input_state.top_item().as_ref(),
1504 false,
1505 );
1506 let accepted = r.has_aborted();
1507 if matches!(mouse_event, MouseEvent::DragMove { .. }) {
1508 result.drop_target =
1511 accepted.then(|| result.item_stack.last().map(|(w, _)| w.clone())).flatten();
1512 }
1513 if mouse_input_state.delayed.is_some()
1514 && (!accepted
1515 || Option::zip(result.item_stack.last(), mouse_input_state.item_stack.last())
1516 .is_none_or(|(a, b)| a.0 != b.0))
1517 {
1518 mouse_input_state.cursor = result.cursor;
1520 return MouseInputResult { state: mouse_input_state, accepted };
1521 }
1522 send_exit_events(&mouse_input_state, &mut result, mouse_event.position(), window_adapter);
1523
1524 if let MouseEvent::Wheel { position, .. } = mouse_event
1525 && accepted
1526 {
1527 let moved = process_mouse_input(
1531 root,
1532 &MouseEvent::Moved { position: *position, touch_finger_id: 0 },
1533 window_adapter,
1534 result,
1535 );
1536 return MouseInputResult { state: moved.state, accepted: true };
1537 }
1538
1539 MouseInputResult { state: result, accepted }
1540}
1541
1542pub(crate) fn process_delayed_event(
1543 window_adapter: &Rc<dyn WindowAdapter>,
1544 mut mouse_input_state: MouseInputState,
1545) -> MouseInputState {
1546 let event = match mouse_input_state.delayed.take() {
1548 Some(e) => e.1,
1549 None => return mouse_input_state,
1550 };
1551
1552 let top_item = match mouse_input_state.top_item() {
1553 Some(i) => i,
1554 None => return MouseInputState::default(),
1555 };
1556
1557 let prev_target = mouse_input_state.delayed_exit_items.last().and_then(|x| x.upgrade());
1559 let last_top_item = prev_target.as_ref().unwrap_or(&top_item);
1560
1561 let mut actual_visitor =
1562 |component: &ItemTreeRc, index: u32, _: Pin<ItemRef>| -> VisitChildrenResult {
1563 send_mouse_event_to_item(
1564 &event,
1565 ItemRc::new(component.clone(), index),
1566 window_adapter,
1567 &mut mouse_input_state,
1568 Some(last_top_item),
1569 true,
1570 )
1571 };
1572 vtable::new_vref!(let mut actual_visitor : VRefMut<crate::item_tree::ItemVisitorVTable> for crate::item_tree::ItemVisitor = &mut actual_visitor);
1573 vtable::VRc::borrow_pin(top_item.item_tree()).as_ref().visit_children_item(
1574 top_item.index() as isize,
1575 crate::item_tree::TraversalOrder::FrontToBack,
1576 actual_visitor,
1577 );
1578 mouse_input_state
1579}
1580
1581fn send_mouse_event_to_item(
1582 mouse_event: &MouseEvent,
1583 item_rc: ItemRc,
1584 window_adapter: &Rc<dyn WindowAdapter>,
1585 result: &mut MouseInputState,
1586 last_top_item: Option<&ItemRc>,
1587 ignore_delays: bool,
1588) -> VisitChildrenResult {
1589 let item = item_rc.borrow();
1590 let geom = item_rc.geometry();
1591 let mut event_for_children = mouse_event.clone();
1593 event_for_children.translate(-geom.origin.to_vector());
1595 if window_adapter.renderer().supports_transformations() {
1596 if let Some(inverse_transform) = item_rc.inverse_children_transform() {
1598 event_for_children.transform(inverse_transform);
1599 }
1600 }
1601
1602 let filter_result = if mouse_event.position().is_some_and(|p| geom.contains(p))
1603 || item.as_ref().clips_children()
1604 {
1605 item.as_ref().input_event_filter_before_children(
1606 &event_for_children,
1607 window_adapter,
1608 &item_rc,
1609 &mut result.cursor,
1610 )
1611 } else {
1612 InputEventFilterResult::ForwardAndIgnore
1613 };
1614
1615 let (forward_to_children, ignore) = match filter_result {
1616 InputEventFilterResult::ForwardEvent => (true, false),
1617 InputEventFilterResult::ForwardAndIgnore => (true, true),
1618 InputEventFilterResult::ForwardAndInterceptGrab => (true, false),
1619 InputEventFilterResult::Intercept => (false, false),
1620 InputEventFilterResult::DelayForwarding(_) if ignore_delays => (true, false),
1621 InputEventFilterResult::DelayForwarding(duration) => {
1622 let timer = Timer::default();
1623 let w = Rc::downgrade(window_adapter);
1624 timer.start(
1625 crate::timers::TimerMode::SingleShot,
1626 Duration::from_millis(duration),
1627 move || {
1628 if let Some(w) = w.upgrade() {
1629 WindowInner::from_pub(w.window()).process_delayed_event();
1630 }
1631 },
1632 );
1633 result.delayed = Some((timer, event_for_children));
1634 result
1635 .item_stack
1636 .push((item_rc.downgrade(), InputEventFilterResult::DelayForwarding(duration)));
1637 return VisitChildrenResult::abort(item_rc.index(), 0);
1638 }
1639 InputEventFilterResult::ForwardAndObserve => (true, true),
1643 };
1644
1645 result.item_stack.push((item_rc.downgrade(), filter_result));
1646 if forward_to_children {
1647 let mut actual_visitor =
1648 |component: &ItemTreeRc, index: u32, _: Pin<ItemRef>| -> VisitChildrenResult {
1649 send_mouse_event_to_item(
1650 &event_for_children,
1651 ItemRc::new(component.clone(), index),
1652 window_adapter,
1653 result,
1654 last_top_item,
1655 ignore_delays,
1656 )
1657 };
1658 vtable::new_vref!(let mut actual_visitor : VRefMut<crate::item_tree::ItemVisitorVTable> for crate::item_tree::ItemVisitor = &mut actual_visitor);
1659 let r = vtable::VRc::borrow_pin(item_rc.item_tree()).as_ref().visit_children_item(
1660 item_rc.index() as isize,
1661 crate::item_tree::TraversalOrder::FrontToBack,
1662 actual_visitor,
1663 );
1664 if r.has_aborted() {
1665 return r;
1666 }
1667 };
1668
1669 let r = if ignore {
1670 InputEventResult::EventIgnored
1671 } else {
1672 let mut event = mouse_event.clone();
1673 event.translate(-geom.origin.to_vector());
1674 if last_top_item.is_none_or(|x| *x != item_rc) {
1675 event.set_click_count(0);
1676 }
1677 item.as_ref().input_event(&event, window_adapter, &item_rc, &mut result.cursor)
1678 };
1679 match r {
1680 InputEventResult::EventAccepted => VisitChildrenResult::abort(item_rc.index(), 0),
1681 InputEventResult::EventIgnored => {
1682 let popped = result.item_stack.pop();
1683 debug_assert_eq!(
1684 popped.as_ref().map(|x| (x.0.upgrade().unwrap().index(), x.1)).unwrap(),
1685 (item_rc.index(), filter_result)
1686 );
1687 if filter_result == InputEventFilterResult::ForwardAndObserve
1690 && let Some((weak, _)) = popped
1691 && !result.observers.contains(&weak)
1692 {
1693 result.observers.push(weak);
1694 }
1695 VisitChildrenResult::CONTINUE
1696 }
1697 InputEventResult::GrabMouse => {
1698 result.item_stack.last_mut().unwrap().1 =
1699 InputEventFilterResult::ForwardAndInterceptGrab;
1700 result.grabbed = true;
1701 VisitChildrenResult::abort(item_rc.index(), 0)
1702 }
1703 InputEventResult::StartDrag => {
1704 result.item_stack.last_mut().unwrap().1 =
1705 InputEventFilterResult::ForwardAndInterceptGrab;
1706 result.grabbed = false;
1707 let drag_area_item = item_rc.downcast::<crate::items::DragArea>().unwrap();
1708 let drag_area = drag_area_item.as_pin_ref();
1709 let (mut drop_event, allowed) = drag_area.initial_drop_event();
1710 drop_event.position = mouse_event
1714 .position()
1715 .map(|p| p - geom.origin.to_vector())
1716 .map(|p| item_rc.map_to_window(p))
1717 .map(crate::lengths::logical_position_to_api)
1718 .unwrap_or_default();
1719 result.drag_data = Some(DragData { event: drop_event, allowed });
1720 result.drag_source = Some(item_rc.downgrade());
1721 drag_area.dragging.set(true);
1722 VisitChildrenResult::abort(item_rc.index(), 0)
1723 }
1724 }
1725}
1726
1727#[derive(FieldOffsets)]
1734#[repr(C)]
1735#[pin]
1736pub(crate) struct TextCursorBlinker {
1737 cursor_visible: Property<bool>,
1738 cursor_blink_timer: crate::timers::Timer,
1739}
1740
1741impl TextCursorBlinker {
1742 pub fn new() -> Pin<Rc<Self>> {
1745 Rc::pin(Self {
1746 cursor_visible: Property::new(true),
1747 cursor_blink_timer: Default::default(),
1748 })
1749 }
1750
1751 pub fn set_binding(
1754 instance: Pin<Rc<TextCursorBlinker>>,
1755 prop: &Property<bool>,
1756 cycle_duration: Duration,
1757 ) {
1758 instance.as_ref().cursor_visible.set(true);
1759 Self::start(&instance, cycle_duration);
1761 prop.set_binding(move || {
1762 TextCursorBlinker::FIELD_OFFSETS.cursor_visible().apply_pin(instance.as_ref()).get()
1763 });
1764 }
1765
1766 pub fn start(self: &Pin<Rc<Self>>, cycle_duration: Duration) {
1769 if self.cursor_blink_timer.running() {
1770 self.cursor_blink_timer.restart();
1771 } else {
1772 let toggle_cursor = {
1773 let weak_blinker = pin_weak::rc::PinWeak::downgrade(self.clone());
1774 move || {
1775 if let Some(blinker) = weak_blinker.upgrade() {
1776 let visible = TextCursorBlinker::FIELD_OFFSETS
1777 .cursor_visible()
1778 .apply_pin(blinker.as_ref())
1779 .get();
1780 blinker.cursor_visible.set(!visible);
1781 }
1782 }
1783 };
1784 if !cycle_duration.is_zero() {
1785 self.cursor_blink_timer.start(
1786 crate::timers::TimerMode::Repeated,
1787 cycle_duration / 2,
1788 toggle_cursor,
1789 );
1790 }
1791 }
1792 }
1793
1794 pub fn stop(&self) {
1797 self.cursor_blink_timer.stop()
1798 }
1799}
1800
1801#[derive(Clone, Copy, Default)]
1803struct TouchPoint {
1804 id: i32,
1805 position: LogicalPoint,
1806}
1807
1808const MAX_TRACKED_TOUCHES: usize = 5;
1814
1815#[derive(Clone)]
1816struct TouchMap {
1817 entries: [TouchPoint; MAX_TRACKED_TOUCHES],
1818 len: usize,
1819}
1820
1821impl Default for TouchMap {
1822 fn default() -> Self {
1823 Self { entries: [TouchPoint::default(); MAX_TRACKED_TOUCHES], len: 0 }
1824 }
1825}
1826
1827impl TouchMap {
1828 fn get(&self, id: i32) -> Option<&TouchPoint> {
1829 self.entries[..self.len].iter().find(|tp| tp.id == id)
1830 }
1831
1832 fn get_mut(&mut self, id: i32) -> Option<&mut TouchPoint> {
1833 self.entries[..self.len].iter_mut().find(|tp| tp.id == id)
1834 }
1835
1836 fn insert(&mut self, point: TouchPoint) {
1837 if let Some(existing) = self.entries[..self.len].iter_mut().find(|tp| tp.id == point.id) {
1838 *existing = point;
1839 } else if self.len < MAX_TRACKED_TOUCHES {
1840 self.entries[self.len] = point;
1841 self.len += 1;
1842 }
1843 }
1844
1845 fn remove(&mut self, id: i32) {
1846 if let Some(idx) = self.entries[..self.len].iter().position(|tp| tp.id == id) {
1847 self.len -= 1;
1848 self.entries[idx] = self.entries[self.len];
1849 }
1850 }
1851
1852 fn len(&self) -> usize {
1853 self.len
1854 }
1855
1856 fn first_two_ids(&self) -> Option<(i32, i32)> {
1858 if self.len >= 2 { Some((self.entries[0].id, self.entries[1].id)) } else { None }
1859 }
1860
1861 fn first(&self) -> Option<&TouchPoint> {
1863 if self.len > 0 { Some(&self.entries[0]) } else { None }
1864 }
1865}
1866
1867const MAX_TOUCH_EVENTS: usize = 4;
1873
1874#[derive(Clone)]
1875pub(crate) struct TouchEventBuffer {
1876 events: [Option<MouseEvent>; MAX_TOUCH_EVENTS],
1877 len: usize,
1878}
1879
1880impl TouchEventBuffer {
1881 fn new() -> Self {
1882 Self { events: [None, None, None, None], len: 0 }
1883 }
1884
1885 fn push(&mut self, event: MouseEvent) {
1886 debug_assert!(self.len < MAX_TOUCH_EVENTS, "TouchEventBuffer overflow");
1887 if self.len < MAX_TOUCH_EVENTS {
1888 self.events[self.len] = Some(event);
1889 self.len += 1;
1890 }
1891 }
1892
1893 pub(crate) fn into_iter(self) -> impl Iterator<Item = MouseEvent> {
1895 let len = self.len;
1896 self.events.into_iter().take(len).flatten()
1897 }
1898}
1899
1900#[derive(Default, Debug, Clone, Copy)]
1902enum GestureRecognitionState {
1903 #[default]
1905 Idle,
1906 TwoFingersDown { finger_ids: (i32, i32), initial_distance: f32, last_angle: euclid::Angle<f32> },
1908 Pinching {
1910 finger_ids: (i32, i32),
1911 initial_distance: f32,
1912 last_scale: f32,
1913 last_angle: euclid::Angle<f32>,
1914 },
1915}
1916
1917pub(crate) struct TouchState {
1924 active_touches: TouchMap,
1925 primary_touch_id: Option<i32>,
1927 gesture_state: GestureRecognitionState,
1928}
1929
1930impl Default for TouchState {
1931 fn default() -> Self {
1932 Self {
1933 active_touches: TouchMap::default(),
1934 primary_touch_id: None,
1935 gesture_state: GestureRecognitionState::Idle,
1936 }
1937 }
1938}
1939
1940impl TouchState {
1941 const PINCH_THRESHOLD: f32 = 8.0;
1943
1944 const ROTATION_THRESHOLD: f32 = 5.0;
1946
1947 fn gesture_finger_ids(&self) -> Option<(i32, i32)> {
1949 match self.gesture_state {
1950 GestureRecognitionState::TwoFingersDown { finger_ids, .. }
1951 | GestureRecognitionState::Pinching { finger_ids, .. } => Some(finger_ids),
1952 GestureRecognitionState::Idle => None,
1953 }
1954 }
1955
1956 fn geometry_for(&self, (id_a, id_b): (i32, i32)) -> Option<(f32, euclid::Angle<f32>)> {
1958 let a = self.active_touches.get(id_a)?;
1959 let b = self.active_touches.get(id_b)?;
1960 let delta = (b.position - a.position).cast::<f32>();
1961 Some((delta.length(), delta.angle_from_x_axis()))
1962 }
1963
1964 fn gesture_finger_positions(&self) -> Option<(&TouchPoint, &TouchPoint)> {
1966 let (id_a, id_b) = self.gesture_finger_ids()?;
1967 let a = self.active_touches.get(id_a)?;
1968 let b = self.active_touches.get(id_b)?;
1969 Some((a, b))
1970 }
1971
1972 fn gesture_midpoint(&self) -> Option<LogicalPoint> {
1974 let (a, b) = self.gesture_finger_positions()?;
1975 let mid = a.position.cast::<f32>().lerp(b.position.cast::<f32>(), 0.5);
1976 Some(mid.cast())
1977 }
1978
1979 fn gesture_geometry(&self) -> Option<(f32, euclid::Angle<f32>)> {
1981 let (a, b) = self.gesture_finger_positions()?;
1982 let delta = (b.position - a.position).cast::<f32>();
1983 Some((delta.length(), delta.angle_from_x_axis()))
1984 }
1985
1986 fn is_gesture_finger(&self, id: i32) -> bool {
1988 self.gesture_finger_ids().is_some_and(|(a, b)| id == a || id == b)
1989 }
1990
1991 pub(crate) fn process(
1998 &mut self,
1999 id: i32,
2000 position: LogicalPoint,
2001 phase: TouchPhase,
2002 ) -> TouchEventBuffer {
2003 let mut events = TouchEventBuffer::new();
2004 match phase {
2005 TouchPhase::Started => self.process_started(id, position, &mut events),
2006 TouchPhase::Moved => self.process_moved(id, position, &mut events),
2007 TouchPhase::Ended => self.process_ended(id, position, false, &mut events),
2008 TouchPhase::Cancelled => self.process_ended(id, position, true, &mut events),
2009 }
2010 events
2011 }
2012
2013 fn process_started(&mut self, id: i32, position: LogicalPoint, events: &mut TouchEventBuffer) {
2014 self.active_touches.insert(TouchPoint { id, position });
2015
2016 let total = self.active_touches.len();
2017 if total == 1 {
2018 self.primary_touch_id = Some(id);
2020 self.gesture_state = GestureRecognitionState::Idle;
2021 events.push(MouseEvent::Pressed {
2022 position,
2023 button: PointerEventButton::Left,
2024 click_count: 0,
2025 touch_finger_id: id + 1,
2026 });
2027 } else if total == 2 {
2028 let finger_ids = self.active_touches.first_two_ids().unwrap_or((0, 0));
2030
2031 let primary_pos = self
2034 .primary_touch_id
2035 .and_then(|pid| self.active_touches.get(pid))
2036 .map(|tp| tp.position)
2037 .unwrap_or(position);
2038
2039 let (initial_distance, last_angle) =
2041 self.geometry_for(finger_ids).unwrap_or((0.0, euclid::Angle::zero()));
2042 self.gesture_state = GestureRecognitionState::TwoFingersDown {
2043 finger_ids,
2044 initial_distance,
2045 last_angle,
2046 };
2047
2048 events.push(MouseEvent::Released {
2049 position: primary_pos,
2050 button: PointerEventButton::Left,
2051 click_count: 0,
2052 touch_finger_id: id + 1,
2053 });
2054 }
2055 }
2057
2058 #[allow(clippy::collapsible_match)]
2059 fn process_moved(&mut self, id: i32, position: LogicalPoint, events: &mut TouchEventBuffer) {
2060 if let Some(tp) = self.active_touches.get_mut(id) {
2061 tp.position = position;
2062 }
2063
2064 let is_gesture_finger = self.is_gesture_finger(id);
2065
2066 match self.gesture_state {
2067 GestureRecognitionState::Idle => {
2068 if self.primary_touch_id == Some(id) {
2069 events.push(MouseEvent::Moved { position, touch_finger_id: id + 1 });
2070 }
2071 }
2072 GestureRecognitionState::TwoFingersDown {
2073 finger_ids,
2074 initial_distance,
2075 last_angle,
2076 } if is_gesture_finger => {
2077 if let Some((dist, angle)) = self.gesture_geometry() {
2078 let delta_dist = (dist - initial_distance).abs();
2079 let delta_angle = (angle - last_angle).signed().to_degrees().abs();
2080 if delta_dist > Self::PINCH_THRESHOLD || delta_angle > Self::ROTATION_THRESHOLD
2081 {
2082 self.gesture_state = GestureRecognitionState::Pinching {
2086 finger_ids,
2087 initial_distance: dist,
2088 last_scale: 1.0,
2089 last_angle: angle,
2090 };
2091
2092 let midpoint = self.gesture_midpoint().unwrap_or(position);
2093
2094 events.push(MouseEvent::PinchGesture {
2095 position: midpoint,
2096 delta: 0.0,
2097 phase: TouchPhase::Started,
2098 });
2099 events.push(MouseEvent::RotationGesture {
2100 position: midpoint,
2101 delta: 0.0,
2102 phase: TouchPhase::Started,
2103 });
2104 }
2105 }
2106 }
2107 GestureRecognitionState::Pinching {
2108 initial_distance, last_scale, last_angle, ..
2109 } if is_gesture_finger => {
2110 if let Some((dist, angle)) = self.gesture_geometry() {
2111 let midpoint = self.gesture_midpoint().unwrap_or(position);
2112
2113 let current_scale =
2114 if initial_distance > 0.0 { dist / initial_distance } else { 1.0 };
2115 let scale_delta = current_scale - last_scale;
2116
2117 let rotation_delta = (angle - last_angle).signed().to_degrees();
2120
2121 if let GestureRecognitionState::Pinching {
2123 last_scale: ref mut ls,
2124 last_angle: ref mut la,
2125 ..
2126 } = self.gesture_state
2127 {
2128 *ls = current_scale;
2129 *la = angle;
2130 }
2131
2132 events.push(MouseEvent::PinchGesture {
2133 position: midpoint,
2134 delta: scale_delta,
2135 phase: TouchPhase::Moved,
2136 });
2137 events.push(MouseEvent::RotationGesture {
2138 position: midpoint,
2139 delta: rotation_delta,
2140 phase: TouchPhase::Moved,
2141 });
2142 }
2143 }
2144 _ => {}
2145 }
2146 }
2147
2148 #[allow(clippy::collapsible_match)]
2149 fn process_ended(
2150 &mut self,
2151 id: i32,
2152 position: LogicalPoint,
2153 is_cancelled: bool,
2154 events: &mut TouchEventBuffer,
2155 ) {
2156 let is_gesture_finger = self.is_gesture_finger(id);
2158 let midpoint = self.gesture_midpoint().unwrap_or(position);
2159 self.active_touches.remove(id);
2160
2161 match self.gesture_state {
2162 GestureRecognitionState::Idle => {
2163 if self.primary_touch_id == Some(id) {
2164 self.primary_touch_id = None;
2165 events.push(MouseEvent::Released {
2166 position,
2167 button: PointerEventButton::Left,
2168 click_count: 0,
2169 touch_finger_id: id + 1,
2170 });
2171 events.push(MouseEvent::Exit);
2172 }
2173 }
2174 GestureRecognitionState::TwoFingersDown { .. } if is_gesture_finger => {
2175 self.gesture_state = GestureRecognitionState::Idle;
2176 if !is_cancelled {
2177 if let Some(remaining) = self.active_touches.first() {
2178 let remaining_pos = remaining.position;
2179 self.primary_touch_id = Some(remaining.id);
2180 events.push(MouseEvent::Pressed {
2181 position: remaining_pos,
2182 button: PointerEventButton::Left,
2183 click_count: 0,
2184 touch_finger_id: remaining.id + 1,
2185 });
2186 } else {
2187 self.primary_touch_id = None;
2188 events.push(MouseEvent::Exit);
2189 }
2190 } else {
2191 self.primary_touch_id = None;
2192 events.push(MouseEvent::Exit);
2193 }
2194 }
2195 GestureRecognitionState::Pinching { .. } if is_gesture_finger => {
2196 self.gesture_state = GestureRecognitionState::Idle;
2197
2198 let gesture_phase =
2199 if is_cancelled { TouchPhase::Cancelled } else { TouchPhase::Ended };
2200
2201 let remaining = if !is_cancelled {
2202 self.active_touches.first().map(|tp| (tp.id, tp.position))
2203 } else {
2204 None
2205 };
2206 if let Some((rid, _)) = remaining {
2207 self.primary_touch_id = Some(rid);
2208 } else {
2209 self.primary_touch_id = None;
2210 }
2211
2212 events.push(MouseEvent::PinchGesture {
2213 position: midpoint,
2214 delta: 0.0,
2215 phase: gesture_phase,
2216 });
2217 events.push(MouseEvent::RotationGesture {
2218 position: midpoint,
2219 delta: 0.0,
2220 phase: gesture_phase,
2221 });
2222
2223 if let Some((rid, rpos)) = remaining {
2224 events.push(MouseEvent::Pressed {
2225 position: rpos,
2226 button: PointerEventButton::Left,
2227 click_count: 0,
2228 touch_finger_id: rid + 1,
2229 });
2230 } else {
2231 events.push(MouseEvent::Exit);
2232 }
2233 }
2234 _ => {}
2235 }
2236 }
2237}
2238
2239#[cfg(test)]
2240mod touch_tests {
2241 extern crate alloc;
2242 use alloc::vec;
2243 use alloc::vec::Vec;
2244
2245 use super::*;
2246 use crate::lengths::LogicalPoint;
2247
2248 fn pt(x: f32, y: f32) -> LogicalPoint {
2249 euclid::point2(x, y)
2250 }
2251
2252 #[test]
2257 fn touch_map_insert_and_get() {
2258 let mut map = TouchMap::default();
2259 assert_eq!(map.len(), 0);
2260 map.insert(TouchPoint { id: 1, position: pt(10.0, 20.0) });
2261 assert_eq!(map.len(), 1);
2262 assert!(map.get(1).is_some());
2263 assert!((map.get(1).unwrap().position.x - 10.0).abs() < f32::EPSILON);
2264 assert!(map.get(2).is_none());
2265 }
2266
2267 #[test]
2268 fn touch_map_update_existing() {
2269 let mut map = TouchMap::default();
2270 map.insert(TouchPoint { id: 1, position: pt(10.0, 20.0) });
2271 map.insert(TouchPoint { id: 1, position: pt(30.0, 40.0) });
2272 assert_eq!(map.len(), 1);
2273 assert!((map.get(1).unwrap().position.x - 30.0).abs() < f32::EPSILON);
2274 }
2275
2276 #[test]
2277 fn touch_map_remove() {
2278 let mut map = TouchMap::default();
2279 map.insert(TouchPoint { id: 1, position: pt(10.0, 20.0) });
2280 map.insert(TouchPoint { id: 2, position: pt(30.0, 40.0) });
2281 assert_eq!(map.len(), 2);
2282 map.remove(1);
2283 assert_eq!(map.len(), 1);
2284 assert!(map.get(1).is_none());
2285 assert!(map.get(2).is_some());
2286 }
2287
2288 #[test]
2289 fn touch_map_remove_nonexistent() {
2290 let mut map = TouchMap::default();
2291 map.insert(TouchPoint { id: 1, position: pt(10.0, 20.0) });
2292 map.remove(99);
2293 assert_eq!(map.len(), 1);
2294 }
2295
2296 #[test]
2297 fn touch_map_capacity() {
2298 let mut map = TouchMap::default();
2299 for i in 0..MAX_TRACKED_TOUCHES {
2300 map.insert(TouchPoint { id: i as i32, position: pt(i as f32, 0.0) });
2301 }
2302 assert_eq!(map.len(), MAX_TRACKED_TOUCHES);
2303 map.insert(TouchPoint { id: 99, position: pt(99.0, 0.0) });
2305 assert_eq!(map.len(), MAX_TRACKED_TOUCHES);
2306 assert!(map.get(99).is_none());
2307 }
2308
2309 #[test]
2310 fn touch_map_first_two_ids() {
2311 let mut map = TouchMap::default();
2312 assert!(map.first_two_ids().is_none());
2313 map.insert(TouchPoint { id: 5, position: pt(0.0, 0.0) });
2314 assert!(map.first_two_ids().is_none());
2315 map.insert(TouchPoint { id: 10, position: pt(0.0, 0.0) });
2316 assert_eq!(map.first_two_ids(), Some((5, 10)));
2317 }
2318
2319 #[test]
2320 fn touch_map_first() {
2321 let mut map = TouchMap::default();
2322 assert!(map.first().is_none());
2323 map.insert(TouchPoint { id: 7, position: pt(1.0, 2.0) });
2324 let tp = map.first().unwrap();
2325 assert_eq!(tp.id, 7);
2326 assert!((tp.position.x - 1.0).abs() < f32::EPSILON);
2327 }
2328
2329 #[test]
2330 fn touch_map_get_mut() {
2331 let mut map = TouchMap::default();
2332 map.insert(TouchPoint { id: 1, position: pt(0.0, 0.0) });
2333 map.get_mut(1).unwrap().position = pt(5.0, 6.0);
2334 assert!((map.get(1).unwrap().position.x - 5.0).abs() < f32::EPSILON);
2335 }
2336
2337 #[derive(Debug, PartialEq)]
2342 enum Ev {
2343 Pressed(f32, f32),
2344 Released(f32, f32),
2345 Moved(f32, f32),
2346 Exit,
2347 PinchStarted,
2348 PinchMoved(f32),
2349 PinchEnded,
2350 PinchCancelled,
2351 RotationStarted,
2352 RotationMoved(f32),
2353 RotationEnded,
2354 RotationCancelled,
2355 }
2356
2357 fn classify(events: &TouchEventBuffer) -> Vec<Ev> {
2358 events
2359 .clone()
2360 .into_iter()
2361 .map(|e| match e {
2362 MouseEvent::Pressed { position, .. } => Ev::Pressed(position.x, position.y),
2363 MouseEvent::Released { position, .. } => Ev::Released(position.x, position.y),
2364 MouseEvent::Moved { position, .. } => Ev::Moved(position.x, position.y),
2365 MouseEvent::Exit => Ev::Exit,
2366 MouseEvent::PinchGesture { delta, phase, .. } => match phase {
2367 TouchPhase::Started => Ev::PinchStarted,
2368 TouchPhase::Moved => Ev::PinchMoved(delta),
2369 TouchPhase::Ended => Ev::PinchEnded,
2370 TouchPhase::Cancelled => Ev::PinchCancelled,
2371 },
2372 MouseEvent::RotationGesture { delta, phase, .. } => match phase {
2373 TouchPhase::Started => Ev::RotationStarted,
2374 TouchPhase::Moved => Ev::RotationMoved(delta),
2375 TouchPhase::Ended => Ev::RotationEnded,
2376 TouchPhase::Cancelled => Ev::RotationCancelled,
2377 },
2378 _ => panic!("unexpected event: {:?}", e),
2379 })
2380 .collect()
2381 }
2382
2383 #[test]
2388 fn single_finger_press_move_release() {
2389 let mut state = TouchState::default();
2390
2391 let evs = state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2392 assert_eq!(classify(&evs), vec![Ev::Pressed(100.0, 200.0)]);
2393
2394 let evs = state.process(1, pt(110.0, 200.0), TouchPhase::Moved);
2395 assert_eq!(classify(&evs), vec![Ev::Moved(110.0, 200.0)]);
2396
2397 let evs = state.process(1, pt(110.0, 200.0), TouchPhase::Ended);
2398 assert_eq!(classify(&evs), vec![Ev::Released(110.0, 200.0), Ev::Exit]);
2399 }
2400
2401 #[test]
2402 fn single_finger_cancel() {
2403 let mut state = TouchState::default();
2404
2405 state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2406
2407 let evs = state.process(1, pt(100.0, 200.0), TouchPhase::Cancelled);
2408 assert_eq!(classify(&evs), vec![Ev::Released(100.0, 200.0), Ev::Exit]);
2409 }
2410
2411 #[test]
2412 fn non_primary_move_ignored() {
2413 let mut state = TouchState::default();
2414 state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2416
2417 let evs = state.process(99, pt(50.0, 50.0), TouchPhase::Moved);
2419 assert!(classify(&evs).is_empty());
2420 }
2421
2422 #[test]
2427 fn two_fingers_synthesize_release_then_gesture() {
2428 let mut state = TouchState::default();
2429
2430 let evs = state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2432 assert_eq!(classify(&evs), vec![Ev::Pressed(100.0, 200.0)]);
2433
2434 let evs = state.process(2, pt(200.0, 200.0), TouchPhase::Started);
2436 assert_eq!(classify(&evs), vec![Ev::Released(100.0, 200.0)]);
2437 assert!(matches!(state.gesture_state, GestureRecognitionState::TwoFingersDown { .. }));
2438
2439 let evs = state.process(2, pt(220.0, 200.0), TouchPhase::Moved);
2441 assert_eq!(classify(&evs), vec![Ev::PinchStarted, Ev::RotationStarted]);
2442 assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2443 }
2444
2445 #[test]
2446 fn two_fingers_below_threshold_no_gesture() {
2447 let mut state = TouchState::default();
2448
2449 state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2450 state.process(2, pt(200.0, 200.0), TouchPhase::Started);
2451
2452 let evs = state.process(2, pt(202.0, 200.0), TouchPhase::Moved);
2454 assert!(classify(&evs).is_empty());
2455 assert!(matches!(state.gesture_state, GestureRecognitionState::TwoFingersDown { .. }));
2456 }
2457
2458 #[test]
2459 fn pinch_produces_scale_deltas() {
2460 let mut state = TouchState::default();
2461
2462 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2464 state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2465
2466 state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2468 assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2469
2470 let evs = state.process(2, pt(180.0, 0.0), TouchPhase::Moved);
2474 let classified = classify(&evs);
2475 assert_eq!(classified.len(), 2);
2476 if let Ev::PinchMoved(delta) = classified[0] {
2477 assert!((delta - 0.5).abs() < 0.01, "expected ~0.5, got {}", delta);
2478 } else {
2479 panic!("expected PinchMoved, got {:?}", classified[0]);
2480 }
2481 }
2482
2483 #[test]
2484 fn rotation_produces_correct_deltas() {
2485 let mut state = TouchState::default();
2486
2487 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2490 state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2491
2492 state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2494 assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2495
2496 let evs = state.process(2, pt(70.7, 70.7), TouchPhase::Moved);
2501 let classified = classify(&evs);
2502 assert_eq!(classified.len(), 2);
2503 if let Ev::RotationMoved(delta) = classified[1] {
2504 assert!((delta - 45.0).abs() < 1.0, "expected ~45.0 (clockwise), got {}", delta);
2505 } else {
2506 panic!("expected RotationMoved, got {:?}", classified[1]);
2507 }
2508 }
2509
2510 #[test]
2511 fn rotation_across_180_degree_boundary() {
2512 let mut state = TouchState::default();
2513
2514 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2517 state.process(2, pt(-100.0, -10.0), TouchPhase::Started);
2518
2519 state.process(2, pt(-120.0, -10.0), TouchPhase::Moved);
2521 assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2522
2523 let evs = state.process(2, pt(-100.0, 10.0), TouchPhase::Moved);
2528 let classified = classify(&evs);
2529 if let Ev::RotationMoved(delta) = classified[1] {
2530 assert!(
2531 delta.abs() < 20.0,
2532 "rotation should be a small delta (~11°), got {} (discontinuity!)",
2533 delta
2534 );
2535 } else {
2536 panic!("expected RotationMoved, got {:?}", classified[1]);
2537 }
2538 }
2539
2540 #[test]
2545 fn pinch_end_with_remaining_finger() {
2546 let mut state = TouchState::default();
2547
2548 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2549 state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2550 state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2552
2553 let evs = state.process(2, pt(120.0, 0.0), TouchPhase::Ended);
2555 let classified = classify(&evs);
2556 assert_eq!(classified, vec![Ev::PinchEnded, Ev::RotationEnded, Ev::Pressed(0.0, 0.0)]);
2557 assert!(matches!(state.gesture_state, GestureRecognitionState::Idle));
2558 assert_eq!(state.primary_touch_id, Some(1));
2559 }
2560
2561 #[test]
2562 fn pinch_cancel_emits_cancelled_and_exit() {
2563 let mut state = TouchState::default();
2564
2565 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2566 state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2567 state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2568
2569 let evs = state.process(2, pt(120.0, 0.0), TouchPhase::Cancelled);
2571 let classified = classify(&evs);
2572 assert_eq!(classified, vec![Ev::PinchCancelled, Ev::RotationCancelled, Ev::Exit]);
2573 assert!(state.primary_touch_id.is_none());
2574 }
2575
2576 #[test]
2577 fn two_fingers_down_lift_before_threshold_returns_to_idle() {
2578 let mut state = TouchState::default();
2579
2580 state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2581 state.process(2, pt(200.0, 200.0), TouchPhase::Started);
2582 assert!(matches!(state.gesture_state, GestureRecognitionState::TwoFingersDown { .. }));
2583
2584 let evs = state.process(2, pt(200.0, 200.0), TouchPhase::Ended);
2586 let classified = classify(&evs);
2587 assert_eq!(classified, vec![Ev::Pressed(100.0, 200.0)]);
2589 assert!(matches!(state.gesture_state, GestureRecognitionState::Idle));
2590 assert_eq!(state.primary_touch_id, Some(1));
2591 }
2592
2593 #[test]
2594 fn two_fingers_down_cancel_both_emits_exit() {
2595 let mut state = TouchState::default();
2596
2597 state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2598 state.process(2, pt(200.0, 200.0), TouchPhase::Started);
2599
2600 let evs = state.process(2, pt(200.0, 200.0), TouchPhase::Cancelled);
2602 assert_eq!(classify(&evs), vec![Ev::Exit]);
2603
2604 let evs = state.process(1, pt(100.0, 200.0), TouchPhase::Cancelled);
2606 assert!(classify(&evs).is_empty());
2607 }
2608
2609 #[test]
2614 fn third_finger_ignored_for_gesture() {
2615 let mut state = TouchState::default();
2616
2617 state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2618 state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2619
2620 let evs = state.process(3, pt(50.0, 50.0), TouchPhase::Started);
2622 assert!(classify(&evs).is_empty());
2623 assert_eq!(state.active_touches.len(), 3);
2624 }
2625
2626 #[test]
2631 fn euclid_angle_signed_wrapping() {
2632 use euclid::Angle;
2633 let wrap = |deg: f32| Angle::degrees(deg).signed().to_degrees();
2634 assert!(wrap(0.0).abs() < f32::EPSILON);
2635 assert!((wrap(180.0) - 180.0).abs() < 0.01);
2636 assert!((wrap(181.0) - (-179.0)).abs() < 0.01);
2637 assert!((wrap(-181.0) - 179.0).abs() < 0.01);
2638 assert!(wrap(360.0).abs() < 0.01);
2639 }
2640
2641 #[test]
2642 fn zero_distance_fingers_no_division_by_zero() {
2643 let mut state = TouchState::default();
2644
2645 state.process(1, pt(100.0, 100.0), TouchPhase::Started);
2647 state.process(2, pt(100.0, 100.0), TouchPhase::Started);
2648 assert!(matches!(state.gesture_state, GestureRecognitionState::TwoFingersDown { .. }));
2649
2650 let evs = state.process(2, pt(120.0, 100.0), TouchPhase::Moved);
2652 assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2653 let classified = classify(&evs);
2654 assert_eq!(classified.len(), 2);
2655 assert_eq!(classified[0], Ev::PinchStarted);
2656
2657 let evs = state.process(2, pt(140.0, 100.0), TouchPhase::Moved);
2660 let classified = classify(&evs);
2661 if let Ev::PinchMoved(delta) = classified[0] {
2662 assert!(delta.is_finite(), "scale delta should be finite, got {}", delta);
2663 } else {
2664 panic!("expected PinchMoved, got {:?}", classified[0]);
2665 }
2666 }
2667}
2668
2669#[cfg(test)]
2670mod tests {
2671 use super::*;
2672 extern crate alloc;
2673
2674 #[test]
2675 fn test_to_string() {
2676 let test_cases = [
2677 (
2678 "a",
2679 KeyboardModifiers { alt: false, control: true, shift: false, meta: false },
2680 false,
2681 false,
2682 "⌘A",
2683 "Ctrl+A",
2684 "Ctrl+A",
2685 ),
2686 (
2687 "a",
2688 KeyboardModifiers { alt: true, control: true, shift: true, meta: true },
2689 false,
2690 false,
2691 "⌃⌥⇧⌘A",
2692 "Win+Ctrl+Alt+Shift+A",
2693 "Super+Ctrl+Alt+Shift+A",
2694 ),
2695 (
2696 "\u{001b}",
2697 KeyboardModifiers { alt: false, control: true, shift: true, meta: false },
2698 false,
2699 false,
2700 "⇧⌘Escape",
2701 "Ctrl+Shift+Escape",
2702 "Ctrl+Shift+Escape",
2703 ),
2704 (
2705 "+",
2706 KeyboardModifiers { alt: false, control: true, shift: false, meta: false },
2707 true,
2708 false,
2709 "⌘+",
2710 "Ctrl++",
2711 "Ctrl++",
2712 ),
2713 (
2714 "a",
2715 KeyboardModifiers { alt: true, control: true, shift: false, meta: false },
2716 false,
2717 true,
2718 "⌘A",
2719 "Ctrl+A",
2720 "Ctrl+A",
2721 ),
2722 (
2723 "",
2724 KeyboardModifiers { alt: false, control: true, shift: false, meta: false },
2725 false,
2726 false,
2727 "",
2728 "",
2729 "",
2730 ),
2731 (
2732 "\u{000a}",
2733 KeyboardModifiers { alt: false, control: false, shift: false, meta: false },
2734 false,
2735 false,
2736 "Return",
2737 "Return",
2738 "Return",
2739 ),
2740 (
2741 "\u{0009}",
2742 KeyboardModifiers { alt: false, control: false, shift: false, meta: false },
2743 false,
2744 false,
2745 "Tab",
2746 "Tab",
2747 "Tab",
2748 ),
2749 (
2750 "\u{0020}",
2751 KeyboardModifiers { alt: false, control: false, shift: false, meta: false },
2752 false,
2753 false,
2754 "Space",
2755 "Space",
2756 "Space",
2757 ),
2758 (
2759 "\u{0008}",
2760 KeyboardModifiers { alt: false, control: false, shift: false, meta: false },
2761 false,
2762 false,
2763 "Backspace",
2764 "Backspace",
2765 "Backspace",
2766 ),
2767 ];
2768
2769 for (
2770 key,
2771 modifiers,
2772 ignore_shift,
2773 ignore_alt,
2774 _expected_macos,
2775 _expected_windows,
2776 _expected_linux,
2777 ) in test_cases
2778 {
2779 let shortcut = make_keys(key.into(), modifiers, ignore_shift, ignore_alt);
2780
2781 use crate::alloc::string::ToString;
2782 let result = shortcut.to_string();
2783
2784 #[cfg(target_os = "macos")]
2785 assert_eq!(result.as_str(), _expected_macos, "Failed for key: {:?}", key);
2786
2787 #[cfg(target_os = "windows")]
2788 assert_eq!(result.as_str(), _expected_windows, "Failed for key: {:?}", key);
2789
2790 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
2791 assert_eq!(result.as_str(), _expected_linux, "Failed for key: {:?}", key);
2792 }
2793 }
2794
2795 #[test]
2796 fn test_from_parts_valid() {
2797 let f5_key = alloc::string::String::from(char::from(key_codes::Key::F5));
2798 let ret_key = alloc::string::String::from(char::from(key_codes::Key::Return));
2799
2800 let cases: &[(&str, &[&str], &str, KeyboardModifiers, bool, bool)] = &[
2802 (
2803 "Control+A",
2804 &["Control", "A"],
2805 "a",
2806 KeyboardModifiers { control: true, ..Default::default() },
2807 false,
2808 false,
2809 ),
2810 (
2811 "Control+Shift+A",
2812 &["Control", "Shift", "A"],
2813 "a",
2814 KeyboardModifiers { control: true, shift: true, ..Default::default() },
2815 false,
2816 false,
2817 ),
2818 (
2819 "Control+Shift?+Z (explicit ignore_shift)",
2820 &["Control", "Shift?", "Z"],
2821 "z",
2822 KeyboardModifiers { control: true, ..Default::default() },
2823 true,
2824 false,
2825 ),
2826 (
2827 "Control+Alt?+A (ignore_alt)",
2828 &["Control", "Alt?", "A"],
2829 "a",
2830 KeyboardModifiers { control: true, ..Default::default() },
2831 false,
2832 true,
2833 ),
2834 (
2835 "F5 alone (special key)",
2836 &["F5"],
2837 &f5_key,
2838 KeyboardModifiers::default(),
2839 false,
2840 false,
2841 ),
2842 ("Return key", &["Return"], &ret_key, KeyboardModifiers::default(), false, false),
2843 (
2844 "Control+Plus (LocalizedShiftable → auto ignore_shift)",
2845 &["Control", "Plus"],
2846 "+",
2847 KeyboardModifiers { control: true, ..Default::default() },
2848 true,
2849 false,
2850 ),
2851 (
2852 "Control+'+' (literal, no auto ignore_shift)",
2853 &["Control", "+"],
2854 "+",
2855 KeyboardModifiers { control: true, ..Default::default() },
2856 false,
2857 false,
2858 ),
2859 (
2860 "Control+Shift+Alt+A (all modifiers)",
2861 &["Control", "Shift", "Alt", "A"],
2862 "a",
2863 KeyboardModifiers { control: true, shift: true, alt: true, ..Default::default() },
2864 false,
2865 false,
2866 ),
2867 ("empty input → Keys::default()", &[], "", KeyboardModifiers::default(), false, false),
2868 (
2869 "Control+€ (unicode literal)",
2870 &["Control", "€"],
2871 "€",
2872 KeyboardModifiers { control: true, ..Default::default() },
2873 false,
2874 false,
2875 ),
2876 (
2877 "Control+é (lowercase literal)",
2878 &["Control", "é"],
2879 "é",
2880 KeyboardModifiers { control: true, ..Default::default() },
2881 false,
2882 false,
2883 ),
2884 ("A alone (named key)", &["A"], "a", KeyboardModifiers::default(), false, false),
2885 (
2886 "a alone (literal fallback, same result as named A)",
2887 &["a"],
2888 "a",
2889 KeyboardModifiers::default(),
2890 false,
2891 false,
2892 ),
2893 ];
2894
2895 for (desc, parts, expected_key, mods, is, ia) in cases {
2896 let result =
2897 Keys::from_parts(parts.iter().copied()).unwrap_or_else(|e| panic!("{desc}: {e}"));
2898 assert_eq!(result, make_keys((*expected_key).into(), *mods, *is, *ia), "{desc}");
2899 }
2900 }
2901
2902 #[test]
2903 fn test_from_parts_invalid() {
2904 use super::KeysParseErrorInner;
2905 let cases: &[(&str, &[&str], KeysParseError)] = &[
2906 ("lowercase 'control'", &["control", "A"], KeysParseError(KeysParseErrorInner::MultipleKeys)),
2908 ("uppercase 'CONTROL'", &["CONTROL", "A"], KeysParseError(KeysParseErrorInner::MultipleKeys)),
2909 ("'Ctrl' alias", &["Ctrl", "A"], KeysParseError(KeysParseErrorInner::MultipleKeys)),
2910 ("'ctrl' alias", &["ctrl", "A"], KeysParseError(KeysParseErrorInner::MultipleKeys)),
2911 ("'Win' alias", &["Win", "A"], KeysParseError(KeysParseErrorInner::MultipleKeys)),
2913 ("'Super' alias", &["Super", "A"], KeysParseError(KeysParseErrorInner::MultipleKeys)),
2914 ("modifiers only", &["Control", "Shift"], KeysParseError(KeysParseErrorInner::NoKey)),
2916 ("two keys", &["A", "B"], KeysParseError(KeysParseErrorInner::MultipleKeys)),
2918 (
2920 "multi-char unknown",
2921 &["Control", "Foobar"],
2922 KeysParseError(KeysParseErrorInner::MultipleGraphemeClusters("Foobar".into())),
2923 ),
2924 (
2925 "two-char literal",
2926 &["Control", "ab"],
2927 KeysParseError(KeysParseErrorInner::MultipleGraphemeClusters("ab".into())),
2928 ),
2929 (
2930 "lowercase 'return' (not a named key)",
2931 &["return"],
2932 KeysParseError(KeysParseErrorInner::MultipleGraphemeClusters("return".into())),
2933 ),
2934 (
2936 "uppercase literal É",
2937 &["Control", "É"],
2938 KeysParseError(KeysParseErrorInner::NotLowercase("É".into())),
2939 ),
2940 (
2942 "Shift + Shift?",
2943 &["Shift", "Shift?", "A"],
2944 KeysParseError(KeysParseErrorInner::IncompatibleModifiers("Shift and Shift? cannot be combined".into())),
2945 ),
2946 (
2947 "Alt + Alt?",
2948 &["Alt", "Alt?", "A"],
2949 KeysParseError(KeysParseErrorInner::IncompatibleModifiers("Alt and Alt? cannot be combined".into())),
2950 ),
2951 (
2952 "Shift + LocalizedShiftable key (Plus)",
2953 &["Control", "Shift", "Plus"],
2954 KeysParseError(KeysParseErrorInner::IncompatibleModifiers(
2955 "Key bindings involving Plus ignore Shift to support different keyboard layouts; remove Shift".into(),
2956 )),
2957 ),
2958 ];
2959
2960 for (desc, parts, expected_err) in cases {
2961 let result = Keys::from_parts(parts.iter().copied());
2962 assert!(result.is_err(), "{desc}: expected error, got {result:?}");
2963 assert_eq!(&result.unwrap_err(), expected_err, "{desc}");
2964 }
2965 }
2966
2967 #[test]
2968 fn test_from_parts_matching() {
2969 let cases: &[(&str, &[&str], &str, KeyboardModifiers, bool)] = &[
2971 (
2972 "Control+A matches",
2973 &["Control", "A"],
2974 "a",
2975 KeyboardModifiers { control: true, ..Default::default() },
2976 true,
2977 ),
2978 (
2979 "Control+A wrong key",
2980 &["Control", "A"],
2981 "b",
2982 KeyboardModifiers { control: true, ..Default::default() },
2983 false,
2984 ),
2985 (
2986 "Control+A wrong modifier",
2987 &["Control", "A"],
2988 "a",
2989 KeyboardModifiers { alt: true, ..Default::default() },
2990 false,
2991 ),
2992 (
2993 "Shift? matches with shift",
2994 &["Control", "Shift?", "Z"],
2995 "z",
2996 KeyboardModifiers { control: true, shift: true, ..Default::default() },
2997 true,
2998 ),
2999 (
3000 "Shift? matches without shift",
3001 &["Control", "Shift?", "Z"],
3002 "z",
3003 KeyboardModifiers { control: true, ..Default::default() },
3004 true,
3005 ),
3006 ];
3007
3008 for (desc, parts, text, mods, expected) in cases {
3009 let k =
3010 Keys::from_parts(parts.iter().copied()).unwrap_or_else(|e| panic!("{desc}: {e}"));
3011 let event = KeyEvent { text: (*text).into(), modifiers: *mods, ..Default::default() };
3012 assert_eq!(k.matches(&event), *expected, "{desc}");
3013 }
3014
3015 let return_char: char = key_codes::Key::Return.into();
3017 let k = Keys::from_parts(["Return"]).unwrap();
3018 let event = KeyEvent {
3019 text: SharedString::from(alloc::string::String::from(return_char)),
3020 modifiers: KeyboardModifiers::default(),
3021 ..Default::default()
3022 };
3023 assert!(k.matches(&event), "Return key should match Return event");
3024 }
3025}