Skip to main content

i_slint_core/
input.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4/*! Module handling mouse events
5*/
6#![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/// A mouse or touch event
25///
26/// The only difference with [`crate::platform::WindowEvent`] is that it uses untyped `Point`
27/// TODO: merge with platform::WindowEvent
28#[repr(C)]
29#[derive(Debug, Clone, PartialEq)]
30pub enum MouseEvent {
31    /// The mouse or finger was pressed
32    Pressed {
33        /// The position of the pointer when the event happened.
34        position: LogicalPoint,
35        /// The button that was pressed.
36        button: PointerEventButton,
37        /// The current click count reported for this press.
38        click_count: u8,
39        /// Whether the event originated from touch input.
40        is_touch: bool,
41    },
42    /// The mouse or finger was released
43    Released {
44        /// The position of the pointer when the event happened.
45        position: LogicalPoint,
46        /// The button that was released.
47        button: PointerEventButton,
48        /// The current click count reported for this release.
49        click_count: u8,
50        /// Whether the event originated from touch input.
51        is_touch: bool,
52    },
53    /// The position of the pointer has changed
54    Moved {
55        /// The new position of the pointer.
56        position: LogicalPoint,
57        /// Whether the event originated from touch input.
58        is_touch: bool,
59    },
60    /// Wheel was operated.
61    Wheel {
62        /// The position of the pointer when the event happened.
63        position: LogicalPoint,
64        /// The horizontal scroll delta in logical pixels.
65        delta_x: Coord,
66        /// The vertical scroll delta in logical pixels.
67        delta_y: Coord,
68        /// The gesture phase reported for the wheel event.
69        phase: TouchPhase,
70    },
71    /// The mouse is being dragged over this item.
72    /// [`InputEventResult::EventIgnored`] means that the item does not handle the drag operation
73    /// and [`InputEventResult::EventAccepted`] means that the item can accept it.
74    DragMove(DropEvent),
75    /// The mouse is released while dragging over this item.
76    Drop(DropEvent),
77    /// A platform-recognized pinch gesture (macOS/iOS trackpad, Qt).
78    PinchGesture {
79        /// The focal position of the gesture.
80        position: LogicalPoint,
81        /// The incremental scale delta for this gesture update.
82        delta: f32,
83        /// The gesture phase reported by the platform.
84        phase: TouchPhase,
85    },
86    /// A platform-recognized rotation gesture (macOS/iOS trackpad, Qt).
87    RotationGesture {
88        /// The focal position of the gesture.
89        position: LogicalPoint,
90        /// The incremental rotation in degrees, where positive means clockwise.
91        delta: f32,
92        /// The gesture phase reported by the platform.
93        phase: TouchPhase,
94    },
95    /// The mouse exited the item or component
96    Exit,
97}
98
99impl MouseEvent {
100    /// The flag for when event generated from touch
101    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    /// The position of the cursor for this event, if any
114    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    /// Translate the position by the given value
130    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    /// Transform the position by the given item transform.
152    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    /// Set the click count of the pressed or released event
176    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/// Phase of a touch, gesture event or wheel event.
187/// A touchpad is recognized as wheel event and therefore
188/// we need to find out when the touch event starts and ends
189#[repr(u8)]
190#[derive(Debug, Clone, Copy, PartialEq)]
191pub enum TouchPhase {
192    /// The gesture began (e.g., first finger touched or platform gesture started).
193    Started,
194    /// The gesture is ongoing (e.g., fingers moved or platform gesture updated).
195    Moved,
196    /// The gesture completed normally.
197    Ended,
198    /// The gesture was cancelled (e.g., interrupted by the system) or the mouse wheel was used
199    Cancelled,
200}
201
202/// This value is returned by the `input_event` function of an Item
203/// to notify the run-time about how the event was handled and
204/// what the next steps are.
205/// See [`crate::items::ItemVTable::input_event`].
206#[repr(u8)]
207#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
208pub enum InputEventResult {
209    /// The event was accepted. This may result in additional events, for example
210    /// accepting a mouse move will result in a MouseExit event later.
211    EventAccepted,
212    /// The event was ignored.
213    #[default]
214    EventIgnored,
215    /// All further mouse events need to be sent to this item or component
216    GrabMouse,
217    /// Will start a drag operation. Can only be returned from a [`crate::items::DragArea`] item.
218    StartDrag,
219}
220
221/// This value is returned by the `input_event_filter_before_children` function, which
222/// can specify how to further process the event.
223/// See [`crate::items::ItemVTable::input_event_filter_before_children`].
224#[repr(C)]
225#[derive(Debug, Copy, Clone, PartialEq, Default)]
226pub enum InputEventFilterResult {
227    /// The event is going to be forwarded to children, then the [`crate::items::ItemVTable::input_event`]
228    /// function is called
229    #[default]
230    ForwardEvent,
231    /// The event will be forwarded to the children, but the [`crate::items::ItemVTable::input_event`] is not
232    /// going to be called for this item
233    ForwardAndIgnore,
234    /// Just like `ForwardEvent`, but even in the case that children grabs the mouse, this function
235    /// will still be called for further events
236    ForwardAndInterceptGrab,
237    /// The event will not be forwarded to children, if a child already had the grab, the
238    /// grab will be cancelled with a [`MouseEvent::Exit`] event
239    Intercept,
240    /// The event will be forwarded to the children with a delay (in milliseconds), unless it is
241    /// being intercepted.
242    /// This is what happens when the flickable wants to delay the event.
243    /// This should only be used for Press event, and the event will be sent after the delay, or
244    /// if a release event is seen before that delay
245    /// If any other component is handling the event it will be not handled by the component returned this result
246    //(Can't use core::time::Duration because it is not repr(c))
247    DelayForwarding(u64),
248}
249
250/// This module contains the constant character code used to represent the keys.
251#[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            /// The `Key` enum is used to map a specific key by name e.g. `Key::Control` to an
261            /// internal used unicode representation. The enum is convertible to [`std::char`] and [`slint::SharedString`](`crate::SharedString`).
262            /// Use this with [`slint::platform::WindowEvent`](`crate::platform::WindowEvent`) to supply key events to Slint's platform abstraction.
263            ///
264            /// # Example
265            ///
266            /// Send an tab key press event to a window
267            ///
268            /// ```
269            /// use slint::platform::{WindowEvent, Key};
270            /// fn send_tab_pressed(window: &slint::Window) {
271            ///     window.dispatch_event(WindowEvent::KeyPressed { text: Key::Tab.into() });
272            /// }
273            /// ```
274            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/// Internal struct to maintain the pressed/released state of the keys that
298/// map to keyboard modifiers.
299#[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    /// Updates a flag of the modifiers if the key of the given text is pressed.
314    /// Returns an updated modifier if detected; None otherwise;
315    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            // Encoded keyboard modifiers must appear as individual key events. This could
330            // be relaxed by implementing a string split, but right now WindowEvent::KeyPressed
331            // holds only a single char.
332            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        // Windows treats Ctrl+Alt as implying AltGr, but not vice-versa
358        // Unfortunately, our different backends produce different key combinations here.
359        //
360        // ## Qt
361        // Qt always sends Ctrl + Alt instead of AltGr, and does not tell us whether this
362        // was interpreted as AltGr or not. So with Qt we have no way of telling whether
363        // AltGr is pressed, and we have to assume that it is pressed whenever Ctrl + Alt is pressed.
364        // In that case the `text_without_modifiers` is also not set.
365        //
366        // ## Winit
367        // Winit sends the actual Ctrl/Alt/AltGr keypresses correctly.
368        // With winit we can detect whether ctrl+alt actually caused a AltGr conversion or not,
369        // by checking whether the text_without_modifiers is different from the event text.
370        //
371        // ## Wasm
372        // Winit on the web for some reasons sends first a Ctrl and then AltGr event when only AltGr
373        // is pressed.
374        // So there we need to get rid of the additional Ctrl event whenever AltGr is pressed.
375        #[cfg(target_os = "windows")]
376        {
377            // Non-web windows (Usually winit or Qt)
378            if !self.altgr && self.control() && self.alt() {
379                // AltGr is not pressed, but Ctrl+Alt is pressed.
380                // Try to detect if an AltGr conversion occured.
381                // If so, disable Ctrl and Alt
382                //
383                // On platforms that don't provide text_without_modifiers, fall back to a simple
384                // heuristic that assumes A-Z & 0-9 are not produced with AltGr, but all other keys are.
385                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            // Non-native windows (e.g. Winit on the web)
400            // This currently injects additional Ctrl events, so remove those if AltGr is
401            // pressed.
402            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/// The `Keys` type is the Rust representation of Slint's `keys` primitive type.
429///
430/// It can be created with the `@keys` macro in Slint and defines which key event(s) activate a KeyBinding.
431///
432/// See also the Slint documentation on [Key Bindings](slint:KeyBindingOverview).
433#[derive(Clone, Eq, PartialEq, Default)]
434#[repr(C)]
435pub struct Keys {
436    inner: KeysInner,
437}
438
439/// Re-exported in private_unstable_api to create a Keys struct.
440pub 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/// Internal representation of the `Keys` type.
489/// This is semver exempt and is only used to set up the native menu in the backends.
490#[derive(PartialEq, Eq, Clone, Default)]
491#[repr(C)]
492pub struct KeysInner {
493    /// The `key` used to trigger the shortcut
494    ///
495    /// Note: This is currently converted to lowercase when the shortcut is created!
496    pub key: SharedString,
497    /// `KeyboardModifier`s that need to be pressed for the shortcut to fire
498    pub modifiers: KeyboardModifiers,
499    /// Whether to ignore shift state when matching the shortcut
500    pub ignore_shift: bool,
501    /// Whether to ignore alt state when matching the shortcut
502    pub ignore_alt: bool,
503}
504
505impl KeysInner {
506    /// Private access to the KeysInner for a given Keys value.
507    pub fn from_pub(keys: &Keys) -> &Self {
508        &keys.inner
509    }
510}
511
512impl Keys {
513    /// Check whether a `Keys` can be triggered by the given `KeyEvent`
514    pub(crate) fn matches(&self, key_event: &KeyEvent) -> bool {
515        let inner = &self.inner;
516        // An empty Keys is never triggered, even if the modifiers match.
517        if inner.key.is_empty() {
518            return false;
519        }
520
521        // TODO: Should this check the event_type and only match on KeyReleased?
522        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        // Note: The shortcut's key is already in lowercase and NFC-normalized
530        // (by the compiler and backends respectively), so we only need to
531        // lowercase the event text. Backends are expected to NFC-normalize
532        // key event text before dispatching.
533        //
534        // This improves our handling of CapsLock and Shift, as the event text will be in uppercase
535        // if caps lock is active, even if shift is not pressed.
536        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                        // Use $qt as a marker - if it exists, generate the check
551                        $char => {
552                            let _ = stringify!($($qt)|*); // Use $qt to enable this branch
553                            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    /// Converts the [`Keys`] to a string that looks native on the current platform.
573    ///
574    /// For example, the shortcut created with `@keys(Meta + Control + A)`
575    /// will be converted like this:
576    /// - **macOS**: `⌃⌘A`
577    /// - **Windows**: `Win+Ctrl+A`
578    /// - **Linux**: `Super+Ctrl+A`
579    ///
580    /// Note that this functions output is best-effort and may be adjusted/improved at any time,
581    /// do not rely on this output to be stable!
582    //
583    // References for implementation
584    // - macOS: <https://developer.apple.com/design/human-interface-guidelines/keyboards>
585    // - Windows: <https://learn.microsoft.com/en-us/windows/apps/design/input/keyboard-accelerators>
586    // - Linux: <https://developer.gnome.org/hig/guidelines/keyboard.html>
587    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            // Slint remaps modifiers on macOS: control → Command, meta → Control
595            // From Apple's documentation:
596            //
597            // List modifier keys in the correct order.
598            // If you use more than one modifier key in a custom shortcut, always list them in this order:
599            //  Control, Option, Shift, Command
600            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            // TODO: These should probably be translated, but better to have at least
616            // platform-local names than nothing.
617            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    /// Formats the keyboard shortcut so that the output would be accepted by the @keys macro in Slint.
647    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
648        let inner = &self.inner;
649        // Make sure to keep this in sync with the implemenation in compiler/langtype.rs
650        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/// This enum defines the different kinds of key events that can happen.
684#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
685#[repr(u8)]
686pub enum KeyEventType {
687    /// A key on a keyboard was pressed.
688    #[default]
689    KeyPressed = 0,
690    /// A key on a keyboard was released.
691    KeyReleased = 1,
692    /// The input method updates the currently composed text. The KeyEvent's text field is the pre-edit text and
693    /// composition_selection specifies the placement of the cursor within the pre-edit text.
694    UpdateComposition = 2,
695    /// The input method replaces the currently composed text with the final result of the composition.
696    CommitComposition = 3,
697}
698
699#[derive(Default)]
700/// This struct is used to pass key events to the runtime.
701pub struct InternalKeyEvent {
702    /// That's the public type with only public fields
703    pub key_event: KeyEvent,
704    /// Indicates whether the key was pressed or released
705    pub event_type: KeyEventType,
706    /// The key without any modifiers held
707    /// Important on Windows, to distinguish between key presses when Ctrl+Alt was pressed
708    /// vs. AltGr.
709    /// This is optional, and we will fall back to a heuristic for Ctrl+Alt on Windows if this
710    /// isn't provided.
711    #[cfg(target_os = "windows")]
712    pub text_without_modifiers: SharedString,
713    /// If the event type is KeyEventType::UpdateComposition or KeyEventType::CommitComposition,
714    /// then this field specifies what part of the current text to replace.
715    /// Relative to the offset of the pre-edit text within the text input element's text.
716    pub replacement_range: Option<core::ops::Range<i32>>,
717    /// If the event type is KeyEventType::UpdateComposition, this is the new pre-edit text
718    pub preedit_text: SharedString,
719    /// The selection within the preedit_text
720    pub preedit_selection: Option<core::ops::Range<i32>>,
721    /// The new cursor position, when None, the cursor is put after the text that was just inserted
722    pub cursor_position: Option<i32>,
723    /// The anchor position, when None, the cursor is put after the text that was just inserted
724    pub anchor_position: Option<i32>,
725}
726
727impl InternalKeyEvent {
728    /// If a shortcut was pressed, this function returns `Some(StandardShortcut)`.
729    /// Otherwise it returns None.
730    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    /// If a shortcut concerning text editing was pressed, this function
761    /// returns `Some(TextShortcut)`. Otherwise it returns None.
762    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
846/// Represents a non context specific shortcut.
847pub enum StandardShortcut {
848    /// Copy Something
849    Copy,
850    /// Cut Something
851    Cut,
852    /// Paste Something
853    Paste,
854    /// Select All
855    SelectAll,
856    /// Find/Search Something
857    Find,
858    /// Save Something
859    Save,
860    /// Print Something
861    Print,
862    /// Undo the last action
863    Undo,
864    /// Redo the last undone action
865    Redo,
866    /// Refresh
867    Refresh,
868}
869
870/// Shortcuts that are used when editing text
871pub enum TextShortcut {
872    /// Move the cursor
873    Move(TextCursorDirection),
874    /// Delete the Character to the right of the cursor
875    DeleteForward,
876    /// Delete the Character to the left of the cursor (aka Backspace).
877    DeleteBackward,
878    /// Delete the word to the right of the cursor
879    DeleteWordForward,
880    /// Delete the word to the left of the cursor (aka Ctrl + Backspace).
881    DeleteWordBackward,
882    /// Delete to the left of the cursor until the start of the line
883    DeleteToStartOfLine,
884}
885
886/// Represents how an item's key_event handler dealt with a key event.
887/// An accepted event results in no further event propagation.
888#[repr(u8)]
889#[derive(Debug, Clone, Copy, PartialEq, Default)]
890pub enum KeyEventResult {
891    /// The event was handled.
892    EventAccepted,
893    /// The event was not handled and should be sent to other items.
894    #[default]
895    EventIgnored,
896}
897
898/// Represents how an item's focus_event handler dealt with a focus event.
899/// An accepted event results in no further event propagation.
900#[repr(u8)]
901#[derive(Debug, Clone, Copy, PartialEq, Default)]
902pub enum FocusEventResult {
903    /// The event was handled.
904    FocusAccepted,
905    /// The event was not handled and should be sent to other items.
906    #[default]
907    FocusIgnored,
908}
909
910/// This event is sent to a component and items when they receive or lose
911/// the keyboard focus.
912#[derive(Debug, Clone, Copy, PartialEq)]
913#[repr(u8)]
914pub enum FocusEvent {
915    /// This event is sent when an item receives the focus.
916    FocusIn(FocusReason),
917    /// This event is sent when an item loses the focus.
918    FocusOut(FocusReason),
919}
920
921/// This state is used to count the clicks separated by [`crate::platform::Platform::click_interval`]
922#[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    /// Resets the timer and count.
932    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    /// Reset to an invalid state
940    pub fn reset(&self) {
941        self.click_count.set(0);
942        self.click_count_time_stamp.replace(None);
943    }
944
945    /// Check if the click is repeated.
946    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/// The state which a window should hold for the mouse input
988#[derive(Default)]
989pub struct MouseInputState {
990    /// The stack of item which contain the mouse cursor (or grab),
991    /// along with the last result from the input function
992    item_stack: Vec<(ItemWeak, InputEventFilterResult)>,
993    /// Offset to apply to the first item of the stack (used if there is a popup)
994    pub(crate) offset: LogicalPoint,
995    /// true if the top item of the stack has the mouse grab
996    grabbed: bool,
997    /// When this is Some, it means we are in the middle of a drag-drop operation and it contains the dragged data.
998    /// The `position` field has no signification
999    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    /// Return the item in the top of the stack
1007    fn top_item(&self) -> Option<ItemRc> {
1008        self.item_stack.last().and_then(|x| x.0.upgrade())
1009    }
1010
1011    /// Returns the item in the top of the stack, if there is a delayed event, this would be the top of the delayed stack
1012    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
1017/// Try to handle the mouse grabber. Return None if the event has been handled, otherwise
1018/// return the event that must be handled
1019pub(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            // Return a move event so that the new position can be registered properly
1105            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    // Note that exit events can't actually change the cursor from default so we'll ignore the result
1120    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            // The item is still under the mouse, but no longer in the item stack. We should also sent the exit event, unless we delay it
1147            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
1161/// Process the `mouse_event` on the `component`, the `mouse_grabber_stack` is the previous stack
1162/// of mouse grabber.
1163/// Returns a new mouse grabber stack.
1164pub 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        // Keep the delayed event
1189        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        // An accepted wheel event might have moved things. Send a move event at the position to reset the has-hover
1197        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    // the take bellow will also destroy the Timer
1213    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    // translated in our coordinate
1254    let mut event_for_children = mouse_event.clone();
1255    // Unapply the translation to go from 'world' space to local space
1256    event_for_children.translate(-geom.origin.to_vector());
1257    if window_adapter.renderer().supports_transformations() {
1258        // Unapply other transforms.
1259        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/// The TextCursorBlinker takes care of providing a toggled boolean property
1369/// that can be used to animate a blinking cursor. It's typically stored in the
1370/// Window using a Weak and set_binding() can be used to set up a binding on a given
1371/// property that'll keep it up-to-date. That binding keeps a strong reference to the
1372/// blinker. If the underlying item that uses it goes away, the binding goes away and
1373/// so does the blinker.
1374#[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    /// Creates a new instance, wrapped in a Pin<Rc<_>> because the boolean property
1384    /// the blinker properties uses the property system that requires pinning.
1385    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    /// Sets a binding on the provided property that will ensure that the property value
1393    /// is true when the cursor should be shown and false if not.
1394    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        // Re-start timer, in case.
1401        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    /// Starts the blinking cursor timer that will toggle the cursor and update all bindings that
1408    /// were installed on properties with set_binding call.
1409    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    /// Stops the blinking cursor timer. This is usually used for example when the window that contains
1436    /// text editable elements looses the focus or is hidden.
1437    pub fn stop(&self) {
1438        self.cursor_blink_timer.stop()
1439    }
1440}
1441
1442/// A single active touch point.
1443#[derive(Clone, Copy, Default)]
1444struct TouchPoint {
1445    id: u64,
1446    position: LogicalPoint,
1447}
1448
1449/// Fixed-capacity map of touch IDs to touch points.
1450///
1451/// Touchscreens rarely report more than 5 simultaneous contacts, and gesture
1452/// recognition only uses the first two. A linear-scan array avoids the heap
1453/// allocation and pointer-chasing overhead of `BTreeMap` for this tiny collection.
1454const 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    /// Returns the first two distinct IDs, or `None` if fewer than 2 entries.
1498    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    /// Returns the first entry, if any.
1503    fn first(&self) -> Option<&TouchPoint> {
1504        if self.len > 0 { Some(&self.entries[0]) } else { None }
1505    }
1506}
1507
1508/// Fixed-capacity buffer for [`MouseEvent`]s produced by the touch state machine.
1509///
1510/// No branch in [`TouchState::process`] emits more than 3 events (gesture end
1511/// produces PinchEnded + RotationEnded + Pressed/Exit). Capacity 4 provides a
1512/// margin without heap allocation.
1513const 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    /// Returns an iterator over the buffered events.
1535    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/// State of the multi-touch gesture recognizer.
1542#[derive(Default, Debug, Clone, Copy)]
1543enum GestureRecognitionState {
1544    /// 0-1 fingers; forwarding as mouse events.
1545    #[default]
1546    Idle,
1547    /// 2 fingers down, waiting for movement to exceed threshold.
1548    TwoFingersDown { finger_ids: (u64, u64), initial_distance: f32, last_angle: euclid::Angle<f32> },
1549    /// Actively synthesizing PinchGesture/RotationGesture events.
1550    Pinching {
1551        finger_ids: (u64, u64),
1552        initial_distance: f32,
1553        last_scale: f32,
1554        last_angle: euclid::Angle<f32>,
1555    },
1556}
1557
1558/// Tracks all active touch points and recognizes pinch/rotation gestures.
1559///
1560/// When only one finger is down, touch events are forwarded as mouse events.
1561/// When two fingers are down and move beyond a threshold, synthesized
1562/// `PinchGesture` and `RotationGesture` events are emitted — the same events
1563/// that platform gesture recognition (e.g. macOS trackpad) produces.
1564pub(crate) struct TouchState {
1565    active_touches: TouchMap,
1566    /// The finger forwarded as mouse events during single-touch.
1567    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    /// Minimum movement (in logical pixels) before two fingers are recognized as a pinch.
1583    const PINCH_THRESHOLD: f32 = 8.0;
1584
1585    /// Minimum angular change (in degrees) before two fingers are recognized as a rotation.
1586    const ROTATION_THRESHOLD: f32 = 5.0;
1587
1588    /// Returns the finger IDs from the current gesture state, if any.
1589    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    /// Returns (distance, angle) between two specific touch points.
1598    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    /// Returns the positions of the two gesture fingers, or `None` if not available.
1606    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    /// Returns the midpoint between the two gesture fingers, or `None`.
1614    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    /// Returns (distance, angle) between the two gesture fingers.
1621    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    /// Returns true if the given touch ID is one of the two gesture fingers.
1628    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    /// Run the touch state machine for a single event and return the
1633    /// [`MouseEvent`]s to dispatch.
1634    ///
1635    /// This is intentionally separated from [`crate::window::WindowInner::process_touch_input`]
1636    /// so that the `RefCell` borrow can be dropped *once* before dispatching,
1637    /// rather than requiring a manual `drop` at every branch.
1638    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            // First finger: become primary, forward as mouse press.
1660            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            // Second finger: transition Idle → TwoFingersDown.
1670            let finger_ids = self.active_touches.first_two_ids().unwrap_or((0, 0));
1671
1672            // Synthesize a Release for the primary finger to clear any
1673            // Flickable grab / delay state.
1674            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            // Compute initial geometry for threshold detection.
1681            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        // 3+ fingers: tracked in active_touches but ignored for gesture.
1697    }
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                        // Re-snapshot so the first gesture event starts from
1723                        // the current geometry rather than accumulating the
1724                        // threshold movement.
1725                        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                    // `.signed()` wraps to [-pi, pi] so crossing the ±180°
1758                    // atan2 boundary doesn't produce a full-revolution jump.
1759                    let rotation_delta = (angle - last_angle).signed().to_degrees();
1760
1761                    // Update the mutable state for next frame.
1762                    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        // Check gesture membership *before* removing from the map.
1796        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    // -----------------------------------------------------------------------
1892    // TouchMap tests
1893    // -----------------------------------------------------------------------
1894
1895    #[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        // Inserting beyond capacity is silently ignored.
1943        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    // -----------------------------------------------------------------------
1977    // Helper: extract event types for readable assertions
1978    // -----------------------------------------------------------------------
1979
1980    #[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    // -----------------------------------------------------------------------
2023    // TouchState: single-finger forwarding
2024    // -----------------------------------------------------------------------
2025
2026    #[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        // Touch 1 is primary.
2054        state.process(1, pt(100.0, 200.0), TouchPhase::Started);
2055
2056        // Move for a different ID that was never started (edge case).
2057        let evs = state.process(99, pt(50.0, 50.0), TouchPhase::Moved);
2058        assert!(classify(&evs).is_empty());
2059    }
2060
2061    // -----------------------------------------------------------------------
2062    // TouchState: two-finger → gesture transition
2063    // -----------------------------------------------------------------------
2064
2065    #[test]
2066    fn two_fingers_synthesize_release_then_gesture() {
2067        let mut state = TouchState::default();
2068
2069        // Finger 1 down.
2070        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        // Finger 2 down → synthesized release for finger 1.
2074        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        // Move finger 2 far enough to trigger pinch (> 8px threshold).
2079        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        // Small movement within threshold.
2092        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        // Set up: finger 1 at (0, 0), finger 2 at (100, 0) → distance = 100.
2102        state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2103        state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2104
2105        // Move finger 2 to (120, 0) to exceed threshold and start pinching.
2106        state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2107        assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2108
2109        // Now move finger 2 further to (180, 0).
2110        // New distance = 180, initial distance (re-snapshotted) = 120.
2111        // Scale = 180/120 = 1.5, delta = 1.5 - 1.0 = 0.5.
2112        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        // Finger 1 at origin, finger 2 on the X axis at (100, 0).
2127        // Initial angle = atan2(0, 100) = 0°.
2128        state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2129        state.process(2, pt(100.0, 0.0), TouchPhase::Started);
2130
2131        // Move finger 2 far enough to trigger gesture.
2132        state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2133        assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2134
2135        // Rotate ~45° clockwise: move finger 2 from (120, 0) to roughly
2136        // (70.7, 70.7) which is at 45° from origin.
2137        // atan2(70.7, 70.7) ≈ 45°. Delta from re-snapshotted 0° = +45°.
2138        // Slint convention: positive = clockwise → delta ≈ +45°.
2139        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        // Finger 1 at origin, finger 2 at (-100, -10).
2154        // angle = atan2(-10, -100) ≈ -174.3°.
2155        state.process(1, pt(0.0, 0.0), TouchPhase::Started);
2156        state.process(2, pt(-100.0, -10.0), TouchPhase::Started);
2157
2158        // Trigger gesture by moving far enough.
2159        state.process(2, pt(-120.0, -10.0), TouchPhase::Moved);
2160        assert!(matches!(state.gesture_state, GestureRecognitionState::Pinching { .. }));
2161
2162        // Rotate across the ±180° boundary: move finger 2 to (-100, 10).
2163        // New angle = atan2(10, -100) ≈ 174.3°.
2164        // Raw angular change crosses ±180°, but per-frame delta should be
2165        // small (~11.4° which is 2 * 5.7°), NOT a ~349° jump.
2166        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    // -----------------------------------------------------------------------
2180    // TouchState: gesture end transitions
2181    // -----------------------------------------------------------------------
2182
2183    #[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        // Trigger pinch.
2190        state.process(2, pt(120.0, 0.0), TouchPhase::Moved);
2191
2192        // Lift finger 2 → gesture ends, finger 1 gets re-pressed.
2193        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        // Cancel finger 2.
2209        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        // Lift finger 2 without exceeding movement threshold.
2224        let evs = state.process(2, pt(200.0, 200.0), TouchPhase::Ended);
2225        let classified = classify(&evs);
2226        // Remaining finger 1 gets re-pressed.
2227        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        // Cancel finger 2 (gesture finger, no remaining → Exit).
2240        let evs = state.process(2, pt(200.0, 200.0), TouchPhase::Cancelled);
2241        assert_eq!(classify(&evs), vec![Ev::Exit]);
2242
2243        // Cancel finger 1 (now in Idle, but not primary since cancel cleared it).
2244        let evs = state.process(1, pt(100.0, 200.0), TouchPhase::Cancelled);
2245        assert!(classify(&evs).is_empty());
2246    }
2247
2248    // -----------------------------------------------------------------------
2249    // TouchState: 3+ fingers
2250    // -----------------------------------------------------------------------
2251
2252    #[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        // Third finger: no additional events.
2260        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    // -----------------------------------------------------------------------
2266    // Angle wrapping via Euclid
2267    // -----------------------------------------------------------------------
2268
2269    #[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        // Two fingers at the exact same position → distance = 0.
2285        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        // Move one finger far enough to trigger gesture.
2290        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        // Move further — scale should not be inf/NaN despite initial_distance
2297        // having been 0 (re-snapshotted to 20.0 at threshold crossing).
2298        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}