1use super::{
9 Item, ItemConsts, ItemRc, ItemRendererRef, KeyEventResult, PointerEventButton, RenderingResult,
10 VoidArg,
11};
12use crate::animations::Instant;
13use crate::animations::physics_simulation;
14use crate::input::InternalKeyEvent;
15use crate::input::{
16 FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, MouseEvent, TouchPhase,
17};
18use crate::item_rendering::CachedRenderingData;
19use crate::layout::{LayoutInfo, Orientation};
20use crate::lengths::{
21 LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
22 PointLengths, RectLengths,
23};
24#[cfg(feature = "rtti")]
25use crate::rtti::*;
26use crate::window::WindowAdapter;
27use crate::{Callback, Coord, Property};
28use alloc::boxed::Box;
29use alloc::rc::Rc;
30use const_field_offset::FieldOffsets;
31use core::cell::RefCell;
32use core::pin::Pin;
33use core::time::Duration;
34#[allow(unused)]
35use euclid::num::Ceil;
36use euclid::num::Zero;
37use i_slint_core_macros::*;
38#[allow(unused)]
39use num_traits::Float;
40mod data_ringbuffer;
41use data_ringbuffer::PositionTimeRingBuffer;
42
43const DECELERATION: f32 = 2000.;
47const MAX_DURATION: Duration = Duration::from_millis(100);
51
52#[repr(C)]
54#[derive(FieldOffsets, Default, SlintElement)]
55#[pin]
56pub struct Flickable {
57 pub viewport_x: Property<LogicalLength>,
58 pub viewport_y: Property<LogicalLength>,
59 pub viewport_width: Property<LogicalLength>,
60 pub viewport_height: Property<LogicalLength>,
61
62 pub interactive: Property<bool>,
63
64 pub flicked: Callback<VoidArg>,
65
66 data: FlickableDataBox,
67
68 pub cached_rendering_data: CachedRenderingData,
70}
71
72impl Item for Flickable {
73 fn init(self: Pin<&Self>, self_rc: &ItemRc) {
74 self.data.in_bound_change_handler.init_delayed(
75 self_rc.downgrade(),
76 |self_weak| {
78 let Some(flick_rc) = self_weak.upgrade() else {
79 return (false, false);
80 };
81 let Some(flick) = flick_rc.downcast::<Flickable>() else {
82 return (false, false);
83 };
84 let flick = flick.as_pin_ref();
85 let geo = Self::geometry_without_virtual_keyboard(&flick_rc);
86
87 let zero = LogicalLength::zero();
88 let vpx = flick.viewport_x();
89 let vpy = flick.viewport_y();
90 let x_out_of_bounds =
91 vpx > zero || vpx < (geo.width_length() - flick.viewport_width()).min(zero);
92 let y_out_of_bounds =
93 vpy > zero || vpy < (geo.height_length() - flick.viewport_height()).min(zero);
94
95 (x_out_of_bounds, y_out_of_bounds)
96 },
97 |self_weak, (x_out_of_bounds, y_out_of_bounds)| {
99 let Some(flick_rc) = self_weak.upgrade() else { return };
100 let Some(flick) = flick_rc.downcast::<Flickable>() else { return };
101 let flick = flick.as_pin_ref();
102 let vpx = flick.viewport_x();
103 let vpy = flick.viewport_y();
104 let p = ensure_in_bound(flick, LogicalPoint::from_lengths(vpx, vpy), &flick_rc);
105
106 let x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick);
107 if *x_out_of_bounds && !x.has_binding() {
108 x.set(p.x_length());
109 }
110
111 let y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick);
112 if *y_out_of_bounds && !y.has_binding() {
113 y.set(p.y_length());
114 }
115 },
116 );
117 }
118
119 fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
120
121 fn layout_info(
122 self: Pin<&Self>,
123 _orientation: Orientation,
124 _cross_axis_constraint: Coord,
125 _window_adapter: &Rc<dyn WindowAdapter>,
126 _self_rc: &ItemRc,
127 ) -> LayoutInfo {
128 LayoutInfo { stretch: 1., ..LayoutInfo::default() }
129 }
130
131 fn input_event_filter_before_children(
132 self: Pin<&Self>,
133 event: &MouseEvent,
134 window_adapter: &Rc<dyn WindowAdapter>,
135 self_rc: &ItemRc,
136 _: &mut super::MouseCursor,
137 ) -> InputEventFilterResult {
138 if let Some(pos) = event.position() {
139 let geometry = Self::geometry_without_virtual_keyboard(self_rc);
140
141 if (pos.x < 0 as _
142 || pos.y < 0 as _
143 || pos.x_length() > geometry.width_length()
144 || pos.y_length() > geometry.height_length())
145 && self.data.inner.borrow().pressed_time.is_none()
146 {
147 return InputEventFilterResult::Intercept;
148 }
149 }
150 if !self.interactive() && !matches!(event, MouseEvent::Wheel { .. }) {
151 return InputEventFilterResult::ForwardAndIgnore;
152 }
153 self.data.handle_mouse_filter(self, event, window_adapter, self_rc)
154 }
155
156 fn input_event(
157 self: Pin<&Self>,
158 event: &MouseEvent,
159 window_adapter: &Rc<dyn WindowAdapter>,
160 self_rc: &ItemRc,
161 _: &mut super::MouseCursor,
162 ) -> InputEventResult {
163 if !self.interactive() && !matches!(event, MouseEvent::Wheel { .. }) {
164 return InputEventResult::EventIgnored;
165 }
166 if let Some(pos) = event.position() {
167 let geometry = Self::geometry_without_virtual_keyboard(self_rc);
168 if matches!(event, MouseEvent::Wheel { .. } | MouseEvent::Pressed { .. })
169 && (pos.x < 0 as _
170 || pos.y < 0 as _
171 || pos.x_length() > geometry.width_length()
172 || pos.y_length() > geometry.height_length())
173 {
174 return InputEventResult::EventIgnored;
175 }
176 }
177
178 self.data.handle_mouse(self, event, window_adapter, self_rc)
179 }
180
181 fn capture_key_event(
182 self: Pin<&Self>,
183 _: &InternalKeyEvent,
184 _window_adapter: &Rc<dyn WindowAdapter>,
185 _self_rc: &ItemRc,
186 ) -> KeyEventResult {
187 KeyEventResult::EventIgnored
188 }
189
190 fn key_event(
191 self: Pin<&Self>,
192 _: &InternalKeyEvent,
193 _window_adapter: &Rc<dyn WindowAdapter>,
194 _self_rc: &ItemRc,
195 ) -> KeyEventResult {
196 KeyEventResult::EventIgnored
197 }
198
199 fn focus_event(
200 self: Pin<&Self>,
201 _: &FocusEvent,
202 _window_adapter: &Rc<dyn WindowAdapter>,
203 _self_rc: &ItemRc,
204 ) -> FocusEventResult {
205 FocusEventResult::FocusIgnored
206 }
207
208 fn render(
209 self: Pin<&Self>,
210 backend: &mut ItemRendererRef,
211 _self_rc: &ItemRc,
212 size: LogicalSize,
213 ) -> RenderingResult {
214 (*backend).combine_clip(
215 LogicalRect::new(LogicalPoint::default(), size),
216 LogicalBorderRadius::zero(),
217 LogicalLength::zero(),
218 );
219 RenderingResult::ContinueRenderingChildren
220 }
221
222 fn bounding_rect(
223 self: core::pin::Pin<&Self>,
224 _window_adapter: &Rc<dyn WindowAdapter>,
225 _self_rc: &ItemRc,
226 geometry: LogicalRect,
227 ) -> LogicalRect {
228 geometry
229 }
230
231 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
232 true
233 }
234}
235
236impl ItemConsts for Flickable {
237 const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
238 Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
239}
240
241impl Flickable {
242 fn choose_min_move(
243 current_view_start: Coord, view_len: Coord, content_len: Coord, points: impl Iterator<Item = Coord>,
247 ) -> Coord {
248 let zero = 0 as Coord;
251 let mut lower = Coord::MIN;
252 let mut upper = Coord::MAX;
253
254 for p in points {
255 lower = lower.max(p - (current_view_start + view_len));
256 upper = upper.min(p - current_view_start);
257 }
258
259 if lower > upper {
260 return zero;
263 }
264
265 let max_scroll = (content_len - view_len).max(zero);
267 let tmin = -current_view_start; let tmax = max_scroll - current_view_start; let i_min = lower.max(tmin);
271 let i_max = upper.min(tmax);
272
273 if i_min <= i_max {
274 if zero < i_min {
275 i_min
276 } else if zero > i_max {
277 i_max
278 } else {
279 zero
280 }
281 } else if tmax < lower {
284 tmax
285 } else {
286 tmin
287 }
288 }
289
290 pub(crate) fn reveal_points(self: Pin<&Self>, self_rc: &ItemRc, pts: &[LogicalPoint]) {
293 if pts.is_empty() {
294 return;
295 }
296
297 let geo = Self::geometry_without_virtual_keyboard(self_rc);
299
300 let vw = Self::FIELD_OFFSETS.viewport_width().apply_pin(self).get().0;
302 let vh = Self::FIELD_OFFSETS.viewport_height().apply_pin(self).get().0;
303 let vx = -Self::FIELD_OFFSETS.viewport_x().apply_pin(self).get().0;
304 let vy = -Self::FIELD_OFFSETS.viewport_y().apply_pin(self).get().0;
305
306 let tx = Self::choose_min_move(vx, geo.width(), vw, pts.iter().map(|p| p.x));
308 let ty = Self::choose_min_move(vy, geo.height(), vh, pts.iter().map(|p| p.y));
309
310 let new_vx = vx + tx;
311 let new_vy = vy + ty;
312
313 Self::FIELD_OFFSETS.viewport_x().apply_pin(self).set(euclid::Length::new(-new_vx));
314 Self::FIELD_OFFSETS.viewport_y().apply_pin(self).set(euclid::Length::new(-new_vy));
315 }
316
317 fn geometry_without_virtual_keyboard(self_rc: &ItemRc) -> LogicalRect {
318 let mut geometry = self_rc.geometry();
319
320 if let Some(keyboard_rect) = self_rc.window_adapter().and_then(|window_adapter| {
322 window_adapter.window().virtual_keyboard(crate::InternalToken)
323 }) {
324 let keyboard_top_left = self_rc.map_from_window(keyboard_rect.0.to_euclid());
325 if keyboard_top_left.y > geometry.origin.y {
326 geometry.size.height = keyboard_top_left.y - geometry.origin.y;
327 }
328 }
329 geometry
330 }
331}
332
333#[repr(C)]
334pub struct FlickableDataBox(core::ptr::NonNull<FlickableData>);
336
337impl Default for FlickableDataBox {
338 fn default() -> Self {
339 FlickableDataBox(Box::leak(Box::<FlickableData>::default()).into())
340 }
341}
342impl Drop for FlickableDataBox {
343 fn drop(&mut self) {
344 drop(unsafe { Box::from_raw(self.0.as_ptr()) });
346 }
347}
348
349impl core::ops::Deref for FlickableDataBox {
350 type Target = FlickableData;
351 fn deref(&self) -> &Self::Target {
352 unsafe { self.0.as_ref() }
354 }
355}
356
357pub(super) const DISTANCE_THRESHOLD: LogicalLength = LogicalLength::new(8 as _);
359pub(super) const DURATION_THRESHOLD: Duration = Duration::from_millis(500);
361pub(super) const FORWARD_DELAY: Duration = Duration::from_millis(100);
363pub(super) const SCROLL_FILTER_DURATION: Duration = Duration::from_millis(800);
369pub(super) const SHORT_SCROLL_FILTER_DURATION: Duration =
371 Duration::from_millis(SCROLL_FILTER_DURATION.as_millis() as u64 / 2);
372pub(super) const SCROLL_FILTER_DISTANCE_SQUARED: LogicalLength = LogicalLength::new(4 as _);
374
375#[derive(Debug, PartialEq, Eq, Clone, Copy)]
376enum CaptureEvents {
377 MouseOrTouchScreen,
378 MouseWheel,
379}
380
381#[derive(Default, Debug)]
382struct FlickableDataInner {
383 pressed_pos: LogicalPoint,
385 pressed_time: Option<Instant>,
386 pressed_viewport_pos: LogicalPoint,
387 pressed_viewport_size: LogicalSize,
388 capture_events: Option<CaptureEvents>,
390 last_scroll_event: Option<(Instant, LogicalPoint)>,
396
397 position_time_rb: PositionTimeRingBuffer<5>,
400}
401
402impl FlickableDataInner {
403 fn should_capture_scroll(&self, timeout: Duration, position: LogicalPoint) -> bool {
404 self.last_scroll_event.is_some_and(|(last_time, last_position)| {
405 crate::animations::current_tick() - last_time < timeout
407 && LogicalLength::new((last_position - position).square_length().abs())
408 < SCROLL_FILTER_DISTANCE_SQUARED
409 })
410 }
411
412 #[allow(clippy::nonminimal_bool)] fn is_allowed_scroll_direction(
415 flick: Pin<&Flickable>,
416 delta: LogicalVector,
417 flick_rc: &ItemRc,
418 ) -> bool {
419 let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
420 !(delta.x == 0 as Coord && flick.viewport_height() <= geo.height_length())
421 && !(delta.y == 0 as Coord && flick.viewport_width() <= geo.width_length())
422 }
423
424 fn process_wheel_event(
425 &mut self,
426 flick: Pin<&Flickable>,
427 delta: LogicalVector,
428 position: LogicalPoint,
429 phase: TouchPhase,
430 flick_rc: &ItemRc,
431 ) -> InputEventResult {
432 let old_pos = LogicalPoint::from_lengths(
433 (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick).get(),
434 (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick).get(),
435 );
436 let new_pos = ensure_in_bound(flick, old_pos + delta, flick_rc);
437
438 let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick);
439 let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick);
440 let old_pos = (viewport_x.get(), viewport_y.get());
441
442 match phase {
443 TouchPhase::Cancelled => {
444 if !Self::is_allowed_scroll_direction(flick, delta, flick_rc) {
445 self.last_scroll_event = None;
447 return InputEventResult::EventIgnored;
448 }
449
450 viewport_x.set(new_pos.x_length());
451 viewport_y.set(new_pos.y_length());
452 self.last_scroll_event = Some((crate::animations::current_tick(), position));
453 }
454 TouchPhase::Started => {
455 self.position_time_rb = PositionTimeRingBuffer::default();
456 self.capture_events = Some(CaptureEvents::MouseWheel);
457 self.last_scroll_event = Some((crate::animations::current_tick(), position));
458 }
459 TouchPhase::Moved => {
460 if !Self::is_allowed_scroll_direction(flick, delta, flick_rc) {
461 self.last_scroll_event = None;
463 return InputEventResult::EventIgnored;
464 }
465
466 self.position_time_rb.push(crate::animations::current_tick(), new_pos);
467 viewport_x.set(new_pos.x_length());
468 viewport_y.set(new_pos.y_length());
469 self.last_scroll_event = Some((crate::animations::current_tick(), position));
470 }
471 TouchPhase::Ended => {
472 self.animate(flick, flick_rc);
473 self.capture_events = None;
474 }
475 }
476
477 let flicked = old_pos.0 != new_pos.x_length() || old_pos.1 != new_pos.y_length();
478 if flicked {
479 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
480 InputEventResult::EventAccepted
481 } else if self.should_capture_scroll(SHORT_SCROLL_FILTER_DURATION, position) {
482 InputEventResult::EventAccepted
485 } else {
486 InputEventResult::EventIgnored
487 }
488 }
489
490 fn animate(&self, flick: Pin<&Flickable>, flick_rc: &ItemRc) {
491 if let Some(last_time) = self.position_time_rb.last_time() {
492 let (time, dist) = self.position_time_rb.diff();
493 let millis = time.as_millis();
494
495 if self.capture_events.is_some()
496 && dist.square_length() > (DISTANCE_THRESHOLD.get() * DISTANCE_THRESHOLD.get()) as _
497 && millis > 0
498 && crate::animations::current_tick().duration_since(last_time) < MAX_DURATION
499 {
500 let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick);
501 let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick);
502 let vw = (Flickable::FIELD_OFFSETS.viewport_width()).apply_pin(flick).get();
503 let vh = (Flickable::FIELD_OFFSETS.viewport_height()).apply_pin(flick).get();
504 let limit_x =
505 if dist.x < 0 as Coord { -vw } else { euclid::Length::new(Coord::default()) };
506 let limit_y =
507 if dist.y < 0 as Coord { -vh } else { euclid::Length::new(Coord::default()) };
508
509 let limit =
510 ensure_in_bound(flick, LogicalPoint::from_lengths(limit_x, limit_y), flick_rc);
511 {
512 let simulation = physics_simulation::ConstantDecelerationParameters::new(
513 dist.x as f32 / (millis as f32 / 1000.),
514 DECELERATION,
515 );
516 viewport_x.set_physic_animation_value(limit.x_length(), simulation);
517 }
518
519 {
520 let animation_y = physics_simulation::ConstantDecelerationParameters::new(
521 dist.y as f32 / (millis as f32 / 1000.),
522 DECELERATION,
523 );
524 viewport_y.set_physic_animation_value(limit.y_length(), animation_y);
525 }
526
527 if dist.x != 0 as Coord || dist.y != 0 as Coord {
528 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
529 }
530 }
531 }
532 }
533}
534
535#[derive(Default, Debug)]
536pub struct FlickableData {
537 inner: RefCell<FlickableDataInner>,
538 in_bound_change_handler: crate::properties::ChangeTracker,
540}
541
542impl FlickableData {
543 fn scroll_delta(
544 window_adapter: &Rc<dyn WindowAdapter>,
545 delta_x: Coord,
546 delta_y: Coord,
547 ) -> LogicalVector {
548 if window_adapter.window().0.context().0.modifiers.get().shift()
549 && !cfg!(target_os = "macos")
550 {
551 LogicalVector::new(delta_y, delta_x)
554 } else {
555 LogicalVector::new(delta_x, delta_y)
556 }
557 }
558
559 fn handle_mouse_filter(
560 &self,
561 flick: Pin<&Flickable>,
562 event: &MouseEvent,
563 window_adapter: &Rc<dyn WindowAdapter>,
564 flick_rc: &ItemRc,
565 ) -> InputEventFilterResult {
566 let mut inner = self.inner.borrow_mut();
567 match event {
568 MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
569 inner.position_time_rb = PositionTimeRingBuffer::default();
570 inner.pressed_pos = *position;
571 inner.pressed_time = Some(crate::animations::current_tick());
572 inner.pressed_viewport_pos = LogicalPoint::from_lengths(
573 (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick).get(),
574 (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick).get(),
575 );
576 inner.pressed_viewport_size = LogicalSize::from_lengths(
577 (Flickable::FIELD_OFFSETS.viewport_width()).apply_pin(flick).get(),
578 (Flickable::FIELD_OFFSETS.viewport_height()).apply_pin(flick).get(),
579 );
580 let x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick);
581 x.set(x.get()); let y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick);
583 y.set(y.get()); if inner.capture_events.is_some() {
586 InputEventFilterResult::Intercept
587 } else {
588 InputEventFilterResult::DelayForwarding(FORWARD_DELAY.as_millis() as _)
589 }
590 }
591 MouseEvent::Exit | MouseEvent::Released { button: PointerEventButton::Left, .. } => {
592 inner.pressed_time = None;
593 if inner.capture_events.is_some() {
594 InputEventFilterResult::Intercept
595 } else {
596 InputEventFilterResult::ForwardEvent
597 }
598 }
599 MouseEvent::Moved { position, .. } => {
600 let do_intercept = inner.capture_events.is_some()
601 || inner.pressed_time.is_some_and(|pressed_time| {
602 if crate::animations::current_tick() - pressed_time > DURATION_THRESHOLD {
603 return false;
604 }
605 let diff = *position - inner.pressed_pos;
608 let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
609 let w = geo.width_length();
610 let h = geo.height_length();
611 let vw = (Flickable::FIELD_OFFSETS.viewport_width()).apply_pin(flick).get();
612 let vh =
613 (Flickable::FIELD_OFFSETS.viewport_height()).apply_pin(flick).get();
614 let x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick).get();
615 let y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick).get();
616 let zero = LogicalLength::zero();
617 ((vw > w || x != zero) && abs(diff.x_length()) > DISTANCE_THRESHOLD)
618 || ((vh > h || y != zero) && abs(diff.y_length()) > DISTANCE_THRESHOLD)
619 });
620 if do_intercept {
621 InputEventFilterResult::Intercept
622 } else if inner.pressed_time.is_some() {
623 InputEventFilterResult::ForwardAndInterceptGrab
624 } else {
625 InputEventFilterResult::ForwardEvent
626 }
627 }
628 MouseEvent::Wheel { position, delta_x, delta_y, phase } => {
629 match phase {
630 TouchPhase::Cancelled => {
631 let delta = Self::scroll_delta(window_adapter, *delta_x, *delta_y);
635 if FlickableDataInner::is_allowed_scroll_direction(flick, delta, flick_rc)
636 && inner.should_capture_scroll(SCROLL_FILTER_DURATION, *position)
637 {
638 InputEventFilterResult::Intercept
639 } else {
640 inner.last_scroll_event = None;
641 InputEventFilterResult::ForwardEvent
642 }
643 }
644 TouchPhase::Started => InputEventFilterResult::Intercept,
645 TouchPhase::Moved => {
646 if inner.capture_events.is_some() {
647 InputEventFilterResult::Intercept
648 } else {
649 let delta = Self::scroll_delta(window_adapter, *delta_x, *delta_y);
652 if FlickableDataInner::is_allowed_scroll_direction(
653 flick, delta, flick_rc,
654 ) && inner.should_capture_scroll(SCROLL_FILTER_DURATION, *position)
655 {
656 InputEventFilterResult::Intercept
657 } else {
658 inner.last_scroll_event = None;
659 InputEventFilterResult::ForwardEvent
660 }
661 }
662 }
663 TouchPhase::Ended => {
664 if inner.capture_events.is_some() {
665 InputEventFilterResult::Intercept
666 } else {
667 InputEventFilterResult::ForwardEvent
668 }
669 }
670 }
671 }
672 MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
674 InputEventFilterResult::ForwardAndIgnore
675 }
676 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
677 InputEventFilterResult::ForwardEvent
678 }
679 MouseEvent::DragMove(..) | MouseEvent::Drop(..) => {
680 InputEventFilterResult::ForwardAndIgnore
681 }
682 }
683 }
684
685 fn handle_mouse(
686 &self,
687 flick: Pin<&Flickable>,
688 event: &MouseEvent,
689 window_adapter: &Rc<dyn WindowAdapter>,
690 flick_rc: &ItemRc,
691 ) -> InputEventResult {
692 let mut inner = self.inner.borrow_mut();
693 match event {
694 MouseEvent::Pressed { .. } => {
695 inner.capture_events = Some(CaptureEvents::MouseOrTouchScreen);
696 InputEventResult::GrabMouse
697 }
698 MouseEvent::Exit | MouseEvent::Released { .. } => {
699 if inner.capture_events.is_some_and(|f| f == CaptureEvents::MouseOrTouchScreen) {
700 let was_capturing = true;
701 inner.animate(flick, flick_rc);
702 inner.capture_events = None;
703 inner.pressed_time = None;
704 if was_capturing {
705 InputEventResult::EventAccepted
706 } else {
707 InputEventResult::EventIgnored
708 }
709 } else if inner.capture_events.is_none() {
710 inner.pressed_time = None;
711 InputEventResult::EventIgnored
712 } else {
713 InputEventResult::EventIgnored
714 }
715 }
716 MouseEvent::Moved { position, .. } => {
717 if inner.pressed_time.is_some() {
718 inner.position_time_rb.push(crate::animations::current_tick(), *position);
719 let current_viewport_size = LogicalSize::from_lengths(
720 (Flickable::FIELD_OFFSETS.viewport_width()).apply_pin(flick).get(),
721 (Flickable::FIELD_OFFSETS.viewport_height()).apply_pin(flick).get(),
722 );
723
724 if current_viewport_size != inner.pressed_viewport_size {
729 inner.pressed_viewport_size = current_viewport_size;
730
731 inner.pressed_viewport_pos = LogicalPoint::from_lengths(
732 (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick).get(),
733 (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick).get(),
734 );
735
736 inner.pressed_pos = *position;
737 };
738
739 let new_pos = inner.pressed_viewport_pos + (*position - inner.pressed_pos);
740
741 let x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick);
742 let y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick);
743 let should_capture = || {
744 let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
745 let w = geo.width_length();
746 let h = geo.height_length();
747 let vw = (Flickable::FIELD_OFFSETS.viewport_width()).apply_pin(flick).get();
748 let vh =
749 (Flickable::FIELD_OFFSETS.viewport_height()).apply_pin(flick).get();
750 let zero = LogicalLength::zero();
751 ((vw > w || x.get() != zero)
752 && abs(x.get() - new_pos.x_length()) > DISTANCE_THRESHOLD)
753 || ((vh > h || y.get() != zero)
754 && abs(y.get() - new_pos.y_length()) > DISTANCE_THRESHOLD)
755 };
756
757 if inner.capture_events.is_some_and(|f| f == CaptureEvents::MouseOrTouchScreen)
758 || should_capture()
759 {
760 let new_pos = ensure_in_bound(flick, new_pos, flick_rc);
761
762 let old_pos = (x.get(), y.get());
763 x.set(new_pos.x_length());
764 y.set(new_pos.y_length());
765 if old_pos.0 != new_pos.x_length() || old_pos.1 != new_pos.y_length() {
766 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
767 }
768
769 inner.capture_events = Some(CaptureEvents::MouseOrTouchScreen);
770 InputEventResult::GrabMouse
771 } else if abs(x.get() - new_pos.x_length()) > DISTANCE_THRESHOLD
772 || abs(y.get() - new_pos.y_length()) > DISTANCE_THRESHOLD
773 {
774 InputEventResult::EventIgnored
776 } else {
777 InputEventResult::EventAccepted
778 }
779 } else {
780 InputEventResult::EventIgnored
781 }
782 }
783 MouseEvent::Wheel { delta_x, delta_y, position, phase } => {
784 let delta = Self::scroll_delta(window_adapter, *delta_x, *delta_y);
785 inner.process_wheel_event(flick, delta, *position, *phase, flick_rc)
786 }
787 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
788 InputEventResult::EventIgnored
789 }
790 MouseEvent::DragMove(..) | MouseEvent::Drop(..) => InputEventResult::EventIgnored,
791 }
792 }
793}
794
795fn abs(l: LogicalLength) -> LogicalLength {
796 LogicalLength::new(l.get().abs())
797}
798
799fn ensure_in_bound(flick: Pin<&Flickable>, p: LogicalPoint, flick_rc: &ItemRc) -> LogicalPoint {
801 let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
802 let w = geo.width_length();
803 let h = geo.height_length();
804 let vw = (Flickable::FIELD_OFFSETS.viewport_width()).apply_pin(flick).get();
805 let vh = (Flickable::FIELD_OFFSETS.viewport_height()).apply_pin(flick).get();
806
807 let min = LogicalPoint::from_lengths(w - vw, h - vh);
808 let max = LogicalPoint::default();
809 p.max(min).min(max)
810}
811
812#[cfg(feature = "ffi")]
816#[unsafe(no_mangle)]
817pub unsafe extern "C" fn slint_flickable_data_init(data: *mut FlickableDataBox) {
818 unsafe { core::ptr::write(data, FlickableDataBox::default()) };
819}
820
821#[cfg(feature = "ffi")]
824#[unsafe(no_mangle)]
825pub unsafe extern "C" fn slint_flickable_data_free(data: *mut FlickableDataBox) {
826 unsafe {
827 core::ptr::drop_in_place(data);
828 }
829}