Skip to main content

i_slint_core/items/
flickable.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! The implementation details behind the Flickable
5
6//! The `Flickable` item
7
8use super::{
9    Item, ItemConsts, ItemRc, ItemRendererRef, KeyEventResult, PointerEventButton, RenderingResult,
10    VoidArg,
11};
12use crate::animations::{EasingCurve, Instant};
13use crate::input::{
14    FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent, MouseEvent,
15};
16use crate::item_rendering::CachedRenderingData;
17use crate::items::PropertyAnimation;
18use crate::layout::{LayoutInfo, Orientation};
19use crate::lengths::{
20    LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
21    PointLengths, RectLengths,
22};
23#[cfg(feature = "rtti")]
24use crate::rtti::*;
25use crate::window::WindowAdapter;
26use crate::{Callback, Coord, Property};
27use alloc::boxed::Box;
28use alloc::rc::Rc;
29use const_field_offset::FieldOffsets;
30use core::cell::RefCell;
31use core::pin::Pin;
32use core::time::Duration;
33#[allow(unused)]
34use euclid::num::Ceil;
35use euclid::num::Zero;
36use i_slint_core_macros::*;
37#[allow(unused)]
38use num_traits::Float;
39
40/// The implementation of the `Flickable` element
41#[repr(C)]
42#[derive(FieldOffsets, Default, SlintElement)]
43#[pin]
44pub struct Flickable {
45    pub viewport_x: Property<LogicalLength>,
46    pub viewport_y: Property<LogicalLength>,
47    pub viewport_width: Property<LogicalLength>,
48    pub viewport_height: Property<LogicalLength>,
49
50    pub interactive: Property<bool>,
51
52    pub flicked: Callback<VoidArg>,
53
54    data: FlickableDataBox,
55
56    /// FIXME: remove this
57    pub cached_rendering_data: CachedRenderingData,
58}
59
60impl Item for Flickable {
61    fn init(self: Pin<&Self>, self_rc: &ItemRc) {
62        self.data.in_bound_change_handler.init_delayed(
63            self_rc.downgrade(),
64            // Binding that returns if the Flickable is out of bounds:
65            |self_weak| {
66                let Some(flick_rc) = self_weak.upgrade() else { return false };
67                let Some(flick) = flick_rc.downcast::<Flickable>() else { return false };
68                let flick = flick.as_pin_ref();
69                let geo = Self::geometry_without_virtual_keyboard(&flick_rc);
70
71                let zero = LogicalLength::zero();
72                let vpx = flick.viewport_x();
73                if vpx > zero || vpx < (geo.width_length() - flick.viewport_width()).min(zero) {
74                    return true;
75                }
76                let vpy = flick.viewport_y();
77                if vpy > zero || vpy < (geo.height_length() - flick.viewport_height()).min(zero) {
78                    return true;
79                }
80                false
81            },
82            // Change event handler that puts the Flickable in bounds if it's not already
83            |self_weak, out_of_bound| {
84                let Some(flick_rc) = self_weak.upgrade() else { return };
85                let Some(flick) = flick_rc.downcast::<Flickable>() else { return };
86                let flick = flick.as_pin_ref();
87                if *out_of_bound {
88                    let vpx = flick.viewport_x();
89                    let vpy = flick.viewport_y();
90                    let p = ensure_in_bound(flick, LogicalPoint::from_lengths(vpx, vpy), &flick_rc);
91                    (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick).set(p.x_length());
92                    (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick).set(p.y_length());
93                }
94            },
95        );
96    }
97
98    fn layout_info(
99        self: Pin<&Self>,
100        _orientation: Orientation,
101        _window_adapter: &Rc<dyn WindowAdapter>,
102        _self_rc: &ItemRc,
103    ) -> LayoutInfo {
104        LayoutInfo { stretch: 1., ..LayoutInfo::default() }
105    }
106
107    fn input_event_filter_before_children(
108        self: Pin<&Self>,
109        event: &MouseEvent,
110        _window_adapter: &Rc<dyn WindowAdapter>,
111        self_rc: &ItemRc,
112    ) -> InputEventFilterResult {
113        if let Some(pos) = event.position() {
114            let geometry = Self::geometry_without_virtual_keyboard(self_rc);
115
116            if (pos.x < 0 as _
117                || pos.y < 0 as _
118                || pos.x_length() > geometry.width_length()
119                || pos.y_length() > geometry.height_length())
120                && self.data.inner.borrow().pressed_time.is_none()
121            {
122                return InputEventFilterResult::Intercept;
123            }
124        }
125        if !self.interactive() && !matches!(event, MouseEvent::Wheel { .. }) {
126            return InputEventFilterResult::ForwardAndIgnore;
127        }
128        self.data.handle_mouse_filter(self, event, self_rc)
129    }
130
131    fn input_event(
132        self: Pin<&Self>,
133        event: &MouseEvent,
134        window_adapter: &Rc<dyn WindowAdapter>,
135        self_rc: &ItemRc,
136    ) -> InputEventResult {
137        if !self.interactive() && !matches!(event, MouseEvent::Wheel { .. }) {
138            return InputEventResult::EventIgnored;
139        }
140        if let Some(pos) = event.position() {
141            let geometry = Self::geometry_without_virtual_keyboard(self_rc);
142            if matches!(event, MouseEvent::Wheel { .. } | MouseEvent::Pressed { .. })
143                && (pos.x < 0 as _
144                    || pos.y < 0 as _
145                    || pos.x_length() > geometry.width_length()
146                    || pos.y_length() > geometry.height_length())
147            {
148                return InputEventResult::EventIgnored;
149            }
150        }
151
152        self.data.handle_mouse(self, event, window_adapter, self_rc)
153    }
154
155    fn capture_key_event(
156        self: Pin<&Self>,
157        _: &KeyEvent,
158        _window_adapter: &Rc<dyn WindowAdapter>,
159        _self_rc: &ItemRc,
160    ) -> KeyEventResult {
161        KeyEventResult::EventIgnored
162    }
163
164    fn key_event(
165        self: Pin<&Self>,
166        _: &KeyEvent,
167        _window_adapter: &Rc<dyn WindowAdapter>,
168        _self_rc: &ItemRc,
169    ) -> KeyEventResult {
170        KeyEventResult::EventIgnored
171    }
172
173    fn focus_event(
174        self: Pin<&Self>,
175        _: &FocusEvent,
176        _window_adapter: &Rc<dyn WindowAdapter>,
177        _self_rc: &ItemRc,
178    ) -> FocusEventResult {
179        FocusEventResult::FocusIgnored
180    }
181
182    fn render(
183        self: Pin<&Self>,
184        backend: &mut ItemRendererRef,
185        _self_rc: &ItemRc,
186        size: LogicalSize,
187    ) -> RenderingResult {
188        (*backend).combine_clip(
189            LogicalRect::new(LogicalPoint::default(), size),
190            LogicalBorderRadius::zero(),
191            LogicalLength::zero(),
192        );
193        RenderingResult::ContinueRenderingChildren
194    }
195
196    fn bounding_rect(
197        self: core::pin::Pin<&Self>,
198        _window_adapter: &Rc<dyn WindowAdapter>,
199        _self_rc: &ItemRc,
200        geometry: LogicalRect,
201    ) -> LogicalRect {
202        geometry
203    }
204
205    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
206        true
207    }
208}
209
210impl ItemConsts for Flickable {
211    const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
212        Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
213}
214
215impl Flickable {
216    fn choose_min_move(
217        current_view_start: Coord, // vx or vy
218        view_len: Coord,           // w or h
219        content_len: Coord,        // vw or vh
220        points: impl Iterator<Item = Coord>,
221    ) -> Coord {
222        // Feasible translations t such that for all p: vx+t <= p <= vx+t+w
223        // -> t in [max_i(p_i - (vx + w)), min_i(p_i - vx)]
224        let zero = 0 as Coord;
225        let mut lower = Coord::MIN;
226        let mut upper = Coord::MAX;
227
228        for p in points {
229            lower = lower.max(p - (current_view_start + view_len));
230            upper = upper.min(p - current_view_start);
231        }
232
233        if lower > upper {
234            // No translation can include all points simultaneously; pick nearest bound direction.
235            // This happens only with NaNs; guard anyway.
236            return zero;
237        }
238
239        // Allowed translation interval due to scroll limits
240        let max_scroll = (content_len - view_len).max(zero);
241        let tmin = -current_view_start; // cannot scroll before 0
242        let tmax = max_scroll - current_view_start; // cannot scroll past max
243
244        let i_min = lower.max(tmin);
245        let i_max = upper.min(tmax);
246
247        if i_min <= i_max {
248            if zero < i_min {
249                i_min
250            } else if zero > i_max {
251                i_max
252            } else {
253                zero
254            }
255        // Intervals disjoint: choose closest allowed translation to feasible interval
256        // either entirely left or right
257        } else if tmax < lower {
258            tmax
259        } else {
260            tmin
261        }
262    }
263
264    /// Scroll the Flickable so that all of the points are visible at the same time (if possible).
265    /// The points have to be in the parent's coordinate space.
266    pub(crate) fn reveal_points(self: Pin<&Self>, self_rc: &ItemRc, pts: &[LogicalPoint]) {
267        if pts.is_empty() {
268            return;
269        }
270
271        // visible viewport size from base Item
272        let geo = Self::geometry_without_virtual_keyboard(self_rc);
273
274        // content extents and current viewport origin (content coords)
275        let vw = Self::FIELD_OFFSETS.viewport_width.apply_pin(self).get().0;
276        let vh = Self::FIELD_OFFSETS.viewport_height.apply_pin(self).get().0;
277        let vx = -Self::FIELD_OFFSETS.viewport_x.apply_pin(self).get().0;
278        let vy = -Self::FIELD_OFFSETS.viewport_y.apply_pin(self).get().0;
279
280        // choose minimal translation along each axis
281        let tx = Self::choose_min_move(vx, geo.width(), vw, pts.iter().map(|p| p.x));
282        let ty = Self::choose_min_move(vy, geo.height(), vh, pts.iter().map(|p| p.y));
283
284        let new_vx = vx + tx;
285        let new_vy = vy + ty;
286
287        Self::FIELD_OFFSETS.viewport_x.apply_pin(self).set(euclid::Length::new(-new_vx));
288        Self::FIELD_OFFSETS.viewport_y.apply_pin(self).set(euclid::Length::new(-new_vy));
289    }
290
291    fn geometry_without_virtual_keyboard(self_rc: &ItemRc) -> LogicalRect {
292        let mut geometry = self_rc.geometry();
293
294        // subtract keyboard rect if needed
295        if let Some(keyboard_rect) = self_rc.window_adapter().and_then(|window_adapter| {
296            window_adapter.window().virtual_keyboard(crate::InternalToken)
297        }) {
298            let keyboard_top_left = self_rc.map_from_window(keyboard_rect.0.to_euclid());
299            if keyboard_top_left.y > geometry.origin.y {
300                geometry.size.height = keyboard_top_left.y - geometry.origin.y;
301            }
302        }
303        geometry
304    }
305}
306
307#[repr(C)]
308/// Wraps the internal data structure for the Flickable
309pub struct FlickableDataBox(core::ptr::NonNull<FlickableData>);
310
311impl Default for FlickableDataBox {
312    fn default() -> Self {
313        FlickableDataBox(Box::leak(Box::<FlickableData>::default()).into())
314    }
315}
316impl Drop for FlickableDataBox {
317    fn drop(&mut self) {
318        // Safety: the self.0 was constructed from a Box::leak in FlickableDataBox::default
319        drop(unsafe { Box::from_raw(self.0.as_ptr()) });
320    }
321}
322
323impl core::ops::Deref for FlickableDataBox {
324    type Target = FlickableData;
325    fn deref(&self) -> &Self::Target {
326        // Safety: initialized in FlickableDataBox::default
327        unsafe { self.0.as_ref() }
328    }
329}
330
331/// The distance required before it starts flicking if there is another item intercepting the mouse.
332pub(super) const DISTANCE_THRESHOLD: LogicalLength = LogicalLength::new(8 as _);
333/// Time required before we stop caring about child event if the mouse hasn't been moved
334pub(super) const DURATION_THRESHOLD: Duration = Duration::from_millis(500);
335/// The delay to which press are forwarded to the inner item
336pub(super) const FORWARD_DELAY: Duration = Duration::from_millis(100);
337
338#[derive(Default, Debug)]
339struct FlickableDataInner {
340    /// The position in which the press was made
341    pressed_pos: LogicalPoint,
342    pressed_time: Option<Instant>,
343    pressed_viewport_pos: LogicalPoint,
344    pressed_viewport_size: LogicalSize,
345    /// Set to true if the flickable is flicking and capturing all mouse event, not forwarding back to the children
346    capture_events: bool,
347}
348
349#[derive(Default, Debug)]
350pub struct FlickableData {
351    inner: RefCell<FlickableDataInner>,
352    /// Tracker that tracks the property to make sure that the flickable is in bounds
353    in_bound_change_handler: crate::properties::ChangeTracker,
354}
355
356impl FlickableData {
357    fn handle_mouse_filter(
358        &self,
359        flick: Pin<&Flickable>,
360        event: &MouseEvent,
361        flick_rc: &ItemRc,
362    ) -> InputEventFilterResult {
363        let mut inner = self.inner.borrow_mut();
364        match event {
365            MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
366                inner.pressed_pos = *position;
367                inner.pressed_time = Some(crate::animations::current_tick());
368                inner.pressed_viewport_pos = LogicalPoint::from_lengths(
369                    (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick).get(),
370                    (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick).get(),
371                );
372                inner.pressed_viewport_size = LogicalSize::from_lengths(
373                    (Flickable::FIELD_OFFSETS.viewport_width).apply_pin(flick).get(),
374                    (Flickable::FIELD_OFFSETS.viewport_height).apply_pin(flick).get(),
375                );
376                if inner.capture_events {
377                    InputEventFilterResult::Intercept
378                } else {
379                    InputEventFilterResult::DelayForwarding(FORWARD_DELAY.as_millis() as _)
380                }
381            }
382            MouseEvent::Exit | MouseEvent::Released { button: PointerEventButton::Left, .. } => {
383                let was_capturing = inner.capture_events;
384                Self::mouse_released(&mut inner, flick, event, flick_rc);
385                if was_capturing {
386                    InputEventFilterResult::Intercept
387                } else {
388                    InputEventFilterResult::ForwardEvent
389                }
390            }
391            MouseEvent::Moved { position, .. } => {
392                let do_intercept = inner.capture_events
393                    || inner.pressed_time.is_some_and(|pressed_time| {
394                        if crate::animations::current_tick() - pressed_time > DURATION_THRESHOLD {
395                            return false;
396                        }
397                        // Check if the mouse was moved more than the DISTANCE_THRESHOLD in a
398                        // direction in which the flickable can flick
399                        let diff = *position - inner.pressed_pos;
400                        let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
401                        let w = geo.width_length();
402                        let h = geo.height_length();
403                        let vw = (Flickable::FIELD_OFFSETS.viewport_width).apply_pin(flick).get();
404                        let vh = (Flickable::FIELD_OFFSETS.viewport_height).apply_pin(flick).get();
405                        let x = (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick).get();
406                        let y = (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick).get();
407                        let zero = LogicalLength::zero();
408                        ((vw > w || x != zero) && abs(diff.x_length()) > DISTANCE_THRESHOLD)
409                            || ((vh > h || y != zero) && abs(diff.y_length()) > DISTANCE_THRESHOLD)
410                    });
411                if do_intercept {
412                    InputEventFilterResult::Intercept
413                } else if inner.pressed_time.is_some() {
414                    InputEventFilterResult::ForwardAndInterceptGrab
415                } else {
416                    InputEventFilterResult::ForwardEvent
417                }
418            }
419            MouseEvent::Wheel { .. } => InputEventFilterResult::ForwardEvent,
420            // Not the left button
421            MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
422                InputEventFilterResult::ForwardAndIgnore
423            }
424            MouseEvent::DragMove(..) | MouseEvent::Drop(..) => {
425                InputEventFilterResult::ForwardAndIgnore
426            }
427        }
428    }
429
430    fn handle_mouse(
431        &self,
432        flick: Pin<&Flickable>,
433        event: &MouseEvent,
434        window_adapter: &Rc<dyn WindowAdapter>,
435        flick_rc: &ItemRc,
436    ) -> InputEventResult {
437        let mut inner = self.inner.borrow_mut();
438        match event {
439            MouseEvent::Pressed { .. } => {
440                inner.capture_events = true;
441                InputEventResult::GrabMouse
442            }
443            MouseEvent::Exit | MouseEvent::Released { .. } => {
444                let was_capturing = inner.capture_events;
445                Self::mouse_released(&mut inner, flick, event, flick_rc);
446                if was_capturing {
447                    InputEventResult::EventAccepted
448                } else {
449                    InputEventResult::EventIgnored
450                }
451            }
452            MouseEvent::Moved { position, .. } => {
453                if inner.pressed_time.is_some() {
454                    let current_viewport_size = LogicalSize::from_lengths(
455                        (Flickable::FIELD_OFFSETS.viewport_width).apply_pin(flick).get(),
456                        (Flickable::FIELD_OFFSETS.viewport_height).apply_pin(flick).get(),
457                    );
458
459                    // Update reference points when the size of the viewport changes to
460                    // avoid 'jumping' during scrolling.
461                    // This happens when the height estimate of a ListView changes after
462                    // new items are loaded.
463                    if current_viewport_size != inner.pressed_viewport_size {
464                        inner.pressed_viewport_size = current_viewport_size;
465
466                        inner.pressed_viewport_pos = LogicalPoint::from_lengths(
467                            (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick).get(),
468                            (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick).get(),
469                        );
470
471                        inner.pressed_pos = *position;
472                    };
473
474                    let new_pos = inner.pressed_viewport_pos + (*position - inner.pressed_pos);
475
476                    let x = (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick);
477                    let y = (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick);
478                    let should_capture = || {
479                        let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
480                        let w = geo.width_length();
481                        let h = geo.height_length();
482                        let vw = (Flickable::FIELD_OFFSETS.viewport_width).apply_pin(flick).get();
483                        let vh = (Flickable::FIELD_OFFSETS.viewport_height).apply_pin(flick).get();
484                        let zero = LogicalLength::zero();
485                        ((vw > w || x.get() != zero)
486                            && abs(x.get() - new_pos.x_length()) > DISTANCE_THRESHOLD)
487                            || ((vh > h || y.get() != zero)
488                                && abs(y.get() - new_pos.y_length()) > DISTANCE_THRESHOLD)
489                    };
490
491                    if inner.capture_events || should_capture() {
492                        let new_pos = ensure_in_bound(flick, new_pos, flick_rc);
493
494                        let old_pos = (x.get(), y.get());
495                        x.set(new_pos.x_length());
496                        y.set(new_pos.y_length());
497                        if old_pos.0 != new_pos.x_length() || old_pos.1 != new_pos.y_length() {
498                            (Flickable::FIELD_OFFSETS.flicked).apply_pin(flick).call(&());
499                        }
500
501                        inner.capture_events = true;
502                        InputEventResult::GrabMouse
503                    } else if abs(x.get() - new_pos.x_length()) > DISTANCE_THRESHOLD
504                        || abs(y.get() - new_pos.y_length()) > DISTANCE_THRESHOLD
505                    {
506                        // drag in a unsupported direction gives up the grab
507                        InputEventResult::EventIgnored
508                    } else {
509                        InputEventResult::EventAccepted
510                    }
511                } else {
512                    inner.capture_events = false;
513                    InputEventResult::EventIgnored
514                }
515            }
516            MouseEvent::Wheel { delta_x, delta_y, .. } => {
517                let delta = if window_adapter.window().0.modifiers.get().shift()
518                    && !cfg!(target_os = "macos")
519                {
520                    // Shift invert coordinate for the purpose of scrolling. But not on macOs because there the OS already take care of the change
521                    LogicalVector::new(*delta_y, *delta_x)
522                } else {
523                    LogicalVector::new(*delta_x, *delta_y)
524                };
525
526                let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
527
528                if (delta.x == 0 as Coord && flick.viewport_height() <= geo.height_length())
529                    || (delta.y == 0 as Coord && flick.viewport_width() <= geo.width_length())
530                {
531                    // Scroll in a orthogonal direction than what is allowed by the flickable
532                    return InputEventResult::EventIgnored;
533                }
534
535                let old_pos = LogicalPoint::from_lengths(
536                    (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick).get(),
537                    (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick).get(),
538                );
539                let new_pos = ensure_in_bound(flick, old_pos + delta, flick_rc);
540
541                let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick);
542                let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick);
543                let old_pos = (viewport_x.get(), viewport_y.get());
544                viewport_x.set(new_pos.x_length());
545                viewport_y.set(new_pos.y_length());
546                if old_pos.0 != new_pos.x_length() || old_pos.1 != new_pos.y_length() {
547                    (Flickable::FIELD_OFFSETS.flicked).apply_pin(flick).call(&());
548                }
549                InputEventResult::EventAccepted
550            }
551            MouseEvent::DragMove(..) | MouseEvent::Drop(..) => InputEventResult::EventIgnored,
552        }
553    }
554
555    fn mouse_released(
556        inner: &mut FlickableDataInner,
557        flick: Pin<&Flickable>,
558        event: &MouseEvent,
559        flick_rc: &ItemRc,
560    ) {
561        if let (Some(pressed_time), Some(pos)) = (inner.pressed_time, event.position()) {
562            let dist = (pos - inner.pressed_pos).cast::<f32>();
563
564            let millis = (crate::animations::current_tick() - pressed_time).as_millis();
565            if inner.capture_events
566                && dist.square_length() > (DISTANCE_THRESHOLD.get() * DISTANCE_THRESHOLD.get()) as _
567                && millis > 1
568            {
569                let speed = dist / (millis as f32);
570
571                let duration = 250;
572                let final_pos = ensure_in_bound(
573                    flick,
574                    (inner.pressed_viewport_pos.cast() + dist + speed * (duration as f32)).cast(),
575                    flick_rc,
576                );
577                let anim = PropertyAnimation {
578                    duration,
579                    easing: EasingCurve::CubicBezier([0.0, 0.0, 0.58, 1.0]),
580                    ..PropertyAnimation::default()
581                };
582
583                let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick);
584                let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick);
585                let old_pos = (viewport_x.get(), viewport_y.get());
586                viewport_x.set_animated_value(final_pos.x_length(), anim.clone());
587                viewport_y.set_animated_value(final_pos.y_length(), anim);
588                if old_pos.0 != final_pos.x_length() || old_pos.1 != final_pos.y_length() {
589                    (Flickable::FIELD_OFFSETS.flicked).apply_pin(flick).call(&());
590                }
591            }
592        }
593        inner.capture_events = false; // FIXME: should only be set to false once the flick animation is over
594        inner.pressed_time = None;
595    }
596}
597
598fn abs(l: LogicalLength) -> LogicalLength {
599    LogicalLength::new(l.get().abs())
600}
601
602/// Make sure that the point is within the bounds
603fn ensure_in_bound(flick: Pin<&Flickable>, p: LogicalPoint, flick_rc: &ItemRc) -> LogicalPoint {
604    let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
605    let w = geo.width_length();
606    let h = geo.height_length();
607    let vw = (Flickable::FIELD_OFFSETS.viewport_width).apply_pin(flick).get();
608    let vh = (Flickable::FIELD_OFFSETS.viewport_height).apply_pin(flick).get();
609
610    let min = LogicalPoint::from_lengths(w - vw, h - vh);
611    let max = LogicalPoint::default();
612    p.max(min).min(max)
613}
614
615/// # Safety
616/// This must be called using a non-null pointer pointing to a chunk of memory big enough to
617/// hold a FlickableDataBox
618#[cfg(feature = "ffi")]
619#[unsafe(no_mangle)]
620pub unsafe extern "C" fn slint_flickable_data_init(data: *mut FlickableDataBox) {
621    unsafe { core::ptr::write(data, FlickableDataBox::default()) };
622}
623
624/// # Safety
625/// This must be called using a non-null pointer pointing to an initialized FlickableDataBox
626#[cfg(feature = "ffi")]
627#[unsafe(no_mangle)]
628pub unsafe extern "C" fn slint_flickable_data_free(data: *mut FlickableDataBox) {
629    unsafe {
630        core::ptr::drop_in_place(data);
631    }
632}