1use super::{
10 Item, ItemConsts, ItemRc, ItemRendererRef, KeyEventResult, PointerEventButton, RenderingResult,
11 VoidArg,
12};
13use crate::animations::Instant;
14use crate::animations::simulations::constant_deceleration::ConstantDecelerationParameters;
15use crate::input::InternalKeyEvent;
16use crate::input::{
17 FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, MouseEvent, TouchPhase,
18};
19use crate::item_rendering::CachedRenderingData;
20use crate::layout::{LayoutInfo, Orientation};
21use crate::lengths::{
22 LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
23 PointLengths, RectLengths,
24};
25#[cfg(feature = "rtti")]
26use crate::rtti::*;
27use crate::window::WindowAdapter;
28use crate::{Callback, Coord, Property};
29use alloc::boxed::Box;
30use alloc::rc::Rc;
31use const_field_offset::FieldOffsets;
32use core::cell::RefCell;
33use core::pin::Pin;
34use core::time::Duration;
35#[allow(unused)]
36use euclid::num::Ceil;
37use euclid::num::Zero;
38use i_slint_core_macros::*;
39#[allow(unused)]
40use num_traits::Float;
41mod data_ringbuffer;
42use data_ringbuffer::VelocityRingBuffer;
43
44const DECELERATION: f32 = 2000.;
48const WHEEL_SCROLL_DURATION: Duration = Duration::from_millis(180);
52const MAX_DURATION: Duration = Duration::from_millis(100);
56
57#[repr(C)]
59#[derive(FieldOffsets, Default, SlintElement)]
60#[pin]
61pub struct Flickable {
62 pub content_x: Property<LogicalLength>,
63 pub content_y: Property<LogicalLength>,
64 pub content_width: Property<LogicalLength>,
65 pub content_height: Property<LogicalLength>,
66
67 pub interactive: Property<bool>,
68 pub mouse_drag_pan_enabled: Property<bool>,
69
70 pub flicked: Callback<VoidArg>,
71
72 data: FlickableDataBox,
73
74 pub cached_rendering_data: CachedRenderingData,
76}
77
78impl Item for Flickable {
79 fn init(self: Pin<&Self>, self_rc: &ItemRc) {
80 self.data.in_bound_change_handler.init_delayed(
81 self_rc.downgrade(),
82 |self_weak| {
84 let Some(flick_rc) = self_weak.upgrade() else {
85 return (false, false);
86 };
87 let Some(flick) = flick_rc.downcast::<Flickable>() else {
88 return (false, false);
89 };
90 let flick = flick.as_pin_ref();
91 let geo = Self::geometry_without_virtual_keyboard(&flick_rc);
92
93 let zero = LogicalLength::zero();
94 let vpx = flick.content_x();
95 let vpy = flick.content_y();
96 let x_out_of_bounds =
97 vpx > zero || vpx < (geo.width_length() - flick.content_width()).min(zero);
98 let y_out_of_bounds =
99 vpy > zero || vpy < (geo.height_length() - flick.content_height()).min(zero);
100
101 (x_out_of_bounds, y_out_of_bounds)
102 },
103 |self_weak, (x_out_of_bounds, y_out_of_bounds)| {
105 let Some(flick_rc) = self_weak.upgrade() else { return };
106 let Some(flick) = flick_rc.downcast::<Flickable>() else { return };
107 let flick = flick.as_pin_ref();
108 let vpx = flick.content_x();
109 let vpy = flick.content_y();
110 let p = ensure_in_bound(flick, LogicalPoint::from_lengths(vpx, vpy), &flick_rc);
111
112 let x = (Flickable::FIELD_OFFSETS.content_x()).apply_pin(flick);
113 if *x_out_of_bounds && !x.has_binding() {
114 x.set(p.x_length());
115 }
116
117 let y = (Flickable::FIELD_OFFSETS.content_y()).apply_pin(flick);
118 if *y_out_of_bounds && !y.has_binding() {
119 y.set(p.y_length());
120 }
121 },
122 );
123 }
124
125 fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
126
127 fn layout_info(
128 self: Pin<&Self>,
129 _orientation: Orientation,
130 _cross_axis_constraint: Coord,
131 _window_adapter: &Rc<dyn WindowAdapter>,
132 _self_rc: &ItemRc,
133 ) -> LayoutInfo {
134 LayoutInfo { stretch: 1., ..LayoutInfo::default() }
135 }
136
137 fn input_event_filter_before_children(
138 self: Pin<&Self>,
139 event: &MouseEvent,
140 window_adapter: &Rc<dyn WindowAdapter>,
141 self_rc: &ItemRc,
142 _: &mut super::MouseCursorInner,
143 ) -> InputEventFilterResult {
144 if let Some(pos) = event.position() {
145 let geometry = Self::geometry_without_virtual_keyboard(self_rc);
146
147 if (pos.x < 0 as _
148 || pos.y < 0 as _
149 || pos.x_length() > geometry.width_length()
150 || pos.y_length() > geometry.height_length())
151 && self.data.inner.borrow().pressed_mouse_state.is_none()
152 {
153 return InputEventFilterResult::Intercept;
154 }
155 }
156 if !self.accepts_pan_event(event) {
157 return InputEventFilterResult::ForwardAndIgnore;
158 }
159 self.data.handle_mouse_filter(self, event, window_adapter, self_rc)
160 }
161
162 fn input_event(
163 self: Pin<&Self>,
164 event: &MouseEvent,
165 window_adapter: &Rc<dyn WindowAdapter>,
166 self_rc: &ItemRc,
167 _: &mut super::MouseCursorInner,
168 ) -> InputEventResult {
169 if !self.accepts_pan_event(event) {
170 return InputEventResult::EventIgnored;
171 }
172 if let Some(pos) = event.position() {
173 let geometry = Self::geometry_without_virtual_keyboard(self_rc);
174 if matches!(event, MouseEvent::Wheel { .. } | MouseEvent::Pressed { .. })
175 && (pos.x < 0 as _
176 || pos.y < 0 as _
177 || pos.x_length() > geometry.width_length()
178 || pos.y_length() > geometry.height_length())
179 {
180 return InputEventResult::EventIgnored;
181 }
182 }
183
184 self.data.handle_mouse(self, event, window_adapter, self_rc)
185 }
186
187 fn capture_key_event(
188 self: Pin<&Self>,
189 _: &InternalKeyEvent,
190 _window_adapter: &Rc<dyn WindowAdapter>,
191 _self_rc: &ItemRc,
192 ) -> KeyEventResult {
193 KeyEventResult::EventIgnored
194 }
195
196 fn key_event(
197 self: Pin<&Self>,
198 _: &InternalKeyEvent,
199 _window_adapter: &Rc<dyn WindowAdapter>,
200 _self_rc: &ItemRc,
201 ) -> KeyEventResult {
202 KeyEventResult::EventIgnored
203 }
204
205 fn focus_event(
206 self: Pin<&Self>,
207 _: &FocusEvent,
208 _window_adapter: &Rc<dyn WindowAdapter>,
209 _self_rc: &ItemRc,
210 ) -> FocusEventResult {
211 FocusEventResult::FocusIgnored
212 }
213
214 fn render(
215 self: Pin<&Self>,
216 backend: &mut ItemRendererRef,
217 _self_rc: &ItemRc,
218 size: LogicalSize,
219 ) -> RenderingResult {
220 (*backend).combine_clip(
221 LogicalRect::new(LogicalPoint::default(), size),
222 LogicalBorderRadius::zero(),
223 );
224 RenderingResult::ContinueRenderingChildren
225 }
226
227 fn bounding_rect(
228 self: core::pin::Pin<&Self>,
229 _window_adapter: &Rc<dyn WindowAdapter>,
230 _self_rc: &ItemRc,
231 geometry: LogicalRect,
232 ) -> LogicalRect {
233 geometry
234 }
235
236 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
237 true
238 }
239}
240
241impl ItemConsts for Flickable {
242 const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
243 Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
244}
245
246impl Flickable {
247 fn accepts_pan_event(self: Pin<&Self>, event: &MouseEvent) -> bool {
250 match event {
251 MouseEvent::Wheel { .. } => true,
252 MouseEvent::Pressed { .. } | MouseEvent::Moved { .. } | MouseEvent::Released { .. } => {
253 self.interactive() && (event.is_from_touch() || self.mouse_drag_pan_enabled())
254 }
255 MouseEvent::Exit
256 | MouseEvent::DragMove { .. }
257 | MouseEvent::Drop { .. }
258 | MouseEvent::PinchGesture { .. }
259 | MouseEvent::RotationGesture { .. } => self.interactive(),
260 }
261 }
262
263 fn choose_min_move(
264 current_view_start: Coord, view_len: Coord, content_len: Coord, points: impl Iterator<Item = Coord>,
268 ) -> Coord {
269 let zero = 0 as Coord;
272 let mut lower = Coord::MIN;
273 let mut upper = Coord::MAX;
274
275 for p in points {
276 lower = lower.max(p - (current_view_start + view_len));
277 upper = upper.min(p - current_view_start);
278 }
279
280 if lower > upper {
281 return zero;
284 }
285
286 let max_scroll = (content_len - view_len).max(zero);
288 let tmin = -current_view_start; let tmax = max_scroll - current_view_start; let i_min = lower.max(tmin);
292 let i_max = upper.min(tmax);
293
294 if i_min <= i_max {
295 if zero < i_min {
296 i_min
297 } else if zero > i_max {
298 i_max
299 } else {
300 zero
301 }
302 } else if tmax < lower {
305 tmax
306 } else {
307 tmin
308 }
309 }
310
311 pub(crate) fn reveal_points(self: Pin<&Self>, self_rc: &ItemRc, pts: &[LogicalPoint]) {
314 if pts.is_empty() {
315 return;
316 }
317
318 let geo = Self::geometry_without_virtual_keyboard(self_rc);
320
321 let cw = Self::FIELD_OFFSETS.content_width().apply_pin(self).get().0;
323 let ch = Self::FIELD_OFFSETS.content_height().apply_pin(self).get().0;
324 let cx = -Self::FIELD_OFFSETS.content_x().apply_pin(self).get().0;
325 let cy = -Self::FIELD_OFFSETS.content_y().apply_pin(self).get().0;
326
327 let tx = Self::choose_min_move(cx, geo.width(), cw, pts.iter().map(|p| p.x));
329 let ty = Self::choose_min_move(cy, geo.height(), ch, pts.iter().map(|p| p.y));
330
331 let new_cx = cx + tx;
332 let new_cy = cy + ty;
333
334 Self::FIELD_OFFSETS.content_x().apply_pin(self).set(euclid::Length::new(-new_cx));
335 Self::FIELD_OFFSETS.content_y().apply_pin(self).set(euclid::Length::new(-new_cy));
336 }
337
338 fn geometry_without_virtual_keyboard(self_rc: &ItemRc) -> LogicalRect {
339 let mut geometry = self_rc.geometry();
340
341 if let Some(keyboard_rect) = self_rc.window_adapter().and_then(|window_adapter| {
343 window_adapter.window().virtual_keyboard(crate::InternalToken)
344 }) {
345 let keyboard_pos = keyboard_rect.0;
346
347 let self_in_window_coordinates = self_rc.map_to_native_window(geometry.origin);
348 if (keyboard_pos.y as Coord) < (self_in_window_coordinates.y + geometry.height()) {
349 geometry.size.height = keyboard_pos.y as Coord - self_in_window_coordinates.y;
351 }
352 }
353 geometry
354 }
355}
356
357#[repr(C)]
358pub struct FlickableDataBox(core::ptr::NonNull<FlickableData>);
360
361impl Default for FlickableDataBox {
362 fn default() -> Self {
363 FlickableDataBox(Box::leak(Box::<FlickableData>::default()).into())
364 }
365}
366impl Drop for FlickableDataBox {
367 fn drop(&mut self) {
368 drop(unsafe { Box::from_raw(self.0.as_ptr()) });
370 }
371}
372
373impl core::ops::Deref for FlickableDataBox {
374 type Target = FlickableData;
375 fn deref(&self) -> &Self::Target {
376 unsafe { self.0.as_ref() }
378 }
379}
380
381pub(super) const DISTANCE_THRESHOLD: LogicalLength = LogicalLength::new(8 as _);
383pub(super) const DURATION_THRESHOLD: Duration = Duration::from_millis(500);
385pub(super) const FORWARD_DELAY: Duration = Duration::from_millis(100);
387pub(super) const SCROLL_FILTER_DURATION: Duration = Duration::from_millis(800);
393pub(super) const SHORT_SCROLL_FILTER_DURATION: Duration =
395 Duration::from_millis(SCROLL_FILTER_DURATION.as_millis() as u64 / 2);
396pub(super) const SCROLL_FILTER_DISTANCE_SQUARED: LogicalLength = LogicalLength::new(4 as _);
398
399#[derive(Debug, PartialEq, Eq, Clone, Copy)]
400enum CaptureEvents {
401 MouseOrTouchScreen,
402 MouseWheel,
403}
404
405#[derive(Default)]
406struct FlickableDataInner {
407 pressed_mouse_state: Option<(Instant, LogicalPoint)>,
411 last_mouse_position: LogicalPoint,
415 capture_events: Option<CaptureEvents>,
417 last_scroll_event: Option<(Instant, LogicalPoint)>,
423
424 velocity_rb: VelocityRingBuffer<5>,
427
428 running_animation: Option<(Instant, [Option<ConstantDecelerationParameters>; 2])>,
432}
433
434impl FlickableDataInner {
435 fn should_capture_scroll(&self, timeout: Duration, position: LogicalPoint) -> bool {
436 self.last_scroll_event.is_some_and(|(last_time, last_position)| {
437 crate::animations::current_tick() - last_time < timeout
439 && LogicalLength::new((last_position - position).square_length().abs())
440 < SCROLL_FILTER_DISTANCE_SQUARED
441 })
442 }
443
444 #[allow(clippy::nonminimal_bool)] fn is_allowed_scroll_direction(
447 flick: Pin<&Flickable>,
448 delta: LogicalVector,
449 flick_rc: &ItemRc,
450 ) -> bool {
451 let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
452
453 (delta.y != 0 as Coord && flick.content_height() > geo.height_length())
454 || (delta.x != 0 as Coord && flick.content_width() > geo.width_length())
455 }
456
457 fn process_wheel_event(
458 &mut self,
459 flick: Pin<&Flickable>,
460 mut delta: LogicalVector,
461 position: LogicalPoint,
462 phase: TouchPhase,
463 flick_rc: &ItemRc,
464 ) -> InputEventResult {
465 if phase != TouchPhase::Started
466 && delta != LogicalVector::default()
467 && !Self::is_allowed_scroll_direction(flick, delta, flick_rc)
468 {
469 self.capture_events = None;
471 self.last_scroll_event = None;
472 self.running_animation = None;
473 self.velocity_rb = VelocityRingBuffer::default();
474 return InputEventResult::EventIgnored;
475 }
476
477 let content_x = (Flickable::FIELD_OFFSETS.content_x()).apply_pin(flick);
478 let content_y = (Flickable::FIELD_OFFSETS.content_y()).apply_pin(flick);
479 let current_pos = LogicalPoint::from_lengths(content_x.get(), content_y.get());
480
481 if self.capture_events.is_none()
482 && matches!(phase, TouchPhase::Moved)
483 && let Some((start_time, [x_simulation, y_simulation])) = &self.running_animation
484 {
485 let animation_duration = crate::animations::current_tick().duration_since(*start_time);
487
488 if let Some(x_simulation) = x_simulation {
489 delta.x += x_simulation.remaining_distance(animation_duration);
490 }
491 if let Some(y_simulation) = y_simulation {
492 delta.y += y_simulation.remaining_distance(animation_duration);
493 }
494 }
495
496 let new_pos = ensure_in_bound(flick, current_pos + delta, flick_rc);
497 delta = new_pos - current_pos;
498
499 if phase != TouchPhase::Ended {
500 content_x.remove_binding();
501 content_y.remove_binding();
502 self.running_animation = None;
503 }
504
505 match phase {
506 TouchPhase::Cancelled => {
507 content_x.set(new_pos.x_length());
508 content_y.set(new_pos.y_length());
509 self.last_scroll_event = Some((crate::animations::current_tick(), position));
510 }
511 TouchPhase::Started => {
512 self.velocity_rb = VelocityRingBuffer::default();
513 self.capture_events = Some(CaptureEvents::MouseWheel);
514 self.last_scroll_event = Some((crate::animations::current_tick(), position));
515 }
516 TouchPhase::Moved => {
517 if self.capture_events.is_some_and(|capture| capture == CaptureEvents::MouseWheel) {
518 self.velocity_rb.push(crate::animations::current_tick(), new_pos - current_pos);
520 content_x.set(new_pos.x_length());
521 content_y.set(new_pos.y_length());
522 } else {
523 let [limit_x, limit_y] = Self::flick_limits(flick_rc, delta);
532
533 let x_simulation = (delta.x != Coord::default()).then(|| {
534 let simulation = ConstantDecelerationParameters::new_with_distance(
535 delta.x as f32,
536 WHEEL_SCROLL_DURATION.as_secs_f32(),
537 );
538 content_x.set_physic_animation_value(limit_x, simulation.clone());
539 simulation
540 });
541
542 let y_simulation = (delta.y != Coord::default()).then(|| {
543 let simulation = ConstantDecelerationParameters::new_with_distance(
544 delta.y as f32,
545 WHEEL_SCROLL_DURATION.as_secs_f32(),
546 );
547 content_y.set_physic_animation_value(limit_y, simulation.clone());
548 simulation
549 });
550
551 if delta.x != 0 as Coord || delta.y != 0 as Coord {
552 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
553 }
554
555 self.running_animation =
556 Some((crate::animations::current_tick(), [x_simulation, y_simulation]));
557 }
558 self.last_scroll_event = Some((crate::animations::current_tick(), position));
559 }
560 TouchPhase::Ended => {
561 if self.capture_events.is_some_and(|capture| capture == CaptureEvents::MouseWheel) {
562 self.animate(flick, flick_rc);
563 }
564 self.capture_events = None;
565 return if self.should_capture_scroll(SHORT_SCROLL_FILTER_DURATION, position) {
566 InputEventResult::EventAccepted
567 } else {
568 InputEventResult::EventIgnored
569 };
570 }
571 }
572
573 let flicked = current_pos.x_length() != new_pos.x_length()
574 || current_pos.y_length() != new_pos.y_length();
575 if flicked {
576 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
577 InputEventResult::EventAccepted
578 } else if self.should_capture_scroll(SHORT_SCROLL_FILTER_DURATION, position) {
579 InputEventResult::EventAccepted
582 } else {
583 self.last_scroll_event = None;
584 InputEventResult::EventIgnored
585 }
586 }
587
588 fn flick_limits(
589 flick_rc: &ItemRc,
590 flick_velocity: LogicalVector,
591 ) -> [Pin<Box<Property<f32>>>; 2] {
592 let flick_weak = flick_rc.downgrade();
593 let calculate_limits = move || {
594 flick_weak
595 .upgrade()
596 .and_then(|flick_rc| {
597 flick_rc.downcast::<Flickable>().map(move |flick| (flick_rc, flick))
598 })
599 .map(|(flick_rc, flick)| {
600 let flick = flick.as_pin_ref();
601 ensure_in_bound(
602 flick,
603 LogicalPoint::from_lengths(-flick.content_width(), -flick.content_height()),
604 &flick_rc,
605 )
606 })
607 };
608
609 let limit_x = if flick_velocity.x < 0 as Coord {
610 let property = Box::pin(Property::new(0.0));
611 property.set_binding({
612 let calculate_limits = calculate_limits.clone();
613 move || calculate_limits().map(|limit| limit.x_length().get() as f32).unwrap_or(0.0)
614 });
615 property
616 } else {
617 Box::pin(Property::new(0.0))
618 };
619
620 let limit_y = if flick_velocity.y < 0 as Coord {
621 let property = Box::pin(Property::new(0.0));
622 property.set_binding(move || {
623 calculate_limits().map(|limit| limit.y_length().get() as f32).unwrap_or(0.0)
624 });
625 property
626 } else {
627 Box::pin(Property::new(0.0))
628 };
629
630 [limit_x, limit_y]
631 }
632
633 fn animate(&self, flick: Pin<&Flickable>, flick_rc: &ItemRc) {
634 if let Some(last_time) = self.velocity_rb.last_time() {
635 let mean_velocity = self.velocity_rb.mean_velocity();
636 if self.capture_events.is_some()
637 && mean_velocity.square_length() > 0 as Coord
638 && crate::animations::current_tick().duration_since(last_time) < MAX_DURATION
639 {
640 let content_x = (Flickable::FIELD_OFFSETS.content_x()).apply_pin(flick);
641 let content_y = (Flickable::FIELD_OFFSETS.content_y()).apply_pin(flick);
642
643 let [limit_x, limit_y] = Self::flick_limits(flick_rc, mean_velocity);
644
645 {
646 let simulation =
647 ConstantDecelerationParameters::new(mean_velocity.x as f32, DECELERATION);
648 content_x.set_physic_animation_value(limit_x, simulation);
649 }
650
651 {
652 let animation_y =
653 ConstantDecelerationParameters::new(mean_velocity.y as f32, DECELERATION);
654 content_y.set_physic_animation_value(limit_y, animation_y);
655 }
656
657 if mean_velocity.x != 0 as Coord || mean_velocity.y != 0 as Coord {
658 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
659 }
660 }
661 }
662 }
663}
664
665#[derive(Default)]
666pub struct FlickableData {
667 inner: RefCell<FlickableDataInner>,
668 in_bound_change_handler: crate::properties::ChangeTracker,
670}
671
672impl FlickableData {
673 fn scroll_delta(
674 window_adapter: &Rc<dyn WindowAdapter>,
675 delta_x: Coord,
676 delta_y: Coord,
677 ) -> LogicalVector {
678 if window_adapter.window().0.context().0.modifiers.get().shift()
679 && !cfg!(target_os = "macos")
680 {
681 LogicalVector::new(delta_y, delta_x)
684 } else {
685 LogicalVector::new(delta_x, delta_y)
686 }
687 }
688
689 fn handle_mouse_filter(
690 &self,
691 flick: Pin<&Flickable>,
692 event: &MouseEvent,
693 window_adapter: &Rc<dyn WindowAdapter>,
694 flick_rc: &ItemRc,
695 ) -> InputEventFilterResult {
696 let mut inner = self.inner.borrow_mut();
697 match event {
698 MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
699 if inner.capture_events.is_none() && !Self::can_pan(flick, flick_rc) {
700 return InputEventFilterResult::ForwardAndIgnore;
704 }
705
706 inner.velocity_rb = VelocityRingBuffer::default();
707 inner.pressed_mouse_state = Some((crate::animations::current_tick(), *position));
708 inner.last_mouse_position = *position;
709 let content_x = (Flickable::FIELD_OFFSETS.content_x()).apply_pin(flick);
710 content_x.remove_binding(); let content_y = (Flickable::FIELD_OFFSETS.content_y()).apply_pin(flick);
712 content_y.remove_binding(); if inner.capture_events.is_some() {
715 InputEventFilterResult::Intercept
716 } else {
717 InputEventFilterResult::DelayForwarding(FORWARD_DELAY.as_millis() as _)
718 }
719 }
720 MouseEvent::Exit | MouseEvent::Released { button: PointerEventButton::Left, .. } => {
721 inner.pressed_mouse_state = None;
722 if inner.capture_events.is_some() {
723 InputEventFilterResult::Intercept
724 } else {
725 InputEventFilterResult::ForwardEvent
726 }
727 }
728 MouseEvent::Moved { position, .. } => {
729 let do_intercept = inner.capture_events.is_some()
730 || inner.pressed_mouse_state.is_some_and(
731 |(pressed_time, pressed_mouse_position)| {
732 let mouse_delta = *position - pressed_mouse_position;
733
734 crate::animations::current_tick() - pressed_time <= DURATION_THRESHOLD
735 && self.should_capture_mouse_direction(mouse_delta, flick, flick_rc)
736 },
737 );
738 if do_intercept {
739 InputEventFilterResult::Intercept
740 } else if inner.pressed_mouse_state.is_some() {
741 InputEventFilterResult::ForwardAndInterceptGrab
742 } else {
743 InputEventFilterResult::ForwardEvent
744 }
745 }
746 MouseEvent::Wheel { position, delta_x, delta_y, phase } => {
747 match phase {
748 TouchPhase::Cancelled => {
749 let delta = Self::scroll_delta(window_adapter, *delta_x, *delta_y);
753 if FlickableDataInner::is_allowed_scroll_direction(flick, delta, flick_rc)
754 && inner.should_capture_scroll(SCROLL_FILTER_DURATION, *position)
755 {
756 InputEventFilterResult::Intercept
757 } else {
758 inner.last_scroll_event = None;
759 InputEventFilterResult::ForwardEvent
760 }
761 }
762 TouchPhase::Started => InputEventFilterResult::Intercept,
763 TouchPhase::Moved => {
764 if inner.capture_events.is_some() {
765 InputEventFilterResult::Intercept
766 } else {
767 let delta = Self::scroll_delta(window_adapter, *delta_x, *delta_y);
770 if FlickableDataInner::is_allowed_scroll_direction(
771 flick, delta, flick_rc,
772 ) && inner.should_capture_scroll(SCROLL_FILTER_DURATION, *position)
773 {
774 InputEventFilterResult::Intercept
775 } else {
776 inner.last_scroll_event = None;
777 InputEventFilterResult::ForwardEvent
778 }
779 }
780 }
781 TouchPhase::Ended => {
782 if inner.capture_events.is_some() {
783 InputEventFilterResult::Intercept
784 } else {
785 InputEventFilterResult::ForwardEvent
786 }
787 }
788 }
789 }
790 MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
792 InputEventFilterResult::ForwardAndIgnore
793 }
794 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
795 InputEventFilterResult::ForwardEvent
796 }
797 MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => {
798 InputEventFilterResult::ForwardAndIgnore
799 }
800 }
801 }
802
803 fn should_capture_mouse_direction(
804 &self,
805 mouse_delta: LogicalVector,
806 flick: Pin<&Flickable>,
807 flick_rc: &ItemRc,
808 ) -> bool {
809 let flickable_geometry = Flickable::geometry_without_virtual_keyboard(flick_rc);
810 let flickable_width = flickable_geometry.width_length();
811 let flickable_height = flickable_geometry.height_length();
812 let content_width = flick.content_width();
813 let content_height = flick.content_height();
814 let zero = LogicalLength::zero();
815
816 ((content_width > flickable_width || flick.content_x() != zero)
819 && abs(mouse_delta.x_length()) > DISTANCE_THRESHOLD)
820 || ((content_height > flickable_height || flick.content_y() != zero)
821 && abs(mouse_delta.y_length()) > DISTANCE_THRESHOLD)
822 }
823
824 fn can_pan(flick: Pin<&Flickable>, flick_rc: &ItemRc) -> bool {
828 let flickable_geometry = Flickable::geometry_without_virtual_keyboard(flick_rc);
829 let flickable_width = flickable_geometry.width_length();
830 let flickable_height = flickable_geometry.height_length();
831 let content_width = flick.content_width();
832 let content_height = flick.content_height();
833 let zero = LogicalLength::zero();
834
835 (content_width > flickable_width || flick.content_x() != zero)
836 || (content_height > flickable_height || flick.content_y() != zero)
837 }
838
839 fn handle_mouse(
840 &self,
841 flick: Pin<&Flickable>,
842 event: &MouseEvent,
843 window_adapter: &Rc<dyn WindowAdapter>,
844 flick_rc: &ItemRc,
845 ) -> InputEventResult {
846 let mut inner = self.inner.borrow_mut();
847 match event {
848 MouseEvent::Pressed { .. } => {
849 inner.capture_events = Some(CaptureEvents::MouseOrTouchScreen);
850 InputEventResult::GrabMouse
851 }
852 MouseEvent::Exit | MouseEvent::Released { .. } => {
853 if inner.capture_events.is_some_and(|f| f == CaptureEvents::MouseOrTouchScreen) {
854 let was_capturing = true;
855 inner.animate(flick, flick_rc);
856 inner.capture_events = None;
857 inner.pressed_mouse_state = None;
858 if was_capturing {
859 InputEventResult::EventAccepted
860 } else {
861 InputEventResult::EventIgnored
862 }
863 } else if inner.capture_events.is_none() {
864 inner.pressed_mouse_state = None;
865 InputEventResult::EventIgnored
866 } else {
867 InputEventResult::EventIgnored
868 }
869 }
870 MouseEvent::Moved { position, .. } => {
871 if let Some((_pressed_time, _pressed_mouse_position)) = inner.pressed_mouse_state {
881 let mouse_delta = *position - inner.last_mouse_position;
882 inner.velocity_rb.push(crate::animations::current_tick(), mouse_delta);
883
884 let is_capturing = inner
885 .capture_events
886 .is_some_and(|f| f == CaptureEvents::MouseOrTouchScreen);
887 if is_capturing
888 || self.should_capture_mouse_direction(mouse_delta, flick, flick_rc)
889 {
890 let content_x = (Flickable::FIELD_OFFSETS.content_x()).apply_pin(flick);
893 let content_y = (Flickable::FIELD_OFFSETS.content_y()).apply_pin(flick);
894 let current_content_position =
895 LogicalPoint::from_lengths(content_x.get(), content_y.get());
896
897 let new_content_position = current_content_position + mouse_delta;
903 let new_content_position =
904 ensure_in_bound(flick, new_content_position, flick_rc);
905
906 content_x.set(new_content_position.x_length());
907 content_y.set(new_content_position.y_length());
908 if current_content_position != new_content_position {
909 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
910 }
911
912 inner.last_mouse_position = *position;
929
930 inner.capture_events = Some(CaptureEvents::MouseOrTouchScreen);
931
932 InputEventResult::GrabMouse
933 } else if abs(mouse_delta.x_length()) > DISTANCE_THRESHOLD
934 || abs(mouse_delta.y_length()) > DISTANCE_THRESHOLD
935 {
936 InputEventResult::EventIgnored
938 } else {
939 InputEventResult::EventAccepted
942 }
943 } else {
944 InputEventResult::EventIgnored
945 }
946 }
947 MouseEvent::Wheel { delta_x, delta_y, position, phase } => {
948 let delta = Self::scroll_delta(window_adapter, *delta_x, *delta_y);
949 inner.process_wheel_event(flick, delta, *position, *phase, flick_rc)
950 }
951 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
952 InputEventResult::EventIgnored
953 }
954 MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => InputEventResult::EventIgnored,
955 }
956 }
957}
958
959fn abs(l: LogicalLength) -> LogicalLength {
960 LogicalLength::new(l.get().abs())
961}
962
963fn ensure_in_bound(flick: Pin<&Flickable>, p: LogicalPoint, flick_rc: &ItemRc) -> LogicalPoint {
965 let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
966 let w = geo.width_length();
967 let h = geo.height_length();
968 let cw = (Flickable::FIELD_OFFSETS.content_width()).apply_pin(flick).get();
969 let ch = (Flickable::FIELD_OFFSETS.content_height()).apply_pin(flick).get();
970
971 let min = LogicalPoint::from_lengths(w - cw, h - ch);
972 let max = LogicalPoint::default();
973 p.max(min).min(max)
974}
975
976#[cfg(feature = "ffi")]
980#[unsafe(no_mangle)]
981pub unsafe extern "C" fn slint_flickable_data_init(data: *mut FlickableDataBox) {
982 unsafe { core::ptr::write(data, FlickableDataBox::default()) };
983}
984
985#[cfg(feature = "ffi")]
988#[unsafe(no_mangle)]
989pub unsafe extern "C" fn slint_flickable_data_free(data: *mut FlickableDataBox) {
990 unsafe {
991 core::ptr::drop_in_place(data);
992 }
993}