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, KeyEvent,
12    KeyEventResult, KeyEventType, MouseEvent,
13};
14use crate::item_rendering::CachedRenderingData;
15use crate::layout::{LayoutInfo, Orientation};
16use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PointLengths};
17#[cfg(feature = "rtti")]
18use crate::rtti::*;
19use crate::window::{WindowAdapter, WindowInner};
20use crate::{Callback, Coord, Property};
21use alloc::rc::Rc;
22use const_field_offset::FieldOffsets;
23use core::cell::Cell;
24use core::pin::Pin;
25use i_slint_core_macros::*;
26
27/// The implementation of the `TouchArea` element
28#[repr(C)]
29#[derive(FieldOffsets, SlintElement, Default)]
30#[pin]
31pub struct TouchArea {
32    pub enabled: Property<bool>,
33    /// FIXME: We should annotate this as an "output" property.
34    pub pressed: Property<bool>,
35    pub has_hover: Property<bool>,
36    /// FIXME: there should be just one property for the point instead of two.
37    /// Could even be merged with pressed in a `Property<Option<Point>>` (of course, in the
38    /// implementation item only, for the compiler it would stay separate properties)
39    pub pressed_x: Property<LogicalLength>,
40    pub pressed_y: Property<LogicalLength>,
41    /// FIXME: should maybe be as parameter to the mouse event instead. Or at least just one property
42    pub mouse_x: Property<LogicalLength>,
43    pub mouse_y: Property<LogicalLength>,
44    pub mouse_cursor: Property<MouseCursor>,
45    pub clicked: Callback<VoidArg>,
46    pub double_clicked: Callback<VoidArg>,
47    pub moved: Callback<VoidArg>,
48    pub pointer_event: Callback<PointerEventArg>,
49    pub scroll_event: Callback<PointerScrollEventArg, EventResult>,
50    /// FIXME: remove this
51    pub cached_rendering_data: CachedRenderingData,
52    /// true when we are currently grabbing the mouse
53    grabbed: Cell<bool>,
54}
55
56impl Item for TouchArea {
57    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
58
59    fn layout_info(
60        self: Pin<&Self>,
61        _orientation: Orientation,
62        _window_adapter: &Rc<dyn WindowAdapter>,
63        _self_rc: &ItemRc,
64    ) -> LayoutInfo {
65        LayoutInfo { stretch: 1., ..LayoutInfo::default() }
66    }
67
68    fn input_event_filter_before_children(
69        self: Pin<&Self>,
70        event: MouseEvent,
71        window_adapter: &Rc<dyn WindowAdapter>,
72        _self_rc: &ItemRc,
73    ) -> InputEventFilterResult {
74        if !self.enabled() {
75            self.has_hover.set(false);
76            if self.grabbed.replace(false) {
77                self.pressed.set(false);
78                Self::FIELD_OFFSETS.pointer_event.apply_pin(self).call(&(PointerEvent {
79                    button: PointerEventButton::Other,
80                    kind: PointerEventKind::Cancel,
81                    modifiers: window_adapter.window().0.modifiers.get().into(),
82                },));
83            }
84            return InputEventFilterResult::ForwardAndIgnore;
85        }
86        if let Some(pos) = event.position() {
87            Self::FIELD_OFFSETS.mouse_x.apply_pin(self).set(pos.x_length());
88            Self::FIELD_OFFSETS.mouse_y.apply_pin(self).set(pos.y_length());
89        }
90        let hovering = !matches!(event, MouseEvent::Exit);
91        Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(hovering);
92        if hovering {
93            if let Some(x) = window_adapter.internal(crate::InternalToken) {
94                x.set_mouse_cursor(self.mouse_cursor());
95            }
96        }
97        InputEventFilterResult::ForwardAndInterceptGrab
98    }
99
100    fn input_event(
101        self: Pin<&Self>,
102        event: MouseEvent,
103        window_adapter: &Rc<dyn WindowAdapter>,
104        self_rc: &ItemRc,
105    ) -> InputEventResult {
106        if matches!(event, MouseEvent::Exit) {
107            Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(false);
108            if let Some(x) = window_adapter.internal(crate::InternalToken) {
109                x.set_mouse_cursor(MouseCursor::Default);
110            }
111        }
112        if !self.enabled() {
113            return InputEventResult::EventIgnored;
114        }
115
116        match event {
117            MouseEvent::Pressed { position, button, .. } => {
118                self.grabbed.set(true);
119                if button == PointerEventButton::Left {
120                    Self::FIELD_OFFSETS.pressed_x.apply_pin(self).set(position.x_length());
121                    Self::FIELD_OFFSETS.pressed_y.apply_pin(self).set(position.y_length());
122                    Self::FIELD_OFFSETS.pressed.apply_pin(self).set(true);
123                }
124                Self::FIELD_OFFSETS.pointer_event.apply_pin(self).call(&(PointerEvent {
125                    button,
126                    kind: PointerEventKind::Down,
127                    modifiers: window_adapter.window().0.modifiers.get().into(),
128                },));
129
130                InputEventResult::GrabMouse
131            }
132            MouseEvent::Exit => {
133                Self::FIELD_OFFSETS.pressed.apply_pin(self).set(false);
134                if self.grabbed.replace(false) {
135                    Self::FIELD_OFFSETS.pointer_event.apply_pin(self).call(&(PointerEvent {
136                        button: PointerEventButton::Other,
137                        kind: PointerEventKind::Cancel,
138                        modifiers: window_adapter.window().0.modifiers.get().into(),
139                    },));
140                }
141
142                InputEventResult::EventAccepted
143            }
144
145            MouseEvent::Released { button, position, click_count } => {
146                let geometry = self_rc.geometry();
147                if button == PointerEventButton::Left
148                    && LogicalRect::new(LogicalPoint::default(), geometry.size).contains(position)
149                    && self.pressed()
150                {
151                    Self::FIELD_OFFSETS.clicked.apply_pin(self).call(&());
152                    if (click_count % 2) == 1 {
153                        Self::FIELD_OFFSETS.double_clicked.apply_pin(self).call(&())
154                    }
155                }
156
157                self.grabbed.set(false);
158                if button == PointerEventButton::Left {
159                    Self::FIELD_OFFSETS.pressed.apply_pin(self).set(false);
160                }
161                Self::FIELD_OFFSETS.pointer_event.apply_pin(self).call(&(PointerEvent {
162                    button,
163                    kind: PointerEventKind::Up,
164                    modifiers: window_adapter.window().0.modifiers.get().into(),
165                },));
166
167                InputEventResult::EventAccepted
168            }
169            MouseEvent::Moved { .. } => {
170                Self::FIELD_OFFSETS.pointer_event.apply_pin(self).call(&(PointerEvent {
171                    button: PointerEventButton::Other,
172                    kind: PointerEventKind::Move,
173                    modifiers: window_adapter.window().0.modifiers.get().into(),
174                },));
175                if self.grabbed.get() {
176                    Self::FIELD_OFFSETS.moved.apply_pin(self).call(&());
177                    InputEventResult::GrabMouse
178                } else {
179                    InputEventResult::EventAccepted
180                }
181            }
182            MouseEvent::Wheel { delta_x, delta_y, .. } => {
183                let modifiers = window_adapter.window().0.modifiers.get().into();
184                let r = Self::FIELD_OFFSETS
185                    .scroll_event
186                    .apply_pin(self)
187                    .call(&(PointerScrollEvent { delta_x, delta_y, modifiers },));
188                if self.grabbed.get() {
189                    InputEventResult::GrabMouse
190                } else {
191                    match r {
192                        EventResult::Reject => {
193                            // We are ignoring the event, so we will be removed from the item_stack,
194                            // therefore we must remove the has_hover flag as there might be a scroll under us.
195                            // It will be put back later.
196                            Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(false);
197                            InputEventResult::EventIgnored
198                        }
199                        EventResult::Accept => InputEventResult::EventAccepted,
200                    }
201                }
202            }
203        }
204    }
205
206    fn key_event(
207        self: Pin<&Self>,
208        _: &KeyEvent,
209        _window_adapter: &Rc<dyn WindowAdapter>,
210        _self_rc: &ItemRc,
211    ) -> KeyEventResult {
212        KeyEventResult::EventIgnored
213    }
214
215    fn focus_event(
216        self: Pin<&Self>,
217        _: &FocusEvent,
218        _window_adapter: &Rc<dyn WindowAdapter>,
219        _self_rc: &ItemRc,
220    ) -> FocusEventResult {
221        FocusEventResult::FocusIgnored
222    }
223
224    fn render(
225        self: Pin<&Self>,
226        _backend: &mut ItemRendererRef,
227        _self_rc: &ItemRc,
228        _size: LogicalSize,
229    ) -> RenderingResult {
230        RenderingResult::ContinueRenderingChildren
231    }
232
233    fn bounding_rect(
234        self: core::pin::Pin<&Self>,
235        _window_adapter: &Rc<dyn WindowAdapter>,
236        _self_rc: &ItemRc,
237        mut geometry: LogicalRect,
238    ) -> LogicalRect {
239        geometry.size = LogicalSize::zero();
240        geometry
241    }
242
243    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
244        false
245    }
246}
247
248impl ItemConsts for TouchArea {
249    const cached_rendering_data_offset: const_field_offset::FieldOffset<
250        TouchArea,
251        CachedRenderingData,
252    > = TouchArea::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
253}
254
255/// A runtime item that exposes key
256#[repr(C)]
257#[derive(FieldOffsets, Default, SlintElement)]
258#[pin]
259pub struct FocusScope {
260    pub enabled: Property<bool>,
261    pub has_focus: Property<bool>,
262    pub key_pressed: Callback<KeyEventArg, EventResult>,
263    pub key_released: Callback<KeyEventArg, EventResult>,
264    pub focus_changed_event: Callback<FocusReasonArg>,
265    pub focus_gained: Callback<FocusReasonArg>,
266    pub focus_lost: Callback<FocusReasonArg>,
267    /// FIXME: remove this
268    pub cached_rendering_data: CachedRenderingData,
269}
270
271impl Item for FocusScope {
272    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
273
274    fn layout_info(
275        self: Pin<&Self>,
276        _orientation: Orientation,
277        _window_adapter: &Rc<dyn WindowAdapter>,
278        _self_rc: &ItemRc,
279    ) -> LayoutInfo {
280        LayoutInfo { stretch: 1., ..LayoutInfo::default() }
281    }
282
283    fn input_event_filter_before_children(
284        self: Pin<&Self>,
285        _: MouseEvent,
286        _window_adapter: &Rc<dyn WindowAdapter>,
287        _self_rc: &ItemRc,
288    ) -> InputEventFilterResult {
289        InputEventFilterResult::ForwardEvent
290    }
291
292    fn input_event(
293        self: Pin<&Self>,
294        event: MouseEvent,
295        window_adapter: &Rc<dyn WindowAdapter>,
296        self_rc: &ItemRc,
297    ) -> InputEventResult {
298        if self.enabled() && matches!(event, MouseEvent::Pressed { .. }) && !self.has_focus() {
299            WindowInner::from_pub(window_adapter.window()).set_focus_item(
300                self_rc,
301                true,
302                FocusReason::PointerClick,
303            );
304            InputEventResult::EventAccepted
305        } else {
306            InputEventResult::EventIgnored
307        }
308    }
309
310    fn key_event(
311        self: Pin<&Self>,
312        event: &KeyEvent,
313        _window_adapter: &Rc<dyn WindowAdapter>,
314        _self_rc: &ItemRc,
315    ) -> KeyEventResult {
316        let r = match event.event_type {
317            KeyEventType::KeyPressed => {
318                Self::FIELD_OFFSETS.key_pressed.apply_pin(self).call(&(event.clone(),))
319            }
320            KeyEventType::KeyReleased => {
321                Self::FIELD_OFFSETS.key_released.apply_pin(self).call(&(event.clone(),))
322            }
323            KeyEventType::UpdateComposition | KeyEventType::CommitComposition => {
324                EventResult::Reject
325            }
326        };
327        match r {
328            EventResult::Accept => KeyEventResult::EventAccepted,
329            EventResult::Reject => KeyEventResult::EventIgnored,
330        }
331    }
332
333    fn focus_event(
334        self: Pin<&Self>,
335        event: &FocusEvent,
336        _window_adapter: &Rc<dyn WindowAdapter>,
337        _self_rc: &ItemRc,
338    ) -> FocusEventResult {
339        if !self.enabled() {
340            return FocusEventResult::FocusIgnored;
341        }
342
343        match event {
344            FocusEvent::FocusIn(reason) => {
345                self.has_focus.set(true);
346                Self::FIELD_OFFSETS.focus_changed_event.apply_pin(self).call(&((*reason,)));
347                Self::FIELD_OFFSETS.focus_gained.apply_pin(self).call(&((*reason,)));
348            }
349            FocusEvent::FocusOut(reason) => {
350                self.has_focus.set(false);
351                Self::FIELD_OFFSETS.focus_changed_event.apply_pin(self).call(&((*reason,)));
352                Self::FIELD_OFFSETS.focus_lost.apply_pin(self).call(&((*reason,)));
353            }
354        }
355        FocusEventResult::FocusAccepted
356    }
357
358    fn render(
359        self: Pin<&Self>,
360        _backend: &mut ItemRendererRef,
361        _self_rc: &ItemRc,
362        _size: LogicalSize,
363    ) -> RenderingResult {
364        RenderingResult::ContinueRenderingChildren
365    }
366
367    fn bounding_rect(
368        self: core::pin::Pin<&Self>,
369        _window_adapter: &Rc<dyn WindowAdapter>,
370        _self_rc: &ItemRc,
371        mut geometry: LogicalRect,
372    ) -> LogicalRect {
373        geometry.size = LogicalSize::zero();
374        geometry
375    }
376
377    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
378        false
379    }
380}
381
382impl ItemConsts for FocusScope {
383    const cached_rendering_data_offset: const_field_offset::FieldOffset<
384        FocusScope,
385        CachedRenderingData,
386    > = FocusScope::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
387}
388
389#[repr(C)]
390#[derive(FieldOffsets, Default, SlintElement)]
391#[pin]
392pub struct SwipeGestureHandler {
393    pub enabled: Property<bool>,
394    pub handle_swipe_left: Property<bool>,
395    pub handle_swipe_right: Property<bool>,
396    pub handle_swipe_up: Property<bool>,
397    pub handle_swipe_down: Property<bool>,
398
399    pub moved: Callback<VoidArg>,
400    pub swiped: Callback<VoidArg>,
401    pub cancelled: Callback<VoidArg>,
402
403    pub pressed_position: Property<LogicalPosition>,
404    pub current_position: Property<LogicalPosition>,
405    pub swiping: Property<bool>,
406
407    // true when the cursor is pressed down and we haven't cancelled yet for another reason
408    pressed: Cell<bool>,
409    // capture_events: Cell<bool>,
410    /// FIXME: remove this
411    pub cached_rendering_data: CachedRenderingData,
412}
413
414impl Item for SwipeGestureHandler {
415    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
416
417    fn layout_info(
418        self: Pin<&Self>,
419        _orientation: Orientation,
420        _window_adapter: &Rc<dyn WindowAdapter>,
421        _self_rc: &ItemRc,
422    ) -> LayoutInfo {
423        LayoutInfo { stretch: 1., ..LayoutInfo::default() }
424    }
425
426    fn input_event_filter_before_children(
427        self: Pin<&Self>,
428        event: MouseEvent,
429        _window_adapter: &Rc<dyn WindowAdapter>,
430        _self_rc: &ItemRc,
431    ) -> InputEventFilterResult {
432        if !self.enabled() {
433            if self.pressed.get() {
434                self.cancel_impl();
435            }
436            return InputEventFilterResult::ForwardAndIgnore;
437        }
438
439        match event {
440            MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
441                Self::FIELD_OFFSETS
442                    .pressed_position
443                    .apply_pin(self)
444                    .set(crate::lengths::logical_position_to_api(position));
445                self.pressed.set(true);
446                InputEventFilterResult::DelayForwarding(
447                    super::flickable::FORWARD_DELAY.as_millis() as _
448                )
449            }
450            MouseEvent::Exit => {
451                self.cancel_impl();
452                InputEventFilterResult::ForwardAndIgnore
453            }
454            MouseEvent::Released { button: PointerEventButton::Left, .. } => {
455                if self.swiping() {
456                    InputEventFilterResult::Intercept
457                } else {
458                    self.pressed.set(false);
459                    InputEventFilterResult::ForwardEvent
460                }
461            }
462            MouseEvent::Moved { position } => {
463                if self.swiping() {
464                    InputEventFilterResult::Intercept
465                } else if !self.pressed.get() {
466                    InputEventFilterResult::ForwardEvent
467                } else {
468                    let pressed_pos = self.pressed_position();
469                    let dx = position.x - pressed_pos.x as Coord;
470                    let dy = position.y - pressed_pos.y as Coord;
471                    let threshold = super::flickable::DISTANCE_THRESHOLD.get();
472                    if (self.handle_swipe_down() && dy > threshold)
473                        || (self.handle_swipe_up() && dy < -threshold)
474                        || (self.handle_swipe_left() && dx < -threshold)
475                        || (self.handle_swipe_right() && dx > threshold)
476                    {
477                        InputEventFilterResult::Intercept
478                    } else {
479                        InputEventFilterResult::ForwardAndInterceptGrab
480                    }
481                }
482            }
483            MouseEvent::Wheel { .. } => InputEventFilterResult::ForwardAndIgnore,
484            // Not the left button
485            MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
486                InputEventFilterResult::ForwardAndIgnore
487            }
488        }
489    }
490
491    fn input_event(
492        self: Pin<&Self>,
493        event: MouseEvent,
494        _window_adapter: &Rc<dyn WindowAdapter>,
495        _self_rc: &ItemRc,
496    ) -> InputEventResult {
497        match event {
498            MouseEvent::Pressed { .. } => InputEventResult::GrabMouse,
499            MouseEvent::Exit => {
500                self.cancel_impl();
501                InputEventResult::EventIgnored
502            }
503            MouseEvent::Released { position, .. } => {
504                if !self.pressed.get() && !self.swiping() {
505                    return InputEventResult::EventIgnored;
506                }
507                self.current_position.set(crate::lengths::logical_position_to_api(position));
508                self.pressed.set(false);
509                if self.swiping() {
510                    Self::FIELD_OFFSETS.swiping.apply_pin(self).set(false);
511                    Self::FIELD_OFFSETS.swiped.apply_pin(self).call(&());
512                    InputEventResult::EventAccepted
513                } else {
514                    InputEventResult::EventIgnored
515                }
516            }
517            MouseEvent::Moved { position } => {
518                if !self.pressed.get() {
519                    return InputEventResult::EventIgnored;
520                }
521                self.current_position.set(crate::lengths::logical_position_to_api(position));
522                if !self.swiping() {
523                    let pressed_pos = self.pressed_position();
524                    let dx = position.x - pressed_pos.x as Coord;
525                    let dy = position.y - pressed_pos.y as Coord;
526                    let threshold = super::flickable::DISTANCE_THRESHOLD.get();
527                    let start_swipe = (self.handle_swipe_down() && dy > threshold)
528                        || (self.handle_swipe_up() && dy < -threshold)
529                        || (self.handle_swipe_left() && dx < -threshold)
530                        || (self.handle_swipe_right() && dx > threshold);
531
532                    if start_swipe {
533                        Self::FIELD_OFFSETS.swiping.apply_pin(self).set(true);
534                    }
535                }
536                Self::FIELD_OFFSETS.moved.apply_pin(self).call(&());
537                InputEventResult::GrabMouse
538            }
539            MouseEvent::Wheel { .. } => InputEventResult::EventIgnored,
540        }
541    }
542
543    fn key_event(
544        self: Pin<&Self>,
545        _event: &KeyEvent,
546        _window_adapter: &Rc<dyn WindowAdapter>,
547        _self_rc: &ItemRc,
548    ) -> KeyEventResult {
549        KeyEventResult::EventIgnored
550    }
551
552    fn focus_event(
553        self: Pin<&Self>,
554        _: &FocusEvent,
555        _window_adapter: &Rc<dyn WindowAdapter>,
556        _self_rc: &ItemRc,
557    ) -> FocusEventResult {
558        FocusEventResult::FocusIgnored
559    }
560
561    fn render(
562        self: Pin<&Self>,
563        _backend: &mut ItemRendererRef,
564        _self_rc: &ItemRc,
565        _size: LogicalSize,
566    ) -> RenderingResult {
567        RenderingResult::ContinueRenderingChildren
568    }
569
570    fn bounding_rect(
571        self: core::pin::Pin<&Self>,
572        _window_adapter: &Rc<dyn WindowAdapter>,
573        _self_rc: &ItemRc,
574        mut geometry: LogicalRect,
575    ) -> LogicalRect {
576        geometry.size = LogicalSize::zero();
577        geometry
578    }
579
580    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
581        false
582    }
583}
584
585impl ItemConsts for SwipeGestureHandler {
586    const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
587        Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
588}
589
590impl SwipeGestureHandler {
591    pub fn cancel(self: Pin<&Self>, _: &Rc<dyn WindowAdapter>, _: &ItemRc) {
592        self.cancel_impl();
593    }
594
595    fn cancel_impl(self: Pin<&Self>) {
596        if !self.pressed.replace(false) {
597            debug_assert!(!self.swiping());
598            return;
599        }
600        if self.swiping() {
601            Self::FIELD_OFFSETS.swiping.apply_pin(self).set(false);
602            Self::FIELD_OFFSETS.cancelled.apply_pin(self).call(&());
603        }
604    }
605}
606
607#[cfg(feature = "ffi")]
608#[unsafe(no_mangle)]
609pub unsafe extern "C" fn slint_swipegesturehandler_cancel(
610    s: Pin<&SwipeGestureHandler>,
611    window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
612    self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
613    self_index: u32,
614) {
615    let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
616    let self_rc = ItemRc::new(self_component.clone(), self_index);
617    s.cancel(window_adapter, &self_rc);
618}