Skip to main content

i_slint_core/items/
input_items.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
4use super::{
5    EventResult, FocusReasonArg, Item, ItemConsts, ItemRc, ItemRendererRef, KeyEventArg,
6    MouseCursor, PointerEvent, PointerEventArg, PointerEventButton, PointerEventKind,
7    PointerScrollEvent, PointerScrollEventArg, RenderingResult, VoidArg,
8};
9use crate::api::LogicalPosition;
10use crate::input::{
11    FocusEvent, FocusEventResult, FocusReason, InputEventFilterResult, InputEventResult,
12    InternalKeyEvent, KeyEventResult, KeyEventType, Keys, MouseEvent,
13};
14use crate::item_rendering::CachedRenderingData;
15use crate::items::ItemTreeVTable;
16use crate::layout::{LayoutInfo, Orientation};
17use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PointLengths};
18use crate::properties::PropertyTracker;
19#[cfg(feature = "rtti")]
20use crate::rtti::*;
21use crate::window::{WindowAdapter, WindowInner};
22use crate::{Callback, Coord, Property};
23use alloc::{boxed::Box, rc::Rc, vec::Vec};
24use const_field_offset::FieldOffsets;
25use core::cell::Cell;
26use core::pin::Pin;
27use i_slint_core_macros::*;
28use vtable::{VRcMapped, VWeakMapped};
29
30/// The implementation of the `TouchArea` element
31#[repr(C)]
32#[derive(FieldOffsets, SlintElement, Default)]
33#[pin]
34pub struct TouchArea {
35    pub enabled: Property<bool>,
36    /// FIXME: We should annotate this as an "output" property.
37    pub pressed: Property<bool>,
38    pub has_hover: Property<bool>,
39    /// FIXME: there should be just one property for the point instead of two.
40    /// Could even be merged with pressed in a `Property<Option<Point>>` (of course, in the
41    /// implementation item only, for the compiler it would stay separate properties)
42    pub pressed_x: Property<LogicalLength>,
43    pub pressed_y: Property<LogicalLength>,
44    /// FIXME: should maybe be as parameter to the mouse event instead. Or at least just one property
45    pub mouse_x: Property<LogicalLength>,
46    pub mouse_y: Property<LogicalLength>,
47    pub mouse_cursor: Property<MouseCursor>,
48    pub clicked: Callback<VoidArg>,
49    pub double_clicked: Callback<VoidArg>,
50    pub moved: Callback<VoidArg>,
51    pub pointer_event: Callback<PointerEventArg>,
52    pub scroll_event: Callback<PointerScrollEventArg, EventResult>,
53    /// FIXME: remove this
54    pub cached_rendering_data: CachedRenderingData,
55    /// true when we are currently grabbing the mouse
56    grabbed: Cell<bool>,
57}
58
59impl Item for TouchArea {
60    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
61
62    fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
63
64    fn layout_info(
65        self: Pin<&Self>,
66        _orientation: Orientation,
67        _cross_axis_constraint: Coord,
68        _window_adapter: &Rc<dyn WindowAdapter>,
69        _self_rc: &ItemRc,
70    ) -> LayoutInfo {
71        LayoutInfo { stretch: 1., ..LayoutInfo::default() }
72    }
73
74    fn input_event_filter_before_children(
75        self: Pin<&Self>,
76        event: &MouseEvent,
77        window_adapter: &Rc<dyn WindowAdapter>,
78        _self_rc: &ItemRc,
79        cursor: &mut MouseCursor,
80    ) -> InputEventFilterResult {
81        if !self.enabled() {
82            self.has_hover.set(false);
83            if self.grabbed.replace(false) {
84                self.pressed.set(false);
85                Self::FIELD_OFFSETS.pointer_event().apply_pin(self).call(&(PointerEvent {
86                    button: PointerEventButton::Other,
87                    kind: PointerEventKind::Cancel,
88                    modifiers: window_adapter.window().0.context().0.modifiers.get().into(),
89                    is_touch: false,
90                },));
91            }
92            return InputEventFilterResult::ForwardAndIgnore;
93        }
94        if matches!(event, MouseEvent::DragMove(..) | MouseEvent::Drop(..)) {
95            // Someone else has the grab, don't handle hover
96            return InputEventFilterResult::ForwardAndIgnore;
97        }
98        if let Some(pos) = event.position() {
99            Self::FIELD_OFFSETS.mouse_x().apply_pin(self).set(pos.x_length());
100            Self::FIELD_OFFSETS.mouse_y().apply_pin(self).set(pos.y_length());
101        }
102        let hovering = !matches!(event, MouseEvent::Exit);
103        Self::FIELD_OFFSETS.has_hover().apply_pin(self).set(hovering);
104        if hovering {
105            *cursor = self.mouse_cursor();
106        }
107        InputEventFilterResult::ForwardAndInterceptGrab
108    }
109
110    fn input_event(
111        self: Pin<&Self>,
112        event: &MouseEvent,
113        window_adapter: &Rc<dyn WindowAdapter>,
114        self_rc: &ItemRc,
115        _: &mut MouseCursor,
116    ) -> InputEventResult {
117        if matches!(event, MouseEvent::Exit) {
118            Self::FIELD_OFFSETS.has_hover().apply_pin(self).set(false);
119        }
120        if !self.enabled() {
121            return InputEventResult::EventIgnored;
122        }
123        match event {
124            MouseEvent::Pressed { position, button, is_touch, .. } => {
125                self.grabbed.set(true);
126                if *button == PointerEventButton::Left {
127                    Self::FIELD_OFFSETS.pressed_x().apply_pin(self).set(position.x_length());
128                    Self::FIELD_OFFSETS.pressed_y().apply_pin(self).set(position.y_length());
129                    Self::FIELD_OFFSETS.pressed().apply_pin(self).set(true);
130                }
131                Self::FIELD_OFFSETS.pointer_event().apply_pin(self).call(&(PointerEvent {
132                    button: *button,
133                    kind: PointerEventKind::Down,
134                    modifiers: window_adapter.window().0.context().0.modifiers.get().into(),
135                    is_touch: *is_touch,
136                },));
137
138                InputEventResult::GrabMouse
139            }
140            MouseEvent::Exit => {
141                Self::FIELD_OFFSETS.pressed().apply_pin(self).set(false);
142                if self.grabbed.replace(false) {
143                    Self::FIELD_OFFSETS.pointer_event().apply_pin(self).call(&(PointerEvent {
144                        button: PointerEventButton::Other,
145                        kind: PointerEventKind::Cancel,
146                        modifiers: window_adapter.window().0.context().0.modifiers.get().into(),
147                        is_touch: false,
148                    },));
149                }
150
151                InputEventResult::EventAccepted
152            }
153
154            MouseEvent::Released { button, position, click_count, is_touch } => {
155                let geometry = self_rc.geometry();
156                if *button == PointerEventButton::Left
157                    && LogicalRect::new(LogicalPoint::default(), geometry.size).contains(*position)
158                    && self.pressed()
159                {
160                    Self::FIELD_OFFSETS.clicked().apply_pin(self).call(&());
161                    if (click_count % 2) == 1 {
162                        Self::FIELD_OFFSETS.double_clicked().apply_pin(self).call(&())
163                    }
164                }
165
166                self.grabbed.set(false);
167                if *button == PointerEventButton::Left {
168                    Self::FIELD_OFFSETS.pressed().apply_pin(self).set(false);
169                }
170                Self::FIELD_OFFSETS.pointer_event().apply_pin(self).call(&(PointerEvent {
171                    button: *button,
172                    kind: PointerEventKind::Up,
173                    modifiers: window_adapter.window().0.context().0.modifiers.get().into(),
174                    is_touch: *is_touch,
175                },));
176
177                InputEventResult::EventAccepted
178            }
179            MouseEvent::Moved { is_touch, .. } => {
180                Self::FIELD_OFFSETS.pointer_event().apply_pin(self).call(&(PointerEvent {
181                    button: PointerEventButton::Other,
182                    kind: PointerEventKind::Move,
183                    modifiers: window_adapter.window().0.context().0.modifiers.get().into(),
184                    is_touch: *is_touch,
185                },));
186                if self.grabbed.get() {
187                    Self::FIELD_OFFSETS.moved().apply_pin(self).call(&());
188                    InputEventResult::GrabMouse
189                } else {
190                    InputEventResult::EventAccepted
191                }
192            }
193            MouseEvent::Wheel { delta_x, delta_y, .. } => {
194                let modifiers = window_adapter.window().0.context().0.modifiers.get().into();
195                let r = Self::FIELD_OFFSETS.scroll_event().apply_pin(self).call(&(
196                    PointerScrollEvent { delta_x: *delta_x, delta_y: *delta_y, modifiers },
197                ));
198                if self.grabbed.get() {
199                    InputEventResult::GrabMouse
200                } else {
201                    match r {
202                        EventResult::Reject => {
203                            // We are ignoring the event, so we will be removed from the item_stack,
204                            // therefore we must remove the has_hover flag as there might be a scroll under us.
205                            // It will be put back later.
206                            Self::FIELD_OFFSETS.has_hover().apply_pin(self).set(false);
207                            InputEventResult::EventIgnored
208                        }
209                        EventResult::Accept => InputEventResult::EventAccepted,
210                    }
211                }
212            }
213            MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
214                InputEventResult::EventIgnored
215            }
216            MouseEvent::DragMove(..) | MouseEvent::Drop(..) => InputEventResult::EventIgnored,
217        }
218    }
219
220    fn capture_key_event(
221        self: Pin<&Self>,
222        _: &InternalKeyEvent,
223        _window_adapter: &Rc<dyn WindowAdapter>,
224        _self_rc: &ItemRc,
225    ) -> KeyEventResult {
226        KeyEventResult::EventIgnored
227    }
228
229    fn key_event(
230        self: Pin<&Self>,
231        _: &InternalKeyEvent,
232        _window_adapter: &Rc<dyn WindowAdapter>,
233        _self_rc: &ItemRc,
234    ) -> KeyEventResult {
235        KeyEventResult::EventIgnored
236    }
237
238    fn focus_event(
239        self: Pin<&Self>,
240        _: &FocusEvent,
241        _window_adapter: &Rc<dyn WindowAdapter>,
242        _self_rc: &ItemRc,
243    ) -> FocusEventResult {
244        FocusEventResult::FocusIgnored
245    }
246
247    fn render(
248        self: Pin<&Self>,
249        _backend: &mut ItemRendererRef,
250        _self_rc: &ItemRc,
251        _size: LogicalSize,
252    ) -> RenderingResult {
253        RenderingResult::ContinueRenderingChildren
254    }
255
256    fn bounding_rect(
257        self: core::pin::Pin<&Self>,
258        _window_adapter: &Rc<dyn WindowAdapter>,
259        _self_rc: &ItemRc,
260        mut geometry: LogicalRect,
261    ) -> LogicalRect {
262        geometry.size = LogicalSize::zero();
263        geometry
264    }
265
266    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
267        false
268    }
269}
270
271impl ItemConsts for TouchArea {
272    const cached_rendering_data_offset: const_field_offset::FieldOffset<
273        TouchArea,
274        CachedRenderingData,
275    > = TouchArea::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
276}
277
278impl ItemConsts for KeyBinding {
279    const cached_rendering_data_offset: const_field_offset::FieldOffset<
280        KeyBinding,
281        CachedRenderingData,
282    > = KeyBinding::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
283}
284
285#[repr(C)]
286#[derive(FieldOffsets, Default, SlintElement)]
287#[pin]
288pub struct KeyBinding {
289    pub keys: Property<Keys>,
290    pub enabled: Property<bool>,
291    pub activated: Callback<VoidArg>,
292    pub cached_rendering_data: CachedRenderingData,
293}
294
295impl Item for KeyBinding {
296    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
297
298    fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
299
300    fn layout_info(
301        self: Pin<&Self>,
302        _orientation: crate::items::Orientation,
303        _cross_axis_constraint: Coord,
304        _window_adapter: &Rc<dyn WindowAdapter>,
305        _self_rc: &ItemRc,
306    ) -> crate::layout::LayoutInfo {
307        Default::default()
308    }
309
310    fn input_event_filter_before_children(
311        self: Pin<&Self>,
312        _: &crate::input::MouseEvent,
313        _window_adapter: &Rc<dyn WindowAdapter>,
314        _self_rc: &ItemRc,
315        _: &mut crate::items::MouseCursor,
316    ) -> crate::input::InputEventFilterResult {
317        Default::default()
318    }
319
320    fn input_event(
321        self: Pin<&Self>,
322        _: &crate::input::MouseEvent,
323        _window_adapter: &Rc<dyn WindowAdapter>,
324        _self_rc: &ItemRc,
325        _: &mut crate::items::MouseCursor,
326    ) -> crate::input::InputEventResult {
327        Default::default()
328    }
329
330    fn capture_key_event(
331        self: Pin<&Self>,
332        _: &InternalKeyEvent,
333        _window_adapter: &Rc<dyn WindowAdapter>,
334        _self_rc: &ItemRc,
335    ) -> crate::input::KeyEventResult {
336        crate::input::KeyEventResult::EventIgnored
337    }
338
339    fn key_event(
340        self: Pin<&Self>,
341        _: &InternalKeyEvent,
342        _window_adapter: &Rc<dyn WindowAdapter>,
343        _self_rc: &ItemRc,
344    ) -> crate::input::KeyEventResult {
345        Default::default()
346    }
347
348    fn focus_event(
349        self: Pin<&Self>,
350        _: &crate::input::FocusEvent,
351        _window_adapter: &Rc<dyn WindowAdapter>,
352        _self_rc: &ItemRc,
353    ) -> crate::input::FocusEventResult {
354        Default::default()
355    }
356
357    fn render(
358        self: Pin<&Self>,
359        _backend: &mut &mut dyn crate::item_rendering::ItemRenderer,
360        _self_rc: &ItemRc,
361        _size: crate::lengths::LogicalSize,
362    ) -> crate::items::RenderingResult {
363        Default::default()
364    }
365
366    fn bounding_rect(
367        self: Pin<&Self>,
368        _window_adapter: &Rc<dyn WindowAdapter>,
369        _self_rc: &ItemRc,
370        geometry: crate::lengths::LogicalRect,
371    ) -> crate::lengths::LogicalRect {
372        geometry
373    }
374
375    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
376        false
377    }
378}
379
380/// An optimized ShortcutList that is only initialized when it is
381/// first accessed.
382#[repr(C)]
383#[derive(Default)] // results in a null pointer, which we will initialize on first access
384pub struct MaybeKeyBindingList(Cell<*const KeyBindingList>);
385
386impl MaybeKeyBindingList {
387    fn ensure_init(&self) {
388        // This would be a race condition in Multi-threaded code, but
389        // this type isn't Sync, so this function cannot race with another thread.
390        if self.0.get().is_null() {
391            self.0.set(Box::leak(Box::default()));
392        }
393    }
394}
395
396impl Drop for MaybeKeyBindingList {
397    fn drop(&mut self) {
398        let ptr = self.0.replace(core::ptr::null());
399        if !ptr.is_null() {
400            // SAFETY: Must be a pointer returned by `Box::leak`, which is guaranteed by `ensure_init`.
401            drop(unsafe { Box::from_raw(ptr as *mut KeyBindingList) });
402        }
403    }
404}
405
406impl MaybeKeyBindingList {
407    fn deref_pin(self: Pin<&Self>) -> Pin<&KeyBindingList> {
408        self.ensure_init();
409        // SAFETY: Must be non-null and properly aligned, which is guaranteed by `ensure_init`.
410        unsafe { Pin::new_unchecked(&*self.get_ref().0.get()) }
411    }
412}
413
414#[derive(Default)]
415#[pin_project::pin_project]
416pub struct KeyBindingList {
417    found: core::cell::RefCell<Vec<VWeakMapped<ItemTreeVTable, KeyBinding>>>,
418    #[pin]
419    property_tracker: PropertyTracker,
420}
421
422/// A runtime item that exposes key events
423#[repr(C)]
424#[derive(FieldOffsets, Default, SlintElement)]
425#[pin]
426pub struct FocusScope {
427    pub enabled: Property<bool>,
428    pub has_focus: Property<bool>,
429    pub focus_on_click: Property<bool>,
430    pub focus_on_tab_navigation: Property<bool>,
431    pub key_pressed: Callback<KeyEventArg, EventResult>,
432    pub key_released: Callback<KeyEventArg, EventResult>,
433    pub capture_key_pressed: Callback<KeyEventArg, EventResult>,
434    pub capture_key_released: Callback<KeyEventArg, EventResult>,
435    pub focus_changed_event: Callback<FocusReasonArg>,
436    pub focus_gained: Callback<FocusReasonArg>,
437    pub focus_lost: Callback<FocusReasonArg>,
438    pub key_bindings: MaybeKeyBindingList,
439    /// FIXME: remove this
440    pub cached_rendering_data: CachedRenderingData,
441}
442
443impl FocusScope {
444    fn visit_enabled_key_bindings<R>(
445        self: Pin<&Self>,
446        self_rc: &ItemRc,
447        mut fun: impl FnMut(&VRcMapped<ItemTreeVTable, KeyBinding>) -> Option<R>,
448    ) -> Option<R> {
449        let list = Self::FIELD_OFFSETS.key_bindings().apply_pin(self);
450        let list = list.deref_pin();
451
452        list.project_ref().property_tracker.evaluate_if_dirty(|| {
453            let mut found = list.found.borrow_mut();
454            found.clear();
455
456            let mut next = self_rc.first_child();
457            while let Some(child) = next {
458                if let Some(key_binding) = ItemRc::downcast::<KeyBinding>(&child)
459                    && key_binding.as_pin_ref().enabled()
460                {
461                    found.push(VRcMapped::downgrade(&key_binding));
462                }
463                next = child.next_sibling();
464            }
465        });
466
467        let list = list.found.borrow();
468        for key_binding in &*list {
469            let Some(shortcut) = key_binding.upgrade() else {
470                crate::debug_log!("Warning: Found a dropped KeyBinding!");
471                continue;
472            };
473            if let Some(result) = fun(&shortcut) {
474                return Some(result);
475            }
476        }
477
478        None
479    }
480
481    /// Returns the first matching key binding and whether there are multiple matches.
482    fn key_binding_for_event(
483        self: Pin<&Self>,
484        self_rc: &ItemRc,
485        inter_key_event: &InternalKeyEvent,
486    ) -> Option<(VRcMapped<ItemTreeVTable, KeyBinding>, bool)> {
487        let mut first_match = None;
488
489        let ambiguous = self.visit_enabled_key_bindings(self_rc, |key_binding| {
490            let keys = key_binding.as_pin_ref().keys();
491            if keys.matches(&inter_key_event.key_event) {
492                match &first_match {
493                    Some(key_binding) => {
494                        return Some(VRcMapped::clone(key_binding));
495                    }
496                    None => {
497                        first_match = Some(VRcMapped::clone(key_binding));
498                    }
499                };
500            }
501            None
502        });
503
504        first_match.map(|binding| (VRcMapped::clone(&binding), ambiguous.is_some()))
505    }
506}
507
508impl Item for FocusScope {
509    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
510
511    fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
512
513    fn layout_info(
514        self: Pin<&Self>,
515        _orientation: Orientation,
516        _cross_axis_constraint: Coord,
517        _window_adapter: &Rc<dyn WindowAdapter>,
518        _self_rc: &ItemRc,
519    ) -> LayoutInfo {
520        LayoutInfo { stretch: 1., ..LayoutInfo::default() }
521    }
522
523    fn input_event_filter_before_children(
524        self: Pin<&Self>,
525        _: &MouseEvent,
526        _window_adapter: &Rc<dyn WindowAdapter>,
527        _self_rc: &ItemRc,
528        _: &mut MouseCursor,
529    ) -> InputEventFilterResult {
530        InputEventFilterResult::ForwardEvent
531    }
532
533    fn input_event(
534        self: Pin<&Self>,
535        event: &MouseEvent,
536        window_adapter: &Rc<dyn WindowAdapter>,
537        self_rc: &ItemRc,
538        _: &mut MouseCursor,
539    ) -> InputEventResult {
540        if self.enabled()
541            && self.focus_on_click()
542            && matches!(event, MouseEvent::Pressed { .. })
543            && !self.has_focus()
544        {
545            WindowInner::from_pub(window_adapter.window()).set_focus_item(
546                self_rc,
547                true,
548                FocusReason::PointerClick,
549            );
550            InputEventResult::EventAccepted
551        } else {
552            InputEventResult::EventIgnored
553        }
554    }
555
556    fn capture_key_event(
557        self: Pin<&Self>,
558        event: &InternalKeyEvent,
559        _window_adapter: &Rc<dyn WindowAdapter>,
560        _self_rc: &ItemRc,
561    ) -> KeyEventResult {
562        let r = match event.event_type {
563            KeyEventType::KeyPressed => Self::FIELD_OFFSETS
564                .capture_key_pressed()
565                .apply_pin(self)
566                .call(&(event.key_event.clone(),)),
567            KeyEventType::KeyReleased => Self::FIELD_OFFSETS
568                .capture_key_released()
569                .apply_pin(self)
570                .call(&(event.key_event.clone(),)),
571            KeyEventType::UpdateComposition | KeyEventType::CommitComposition => {
572                EventResult::Reject
573            }
574        };
575        match r {
576            EventResult::Accept => KeyEventResult::EventAccepted,
577            EventResult::Reject => KeyEventResult::EventIgnored,
578        }
579    }
580
581    fn key_event(
582        self: Pin<&Self>,
583        event: &InternalKeyEvent,
584        _window_adapter: &Rc<dyn WindowAdapter>,
585        self_rc: &ItemRc,
586    ) -> KeyEventResult {
587        let r = match event.event_type {
588            KeyEventType::KeyPressed => {
589                if let Some((key_binding, ambiguous)) = self.key_binding_for_event(self_rc, event) {
590                    if ambiguous {
591                        let keys = KeyBinding::FIELD_OFFSETS
592                            .keys()
593                            .apply_pin(key_binding.as_pin_ref())
594                            .get();
595                        crate::debug_log!(
596                            "Warning: Multiple matching KeyBinding elements for keys {:?}!",
597                            keys
598                        );
599                    }
600                    KeyBinding::FIELD_OFFSETS
601                        .activated()
602                        .apply_pin(key_binding.as_pin_ref())
603                        .call(&());
604                    EventResult::Accept
605                } else {
606                    Self::FIELD_OFFSETS
607                        .key_pressed()
608                        .apply_pin(self)
609                        .call(&(event.key_event.clone(),))
610                }
611            }
612            KeyEventType::KeyReleased => {
613                let binding = self.key_binding_for_event(self_rc, event);
614                if binding.is_some() {
615                    // The binding should have already handled the key press,
616                    // so we just accept the release event if it matches a binding,
617                    // to ensure the events remain "symmetric" in the key-pressed and key-released callbacks.
618                    // Either both events appear in the callbacks, or neither do.
619                    EventResult::Accept
620                } else {
621                    Self::FIELD_OFFSETS
622                        .key_released()
623                        .apply_pin(self)
624                        .call(&(event.key_event.clone(),))
625                }
626            }
627            KeyEventType::UpdateComposition | KeyEventType::CommitComposition => {
628                EventResult::Reject
629            }
630        };
631        match r {
632            EventResult::Accept => KeyEventResult::EventAccepted,
633            EventResult::Reject => KeyEventResult::EventIgnored,
634        }
635    }
636
637    fn focus_event(
638        self: Pin<&Self>,
639        event: &FocusEvent,
640        _window_adapter: &Rc<dyn WindowAdapter>,
641        _self_rc: &ItemRc,
642    ) -> FocusEventResult {
643        if !self.enabled() {
644            return FocusEventResult::FocusIgnored;
645        }
646
647        match event {
648            FocusEvent::FocusIn(reason) => {
649                match reason {
650                    FocusReason::TabNavigation if !self.focus_on_tab_navigation() => {
651                        return FocusEventResult::FocusIgnored;
652                    }
653                    FocusReason::PointerClick if !self.focus_on_click() => {
654                        return FocusEventResult::FocusIgnored;
655                    }
656                    _ => (),
657                };
658
659                self.has_focus.set(true);
660                Self::FIELD_OFFSETS.focus_changed_event().apply_pin(self).call(&(*reason,));
661                Self::FIELD_OFFSETS.focus_gained().apply_pin(self).call(&(*reason,));
662            }
663            FocusEvent::FocusOut(reason) => {
664                self.has_focus.set(false);
665                Self::FIELD_OFFSETS.focus_changed_event().apply_pin(self).call(&(*reason,));
666                Self::FIELD_OFFSETS.focus_lost().apply_pin(self).call(&(*reason,));
667            }
668        }
669        FocusEventResult::FocusAccepted
670    }
671
672    fn render(
673        self: Pin<&Self>,
674        _backend: &mut ItemRendererRef,
675        _self_rc: &ItemRc,
676        _size: LogicalSize,
677    ) -> RenderingResult {
678        RenderingResult::ContinueRenderingChildren
679    }
680
681    fn bounding_rect(
682        self: core::pin::Pin<&Self>,
683        _window_adapter: &Rc<dyn WindowAdapter>,
684        _self_rc: &ItemRc,
685        mut geometry: LogicalRect,
686    ) -> LogicalRect {
687        geometry.size = LogicalSize::zero();
688        geometry
689    }
690
691    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
692        false
693    }
694}
695
696impl ItemConsts for FocusScope {
697    const cached_rendering_data_offset: const_field_offset::FieldOffset<
698        FocusScope,
699        CachedRenderingData,
700    > = FocusScope::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
701}
702
703#[repr(C)]
704#[derive(FieldOffsets, Default, SlintElement)]
705#[pin]
706pub struct SwipeGestureHandler {
707    pub enabled: Property<bool>,
708    pub handle_swipe_left: Property<bool>,
709    pub handle_swipe_right: Property<bool>,
710    pub handle_swipe_up: Property<bool>,
711    pub handle_swipe_down: Property<bool>,
712
713    pub moved: Callback<VoidArg>,
714    pub swiped: Callback<VoidArg>,
715    pub cancelled: Callback<VoidArg>,
716
717    pub pressed_position: Property<LogicalPosition>,
718    pub current_position: Property<LogicalPosition>,
719    pub swiping: Property<bool>,
720
721    // true when the cursor is pressed down and we haven't cancelled yet for another reason
722    pressed: Cell<bool>,
723    // capture_events: Cell<bool>,
724    /// FIXME: remove this
725    pub cached_rendering_data: CachedRenderingData,
726}
727
728impl Item for SwipeGestureHandler {
729    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
730
731    fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
732
733    fn layout_info(
734        self: Pin<&Self>,
735        _orientation: Orientation,
736        _cross_axis_constraint: Coord,
737        _window_adapter: &Rc<dyn WindowAdapter>,
738        _self_rc: &ItemRc,
739    ) -> LayoutInfo {
740        LayoutInfo { stretch: 1., ..LayoutInfo::default() }
741    }
742
743    fn input_event_filter_before_children(
744        self: Pin<&Self>,
745        event: &MouseEvent,
746        _window_adapter: &Rc<dyn WindowAdapter>,
747        _self_rc: &ItemRc,
748        _: &mut MouseCursor,
749    ) -> InputEventFilterResult {
750        if !self.enabled() {
751            if self.pressed.get() {
752                self.cancel_impl();
753            }
754            return InputEventFilterResult::ForwardAndIgnore;
755        }
756
757        match event {
758            MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
759                Self::FIELD_OFFSETS
760                    .pressed_position()
761                    .apply_pin(self)
762                    .set(crate::lengths::logical_position_to_api(*position));
763                self.pressed.set(true);
764                InputEventFilterResult::DelayForwarding(
765                    super::flickable::FORWARD_DELAY.as_millis() as _
766                )
767            }
768            MouseEvent::Exit => {
769                self.cancel_impl();
770                InputEventFilterResult::ForwardAndIgnore
771            }
772            MouseEvent::Released { button: PointerEventButton::Left, .. } => {
773                if self.swiping() {
774                    InputEventFilterResult::Intercept
775                } else {
776                    self.pressed.set(false);
777                    InputEventFilterResult::ForwardEvent
778                }
779            }
780            MouseEvent::Moved { position, .. } => {
781                if self.swiping() {
782                    InputEventFilterResult::Intercept
783                } else if !self.pressed.get() {
784                    InputEventFilterResult::ForwardEvent
785                } else if self.is_over_threshold(position) {
786                    InputEventFilterResult::Intercept
787                } else {
788                    InputEventFilterResult::ForwardAndInterceptGrab
789                }
790            }
791            MouseEvent::Wheel { .. } => InputEventFilterResult::ForwardAndIgnore,
792            // Not the left button
793            MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
794                InputEventFilterResult::ForwardAndIgnore
795            }
796            MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
797                InputEventFilterResult::ForwardAndIgnore
798            }
799            MouseEvent::DragMove(..) | MouseEvent::Drop(..) => {
800                InputEventFilterResult::ForwardAndIgnore
801            }
802        }
803    }
804
805    fn input_event(
806        self: Pin<&Self>,
807        event: &MouseEvent,
808        _window_adapter: &Rc<dyn WindowAdapter>,
809        _self_rc: &ItemRc,
810        _: &mut MouseCursor,
811    ) -> InputEventResult {
812        match event {
813            MouseEvent::Pressed { .. } => InputEventResult::GrabMouse,
814            MouseEvent::Exit => {
815                self.cancel_impl();
816                InputEventResult::EventIgnored
817            }
818            MouseEvent::Released { position, .. } => {
819                if !self.pressed.get() && !self.swiping() {
820                    return InputEventResult::EventIgnored;
821                }
822                self.current_position.set(crate::lengths::logical_position_to_api(*position));
823                self.pressed.set(false);
824                if self.swiping() {
825                    Self::FIELD_OFFSETS.swiping().apply_pin(self).set(false);
826                    Self::FIELD_OFFSETS.swiped().apply_pin(self).call(&());
827                    InputEventResult::EventAccepted
828                } else {
829                    InputEventResult::EventIgnored
830                }
831            }
832            MouseEvent::Moved { position, .. } => {
833                if !self.pressed.get() {
834                    return InputEventResult::EventAccepted;
835                }
836                self.current_position.set(crate::lengths::logical_position_to_api(*position));
837                let mut swiping = self.swiping();
838                if !swiping && self.is_over_threshold(position) {
839                    Self::FIELD_OFFSETS.swiping().apply_pin(self).set(true);
840                    swiping = true;
841                }
842                Self::FIELD_OFFSETS.moved().apply_pin(self).call(&());
843                if swiping { InputEventResult::GrabMouse } else { InputEventResult::EventAccepted }
844            }
845            MouseEvent::Wheel { .. } => InputEventResult::EventIgnored,
846            MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
847                InputEventResult::EventIgnored
848            }
849            MouseEvent::DragMove(..) | MouseEvent::Drop(..) => InputEventResult::EventIgnored,
850        }
851    }
852
853    fn capture_key_event(
854        self: Pin<&Self>,
855        _: &InternalKeyEvent,
856        _window_adapter: &Rc<dyn WindowAdapter>,
857        _self_rc: &ItemRc,
858    ) -> KeyEventResult {
859        KeyEventResult::EventIgnored
860    }
861
862    fn key_event(
863        self: Pin<&Self>,
864        _event: &InternalKeyEvent,
865        _window_adapter: &Rc<dyn WindowAdapter>,
866        _self_rc: &ItemRc,
867    ) -> KeyEventResult {
868        KeyEventResult::EventIgnored
869    }
870
871    fn focus_event(
872        self: Pin<&Self>,
873        _: &FocusEvent,
874        _window_adapter: &Rc<dyn WindowAdapter>,
875        _self_rc: &ItemRc,
876    ) -> FocusEventResult {
877        FocusEventResult::FocusIgnored
878    }
879
880    fn render(
881        self: Pin<&Self>,
882        _backend: &mut ItemRendererRef,
883        _self_rc: &ItemRc,
884        _size: LogicalSize,
885    ) -> RenderingResult {
886        RenderingResult::ContinueRenderingChildren
887    }
888
889    fn bounding_rect(
890        self: core::pin::Pin<&Self>,
891        _window_adapter: &Rc<dyn WindowAdapter>,
892        _self_rc: &ItemRc,
893        mut geometry: LogicalRect,
894    ) -> LogicalRect {
895        geometry.size = LogicalSize::zero();
896        geometry
897    }
898
899    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
900        false
901    }
902}
903
904impl ItemConsts for SwipeGestureHandler {
905    const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
906        Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
907}
908
909impl SwipeGestureHandler {
910    pub fn cancel(self: Pin<&Self>, _: &Rc<dyn WindowAdapter>, _: &ItemRc) {
911        self.cancel_impl();
912    }
913
914    fn cancel_impl(self: Pin<&Self>) {
915        if !self.pressed.replace(false) {
916            debug_assert!(!self.swiping());
917            return;
918        }
919        if self.swiping() {
920            Self::FIELD_OFFSETS.swiping().apply_pin(self).set(false);
921            Self::FIELD_OFFSETS.cancelled().apply_pin(self).call(&());
922        }
923    }
924
925    fn is_over_threshold(self: Pin<&Self>, position: &LogicalPoint) -> bool {
926        let pressed_pos = self.pressed_position();
927        let dx = position.x - pressed_pos.x as Coord;
928        let dy = position.y - pressed_pos.y as Coord;
929        let threshold = super::flickable::DISTANCE_THRESHOLD.get();
930        (self.handle_swipe_down() && dy > threshold && dy > dx.abs() / 2 as Coord)
931            || (self.handle_swipe_up() && dy < -threshold && dy < -dx.abs() / 2 as Coord)
932            || (self.handle_swipe_left() && dx < -threshold && dx < -dy.abs() / 2 as Coord)
933            || (self.handle_swipe_right() && dx > threshold && dx > dy.abs() / 2 as Coord)
934    }
935}
936
937#[cfg(feature = "ffi")]
938mod ffi {
939    use super::*;
940
941    #[unsafe(no_mangle)]
942    pub unsafe extern "C" fn slint_swipegesturehandler_cancel(
943        s: Pin<&SwipeGestureHandler>,
944        window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
945        self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
946        self_index: u32,
947    ) {
948        unsafe {
949            let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
950            let self_rc = ItemRc::new(self_component.clone(), self_index);
951            s.cancel(window_adapter, &self_rc);
952        }
953    }
954
955    /// # Safety
956    /// This must be called using a non-null pointer pointing to a chunk of memory big enough to
957    /// hold a MaybeKeybindingList
958    #[unsafe(no_mangle)]
959    pub unsafe extern "C" fn slint_maybe_key_binding_list_init(list: *mut MaybeKeyBindingList) {
960        unsafe {
961            core::ptr::write(list, MaybeKeyBindingList::default());
962        }
963    }
964
965    /// # Safety
966    /// This must be called using a non-null pointer pointing to an initialized MaybeKeybindingList
967    #[unsafe(no_mangle)]
968    pub unsafe extern "C" fn slint_maybe_key_binding_list_free(list: *mut MaybeKeyBindingList) {
969        unsafe { core::ptr::drop_in_place(list) };
970    }
971}
972
973/// The implementation of the `ScaleRotateGestureHandler` element.
974///
975/// Provides an API surface for platform-recognized pinch gesture events.
976/// Receives `MouseEvent::PinchGesture` events via the normal mouse event
977/// tree-walk and exposes cumulative scale, center position, and lifecycle callbacks.
978#[repr(C)]
979#[derive(FieldOffsets, Default, SlintElement)]
980#[pin]
981pub struct ScaleRotateGestureHandler {
982    pub enabled: Property<bool>,
983
984    // Output properties
985    pub active: Property<bool>,
986    /// Cumulative scale factor relative to gesture start. Always 1.0 when the
987    /// gesture starts, then updated as the gesture progresses (e.g., 2.0 means
988    /// doubled, 0.5 means halved).
989    pub scale: Property<f32>,
990    /// Cumulative rotation in degrees relative to gesture start. Always 0.0 when
991    /// the gesture starts.
992    pub rotation: Property<f32>,
993    pub center: Property<LogicalPosition>,
994
995    // Callbacks
996    pub started: Callback<VoidArg>,
997    pub updated: Callback<VoidArg>,
998    pub ended: Callback<VoidArg>,
999    pub cancelled: Callback<VoidArg>,
1000
1001    /// FIXME: remove this
1002    pub cached_rendering_data: CachedRenderingData,
1003}
1004
1005impl Item for ScaleRotateGestureHandler {
1006    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {
1007        self.scale.set(1.0);
1008    }
1009
1010    fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
1011
1012    fn layout_info(
1013        self: Pin<&Self>,
1014        _orientation: Orientation,
1015        _cross_axis_constraint: Coord,
1016        _window_adapter: &Rc<dyn WindowAdapter>,
1017        _self_rc: &ItemRc,
1018    ) -> LayoutInfo {
1019        LayoutInfo { stretch: 1., ..LayoutInfo::default() }
1020    }
1021
1022    fn input_event_filter_before_children(
1023        self: Pin<&Self>,
1024        event: &MouseEvent,
1025        _window_adapter: &Rc<dyn WindowAdapter>,
1026        _self_rc: &ItemRc,
1027        _: &mut MouseCursor,
1028    ) -> InputEventFilterResult {
1029        match event {
1030            // Forward gesture events so inner handlers get first shot
1031            MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. }
1032                if self.enabled() =>
1033            {
1034                InputEventFilterResult::ForwardEvent
1035            }
1036            // While a gesture is active, intercept non-gesture events to
1037            // prevent Flickable and other items from processing them concurrently.
1038            _ if self.active() => InputEventFilterResult::Intercept,
1039            _ => InputEventFilterResult::ForwardAndIgnore,
1040        }
1041    }
1042
1043    fn input_event(
1044        self: Pin<&Self>,
1045        event: &MouseEvent,
1046        _window_adapter: &Rc<dyn WindowAdapter>,
1047        _self_rc: &ItemRc,
1048        _: &mut MouseCursor,
1049    ) -> InputEventResult {
1050        use crate::input::TouchPhase;
1051        match event {
1052            MouseEvent::PinchGesture { delta, phase, position } => {
1053                if !self.enabled() {
1054                    if self.active() {
1055                        self.cancel_impl();
1056                    }
1057                    return InputEventResult::EventIgnored;
1058                }
1059                let new_scale = self.scale() * (1.0 + delta);
1060                Self::FIELD_OFFSETS.scale().apply_pin(self).set(new_scale);
1061                let center = crate::lengths::logical_position_to_api(*position);
1062                Self::FIELD_OFFSETS.center().apply_pin(self).set(center);
1063                match phase {
1064                    TouchPhase::Started => {
1065                        if !self.active() {
1066                            Self::FIELD_OFFSETS.active().apply_pin(self).set(true);
1067                            Self::FIELD_OFFSETS.started().apply_pin(self).call(&());
1068                        }
1069                        InputEventResult::GrabMouse
1070                    }
1071                    TouchPhase::Moved => {
1072                        if !self.active() {
1073                            return InputEventResult::EventIgnored;
1074                        }
1075                        Self::FIELD_OFFSETS.updated().apply_pin(self).call(&());
1076                        InputEventResult::GrabMouse
1077                    }
1078                    TouchPhase::Ended => self.end_impl(),
1079                    TouchPhase::Cancelled => {
1080                        self.cancel_impl();
1081                        InputEventResult::EventAccepted
1082                    }
1083                }
1084            }
1085            MouseEvent::RotationGesture { delta, phase, position } => {
1086                if !self.enabled() {
1087                    return InputEventResult::EventIgnored;
1088                }
1089                let center = crate::lengths::logical_position_to_api(*position);
1090                Self::FIELD_OFFSETS.center().apply_pin(self).set(center);
1091                let new_rotation = self.rotation() + delta;
1092                Self::FIELD_OFFSETS.rotation().apply_pin(self).set(new_rotation);
1093                match phase {
1094                    TouchPhase::Started => {
1095                        if !self.active() {
1096                            Self::FIELD_OFFSETS.active().apply_pin(self).set(true);
1097                            Self::FIELD_OFFSETS.started().apply_pin(self).call(&());
1098                        }
1099                        InputEventResult::GrabMouse
1100                    }
1101                    TouchPhase::Moved => {
1102                        if !self.active() {
1103                            return InputEventResult::EventIgnored;
1104                        }
1105                        Self::FIELD_OFFSETS.updated().apply_pin(self).call(&());
1106                        InputEventResult::GrabMouse
1107                    }
1108                    TouchPhase::Ended => self.end_impl(),
1109                    TouchPhase::Cancelled => {
1110                        self.cancel_impl();
1111                        InputEventResult::EventAccepted
1112                    }
1113                }
1114            }
1115            // Grab mouse during active gesture to maintain exclusivity.
1116            _ if self.active() => InputEventResult::GrabMouse,
1117            _ => InputEventResult::EventIgnored,
1118        }
1119    }
1120
1121    fn capture_key_event(
1122        self: Pin<&Self>,
1123        _: &InternalKeyEvent,
1124        _window_adapter: &Rc<dyn WindowAdapter>,
1125        _self_rc: &ItemRc,
1126    ) -> KeyEventResult {
1127        KeyEventResult::EventIgnored
1128    }
1129
1130    fn key_event(
1131        self: Pin<&Self>,
1132        _event: &InternalKeyEvent,
1133        _window_adapter: &Rc<dyn WindowAdapter>,
1134        _self_rc: &ItemRc,
1135    ) -> KeyEventResult {
1136        KeyEventResult::EventIgnored
1137    }
1138
1139    fn focus_event(
1140        self: Pin<&Self>,
1141        _: &FocusEvent,
1142        _window_adapter: &Rc<dyn WindowAdapter>,
1143        _self_rc: &ItemRc,
1144    ) -> FocusEventResult {
1145        FocusEventResult::FocusIgnored
1146    }
1147
1148    fn render(
1149        self: Pin<&Self>,
1150        _backend: &mut ItemRendererRef,
1151        _self_rc: &ItemRc,
1152        _size: LogicalSize,
1153    ) -> RenderingResult {
1154        RenderingResult::ContinueRenderingChildren
1155    }
1156
1157    fn bounding_rect(
1158        self: core::pin::Pin<&Self>,
1159        _window_adapter: &Rc<dyn WindowAdapter>,
1160        _self_rc: &ItemRc,
1161        mut geometry: LogicalRect,
1162    ) -> LogicalRect {
1163        geometry.size = LogicalSize::zero();
1164        geometry
1165    }
1166
1167    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
1168        false
1169    }
1170}
1171
1172impl ItemConsts for ScaleRotateGestureHandler {
1173    const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
1174        Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
1175}
1176
1177impl ScaleRotateGestureHandler {
1178    fn cancel_impl(self: Pin<&Self>) {
1179        if !self.active() {
1180            return;
1181        }
1182        Self::FIELD_OFFSETS.active().apply_pin(self).set(false);
1183        Self::FIELD_OFFSETS.cancelled().apply_pin(self).call(&());
1184        // Reset after the callback so handlers can read the last known values
1185        // to animate back smoothly, matching the pattern where `ended` leaves
1186        // scale/rotation at their final values.
1187        Self::FIELD_OFFSETS.scale().apply_pin(self).set(1.0);
1188        Self::FIELD_OFFSETS.rotation().apply_pin(self).set(0.0);
1189    }
1190
1191    fn end_impl(self: Pin<&Self>) -> InputEventResult {
1192        if !self.active() {
1193            return InputEventResult::EventIgnored;
1194        }
1195        Self::FIELD_OFFSETS.ended().apply_pin(self).call(&());
1196        Self::FIELD_OFFSETS.active().apply_pin(self).set(false);
1197        Self::FIELD_OFFSETS.scale().apply_pin(self).set(1.0);
1198        Self::FIELD_OFFSETS.rotation().apply_pin(self).set(0.0);
1199        InputEventResult::EventAccepted
1200    }
1201}