1use super::{
10 Item, ItemConsts, ItemRc, ItemRendererRef, KeyEventResult, PointerEventButton, RenderingResult,
11 VoidArg,
12};
13use crate::animations::Instant;
14use crate::animations::physics_simulation::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 viewport_x: Property<LogicalLength>,
63 pub viewport_y: Property<LogicalLength>,
64 pub viewport_width: Property<LogicalLength>,
65 pub viewport_height: Property<LogicalLength>,
66
67 pub interactive: Property<bool>,
68
69 pub flicked: Callback<VoidArg>,
70
71 data: FlickableDataBox,
72
73 pub cached_rendering_data: CachedRenderingData,
75}
76
77impl Item for Flickable {
78 fn init(self: Pin<&Self>, self_rc: &ItemRc) {
79 self.data.in_bound_change_handler.init_delayed(
80 self_rc.downgrade(),
81 |self_weak| {
83 let Some(flick_rc) = self_weak.upgrade() else {
84 return (false, false);
85 };
86 let Some(flick) = flick_rc.downcast::<Flickable>() else {
87 return (false, false);
88 };
89 let flick = flick.as_pin_ref();
90 let geo = Self::geometry_without_virtual_keyboard(&flick_rc);
91
92 let zero = LogicalLength::zero();
93 let vpx = flick.viewport_x();
94 let vpy = flick.viewport_y();
95 let x_out_of_bounds =
96 vpx > zero || vpx < (geo.width_length() - flick.viewport_width()).min(zero);
97 let y_out_of_bounds =
98 vpy > zero || vpy < (geo.height_length() - flick.viewport_height()).min(zero);
99
100 (x_out_of_bounds, y_out_of_bounds)
101 },
102 |self_weak, (x_out_of_bounds, y_out_of_bounds)| {
104 let Some(flick_rc) = self_weak.upgrade() else { return };
105 let Some(flick) = flick_rc.downcast::<Flickable>() else { return };
106 let flick = flick.as_pin_ref();
107 let vpx = flick.viewport_x();
108 let vpy = flick.viewport_y();
109 let p = ensure_in_bound(flick, LogicalPoint::from_lengths(vpx, vpy), &flick_rc);
110
111 let x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick);
112 if *x_out_of_bounds && !x.has_binding() {
113 x.set(p.x_length());
114 }
115
116 let y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick);
117 if *y_out_of_bounds && !y.has_binding() {
118 y.set(p.y_length());
119 }
120 },
121 );
122 }
123
124 fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
125
126 fn layout_info(
127 self: Pin<&Self>,
128 _orientation: Orientation,
129 _cross_axis_constraint: Coord,
130 _window_adapter: &Rc<dyn WindowAdapter>,
131 _self_rc: &ItemRc,
132 ) -> LayoutInfo {
133 LayoutInfo { stretch: 1., ..LayoutInfo::default() }
134 }
135
136 fn input_event_filter_before_children(
137 self: Pin<&Self>,
138 event: &MouseEvent,
139 window_adapter: &Rc<dyn WindowAdapter>,
140 self_rc: &ItemRc,
141 _: &mut super::MouseCursor,
142 ) -> InputEventFilterResult {
143 if let Some(pos) = event.position() {
144 let geometry = Self::geometry_without_virtual_keyboard(self_rc);
145
146 if (pos.x < 0 as _
147 || pos.y < 0 as _
148 || pos.x_length() > geometry.width_length()
149 || pos.y_length() > geometry.height_length())
150 && self.data.inner.borrow().pressed_mouse_state.is_none()
151 {
152 return InputEventFilterResult::Intercept;
153 }
154 }
155 if !self.interactive() && !matches!(event, MouseEvent::Wheel { .. }) {
156 return InputEventFilterResult::ForwardAndIgnore;
157 }
158 self.data.handle_mouse_filter(self, event, window_adapter, self_rc)
159 }
160
161 fn input_event(
162 self: Pin<&Self>,
163 event: &MouseEvent,
164 window_adapter: &Rc<dyn WindowAdapter>,
165 self_rc: &ItemRc,
166 _: &mut super::MouseCursor,
167 ) -> InputEventResult {
168 if !self.interactive() && !matches!(event, MouseEvent::Wheel { .. }) {
169 return InputEventResult::EventIgnored;
170 }
171 if let Some(pos) = event.position() {
172 let geometry = Self::geometry_without_virtual_keyboard(self_rc);
173 if matches!(event, MouseEvent::Wheel { .. } | MouseEvent::Pressed { .. })
174 && (pos.x < 0 as _
175 || pos.y < 0 as _
176 || pos.x_length() > geometry.width_length()
177 || pos.y_length() > geometry.height_length())
178 {
179 return InputEventResult::EventIgnored;
180 }
181 }
182
183 self.data.handle_mouse(self, event, window_adapter, self_rc)
184 }
185
186 fn capture_key_event(
187 self: Pin<&Self>,
188 _: &InternalKeyEvent,
189 _window_adapter: &Rc<dyn WindowAdapter>,
190 _self_rc: &ItemRc,
191 ) -> KeyEventResult {
192 KeyEventResult::EventIgnored
193 }
194
195 fn key_event(
196 self: Pin<&Self>,
197 _: &InternalKeyEvent,
198 _window_adapter: &Rc<dyn WindowAdapter>,
199 _self_rc: &ItemRc,
200 ) -> KeyEventResult {
201 KeyEventResult::EventIgnored
202 }
203
204 fn focus_event(
205 self: Pin<&Self>,
206 _: &FocusEvent,
207 _window_adapter: &Rc<dyn WindowAdapter>,
208 _self_rc: &ItemRc,
209 ) -> FocusEventResult {
210 FocusEventResult::FocusIgnored
211 }
212
213 fn render(
214 self: Pin<&Self>,
215 backend: &mut ItemRendererRef,
216 _self_rc: &ItemRc,
217 size: LogicalSize,
218 ) -> RenderingResult {
219 (*backend).combine_clip(
220 LogicalRect::new(LogicalPoint::default(), size),
221 LogicalBorderRadius::zero(),
222 LogicalLength::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 choose_min_move(
248 current_view_start: Coord, view_len: Coord, content_len: Coord, points: impl Iterator<Item = Coord>,
252 ) -> Coord {
253 let zero = 0 as Coord;
256 let mut lower = Coord::MIN;
257 let mut upper = Coord::MAX;
258
259 for p in points {
260 lower = lower.max(p - (current_view_start + view_len));
261 upper = upper.min(p - current_view_start);
262 }
263
264 if lower > upper {
265 return zero;
268 }
269
270 let max_scroll = (content_len - view_len).max(zero);
272 let tmin = -current_view_start; let tmax = max_scroll - current_view_start; let i_min = lower.max(tmin);
276 let i_max = upper.min(tmax);
277
278 if i_min <= i_max {
279 if zero < i_min {
280 i_min
281 } else if zero > i_max {
282 i_max
283 } else {
284 zero
285 }
286 } else if tmax < lower {
289 tmax
290 } else {
291 tmin
292 }
293 }
294
295 pub(crate) fn reveal_points(self: Pin<&Self>, self_rc: &ItemRc, pts: &[LogicalPoint]) {
298 if pts.is_empty() {
299 return;
300 }
301
302 let geo = Self::geometry_without_virtual_keyboard(self_rc);
304
305 let vw = Self::FIELD_OFFSETS.viewport_width().apply_pin(self).get().0;
307 let vh = Self::FIELD_OFFSETS.viewport_height().apply_pin(self).get().0;
308 let vx = -Self::FIELD_OFFSETS.viewport_x().apply_pin(self).get().0;
309 let vy = -Self::FIELD_OFFSETS.viewport_y().apply_pin(self).get().0;
310
311 let tx = Self::choose_min_move(vx, geo.width(), vw, pts.iter().map(|p| p.x));
313 let ty = Self::choose_min_move(vy, geo.height(), vh, pts.iter().map(|p| p.y));
314
315 let new_vx = vx + tx;
316 let new_vy = vy + ty;
317
318 Self::FIELD_OFFSETS.viewport_x().apply_pin(self).set(euclid::Length::new(-new_vx));
319 Self::FIELD_OFFSETS.viewport_y().apply_pin(self).set(euclid::Length::new(-new_vy));
320 }
321
322 fn geometry_without_virtual_keyboard(self_rc: &ItemRc) -> LogicalRect {
323 let mut geometry = self_rc.geometry();
324
325 if let Some(keyboard_rect) = self_rc.window_adapter().and_then(|window_adapter| {
327 window_adapter.window().virtual_keyboard(crate::InternalToken)
328 }) {
329 let keyboard_pos = keyboard_rect.0;
330
331 let self_in_window_coordinates = self_rc.map_to_native_window(geometry.origin);
332 if (keyboard_pos.y as Coord) < (self_in_window_coordinates.y + geometry.height()) {
333 geometry.size.height = keyboard_pos.y as Coord - self_in_window_coordinates.y;
335 }
336 }
337 geometry
338 }
339}
340
341#[repr(C)]
342pub struct FlickableDataBox(core::ptr::NonNull<FlickableData>);
344
345impl Default for FlickableDataBox {
346 fn default() -> Self {
347 FlickableDataBox(Box::leak(Box::<FlickableData>::default()).into())
348 }
349}
350impl Drop for FlickableDataBox {
351 fn drop(&mut self) {
352 drop(unsafe { Box::from_raw(self.0.as_ptr()) });
354 }
355}
356
357impl core::ops::Deref for FlickableDataBox {
358 type Target = FlickableData;
359 fn deref(&self) -> &Self::Target {
360 unsafe { self.0.as_ref() }
362 }
363}
364
365pub(super) const DISTANCE_THRESHOLD: LogicalLength = LogicalLength::new(8 as _);
367pub(super) const DURATION_THRESHOLD: Duration = Duration::from_millis(500);
369pub(super) const FORWARD_DELAY: Duration = Duration::from_millis(100);
371pub(super) const SCROLL_FILTER_DURATION: Duration = Duration::from_millis(800);
377pub(super) const SHORT_SCROLL_FILTER_DURATION: Duration =
379 Duration::from_millis(SCROLL_FILTER_DURATION.as_millis() as u64 / 2);
380pub(super) const SCROLL_FILTER_DISTANCE_SQUARED: LogicalLength = LogicalLength::new(4 as _);
382
383#[derive(Debug, PartialEq, Eq, Clone, Copy)]
384enum CaptureEvents {
385 MouseOrTouchScreen,
386 MouseWheel,
387}
388
389#[derive(Default)]
390struct FlickableDataInner {
391 pressed_mouse_state: Option<(Instant, LogicalPoint)>,
395 last_mouse_position: LogicalPoint,
399 capture_events: Option<CaptureEvents>,
401 last_scroll_event: Option<(Instant, LogicalPoint)>,
407
408 velocity_rb: VelocityRingBuffer<5>,
411
412 running_animation: Option<(Instant, [Option<ConstantDecelerationParameters>; 2])>,
416}
417
418impl FlickableDataInner {
419 fn should_capture_scroll(&self, timeout: Duration, position: LogicalPoint) -> bool {
420 self.last_scroll_event.is_some_and(|(last_time, last_position)| {
421 crate::animations::current_tick() - last_time < timeout
423 && LogicalLength::new((last_position - position).square_length().abs())
424 < SCROLL_FILTER_DISTANCE_SQUARED
425 })
426 }
427
428 #[allow(clippy::nonminimal_bool)] fn is_allowed_scroll_direction(
431 flick: Pin<&Flickable>,
432 delta: LogicalVector,
433 flick_rc: &ItemRc,
434 ) -> bool {
435 let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
436
437 (delta.y != 0 as Coord && flick.viewport_height() > geo.height_length())
438 || (delta.x != 0 as Coord && flick.viewport_width() > geo.width_length())
439 }
440
441 fn process_wheel_event(
442 &mut self,
443 flick: Pin<&Flickable>,
444 mut delta: LogicalVector,
445 position: LogicalPoint,
446 phase: TouchPhase,
447 flick_rc: &ItemRc,
448 ) -> InputEventResult {
449 if phase != TouchPhase::Started
450 && delta != LogicalVector::default()
451 && !Self::is_allowed_scroll_direction(flick, delta, flick_rc)
452 {
453 self.capture_events = None;
455 self.last_scroll_event = None;
456 self.running_animation = None;
457 self.velocity_rb = VelocityRingBuffer::default();
458 return InputEventResult::EventIgnored;
459 }
460
461 let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick);
462 let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick);
463 let current_pos = LogicalPoint::from_lengths(viewport_x.get(), viewport_y.get());
464
465 if self.capture_events.is_none()
466 && matches!(phase, TouchPhase::Moved)
467 && let Some((start_time, [x_simulation, y_simulation])) = &self.running_animation
468 {
469 let animation_duration = crate::animations::current_tick().duration_since(*start_time);
471
472 if let Some(x_simulation) = x_simulation {
473 delta.x += x_simulation.remaining_distance(animation_duration);
474 }
475 if let Some(y_simulation) = y_simulation {
476 delta.y += y_simulation.remaining_distance(animation_duration);
477 }
478 }
479
480 let new_pos = ensure_in_bound(flick, current_pos + delta, flick_rc);
481 delta = new_pos - current_pos;
482
483 if phase != TouchPhase::Ended {
484 viewport_x.remove_binding();
485 viewport_y.remove_binding();
486 self.running_animation = None;
487 }
488
489 match phase {
490 TouchPhase::Cancelled => {
491 viewport_x.set(new_pos.x_length());
492 viewport_y.set(new_pos.y_length());
493 self.last_scroll_event = Some((crate::animations::current_tick(), position));
494 }
495 TouchPhase::Started => {
496 self.velocity_rb = VelocityRingBuffer::default();
497 self.capture_events = Some(CaptureEvents::MouseWheel);
498 self.last_scroll_event = Some((crate::animations::current_tick(), position));
499 }
500 TouchPhase::Moved => {
501 if self.capture_events.is_some_and(|capture| capture == CaptureEvents::MouseWheel) {
502 self.velocity_rb.push(crate::animations::current_tick(), new_pos - current_pos);
504 viewport_x.set(new_pos.x_length());
505 viewport_y.set(new_pos.y_length());
506 } else {
507 let [limit_x, limit_y] = Self::flick_limits(flick_rc, delta);
516
517 let x_simulation = (delta.x != Coord::default()).then(|| {
518 let simulation = ConstantDecelerationParameters::new_with_distance(
519 delta.x as f32,
520 WHEEL_SCROLL_DURATION.as_secs_f32(),
521 );
522 viewport_x.set_physic_animation_value(limit_x, simulation.clone());
523 simulation
524 });
525
526 let y_simulation = (delta.y != Coord::default()).then(|| {
527 let simulation = ConstantDecelerationParameters::new_with_distance(
528 delta.y as f32,
529 WHEEL_SCROLL_DURATION.as_secs_f32(),
530 );
531 viewport_y.set_physic_animation_value(limit_y, simulation.clone());
532 simulation
533 });
534
535 if delta.x != 0 as Coord || delta.y != 0 as Coord {
536 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
537 }
538
539 self.running_animation =
540 Some((crate::animations::current_tick(), [x_simulation, y_simulation]));
541 }
542 self.last_scroll_event = Some((crate::animations::current_tick(), position));
543 }
544 TouchPhase::Ended => {
545 if self.capture_events.is_some_and(|capture| capture == CaptureEvents::MouseWheel) {
546 self.animate(flick, flick_rc);
547 }
548 self.capture_events = None;
549 return if self.should_capture_scroll(SHORT_SCROLL_FILTER_DURATION, position) {
550 InputEventResult::EventAccepted
551 } else {
552 InputEventResult::EventIgnored
553 };
554 }
555 }
556
557 let flicked = current_pos.x_length() != new_pos.x_length()
558 || current_pos.y_length() != new_pos.y_length();
559 if flicked {
560 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
561 InputEventResult::EventAccepted
562 } else if self.should_capture_scroll(SHORT_SCROLL_FILTER_DURATION, position) {
563 InputEventResult::EventAccepted
566 } else {
567 self.last_scroll_event = None;
568 InputEventResult::EventIgnored
569 }
570 }
571
572 fn flick_limits(
573 flick_rc: &ItemRc,
574 flick_velocity: LogicalVector,
575 ) -> [Pin<Box<Property<f32>>>; 2] {
576 let flick_weak = flick_rc.downgrade();
577 let calculate_limits = move || {
578 flick_weak
579 .upgrade()
580 .and_then(|flick_rc| {
581 flick_rc.downcast::<Flickable>().map(move |flick| (flick_rc, flick))
582 })
583 .map(|(flick_rc, flick)| {
584 let flick = flick.as_pin_ref();
585 ensure_in_bound(
586 flick,
587 LogicalPoint::from_lengths(
588 -flick.viewport_width(),
589 -flick.viewport_height(),
590 ),
591 &flick_rc,
592 )
593 })
594 };
595
596 let limit_x = if flick_velocity.x < 0 as Coord {
597 let property = Box::pin(Property::new(0.0));
598 property.set_binding({
599 let calculate_limits = calculate_limits.clone();
600 move || calculate_limits().map(|limit| limit.x_length().get() as f32).unwrap_or(0.0)
601 });
602 property
603 } else {
604 Box::pin(Property::new(0.0))
605 };
606
607 let limit_y = if flick_velocity.y < 0 as Coord {
608 let property = Box::pin(Property::new(0.0));
609 property.set_binding(move || {
610 calculate_limits().map(|limit| limit.y_length().get() as f32).unwrap_or(0.0)
611 });
612 property
613 } else {
614 Box::pin(Property::new(0.0))
615 };
616
617 [limit_x, limit_y]
618 }
619
620 fn animate(&self, flick: Pin<&Flickable>, flick_rc: &ItemRc) {
621 if let Some(last_time) = self.velocity_rb.last_time() {
622 let mean_velocity = self.velocity_rb.mean_velocity();
623 if self.capture_events.is_some()
624 && mean_velocity.square_length() > 0 as Coord
625 && crate::animations::current_tick().duration_since(last_time) < MAX_DURATION
626 {
627 let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick);
628 let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick);
629
630 let [limit_x, limit_y] = Self::flick_limits(flick_rc, mean_velocity);
631
632 {
633 let simulation =
634 ConstantDecelerationParameters::new(mean_velocity.x as f32, DECELERATION);
635 viewport_x.set_physic_animation_value(limit_x, simulation);
636 }
637
638 {
639 let animation_y =
640 ConstantDecelerationParameters::new(mean_velocity.y as f32, DECELERATION);
641 viewport_y.set_physic_animation_value(limit_y, animation_y);
642 }
643
644 if mean_velocity.x != 0 as Coord || mean_velocity.y != 0 as Coord {
645 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
646 }
647 }
648 }
649 }
650}
651
652#[derive(Default)]
653pub struct FlickableData {
654 inner: RefCell<FlickableDataInner>,
655 in_bound_change_handler: crate::properties::ChangeTracker,
657}
658
659impl FlickableData {
660 fn scroll_delta(
661 window_adapter: &Rc<dyn WindowAdapter>,
662 delta_x: Coord,
663 delta_y: Coord,
664 ) -> LogicalVector {
665 if window_adapter.window().0.context().0.modifiers.get().shift()
666 && !cfg!(target_os = "macos")
667 {
668 LogicalVector::new(delta_y, delta_x)
671 } else {
672 LogicalVector::new(delta_x, delta_y)
673 }
674 }
675
676 fn handle_mouse_filter(
677 &self,
678 flick: Pin<&Flickable>,
679 event: &MouseEvent,
680 window_adapter: &Rc<dyn WindowAdapter>,
681 flick_rc: &ItemRc,
682 ) -> InputEventFilterResult {
683 let mut inner = self.inner.borrow_mut();
684 match event {
685 MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
686 inner.velocity_rb = VelocityRingBuffer::default();
687 inner.pressed_mouse_state = Some((crate::animations::current_tick(), *position));
688 inner.last_mouse_position = *position;
689 let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick);
690 viewport_x.remove_binding(); let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick);
692 viewport_y.remove_binding(); if inner.capture_events.is_some() {
695 InputEventFilterResult::Intercept
696 } else {
697 InputEventFilterResult::DelayForwarding(FORWARD_DELAY.as_millis() as _)
698 }
699 }
700 MouseEvent::Exit | MouseEvent::Released { button: PointerEventButton::Left, .. } => {
701 inner.pressed_mouse_state = None;
702 if inner.capture_events.is_some() {
703 InputEventFilterResult::Intercept
704 } else {
705 InputEventFilterResult::ForwardEvent
706 }
707 }
708 MouseEvent::Moved { position, .. } => {
709 let do_intercept = inner.capture_events.is_some()
710 || inner.pressed_mouse_state.is_some_and(
711 |(pressed_time, pressed_mouse_position)| {
712 let mouse_delta = *position - pressed_mouse_position;
713
714 crate::animations::current_tick() - pressed_time <= DURATION_THRESHOLD
715 && self.should_capture_mouse_direction(mouse_delta, flick, flick_rc)
716 },
717 );
718 if do_intercept {
719 InputEventFilterResult::Intercept
720 } else if inner.pressed_mouse_state.is_some() {
721 InputEventFilterResult::ForwardAndInterceptGrab
722 } else {
723 InputEventFilterResult::ForwardEvent
724 }
725 }
726 MouseEvent::Wheel { position, delta_x, delta_y, phase } => {
727 match phase {
728 TouchPhase::Cancelled => {
729 let delta = Self::scroll_delta(window_adapter, *delta_x, *delta_y);
733 if FlickableDataInner::is_allowed_scroll_direction(flick, delta, flick_rc)
734 && inner.should_capture_scroll(SCROLL_FILTER_DURATION, *position)
735 {
736 InputEventFilterResult::Intercept
737 } else {
738 inner.last_scroll_event = None;
739 InputEventFilterResult::ForwardEvent
740 }
741 }
742 TouchPhase::Started => InputEventFilterResult::Intercept,
743 TouchPhase::Moved => {
744 if inner.capture_events.is_some() {
745 InputEventFilterResult::Intercept
746 } else {
747 let delta = Self::scroll_delta(window_adapter, *delta_x, *delta_y);
750 if FlickableDataInner::is_allowed_scroll_direction(
751 flick, delta, flick_rc,
752 ) && inner.should_capture_scroll(SCROLL_FILTER_DURATION, *position)
753 {
754 InputEventFilterResult::Intercept
755 } else {
756 inner.last_scroll_event = None;
757 InputEventFilterResult::ForwardEvent
758 }
759 }
760 }
761 TouchPhase::Ended => {
762 if inner.capture_events.is_some() {
763 InputEventFilterResult::Intercept
764 } else {
765 InputEventFilterResult::ForwardEvent
766 }
767 }
768 }
769 }
770 MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
772 InputEventFilterResult::ForwardAndIgnore
773 }
774 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
775 InputEventFilterResult::ForwardEvent
776 }
777 MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => {
778 InputEventFilterResult::ForwardAndIgnore
779 }
780 }
781 }
782
783 fn should_capture_mouse_direction(
784 &self,
785 mouse_delta: LogicalVector,
786 flick: Pin<&Flickable>,
787 flick_rc: &ItemRc,
788 ) -> bool {
789 let flickable_geometry = Flickable::geometry_without_virtual_keyboard(flick_rc);
790 let flickable_width = flickable_geometry.width_length();
791 let flickable_height = flickable_geometry.height_length();
792 let viewport_width = flick.viewport_width();
793 let viewport_height = flick.viewport_height();
794 let zero = LogicalLength::zero();
795
796 ((viewport_width > flickable_width || flick.viewport_x() != zero)
799 && abs(mouse_delta.x_length()) > DISTANCE_THRESHOLD)
800 || ((viewport_height > flickable_height || flick.viewport_y() != zero)
801 && abs(mouse_delta.y_length()) > DISTANCE_THRESHOLD)
802 }
803
804 fn handle_mouse(
805 &self,
806 flick: Pin<&Flickable>,
807 event: &MouseEvent,
808 window_adapter: &Rc<dyn WindowAdapter>,
809 flick_rc: &ItemRc,
810 ) -> InputEventResult {
811 let mut inner = self.inner.borrow_mut();
812 match event {
813 MouseEvent::Pressed { .. } => {
814 inner.capture_events = Some(CaptureEvents::MouseOrTouchScreen);
815 InputEventResult::GrabMouse
816 }
817 MouseEvent::Exit | MouseEvent::Released { .. } => {
818 if inner.capture_events.is_some_and(|f| f == CaptureEvents::MouseOrTouchScreen) {
819 let was_capturing = true;
820 inner.animate(flick, flick_rc);
821 inner.capture_events = None;
822 inner.pressed_mouse_state = None;
823 if was_capturing {
824 InputEventResult::EventAccepted
825 } else {
826 InputEventResult::EventIgnored
827 }
828 } else if inner.capture_events.is_none() {
829 inner.pressed_mouse_state = None;
830 InputEventResult::EventIgnored
831 } else {
832 InputEventResult::EventIgnored
833 }
834 }
835 MouseEvent::Moved { position, .. } => {
836 if let Some((_pressed_time, _pressed_mouse_position)) = inner.pressed_mouse_state {
846 let mouse_delta = *position - inner.last_mouse_position;
847 inner.velocity_rb.push(crate::animations::current_tick(), mouse_delta);
848
849 let is_capturing = inner
850 .capture_events
851 .is_some_and(|f| f == CaptureEvents::MouseOrTouchScreen);
852 if is_capturing
853 || self.should_capture_mouse_direction(mouse_delta, flick, flick_rc)
854 {
855 let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x()).apply_pin(flick);
858 let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y()).apply_pin(flick);
859 let current_viewport_position =
860 LogicalPoint::from_lengths(viewport_x.get(), viewport_y.get());
861
862 let new_viewport_position = current_viewport_position + mouse_delta;
868 let new_viewport_position =
869 ensure_in_bound(flick, new_viewport_position, flick_rc);
870
871 viewport_x.set(new_viewport_position.x_length());
872 viewport_y.set(new_viewport_position.y_length());
873 if current_viewport_position != new_viewport_position {
874 (Flickable::FIELD_OFFSETS.flicked()).apply_pin(flick).call(&());
875 }
876
877 inner.last_mouse_position = *position;
894
895 inner.capture_events = Some(CaptureEvents::MouseOrTouchScreen);
896
897 InputEventResult::GrabMouse
898 } else if abs(mouse_delta.x_length()) > DISTANCE_THRESHOLD
899 || abs(mouse_delta.y_length()) > DISTANCE_THRESHOLD
900 {
901 InputEventResult::EventIgnored
903 } else {
904 InputEventResult::EventAccepted
907 }
908 } else {
909 InputEventResult::EventIgnored
910 }
911 }
912 MouseEvent::Wheel { delta_x, delta_y, position, phase } => {
913 let delta = Self::scroll_delta(window_adapter, *delta_x, *delta_y);
914 inner.process_wheel_event(flick, delta, *position, *phase, flick_rc)
915 }
916 MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => {
917 InputEventResult::EventIgnored
918 }
919 MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => InputEventResult::EventIgnored,
920 }
921 }
922}
923
924fn abs(l: LogicalLength) -> LogicalLength {
925 LogicalLength::new(l.get().abs())
926}
927
928fn ensure_in_bound(flick: Pin<&Flickable>, p: LogicalPoint, flick_rc: &ItemRc) -> LogicalPoint {
930 let geo = Flickable::geometry_without_virtual_keyboard(flick_rc);
931 let w = geo.width_length();
932 let h = geo.height_length();
933 let vw = (Flickable::FIELD_OFFSETS.viewport_width()).apply_pin(flick).get();
934 let vh = (Flickable::FIELD_OFFSETS.viewport_height()).apply_pin(flick).get();
935
936 let min = LogicalPoint::from_lengths(w - vw, h - vh);
937 let max = LogicalPoint::default();
938 p.max(min).min(max)
939}
940
941#[cfg(feature = "ffi")]
945#[unsafe(no_mangle)]
946pub unsafe extern "C" fn slint_flickable_data_init(data: *mut FlickableDataBox) {
947 unsafe { core::ptr::write(data, FlickableDataBox::default()) };
948}
949
950#[cfg(feature = "ffi")]
953#[unsafe(no_mangle)]
954pub unsafe extern "C" fn slint_flickable_data_free(data: *mut FlickableDataBox) {
955 unsafe {
956 core::ptr::drop_in_place(data);
957 }
958}