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};
10pub use crate::items::PointerEventButton;
11use crate::items::{DropEvent, ItemRef, TextCursorDirection};
12pub use crate::items::{FocusReason, KeyEvent, KeyboardModifiers};
13use crate::lengths::{ItemTransform, LogicalPoint, LogicalVector};
14use crate::timers::Timer;
15use crate::window::{WindowAdapter, WindowInner};
16use crate::{Coord, Property, SharedString};
17use alloc::rc::Rc;
18use alloc::vec::Vec;
19use const_field_offset::FieldOffsets;
20use core::cell::Cell;
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)]
30#[allow(missing_docs)]
31pub enum MouseEvent {
32    /// The mouse or finger was pressed
33    /// `position` is the position of the mouse when the event happens.
34    /// `button` describes the button that is pressed when the event happens.
35    /// `click_count` represents the current number of clicks.
36    Pressed { position: LogicalPoint, button: PointerEventButton, click_count: u8, is_touch: bool },
37    /// The mouse or finger was released
38    /// `position` is the position of the mouse when the event happens.
39    /// `button` describes the button that is pressed when the event happens.
40    /// `click_count` represents the current number of clicks.
41    Released { position: LogicalPoint, button: PointerEventButton, click_count: u8, is_touch: bool },
42    /// The position of the pointer has changed
43    Moved { position: LogicalPoint, is_touch: bool },
44    /// Wheel was operated.
45    /// `pos` is the position of the mouse when the event happens.
46    /// `delta_x` is the amount of pixels to scroll in horizontal direction,
47    /// `delta_y` is the amount of pixels to scroll in vertical direction.
48    Wheel { position: LogicalPoint, delta_x: Coord, delta_y: Coord },
49    /// The mouse is being dragged over this item.
50    /// [`InputEventResult::EventIgnored`] means that the item does not handle the drag operation
51    /// and [`InputEventResult::EventAccepted`] means that the item can accept it.
52    DragMove(DropEvent),
53    /// The mouse is released while dragging over this item.
54    Drop(DropEvent),
55    /// The mouse exited the item or component
56    Exit,
57}
58
59impl MouseEvent {
60    /// The flag for when event generated from touch
61    pub fn is_touch(&self) -> Option<bool> {
62        match self {
63            MouseEvent::Pressed { is_touch, .. } => Some(*is_touch),
64            MouseEvent::Released { is_touch, .. } => Some(*is_touch),
65            MouseEvent::Moved { is_touch, .. } => Some(*is_touch),
66            MouseEvent::Wheel { .. } => None,
67            MouseEvent::DragMove(..) | MouseEvent::Drop(..) => None,
68            MouseEvent::Exit => None,
69        }
70    }
71
72    /// The position of the cursor for this event, if any
73    pub fn position(&self) -> Option<LogicalPoint> {
74        match self {
75            MouseEvent::Pressed { position, .. } => Some(*position),
76            MouseEvent::Released { position, .. } => Some(*position),
77            MouseEvent::Moved { position, .. } => Some(*position),
78            MouseEvent::Wheel { position, .. } => Some(*position),
79            MouseEvent::DragMove(e) | MouseEvent::Drop(e) => {
80                Some(crate::lengths::logical_point_from_api(e.position))
81            }
82            MouseEvent::Exit => None,
83        }
84    }
85
86    /// Translate the position by the given value
87    pub fn translate(&mut self, vec: LogicalVector) {
88        let pos = match self {
89            MouseEvent::Pressed { position, .. } => Some(position),
90            MouseEvent::Released { position, .. } => Some(position),
91            MouseEvent::Moved { position, .. } => Some(position),
92            MouseEvent::Wheel { position, .. } => Some(position),
93            MouseEvent::DragMove(e) | MouseEvent::Drop(e) => {
94                e.position = crate::api::LogicalPosition::from_euclid(
95                    crate::lengths::logical_point_from_api(e.position) + vec,
96                );
97                None
98            }
99            MouseEvent::Exit => None,
100        };
101        if let Some(pos) = pos {
102            *pos += vec;
103        }
104    }
105
106    /// Transform the position by the given item transform.
107    pub fn transform(&mut self, transform: ItemTransform) {
108        let pos = match self {
109            MouseEvent::Pressed { position, .. } => Some(position),
110            MouseEvent::Released { position, .. } => Some(position),
111            MouseEvent::Moved { position, .. } => Some(position),
112            MouseEvent::Wheel { position, .. } => Some(position),
113            MouseEvent::DragMove(e) | MouseEvent::Drop(e) => {
114                e.position = crate::api::LogicalPosition::from_euclid(
115                    transform
116                        .transform_point(crate::lengths::logical_point_from_api(e.position).cast())
117                        .cast(),
118                );
119                None
120            }
121            MouseEvent::Exit => None,
122        };
123        if let Some(pos) = pos {
124            *pos = transform.transform_point(pos.cast()).cast();
125        }
126    }
127
128    /// Set the click count of the pressed or released event
129    fn set_click_count(&mut self, count: u8) {
130        match self {
131            MouseEvent::Pressed { click_count, .. } | MouseEvent::Released { click_count, .. } => {
132                *click_count = count
133            }
134            _ => (),
135        }
136    }
137}
138
139/// This value is returned by the `input_event` function of an Item
140/// to notify the run-time about how the event was handled and
141/// what the next steps are.
142/// See [`crate::items::ItemVTable::input_event`].
143#[repr(u8)]
144#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
145pub enum InputEventResult {
146    /// The event was accepted. This may result in additional events, for example
147    /// accepting a mouse move will result in a MouseExit event later.
148    EventAccepted,
149    /// The event was ignored.
150    #[default]
151    EventIgnored,
152    /// All further mouse events need to be sent to this item or component
153    GrabMouse,
154    /// Will start a drag operation. Can only be returned from a [`crate::items::DragArea`] item.
155    StartDrag,
156}
157
158/// This value is returned by the `input_event_filter_before_children` function, which
159/// can specify how to further process the event.
160/// See [`crate::items::ItemVTable::input_event_filter_before_children`].
161#[repr(C)]
162#[derive(Debug, Copy, Clone, PartialEq, Default)]
163pub enum InputEventFilterResult {
164    /// The event is going to be forwarded to children, then the [`crate::items::ItemVTable::input_event`]
165    /// function is called
166    #[default]
167    ForwardEvent,
168    /// The event will be forwarded to the children, but the [`crate::items::ItemVTable::input_event`] is not
169    /// going to be called for this item
170    ForwardAndIgnore,
171    /// Just like `ForwardEvent`, but even in the case that children grabs the mouse, this function
172    /// will still be called for further events
173    ForwardAndInterceptGrab,
174    /// The event will not be forwarded to children, if a child already had the grab, the
175    /// grab will be cancelled with a [`MouseEvent::Exit`] event
176    Intercept,
177    /// The event will be forwarded to the children with a delay (in milliseconds), unless it is
178    /// being intercepted.
179    /// This is what happens when the flickable wants to delay the event.
180    /// This should only be used for Press event, and the event will be sent after the delay, or
181    /// if a release event is seen before that delay
182    //(Can't use core::time::Duration because it is not repr(c))
183    DelayForwarding(u64),
184}
185
186/// This module contains the constant character code used to represent the keys.
187#[allow(missing_docs, non_upper_case_globals)]
188pub mod key_codes {
189    macro_rules! declare_consts_for_special_keys {
190       ($($char:literal # $name:ident # $($_qt:ident)|* # $($_winit:ident $(($_pos:ident))?)|*    # $($_xkb:ident)|*;)*) => {
191            $(pub const $name : char = $char;)*
192
193            #[allow(missing_docs)]
194            #[derive(Debug, Copy, Clone, PartialEq)]
195            #[non_exhaustive]
196            /// The `Key` enum is used to map a specific key by name e.g. `Key::Control` to an
197            /// internal used unicode representation. The enum is convertible to [`std::char`] and [`slint::SharedString`](`crate::SharedString`).
198            /// Use this with [`slint::platform::WindowEvent`](`crate::platform::WindowEvent`) to supply key events to Slint's platform abstraction.
199            ///
200            /// # Example
201            ///
202            /// Send an tab key press event to a window
203            ///
204            /// ```
205            /// use slint::platform::{WindowEvent, Key};
206            /// fn send_tab_pressed(window: &slint::Window) {
207            ///     window.dispatch_event(WindowEvent::KeyPressed { text: Key::Tab.into() });
208            /// }
209            /// ```
210            pub enum Key {
211                $($name,)*
212            }
213
214            impl From<Key> for char {
215                fn from(k: Key) -> Self {
216                    match k {
217                        $(Key::$name => $name,)*
218                    }
219                }
220            }
221
222            impl From<Key> for crate::SharedString {
223                fn from(k: Key) -> Self {
224                    char::from(k).into()
225                }
226            }
227        };
228    }
229
230    i_slint_common::for_each_special_keys!(declare_consts_for_special_keys);
231}
232
233/// Internal struct to maintain the pressed/released state of the keys that
234/// map to keyboard modifiers.
235#[derive(Clone, Copy, Default, Debug)]
236pub(crate) struct InternalKeyboardModifierState {
237    left_alt: bool,
238    right_alt: bool,
239    altgr: bool,
240    left_control: bool,
241    right_control: bool,
242    left_meta: bool,
243    right_meta: bool,
244    left_shift: bool,
245    right_shift: bool,
246}
247
248impl InternalKeyboardModifierState {
249    /// Updates a flag of the modifiers if the key of the given text is pressed.
250    /// Returns an updated modifier if detected; None otherwise;
251    pub(crate) fn state_update(mut self, pressed: bool, text: &SharedString) -> Option<Self> {
252        if let Some(key_code) = text.chars().next() {
253            match key_code {
254                key_codes::Alt => self.left_alt = pressed,
255                key_codes::AltGr => self.altgr = pressed,
256                key_codes::Control => self.left_control = pressed,
257                key_codes::ControlR => self.right_control = pressed,
258                key_codes::Shift => self.left_shift = pressed,
259                key_codes::ShiftR => self.right_shift = pressed,
260                key_codes::Meta => self.left_meta = pressed,
261                key_codes::MetaR => self.right_meta = pressed,
262                _ => return None,
263            };
264
265            // Encoded keyboard modifiers must appear as individual key events. This could
266            // be relaxed by implementing a string split, but right now WindowEvent::KeyPressed
267            // holds only a single char.
268            debug_assert_eq!(key_code.len_utf8(), text.len());
269        }
270
271        // Special cases:
272        #[cfg(target_os = "windows")]
273        {
274            if self.altgr {
275                // Windows sends Ctrl followed by AltGr on AltGr. Disable the Ctrl again!
276                self.left_control = false;
277                self.right_control = false;
278            } else if self.control() && self.alt() {
279                // Windows treats Ctrl-Alt as AltGr
280                self.left_control = false;
281                self.right_control = false;
282                self.left_alt = false;
283                self.right_alt = false;
284            }
285        }
286
287        Some(self)
288    }
289
290    pub fn shift(&self) -> bool {
291        self.right_shift || self.left_shift
292    }
293    pub fn alt(&self) -> bool {
294        self.right_alt || self.left_alt
295    }
296    pub fn meta(&self) -> bool {
297        self.right_meta || self.left_meta
298    }
299    pub fn control(&self) -> bool {
300        self.right_control || self.left_control
301    }
302}
303
304impl From<InternalKeyboardModifierState> for KeyboardModifiers {
305    fn from(internal_state: InternalKeyboardModifierState) -> Self {
306        Self {
307            alt: internal_state.alt(),
308            control: internal_state.control(),
309            meta: internal_state.meta(),
310            shift: internal_state.shift(),
311        }
312    }
313}
314
315/// This enum defines the different kinds of key events that can happen.
316#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
317#[repr(u8)]
318pub enum KeyEventType {
319    /// A key on a keyboard was pressed.
320    #[default]
321    KeyPressed = 0,
322    /// A key on a keyboard was released.
323    KeyReleased = 1,
324    /// The input method updates the currently composed text. The KeyEvent's text field is the pre-edit text and
325    /// composition_selection specifies the placement of the cursor within the pre-edit text.
326    UpdateComposition = 2,
327    /// The input method replaces the currently composed text with the final result of the composition.
328    CommitComposition = 3,
329}
330
331impl KeyEvent {
332    /// If a shortcut was pressed, this function returns `Some(StandardShortcut)`.
333    /// Otherwise it returns None.
334    pub fn shortcut(&self) -> Option<StandardShortcut> {
335        if self.modifiers.control && !self.modifiers.shift {
336            match self.text.as_str() {
337                #[cfg(not(target_arch = "wasm32"))]
338                "c" => Some(StandardShortcut::Copy),
339                #[cfg(not(target_arch = "wasm32"))]
340                "x" => Some(StandardShortcut::Cut),
341                #[cfg(not(target_arch = "wasm32"))]
342                "v" => Some(StandardShortcut::Paste),
343                "a" => Some(StandardShortcut::SelectAll),
344                "f" => Some(StandardShortcut::Find),
345                "s" => Some(StandardShortcut::Save),
346                "p" => Some(StandardShortcut::Print),
347                "z" => Some(StandardShortcut::Undo),
348                #[cfg(target_os = "windows")]
349                "y" => Some(StandardShortcut::Redo),
350                "r" => Some(StandardShortcut::Refresh),
351                _ => None,
352            }
353        } else if self.modifiers.control && self.modifiers.shift {
354            match self.text.as_str() {
355                #[cfg(not(target_os = "windows"))]
356                "z" => Some(StandardShortcut::Redo),
357                _ => None,
358            }
359        } else {
360            None
361        }
362    }
363
364    /// If a shortcut concerning text editing was pressed, this function
365    /// returns `Some(TextShortcut)`. Otherwise it returns None.
366    pub fn text_shortcut(&self) -> Option<TextShortcut> {
367        let keycode = self.text.chars().next()?;
368
369        let is_apple = crate::is_apple_platform();
370
371        let move_mod = if is_apple {
372            self.modifiers.alt && !self.modifiers.control && !self.modifiers.meta
373        } else {
374            self.modifiers.control && !self.modifiers.alt && !self.modifiers.meta
375        };
376
377        if move_mod {
378            match keycode {
379                key_codes::LeftArrow => {
380                    return Some(TextShortcut::Move(TextCursorDirection::BackwardByWord));
381                }
382                key_codes::RightArrow => {
383                    return Some(TextShortcut::Move(TextCursorDirection::ForwardByWord));
384                }
385                key_codes::UpArrow => {
386                    return Some(TextShortcut::Move(TextCursorDirection::StartOfParagraph));
387                }
388                key_codes::DownArrow => {
389                    return Some(TextShortcut::Move(TextCursorDirection::EndOfParagraph));
390                }
391                key_codes::Backspace => {
392                    return Some(TextShortcut::DeleteWordBackward);
393                }
394                key_codes::Delete => {
395                    return Some(TextShortcut::DeleteWordForward);
396                }
397                _ => (),
398            };
399        }
400
401        #[cfg(not(target_os = "macos"))]
402        {
403            if self.modifiers.control && !self.modifiers.alt && !self.modifiers.meta {
404                match keycode {
405                    key_codes::Home => {
406                        return Some(TextShortcut::Move(TextCursorDirection::StartOfText));
407                    }
408                    key_codes::End => {
409                        return Some(TextShortcut::Move(TextCursorDirection::EndOfText));
410                    }
411                    _ => (),
412                };
413            }
414        }
415
416        if is_apple && self.modifiers.control {
417            match keycode {
418                key_codes::LeftArrow => {
419                    return Some(TextShortcut::Move(TextCursorDirection::StartOfLine));
420                }
421                key_codes::RightArrow => {
422                    return Some(TextShortcut::Move(TextCursorDirection::EndOfLine));
423                }
424                key_codes::UpArrow => {
425                    return Some(TextShortcut::Move(TextCursorDirection::StartOfText));
426                }
427                key_codes::DownArrow => {
428                    return Some(TextShortcut::Move(TextCursorDirection::EndOfText));
429                }
430                key_codes::Backspace => {
431                    return Some(TextShortcut::DeleteToStartOfLine);
432                }
433                _ => (),
434            };
435        }
436
437        if let Ok(direction) = TextCursorDirection::try_from(keycode) {
438            Some(TextShortcut::Move(direction))
439        } else {
440            match keycode {
441                key_codes::Backspace => Some(TextShortcut::DeleteBackward),
442                key_codes::Delete => Some(TextShortcut::DeleteForward),
443                _ => None,
444            }
445        }
446    }
447}
448
449/// Represents a non context specific shortcut.
450pub enum StandardShortcut {
451    /// Copy Something
452    Copy,
453    /// Cut Something
454    Cut,
455    /// Paste Something
456    Paste,
457    /// Select All
458    SelectAll,
459    /// Find/Search Something
460    Find,
461    /// Save Something
462    Save,
463    /// Print Something
464    Print,
465    /// Undo the last action
466    Undo,
467    /// Redo the last undone action
468    Redo,
469    /// Refresh
470    Refresh,
471}
472
473/// Shortcuts that are used when editing text
474pub enum TextShortcut {
475    /// Move the cursor
476    Move(TextCursorDirection),
477    /// Delete the Character to the right of the cursor
478    DeleteForward,
479    /// Delete the Character to the left of the cursor (aka Backspace).
480    DeleteBackward,
481    /// Delete the word to the right of the cursor
482    DeleteWordForward,
483    /// Delete the word to the left of the cursor (aka Ctrl + Backspace).
484    DeleteWordBackward,
485    /// Delete to the left of the cursor until the start of the line
486    DeleteToStartOfLine,
487}
488
489/// Represents how an item's key_event handler dealt with a key event.
490/// An accepted event results in no further event propagation.
491#[repr(u8)]
492#[derive(Debug, Clone, Copy, PartialEq, Default)]
493pub enum KeyEventResult {
494    /// The event was handled.
495    EventAccepted,
496    /// The event was not handled and should be sent to other items.
497    #[default]
498    EventIgnored,
499}
500
501/// Represents how an item's focus_event handler dealt with a focus event.
502/// An accepted event results in no further event propagation.
503#[repr(u8)]
504#[derive(Debug, Clone, Copy, PartialEq, Default)]
505pub enum FocusEventResult {
506    /// The event was handled.
507    FocusAccepted,
508    /// The event was not handled and should be sent to other items.
509    #[default]
510    FocusIgnored,
511}
512
513/// This event is sent to a component and items when they receive or lose
514/// the keyboard focus.
515#[derive(Debug, Clone, Copy, PartialEq)]
516#[repr(u8)]
517pub enum FocusEvent {
518    /// This event is sent when an item receives the focus.
519    FocusIn(FocusReason),
520    /// This event is sent when an item loses the focus.
521    FocusOut(FocusReason),
522}
523
524/// This state is used to count the clicks separated by [`crate::platform::Platform::click_interval`]
525#[derive(Default)]
526pub struct ClickState {
527    click_count_time_stamp: Cell<Option<crate::animations::Instant>>,
528    click_count: Cell<u8>,
529    click_position: Cell<LogicalPoint>,
530    click_button: Cell<PointerEventButton>,
531}
532
533impl ClickState {
534    /// Resets the timer and count.
535    fn restart(&self, position: LogicalPoint, button: PointerEventButton) {
536        self.click_count.set(0);
537        self.click_count_time_stamp.set(Some(crate::animations::Instant::now()));
538        self.click_position.set(position);
539        self.click_button.set(button);
540    }
541
542    /// Reset to an invalid state
543    pub fn reset(&self) {
544        self.click_count.set(0);
545        self.click_count_time_stamp.replace(None);
546    }
547
548    /// Check if the click is repeated.
549    pub fn check_repeat(&self, mouse_event: MouseEvent, click_interval: Duration) -> MouseEvent {
550        match mouse_event {
551            MouseEvent::Pressed { position, button, is_touch, .. } => {
552                let instant_now = crate::animations::Instant::now();
553
554                if let Some(click_count_time_stamp) = self.click_count_time_stamp.get() {
555                    if instant_now - click_count_time_stamp < click_interval
556                        && button == self.click_button.get()
557                        && (position - self.click_position.get()).square_length() < 100 as _
558                    {
559                        self.click_count.set(self.click_count.get().wrapping_add(1));
560                        self.click_count_time_stamp.set(Some(instant_now));
561                    } else {
562                        self.restart(position, button);
563                    }
564                } else {
565                    self.restart(position, button);
566                }
567
568                return MouseEvent::Pressed {
569                    position,
570                    button,
571                    click_count: self.click_count.get(),
572                    is_touch,
573                };
574            }
575            MouseEvent::Released { position, button, is_touch, .. } => {
576                return MouseEvent::Released {
577                    position,
578                    button,
579                    click_count: self.click_count.get(),
580                    is_touch,
581                };
582            }
583            _ => {}
584        };
585
586        mouse_event
587    }
588}
589
590/// The state which a window should hold for the mouse input
591#[derive(Default)]
592pub struct MouseInputState {
593    /// The stack of item which contain the mouse cursor (or grab),
594    /// along with the last result from the input function
595    item_stack: Vec<(ItemWeak, InputEventFilterResult)>,
596    /// Offset to apply to the first item of the stack (used if there is a popup)
597    pub(crate) offset: LogicalPoint,
598    /// true if the top item of the stack has the mouse grab
599    grabbed: bool,
600    /// When this is Some, it means we are in the middle of a drag-drop operation and it contains the dragged data.
601    /// The `position` field has no signification
602    pub(crate) drag_data: Option<DropEvent>,
603    delayed: Option<(crate::timers::Timer, MouseEvent)>,
604    delayed_exit_items: Vec<ItemWeak>,
605}
606
607impl MouseInputState {
608    /// Return the item in the top of the stack
609    fn top_item(&self) -> Option<ItemRc> {
610        self.item_stack.last().and_then(|x| x.0.upgrade())
611    }
612
613    /// Returns the item in the top of the stack, if there is a delayed event, this would be the top of the delayed stack
614    pub fn top_item_including_delayed(&self) -> Option<ItemRc> {
615        self.delayed_exit_items.last().and_then(|x| x.upgrade()).or_else(|| self.top_item())
616    }
617}
618
619/// Try to handle the mouse grabber. Return None if the event has been handled, otherwise
620/// return the event that must be handled
621pub(crate) fn handle_mouse_grab(
622    mouse_event: &MouseEvent,
623    window_adapter: &Rc<dyn WindowAdapter>,
624    mouse_input_state: &mut MouseInputState,
625) -> Option<MouseEvent> {
626    if !mouse_input_state.grabbed || mouse_input_state.item_stack.is_empty() {
627        return Some(mouse_event.clone());
628    };
629
630    let mut event = mouse_event.clone();
631    let mut intercept = false;
632    let mut invalid = false;
633
634    event.translate(-mouse_input_state.offset.to_vector());
635
636    mouse_input_state.item_stack.retain(|it| {
637        if invalid {
638            return false;
639        }
640        let item = if let Some(item) = it.0.upgrade() {
641            item
642        } else {
643            invalid = true;
644            return false;
645        };
646        if intercept {
647            item.borrow().as_ref().input_event(&MouseEvent::Exit, window_adapter, &item);
648            return false;
649        }
650        let g = item.geometry();
651        event.translate(-g.origin.to_vector());
652        if window_adapter.renderer().supports_transformations() {
653            if let Some(inverse_transform) = item.inverse_children_transform() {
654                event.transform(inverse_transform);
655            }
656        }
657
658        let interested = matches!(
659            it.1,
660            InputEventFilterResult::ForwardAndInterceptGrab
661                | InputEventFilterResult::DelayForwarding(_)
662        );
663
664        if interested
665            && item.borrow().as_ref().input_event_filter_before_children(
666                &event,
667                window_adapter,
668                &item,
669            ) == InputEventFilterResult::Intercept
670        {
671            intercept = true;
672        }
673        true
674    });
675    if invalid {
676        return Some(mouse_event.clone());
677    }
678
679    let grabber = mouse_input_state.top_item().unwrap();
680    let input_result = grabber.borrow().as_ref().input_event(&event, window_adapter, &grabber);
681    match input_result {
682        InputEventResult::GrabMouse => None,
683        InputEventResult::StartDrag => {
684            mouse_input_state.grabbed = false;
685            let drag_area_item = grabber.downcast::<crate::items::DragArea>().unwrap();
686            mouse_input_state.drag_data = Some(DropEvent {
687                mime_type: drag_area_item.as_pin_ref().mime_type(),
688                data: drag_area_item.as_pin_ref().data(),
689                position: Default::default(),
690            });
691            None
692        }
693        _ => {
694            mouse_input_state.grabbed = false;
695            // Return a move event so that the new position can be registered properly
696            Some(mouse_event.position().map_or(MouseEvent::Exit, |position| MouseEvent::Moved {
697                position,
698                is_touch: mouse_event.is_touch().unwrap_or(false),
699            }))
700        }
701    }
702}
703
704pub(crate) fn send_exit_events(
705    old_input_state: &MouseInputState,
706    new_input_state: &mut MouseInputState,
707    mut pos: Option<LogicalPoint>,
708    window_adapter: &Rc<dyn WindowAdapter>,
709) {
710    for it in core::mem::take(&mut new_input_state.delayed_exit_items) {
711        let Some(item) = it.upgrade() else { continue };
712        item.borrow().as_ref().input_event(&MouseEvent::Exit, window_adapter, &item);
713    }
714
715    let mut clipped = false;
716    for (idx, it) in old_input_state.item_stack.iter().enumerate() {
717        let Some(item) = it.0.upgrade() else { break };
718        let g = item.geometry();
719        let contains = pos.is_some_and(|p| g.contains(p));
720        if let Some(p) = pos.as_mut() {
721            *p -= g.origin.to_vector();
722            if window_adapter.renderer().supports_transformations() {
723                if let Some(inverse_transform) = item.inverse_children_transform() {
724                    *p = inverse_transform.transform_point(p.cast()).cast();
725                }
726            }
727        }
728        if !contains || clipped {
729            if item.borrow().as_ref().clips_children() {
730                clipped = true;
731            }
732            item.borrow().as_ref().input_event(&MouseEvent::Exit, window_adapter, &item);
733        } else if new_input_state.item_stack.get(idx).is_none_or(|(x, _)| *x != it.0) {
734            // 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
735            if new_input_state.delayed.is_some() {
736                new_input_state.delayed_exit_items.push(it.0.clone());
737            } else {
738                item.borrow().as_ref().input_event(&MouseEvent::Exit, window_adapter, &item);
739            }
740        }
741    }
742}
743
744/// Process the `mouse_event` on the `component`, the `mouse_grabber_stack` is the previous stack
745/// of mouse grabber.
746/// Returns a new mouse grabber stack.
747pub fn process_mouse_input(
748    root: ItemRc,
749    mouse_event: &MouseEvent,
750    window_adapter: &Rc<dyn WindowAdapter>,
751    mouse_input_state: MouseInputState,
752) -> MouseInputState {
753    let mut result =
754        MouseInputState { drag_data: mouse_input_state.drag_data.clone(), ..Default::default() };
755    let r = send_mouse_event_to_item(
756        mouse_event,
757        root.clone(),
758        window_adapter,
759        &mut result,
760        mouse_input_state.top_item().as_ref(),
761        false,
762    );
763    if mouse_input_state.delayed.is_some()
764        && (!r.has_aborted()
765            || Option::zip(result.item_stack.last(), mouse_input_state.item_stack.last())
766                .is_none_or(|(a, b)| a.0 != b.0))
767    {
768        // Keep the delayed event
769        return mouse_input_state;
770    }
771    send_exit_events(&mouse_input_state, &mut result, mouse_event.position(), window_adapter);
772
773    if let MouseEvent::Wheel { position, .. } = mouse_event {
774        if r.has_aborted() {
775            // An accepted wheel event might have moved things. Send a move event at the position to reset the has-hover
776            return process_mouse_input(
777                root,
778                &MouseEvent::Moved { position: *position, is_touch: false },
779                window_adapter,
780                result,
781            );
782        }
783    }
784
785    result
786}
787
788pub(crate) fn process_delayed_event(
789    window_adapter: &Rc<dyn WindowAdapter>,
790    mut mouse_input_state: MouseInputState,
791) -> MouseInputState {
792    // the take bellow will also destroy the Timer
793    let event = match mouse_input_state.delayed.take() {
794        Some(e) => e.1,
795        None => return mouse_input_state,
796    };
797
798    let top_item = match mouse_input_state.top_item() {
799        Some(i) => i,
800        None => return MouseInputState::default(),
801    };
802
803    let mut actual_visitor =
804        |component: &ItemTreeRc, index: u32, _: Pin<ItemRef>| -> VisitChildrenResult {
805            send_mouse_event_to_item(
806                &event,
807                ItemRc::new(component.clone(), index),
808                window_adapter,
809                &mut mouse_input_state,
810                Some(&top_item),
811                true,
812            )
813        };
814    vtable::new_vref!(let mut actual_visitor : VRefMut<crate::item_tree::ItemVisitorVTable> for crate::item_tree::ItemVisitor = &mut actual_visitor);
815    vtable::VRc::borrow_pin(top_item.item_tree()).as_ref().visit_children_item(
816        top_item.index() as isize,
817        crate::item_tree::TraversalOrder::FrontToBack,
818        actual_visitor,
819    );
820    mouse_input_state
821}
822
823fn send_mouse_event_to_item(
824    mouse_event: &MouseEvent,
825    item_rc: ItemRc,
826    window_adapter: &Rc<dyn WindowAdapter>,
827    result: &mut MouseInputState,
828    last_top_item: Option<&ItemRc>,
829    ignore_delays: bool,
830) -> VisitChildrenResult {
831    let item = item_rc.borrow();
832    let geom = item_rc.geometry();
833    // translated in our coordinate
834    let mut event_for_children = mouse_event.clone();
835    // Unapply the translation to go from 'world' space to local space
836    event_for_children.translate(-geom.origin.to_vector());
837    if window_adapter.renderer().supports_transformations() {
838        // Unapply other transforms.
839        if let Some(inverse_transform) = item_rc.inverse_children_transform() {
840            event_for_children.transform(inverse_transform);
841        }
842    }
843
844    let filter_result = if mouse_event.position().is_some_and(|p| geom.contains(p))
845        || item.as_ref().clips_children()
846    {
847        item.as_ref().input_event_filter_before_children(
848            &event_for_children,
849            window_adapter,
850            &item_rc,
851        )
852    } else {
853        InputEventFilterResult::ForwardAndIgnore
854    };
855
856    let (forward_to_children, ignore) = match filter_result {
857        InputEventFilterResult::ForwardEvent => (true, false),
858        InputEventFilterResult::ForwardAndIgnore => (true, true),
859        InputEventFilterResult::ForwardAndInterceptGrab => (true, false),
860        InputEventFilterResult::Intercept => (false, false),
861        InputEventFilterResult::DelayForwarding(_) if ignore_delays => (true, false),
862        InputEventFilterResult::DelayForwarding(duration) => {
863            let timer = Timer::default();
864            let w = Rc::downgrade(window_adapter);
865            timer.start(
866                crate::timers::TimerMode::SingleShot,
867                Duration::from_millis(duration),
868                move || {
869                    if let Some(w) = w.upgrade() {
870                        WindowInner::from_pub(w.window()).process_delayed_event();
871                    }
872                },
873            );
874            result.delayed = Some((timer, event_for_children));
875            result
876                .item_stack
877                .push((item_rc.downgrade(), InputEventFilterResult::DelayForwarding(duration)));
878            return VisitChildrenResult::abort(item_rc.index(), 0);
879        }
880    };
881
882    result.item_stack.push((item_rc.downgrade(), filter_result));
883    if forward_to_children {
884        let mut actual_visitor =
885            |component: &ItemTreeRc, index: u32, _: Pin<ItemRef>| -> VisitChildrenResult {
886                send_mouse_event_to_item(
887                    &event_for_children,
888                    ItemRc::new(component.clone(), index),
889                    window_adapter,
890                    result,
891                    last_top_item,
892                    ignore_delays,
893                )
894            };
895        vtable::new_vref!(let mut actual_visitor : VRefMut<crate::item_tree::ItemVisitorVTable> for crate::item_tree::ItemVisitor = &mut actual_visitor);
896        let r = vtable::VRc::borrow_pin(item_rc.item_tree()).as_ref().visit_children_item(
897            item_rc.index() as isize,
898            crate::item_tree::TraversalOrder::FrontToBack,
899            actual_visitor,
900        );
901        if r.has_aborted() {
902            return r;
903        }
904    };
905
906    let r = if ignore {
907        InputEventResult::EventIgnored
908    } else {
909        let mut event = mouse_event.clone();
910        event.translate(-geom.origin.to_vector());
911        if last_top_item.is_none_or(|x| *x != item_rc) {
912            event.set_click_count(0);
913        }
914        item.as_ref().input_event(&event, window_adapter, &item_rc)
915    };
916    match r {
917        InputEventResult::EventAccepted => VisitChildrenResult::abort(item_rc.index(), 0),
918        InputEventResult::EventIgnored => {
919            let _pop = result.item_stack.pop();
920            debug_assert_eq!(
921                _pop.map(|x| (x.0.upgrade().unwrap().index(), x.1)).unwrap(),
922                (item_rc.index(), filter_result)
923            );
924            VisitChildrenResult::CONTINUE
925        }
926        InputEventResult::GrabMouse => {
927            result.item_stack.last_mut().unwrap().1 =
928                InputEventFilterResult::ForwardAndInterceptGrab;
929            result.grabbed = true;
930            VisitChildrenResult::abort(item_rc.index(), 0)
931        }
932        InputEventResult::StartDrag => {
933            result.item_stack.last_mut().unwrap().1 =
934                InputEventFilterResult::ForwardAndInterceptGrab;
935            result.grabbed = false;
936            let drag_area_item = item_rc.downcast::<crate::items::DragArea>().unwrap();
937            result.drag_data = Some(DropEvent {
938                mime_type: drag_area_item.as_pin_ref().mime_type(),
939                data: drag_area_item.as_pin_ref().data(),
940                position: Default::default(),
941            });
942            VisitChildrenResult::abort(item_rc.index(), 0)
943        }
944    }
945}
946
947/// The TextCursorBlinker takes care of providing a toggled boolean property
948/// that can be used to animate a blinking cursor. It's typically stored in the
949/// Window using a Weak and set_binding() can be used to set up a binding on a given
950/// property that'll keep it up-to-date. That binding keeps a strong reference to the
951/// blinker. If the underlying item that uses it goes away, the binding goes away and
952/// so does the blinker.
953#[derive(FieldOffsets)]
954#[repr(C)]
955#[pin]
956pub(crate) struct TextCursorBlinker {
957    cursor_visible: Property<bool>,
958    cursor_blink_timer: crate::timers::Timer,
959}
960
961impl TextCursorBlinker {
962    /// Creates a new instance, wrapped in a Pin<Rc<_>> because the boolean property
963    /// the blinker properties uses the property system that requires pinning.
964    pub fn new() -> Pin<Rc<Self>> {
965        Rc::pin(Self {
966            cursor_visible: Property::new(true),
967            cursor_blink_timer: Default::default(),
968        })
969    }
970
971    /// Sets a binding on the provided property that will ensure that the property value
972    /// is true when the cursor should be shown and false if not.
973    pub fn set_binding(
974        instance: Pin<Rc<TextCursorBlinker>>,
975        prop: &Property<bool>,
976        cycle_duration: Duration,
977    ) {
978        instance.as_ref().cursor_visible.set(true);
979        // Re-start timer, in case.
980        Self::start(&instance, cycle_duration);
981        prop.set_binding(move || {
982            TextCursorBlinker::FIELD_OFFSETS.cursor_visible.apply_pin(instance.as_ref()).get()
983        });
984    }
985
986    /// Starts the blinking cursor timer that will toggle the cursor and update all bindings that
987    /// were installed on properties with set_binding call.
988    pub fn start(self: &Pin<Rc<Self>>, cycle_duration: Duration) {
989        if self.cursor_blink_timer.running() {
990            self.cursor_blink_timer.restart();
991        } else {
992            let toggle_cursor = {
993                let weak_blinker = pin_weak::rc::PinWeak::downgrade(self.clone());
994                move || {
995                    if let Some(blinker) = weak_blinker.upgrade() {
996                        let visible = TextCursorBlinker::FIELD_OFFSETS
997                            .cursor_visible
998                            .apply_pin(blinker.as_ref())
999                            .get();
1000                        blinker.cursor_visible.set(!visible);
1001                    }
1002                }
1003            };
1004            if !cycle_duration.is_zero() {
1005                self.cursor_blink_timer.start(
1006                    crate::timers::TimerMode::Repeated,
1007                    cycle_duration / 2,
1008                    toggle_cursor,
1009                );
1010            }
1011        }
1012    }
1013
1014    /// Stops the blinking cursor timer. This is usually used for example when the window that contains
1015    /// text editable elements looses the focus or is hidden.
1016    pub fn stop(&self) {
1017        self.cursor_blink_timer.stop()
1018    }
1019}