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