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