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