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 wheel_gesture_start: Option<Instant>,
427
428 velocity_rb: VelocityRingBuffer<5>,
431
432 running_animation: Option<(Instant, [Option<ConstantDecelerationParameters>; 2])>,
436}
437
438impl FlickableDataInner {
439 fn should_capture_scroll(&self, timeout: Duration, position: LogicalPoint) -> bool {
440 self.last_scroll_event.is_some_and(|(last_time, last_position)| {
441 crate::animations::current_tick() - last_time < timeout
443 && LogicalLength::new((last_position - position).square_length().abs())
444 < SCROLL_FILTER_DISTANCE_SQUARED
445 })
446 }
447
448 #[allow(clippy::nonminimal_bool)] fn is_allowed_scroll_direction(
451 flick: Pin<&Flickable>,
452 delta: LogicalVector,
453 flick_rc: &ItemRc,
454 ) -> bool {
455 let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
456
457 (delta.y != 0 as Coord && flick.content_height() > geo.height_length())
458 || (delta.x != 0 as Coord && flick.content_width() > geo.width_length())
459 }
460
461 fn process_wheel_event(
462 &mut self,
463 flick: Pin<&Flickable>,
464 mut delta: LogicalVector,
465 position: LogicalPoint,
466 phase: TouchPhase,
467 flick_rc: &ItemRc,
468 ) -> InputEventResult {
469 if delta != LogicalVector::default()
470 && !Self::is_allowed_scroll_direction(flick, delta, flick_rc)
471 {
472 self.capture_events = None;
474 self.last_scroll_event = None;
475 self.running_animation = None;
476 self.velocity_rb = VelocityRingBuffer::default();
477 return InputEventResult::EventIgnored;
478 }
479
480 if phase == TouchPhase::Moved
481 && self.capture_events.is_none()
482 && self.wheel_gesture_start.take().is_some_and(|start| {
483 crate::animations::current_tick() - start < SCROLL_FILTER_DURATION
484 })
485 {
486 self.velocity_rb = VelocityRingBuffer::default();
487 self.capture_events = Some(CaptureEvents::MouseWheel);
488 }
489
490 let content_x = (Flickable::FIELD_OFFSETS.content_x()).apply_pin(flick);
491 let content_y = (Flickable::FIELD_OFFSETS.content_y()).apply_pin(flick);
492 let current_pos = LogicalPoint::from_lengths(content_x.get(), content_y.get());
493
494 if self.capture_events.is_none()
495 && matches!(phase, TouchPhase::Moved)
496 && let Some((start_time, [x_simulation, y_simulation])) = &self.running_animation
497 {
498 let animation_duration = crate::animations::current_tick().duration_since(*start_time);
500
501 if let Some(x_simulation) = x_simulation {
502 delta.x += x_simulation.remaining_distance(animation_duration);
503 }
504 if let Some(y_simulation) = y_simulation {
505 delta.y += y_simulation.remaining_distance(animation_duration);
506 }
507 }
508
509 let new_pos = ensure_in_bound(flick, current_pos + delta, flick_rc);
510 delta = new_pos - current_pos;
511
512 if phase != TouchPhase::Ended {
513 content_x.remove_binding();
514 content_y.remove_binding();
515 self.running_animation = None;
516 }
517
518 match phase {
519 TouchPhase::Cancelled => {
520 content_x.set(new_pos.x_length());
521 content_y.set(new_pos.y_length());
522 self.last_scroll_event = Some((crate::animations::current_tick(), position));
523 }
524 TouchPhase::Started => {
525 self.last_scroll_event = Some((crate::animations::current_tick(), position));
526 }
527 TouchPhase::Moved => {
528 if self.capture_events.is_some_and(|capture| capture == CaptureEvents::MouseWheel) {
529 self.velocity_rb.push(crate::animations::current_tick(), new_pos - current_pos);
531 content_x.set(new_pos.x_length());
532 content_y.set(new_pos.y_length());
533 } else {
534 let [limit_x, limit_y] = Self::flick_limits(flick_rc, delta);
543
544 let x_simulation = (delta.x != Coord::default()).then(|| {
545 let simulation = ConstantDecelerationParameters::new_with_distance(
546 delta.x as f32,
547 WHEEL_SCROLL_DURATION.as_secs_f32(),
548 );
549 content_x.set_physic_animation_value(limit_x, simulation.clone());
550 simulation
551 });
552
553 let y_simulation = (delta.y != Coord::default()).then(|| {
554 let simulation = ConstantDecelerationParameters::new_with_distance(
555 delta.y as f32,
556 WHEEL_SCROLL_DURATION.as_secs_f32(),
557 );
558 content_y.set_physic_animation_value(limit_y, simulation.clone());
559 simulation
560 });
561
562 if delta.x != 0 as Coord || delta.y != 0 as Coord {
563 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
564 }
565
566 self.running_animation =
567 Some((crate::animations::current_tick(), [x_simulation, y_simulation]));
568 }
569 self.last_scroll_event = Some((crate::animations::current_tick(), position));
570 }
571 TouchPhase::Ended => {
572 if self.capture_events.is_some_and(|capture| capture == CaptureEvents::MouseWheel) {
573 self.animate(flick, flick_rc);
574 }
575 self.capture_events = None;
576 return if self.should_capture_scroll(SHORT_SCROLL_FILTER_DURATION, position) {
577 InputEventResult::EventAccepted
578 } else {
579 InputEventResult::EventIgnored
580 };
581 }
582 }
583
584 let flicked = current_pos.x_length() != new_pos.x_length()
585 || current_pos.y_length() != new_pos.y_length();
586 if flicked {
587 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
588 InputEventResult::EventAccepted
589 } else if self.should_capture_scroll(SHORT_SCROLL_FILTER_DURATION, position) {
590 InputEventResult::EventAccepted
593 } else {
594 self.last_scroll_event = None;
595 InputEventResult::EventIgnored
596 }
597 }
598
599 fn flick_limits(
600 flick_rc: &ItemRc,
601 flick_velocity: LogicalVector,
602 ) -> [Pin<Box<Property<f32>>>; 2] {
603 let flick_weak = flick_rc.downgrade();
604 let calculate_limits = move || {
605 flick_weak
606 .upgrade()
607 .and_then(|flick_rc| {
608 flick_rc.downcast::<Flickable>().map(move |flick| (flick_rc, flick))
609 })
610 .map(|(flick_rc, flick)| {
611 let flick = flick.as_pin_ref();
612 ensure_in_bound(
613 flick,
614 LogicalPoint::from_lengths(-flick.content_width(), -flick.content_height()),
615 &flick_rc,
616 )
617 })
618 };
619
620 let limit_x = if flick_velocity.x < 0 as Coord {
621 let property = Box::pin(Property::new(0.0));
622 property.set_binding({
623 let calculate_limits = calculate_limits.clone();
624 move || calculate_limits().map(|limit| limit.x_length().get() as f32).unwrap_or(0.0)
625 });
626 property
627 } else {
628 Box::pin(Property::new(0.0))
629 };
630
631 let limit_y = if flick_velocity.y < 0 as Coord {
632 let property = Box::pin(Property::new(0.0));
633 property.set_binding(move || {
634 calculate_limits().map(|limit| limit.y_length().get() as f32).unwrap_or(0.0)
635 });
636 property
637 } else {
638 Box::pin(Property::new(0.0))
639 };
640
641 [limit_x, limit_y]
642 }
643
644 fn animate(&self, flick: Pin<&Flickable>, flick_rc: &ItemRc) {
645 if let Some(last_time) = self.velocity_rb.last_time() {
646 let mean_velocity = self.velocity_rb.mean_velocity();
647 if self.capture_events.is_some()
648 && mean_velocity.square_length() > 0 as Coord
649 && crate::animations::current_tick().duration_since(last_time) < MAX_DURATION
650 {
651 let content_x = (Flickable::FIELD_OFFSETS.content_x()).apply_pin(flick);
652 let content_y = (Flickable::FIELD_OFFSETS.content_y()).apply_pin(flick);
653
654 let [limit_x, limit_y] = Self::flick_limits(flick_rc, mean_velocity);
655
656 {
657 let simulation =
658 ConstantDecelerationParameters::new(mean_velocity.x as f32, DECELERATION);
659 content_x.set_physic_animation_value(limit_x, simulation);
660 }
661
662 {
663 let animation_y =
664 ConstantDecelerationParameters::new(mean_velocity.y as f32, DECELERATION);
665 content_y.set_physic_animation_value(limit_y, animation_y);
666 }
667
668 if mean_velocity.x != 0 as Coord || mean_velocity.y != 0 as Coord {
669 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
670 }
671 }
672 }
673 }
674}
675
676#[derive(Default)]
677pub struct FlickableData {
678 inner: RefCell<FlickableDataInner>,
679 in_bound_change_handler: crate::properties::ChangeTracker,
681}
682
683impl FlickableData {
684 fn scroll_delta(
685 window_adapter: &Rc<dyn WindowAdapter>,
686 delta_x: Coord,
687 delta_y: Coord,
688 ) -> LogicalVector {
689 if window_adapter.window().0.context().0.modifiers.get().shift()
690 && !cfg!(target_os = "macos")
691 {
692 LogicalVector::new(delta_y, delta_x)
695 } else {
696 LogicalVector::new(delta_x, delta_y)
697 }
698 }
699
700 fn handle_mouse_filter(
701 &self,
702 flick: Pin<&Flickable>,
703 event: &MouseEvent,
704 window_adapter: &Rc<dyn WindowAdapter>,
705 flick_rc: &ItemRc,
706 ) -> InputEventFilterResult {
707 let mut inner = self.inner.borrow_mut();
708 match event {
709 MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
710 if inner.capture_events.is_none() && !Self::can_pan(flick, flick_rc) {
711 return InputEventFilterResult::ForwardAndIgnore;
715 }
716
717 inner.velocity_rb = VelocityRingBuffer::default();
718 inner.pressed_mouse_state = Some((crate::animations::current_tick(), *position));
719 inner.last_mouse_position = *position;
720 let content_x = (Flickable::FIELD_OFFSETS.content_x()).apply_pin(flick);
721 content_x.remove_binding(); let content_y = (Flickable::FIELD_OFFSETS.content_y()).apply_pin(flick);
723 content_y.remove_binding(); if inner.capture_events.is_some() {
726 InputEventFilterResult::Intercept
727 } else {
728 InputEventFilterResult::DelayForwarding(FORWARD_DELAY.as_millis() as _)
729 }
730 }
731 MouseEvent::Exit | MouseEvent::Released { button: PointerEventButton::Left, .. } => {
732 inner.pressed_mouse_state = None;
733 if inner.capture_events.is_some() {
734 InputEventFilterResult::Intercept
735 } else {
736 InputEventFilterResult::ForwardEvent
737 }
738 }
739 MouseEvent::Moved { position, .. } => {
740 let do_intercept = inner.capture_events.is_some()
741 || inner.pressed_mouse_state.is_some_and(
742 |(pressed_time, pressed_mouse_position)| {
743 let mouse_delta = *position - pressed_mouse_position;
744
745 crate::animations::current_tick() - pressed_time <= DURATION_THRESHOLD
746 && self.should_capture_mouse_direction(mouse_delta, flick, flick_rc)
747 },
748 );
749 if do_intercept {
750 InputEventFilterResult::Intercept
751 } else if inner.pressed_mouse_state.is_some() {
752 InputEventFilterResult::ForwardAndInterceptGrab
753 } else {
754 InputEventFilterResult::ForwardEvent
755 }
756 }
757 MouseEvent::Wheel { position, delta_x, delta_y, phase } => {
758 match phase {
759 TouchPhase::Started => {
760 inner.wheel_gesture_start = Some(crate::animations::current_tick())
761 }
762 TouchPhase::Ended => inner.wheel_gesture_start = None,
763 TouchPhase::Moved | TouchPhase::Cancelled => {}
764 }
765 if inner.capture_events.is_some() {
766 InputEventFilterResult::Intercept
767 } else if *phase == TouchPhase::Ended {
768 InputEventFilterResult::ForwardEvent
769 } else if inner.should_capture_scroll(SCROLL_FILTER_DURATION, *position)
770 && FlickableDataInner::is_allowed_scroll_direction(
771 flick,
772 Self::scroll_delta(window_adapter, *delta_x, *delta_y),
773 flick_rc,
774 )
775 {
776 InputEventFilterResult::Intercept
779 } else {
780 inner.last_scroll_event = None;
781 InputEventFilterResult::ForwardEvent
782 }
783 }
784 MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
786 InputEventFilterResult::ForwardAndIgnore
787 }
788 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
789 InputEventFilterResult::ForwardEvent
790 }
791 MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => {
792 InputEventFilterResult::ForwardAndIgnore
793 }
794 }
795 }
796
797 fn should_capture_mouse_direction(
798 &self,
799 mouse_delta: LogicalVector,
800 flick: Pin<&Flickable>,
801 flick_rc: &ItemRc,
802 ) -> bool {
803 let flickable_geometry = Flickable::geometry_without_virtual_keyboard(flick_rc);
804 let flickable_width = flickable_geometry.width_length();
805 let flickable_height = flickable_geometry.height_length();
806 let content_width = flick.content_width();
807 let content_height = flick.content_height();
808 let zero = LogicalLength::zero();
809
810 ((content_width > flickable_width || flick.content_x() != zero)
813 && abs(mouse_delta.x_length()) > DISTANCE_THRESHOLD)
814 || ((content_height > flickable_height || flick.content_y() != zero)
815 && abs(mouse_delta.y_length()) > DISTANCE_THRESHOLD)
816 }
817
818 fn can_pan(flick: Pin<&Flickable>, flick_rc: &ItemRc) -> bool {
822 let flickable_geometry = Flickable::geometry_without_virtual_keyboard(flick_rc);
823 let flickable_width = flickable_geometry.width_length();
824 let flickable_height = flickable_geometry.height_length();
825 let content_width = flick.content_width();
826 let content_height = flick.content_height();
827 let zero = LogicalLength::zero();
828
829 (content_width > flickable_width || flick.content_x() != zero)
830 || (content_height > flickable_height || flick.content_y() != zero)
831 }
832
833 fn handle_mouse(
834 &self,
835 flick: Pin<&Flickable>,
836 event: &MouseEvent,
837 window_adapter: &Rc<dyn WindowAdapter>,
838 flick_rc: &ItemRc,
839 ) -> InputEventResult {
840 let mut inner = self.inner.borrow_mut();
841 match event {
842 MouseEvent::Pressed { .. } => {
843 inner.capture_events = Some(CaptureEvents::MouseOrTouchScreen);
844 InputEventResult::GrabMouse
845 }
846 MouseEvent::Exit | MouseEvent::Released { .. } => {
847 if inner.capture_events.is_some_and(|f| f == CaptureEvents::MouseOrTouchScreen) {
848 let was_capturing = true;
849 inner.animate(flick, flick_rc);
850 inner.capture_events = None;
851 inner.pressed_mouse_state = None;
852 if was_capturing {
853 InputEventResult::EventAccepted
854 } else {
855 InputEventResult::EventIgnored
856 }
857 } else if inner.capture_events.is_none() {
858 inner.pressed_mouse_state = None;
859 InputEventResult::EventIgnored
860 } else {
861 InputEventResult::EventIgnored
862 }
863 }
864 MouseEvent::Moved { position, .. } => {
865 if let Some((_pressed_time, _pressed_mouse_position)) = inner.pressed_mouse_state {
875 let mouse_delta = *position - inner.last_mouse_position;
876 inner.velocity_rb.push(crate::animations::current_tick(), mouse_delta);
877
878 let is_capturing = inner
879 .capture_events
880 .is_some_and(|f| f == CaptureEvents::MouseOrTouchScreen);
881 if is_capturing
882 || self.should_capture_mouse_direction(mouse_delta, flick, flick_rc)
883 {
884 let content_x = (Flickable::FIELD_OFFSETS.content_x()).apply_pin(flick);
887 let content_y = (Flickable::FIELD_OFFSETS.content_y()).apply_pin(flick);
888 let current_content_position =
889 LogicalPoint::from_lengths(content_x.get(), content_y.get());
890
891 let new_content_position = current_content_position + mouse_delta;
897 let new_content_position =
898 ensure_in_bound(flick, new_content_position, flick_rc);
899
900 content_x.set(new_content_position.x_length());
901 content_y.set(new_content_position.y_length());
902 if current_content_position != new_content_position {
903 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
904 }
905
906 inner.last_mouse_position = *position;
923
924 inner.capture_events = Some(CaptureEvents::MouseOrTouchScreen);
925
926 InputEventResult::GrabMouse
927 } else if abs(mouse_delta.x_length()) > DISTANCE_THRESHOLD
928 || abs(mouse_delta.y_length()) > DISTANCE_THRESHOLD
929 {
930 InputEventResult::EventIgnored
932 } else {
933 InputEventResult::EventAccepted
936 }
937 } else {
938 InputEventResult::EventIgnored
939 }
940 }
941 MouseEvent::Wheel { delta_x, delta_y, position, phase } => {
942 let delta = Self::scroll_delta(window_adapter, *delta_x, *delta_y);
943 inner.process_wheel_event(flick, delta, *position, *phase, flick_rc)
944 }
945 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
946 InputEventResult::EventIgnored
947 }
948 MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => InputEventResult::EventIgnored,
949 }
950 }
951}
952
953fn abs(l: LogicalLength) -> LogicalLength {
954 LogicalLength::new(l.get().abs())
955}
956
957fn ensure_in_bound(flick: Pin<&Flickable>, p: LogicalPoint, flick_rc: &ItemRc) -> LogicalPoint {
959 let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
960 let w = geo.width_length();
961 let h = geo.height_length();
962 let cw = (Flickable::FIELD_OFFSETS.content_width()).apply_pin(flick).get();
963 let ch = (Flickable::FIELD_OFFSETS.content_height()).apply_pin(flick).get();
964
965 let min = LogicalPoint::from_lengths(w - cw, h - ch);
966 let max = LogicalPoint::default();
967 p.max(min).min(max)
968}
969
970#[cfg(feature = "ffi")]
974#[unsafe(no_mangle)]
975pub unsafe extern "C" fn slint_flickable_data_init(data: *mut FlickableDataBox) {
976 unsafe { core::ptr::write(data, FlickableDataBox::default()) };
977}
978
979#[cfg(feature = "ffi")]
982#[unsafe(no_mangle)]
983pub unsafe extern "C" fn slint_flickable_data_free(data: *mut FlickableDataBox) {
984 unsafe {
985 core::ptr::drop_in_place(data);
986 }
987}