1use super::velocity_tracker::VelocityTracker;
2use super::{GestureRecognizer, GestureRecognizerImpl};
3use crate::input::{
4 IPointer, PointerEventArgs, PointerPressedEventArgs, PointerReleasedEventArgs, PointerType,
5 ScrollGestureEndedEventArgs, ScrollGestureEventArgs, ScrollGestureInertiaStartingEventArgs,
6};
7use crate::media::MediaContext;
8use crate::platform::IPlatformSettings;
9use crate::threading::{Dispatcher, DispatcherPriority};
10use crate::{
11 ferro_class, ferro_impl_classes, ferro_property, instantiate, DirectProperty, DirectPropertyMetadata,
12 FerroLocator, FerroObjectImpl, FerroProperty, LocatorExtensions, Point, Ref, Size, StyledElementImpl, Vector,
13};
14use std::cell::{Cell, RefCell};
15use std::rc::Rc;
16use std::time::{Duration, Instant};
17
18fn default_scroll_start_distance() -> i32 {
19 let height = FerroLocator::current()
20 .get_service::<dyn IPlatformSettings>()
21 .map_or(10.0, |settings| settings.get_tap_size(PointerType::Touch).height);
22 (height / 2.0) as i32
23}
24
25#[repr(C)]
28pub struct ScrollGestureRecognizer {
29 base: GestureRecognizer,
30 can_horizontally_scroll: Cell<bool>,
31 can_vertically_scroll: Cell<bool>,
32 is_scroll_inertia_enabled: Cell<bool>,
33 offset: Cell<Option<Vector>>,
34 viewport: Cell<Option<Size>>,
35 extent: Cell<Option<Size>>,
36 scroll_start_distance: Cell<i32>,
37
38 scrolling: Cell<bool>,
39 tracked_root_point: Cell<Point>,
40 tracking: RefCell<Option<Rc<dyn IPointer>>>,
41 stop_watch: Cell<Option<Instant>>,
44 gesture_id: Cell<i32>,
45 pointer_pressed_point: Cell<Point>,
46 velocity_tracker: RefCell<Option<VelocityTracker>>,
47
48 inertia: Cell<Option<Vector>>,
50 last_move_timestamp: Cell<Option<u64>>,
51 last_time: Cell<Duration>,
52 inertia_start_time: Cell<Duration>,
53 current_inertia_gesture_id: Cell<i32>,
54 delta: Cell<Point>,
55}
56
57ferro_class!(ScrollGestureRecognizer: GestureRecognizer);
58crate::ferro_class_info!(ScrollGestureRecognizer { new: ScrollGestureRecognizer::new });
59ferro_impl_classes!(ScrollGestureRecognizer: FerroObjectImpl, StyledElementImpl);
60
61impl GestureRecognizerImpl for ScrollGestureRecognizer {
62 fn pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
63 let point = e.get_current_point(None);
64
65 if matches!(e.pointer().type_(), PointerType::Touch | PointerType::Pen)
66 && point.properties.is_left_button_pressed
67 {
68 this.end_gesture();
69 drop(this.tracking.replace(Some(e.pointer().clone())));
70 this.inertia.set(None);
71 this.gesture_id.set(ScrollGestureEventArgs::get_next_free_id());
72 this.tracked_root_point.set(point.position);
73 this.pointer_pressed_point.set(point.position);
74
75 let mut tracker = VelocityTracker::new();
76 tracker.add_position(Duration::from_millis(e.timestamp()), Vector::default());
77 *this.velocity_tracker.borrow_mut() = Some(tracker);
78 }
79 }
80
81 fn pointer_moved(this: &Self, e: &PointerEventArgs) {
82 if !this.is_tracking(e.pointer()) {
83 return;
84 }
85
86 let root_point = e.get_position(None);
87 let start_distance = this.scroll_start_distance() as f64;
88
89 if !this.scrolling.get() {
90 let tracked = this.tracked_root_point.get();
91 let (offset, viewport, extent) = (this.offset(), this.viewport(), this.extent());
92
93 let at_end = |offset: Option<f64>, viewport: Option<f64>, extent: Option<f64>| match (offset, viewport, extent)
96 {
97 (Some(offset), Some(viewport), Some(extent)) => offset + viewport - extent == 0.0,
98 _ => false,
99 };
100
101 if this.can_vertically_scroll() {
102 let delta = tracked.y - root_point.y;
103
104 if offset.map(|o| o.y) == Some(0.0) && delta < 0.0 {
105 return;
106 }
107
108 if at_end(offset.map(|o| o.y), viewport.map(|v| v.height), extent.map(|x| x.height)) && delta > 0.0 {
109 return;
110 }
111
112 if delta.abs() > start_distance {
113 this.scrolling.set(true);
114 }
115 }
116
117 if this.can_horizontally_scroll() {
118 let delta = tracked.x - root_point.x;
119
120 if offset.map(|o| o.x) == Some(0.0) && delta < 0.0 {
121 return;
122 }
123
124 if at_end(offset.map(|o| o.x), viewport.map(|v| v.width), extent.map(|x| x.width)) && delta > 0.0 {
125 return;
126 }
127
128 if delta.abs() > start_distance {
129 this.scrolling.set(true);
130 }
131 }
132
133 if this.scrolling.get() {
134 this.tracked_root_point.set(Point::new(
138 tracked.x - if tracked.x >= root_point.x { start_distance } else { -start_distance },
139 tracked.y - if tracked.y >= root_point.y { start_distance } else { -start_distance },
140 ));
141 }
142 }
143
144 if this.scrolling.get() {
145 let vector = Vector::from(this.tracked_root_point.get() - root_point);
146
147 let old_delta = this.delta.get();
148 let pressed = this.pointer_pressed_point.get();
149 let delta = Point::new(pressed.x - root_point.x, pressed.y - root_point.y);
150 this.delta.set(delta);
151
152 if old_delta == delta {
153 return;
154 }
155
156 if let Some(tracker) = this.velocity_tracker.borrow_mut().as_mut() {
157 tracker.add_position(Duration::from_millis(e.timestamp()), Vector::new(delta.x, delta.y));
158 }
159
160 this.last_move_timestamp.set(Some(e.timestamp()));
161
162 let scroll_event_args = ScrollGestureEventArgs::new(this.gesture_id.get(), vector);
163 if let Some(target) = this.target() {
164 target.raise_event(&scroll_event_args);
165 }
166
167 this.tracked_root_point.set(root_point);
168
169 e.set_handled(scroll_event_args.handled());
170
171 if e.handled() {
172 this.capture(e.pointer());
173 }
174 }
175 }
176
177 fn pointer_capture_lost(this: &Self, pointer: &Rc<dyn IPointer>) {
178 if this.is_tracking(pointer) {
179 this.end_gesture();
180 }
181 }
182
183 fn pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
184 if !(this.is_tracking(e.pointer()) && this.scrolling.get()) {
185 return;
186 }
187
188 let inertia = this
189 .velocity_tracker
190 .borrow()
191 .as_ref()
192 .map_or(Vector::default(), |tracker| {
193 tracker.get_fling_velocity(Duration::from_millis(e.timestamp())).pixels_per_second
194 });
195 this.inertia.set(Some(inertia));
196
197 e.set_handled(true);
198
199 let last_move = this.last_move_timestamp.get();
200 let too_late = last_move.is_some_and(|last| e.timestamp().wrapping_sub(last) > 200);
201
202 if inertia == Vector::default()
203 || e.timestamp() == 0
204 || last_move == Some(0)
205 || too_late
206 || !this.is_scroll_inertia_enabled()
207 {
208 this.end_gesture();
209 } else {
210 drop(this.tracking.replace(None));
211 let stop_watch = Instant::now();
217 this.stop_watch.set(Some(stop_watch));
218 this.last_time.set(stop_watch.elapsed());
219 this.inertia_start_time.set(this.last_time.get());
220 this.current_inertia_gesture_id.set(this.gesture_id.get());
221
222 if let Some(target) = this.target() {
223 target.raise_event(&ScrollGestureInertiaStartingEventArgs::new(this.gesture_id.get(), inertia));
224 }
225
226 this.request_animation_frame();
227 }
228 }
229}
230
231impl ScrollGestureRecognizer {
232 pub const INERTIAL_SCROLL_SPEED_END: f64 = 5.0;
235
236 pub const INERTIAL_RESISTANCE: f64 = 0.15;
238
239}
240
241crate::ferro_properties! { impl ScrollGestureRecognizer {
242 ferro_property!(
243 pub fn can_horizontally_scroll_property() -> DirectProperty<ScrollGestureRecognizer, bool> {
245 FerroProperty::register_direct::<ScrollGestureRecognizer, _>(
246 "CanHorizontallyScroll",
247 |o| o.can_horizontally_scroll(),
248 Some(|o, v| o.set_can_horizontally_scroll(v)),
249 false,
250 )
251 }
252 );
253
254 ferro_property!(
255 pub fn can_vertically_scroll_property() -> DirectProperty<ScrollGestureRecognizer, bool> {
257 FerroProperty::register_direct::<ScrollGestureRecognizer, _>(
258 "CanVerticallyScroll",
259 |o| o.can_vertically_scroll(),
260 Some(|o, v| o.set_can_vertically_scroll(v)),
261 false,
262 )
263 }
264 );
265
266 ferro_property!(
267 pub fn is_scroll_inertia_enabled_property() -> DirectProperty<ScrollGestureRecognizer, bool> {
269 FerroProperty::register_direct::<ScrollGestureRecognizer, _>(
270 "IsScrollInertiaEnabled",
271 |o| o.is_scroll_inertia_enabled(),
272 Some(|o, v| o.set_is_scroll_inertia_enabled(v)),
273 false,
274 )
275 }
276 );
277
278 ferro_property!(
279 pub fn scroll_start_distance_property() -> DirectProperty<ScrollGestureRecognizer, i32> {
281 FerroProperty::register_direct_with::<ScrollGestureRecognizer, _>(
282 "ScrollStartDistance",
283 |o| o.scroll_start_distance(),
284 Some(|o, v| o.set_scroll_start_distance(v)),
285 DirectPropertyMetadata::new(Some(default_scroll_start_distance())),
286 )
287 }
288 );
289
290 ferro_property!(
291 pub fn offset_property() -> DirectProperty<ScrollGestureRecognizer, Option<Vector>> {
293 FerroProperty::register_direct::<ScrollGestureRecognizer, _>(
294 "Offset",
295 |o| o.offset(),
296 Some(|o, v| o.set_offset(v)),
297 None,
298 )
299 }
300 );
301
302 ferro_property!(
303 pub fn extent_property() -> DirectProperty<ScrollGestureRecognizer, Option<Size>> {
305 FerroProperty::register_direct::<ScrollGestureRecognizer, _>(
306 "Extent",
307 |o| o.extent(),
308 Some(|o, v| o.set_extent(v)),
309 None,
310 )
311 }
312 );
313
314 ferro_property!(
315 pub fn viewport_property() -> DirectProperty<ScrollGestureRecognizer, Option<Size>> {
317 FerroProperty::register_direct::<ScrollGestureRecognizer, _>(
318 "Viewport",
319 |o| o.viewport(),
320 Some(|o, v| o.set_viewport(v)),
321 None,
322 )
323 }
324 );
325} }
326
327impl ScrollGestureRecognizer {
328 pub fn new() -> Ref<Self> {
329 instantiate(Self {
330 base: GestureRecognizer::construct(),
331 can_horizontally_scroll: Cell::new(false),
332 can_vertically_scroll: Cell::new(false),
333 is_scroll_inertia_enabled: Cell::new(false),
334 offset: Cell::new(None),
335 viewport: Cell::new(None),
336 extent: Cell::new(None),
337 scroll_start_distance: Cell::new(default_scroll_start_distance()),
338 scrolling: Cell::new(false),
339 tracked_root_point: Cell::new(Point::default()),
340 tracking: RefCell::new(None),
341 stop_watch: Cell::new(None),
342 gesture_id: Cell::new(0),
343 pointer_pressed_point: Cell::new(Point::default()),
344 velocity_tracker: RefCell::new(None),
345 inertia: Cell::new(None),
346 last_move_timestamp: Cell::new(None),
347 last_time: Cell::new(Duration::ZERO),
348 inertia_start_time: Cell::new(Duration::ZERO),
349 current_inertia_gesture_id: Cell::new(0),
350 delta: Cell::new(Point::default()),
351 })
352 }
353
354 pub fn can_horizontally_scroll(&self) -> bool {
356 self.can_horizontally_scroll.get()
357 }
358
359 pub fn set_can_horizontally_scroll(&self, value: bool) {
360 self.set_and_raise_cell(Self::can_horizontally_scroll_property(), &self.can_horizontally_scroll, value);
361 }
362
363 pub fn can_vertically_scroll(&self) -> bool {
365 self.can_vertically_scroll.get()
366 }
367
368 pub fn set_can_vertically_scroll(&self, value: bool) {
369 self.set_and_raise_cell(Self::can_vertically_scroll_property(), &self.can_vertically_scroll, value);
370 }
371
372 pub fn is_scroll_inertia_enabled(&self) -> bool {
374 self.is_scroll_inertia_enabled.get()
375 }
376
377 pub fn set_is_scroll_inertia_enabled(&self, value: bool) {
378 self.set_and_raise_cell(Self::is_scroll_inertia_enabled_property(), &self.is_scroll_inertia_enabled, value);
379 }
380
381 pub fn scroll_start_distance(&self) -> i32 {
383 self.scroll_start_distance.get()
384 }
385
386 pub fn set_scroll_start_distance(&self, value: i32) {
387 self.set_and_raise_cell(Self::scroll_start_distance_property(), &self.scroll_start_distance, value);
388 }
389
390 pub fn extent(&self) -> Option<Size> {
392 self.extent.get()
393 }
394
395 pub fn set_extent(&self, value: Option<Size>) {
396 self.set_and_raise_cell(Self::extent_property(), &self.extent, value);
397 }
398
399 pub fn offset(&self) -> Option<Vector> {
401 self.offset.get()
402 }
403
404 pub fn set_offset(&self, value: Option<Vector>) {
405 self.set_and_raise_cell(Self::offset_property(), &self.offset, value);
406 }
407
408 pub fn viewport(&self) -> Option<Size> {
410 self.viewport.get()
411 }
412
413 pub fn set_viewport(&self, value: Option<Size>) {
414 self.set_and_raise_cell(Self::viewport_property(), &self.viewport, value);
415 }
416
417 fn is_tracking(&self, pointer: &Rc<dyn IPointer>) -> bool {
418 self.tracking.borrow().as_ref().is_some_and(|t| std::ptr::addr_eq(Rc::as_ptr(t), Rc::as_ptr(pointer)))
419 }
420
421 fn end_gesture(&self) {
422 drop(self.tracking.replace(None));
423 if self.scrolling.get() {
424 self.stop_watch.set(None);
425 self.inertia.set(Some(Vector::default()));
426 self.delta.set(Point::default());
427 self.scrolling.set(false);
428 drop(self.velocity_tracker.replace(None));
429 if let Some(target) = self.target() {
430 target.raise_event(&ScrollGestureEndedEventArgs::new(self.gesture_id.get()));
431 }
432 self.gesture_id.set(0);
433 self.last_move_timestamp.set(None);
434 }
435 }
436
437 fn request_animation_frame(&self) {
438 let weak = self.to_ref().downgrade();
441 MediaContext::instance().request_animation_frame(move |_| {
442 if let Some(this) = weak.upgrade() {
443 this.on_animation_requested();
444 }
445 });
446 }
447
448 fn on_animation_requested(&self) {
449 let weak = self.to_ref().downgrade();
453 drop(Dispatcher::current_dispatcher().invoke_async_local_with_priority(
454 move || {
455 if let Some(this) = weak.upgrade() {
456 this.inertia_tick();
457 }
458 },
459 DispatcherPriority::INPUT,
460 ));
461 }
462
463 fn inertia_tick(&self) {
464 let Some(inertia) = self.inertia.get() else { return };
466 let Some(stop_watch) = self.stop_watch.get() else { return };
467 if self.gesture_id.get() != self.current_inertia_gesture_id.get() {
468 return;
469 }
470
471 let time_span = stop_watch.elapsed();
472 let elapsed_since_last_tick = time_span.saturating_sub(self.last_time.get());
473 self.last_time.set(time_span);
474
475 let speed = inertia
476 * Self::INERTIAL_RESISTANCE.powf(time_span.saturating_sub(self.inertia_start_time.get()).as_secs_f64());
477 let distance = speed * elapsed_since_last_tick.as_secs_f64();
478 let scroll_gesture_event_args = ScrollGestureEventArgs::new(self.gesture_id.get(), distance);
479 if let Some(target) = self.target() {
480 target.raise_event(&scroll_gesture_event_args);
481 }
482
483 if !scroll_gesture_event_args.handled() || scroll_gesture_event_args.should_end_scroll_gesture() {
484 self.end_gesture();
485 return;
486 }
487
488 let (vertical, horizontal) = (self.can_vertically_scroll(), self.can_horizontally_scroll());
491 if vertical
492 && horizontal
493 && speed.x.abs() < Self::INERTIAL_SCROLL_SPEED_END
494 && speed.y.abs() <= Self::INERTIAL_SCROLL_SPEED_END
495 {
496 } else if vertical && speed.y.abs() <= Self::INERTIAL_SCROLL_SPEED_END {
498 self.end_gesture();
499 return;
500 } else if horizontal && speed.x.abs() < Self::INERTIAL_SCROLL_SPEED_END {
501 self.end_gesture();
502 return;
503 }
504
505 self.request_animation_frame();
507 }
508}