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