1use super::{inspector_metadata, Modifier, Point, PointerEventKind};
18use crate::current_density;
19use crate::fling_animation::FlingAnimation;
20use crate::fling_animation::MIN_FLING_VELOCITY;
21use crate::schedule_draw_repass;
22use crate::scroll::{ScrollElement, ScrollState};
23use cranpose_core::{current_runtime_handle, NodeId};
24use cranpose_foundation::{
25 velocity_tracker::ASSUME_STOPPED_MS, DelegatableNode, ModifierNode, ModifierNodeElement,
26 NodeCapabilities, NodeState, PointerButton, PointerButtons, VelocityTracker1D, DRAG_THRESHOLD,
27 MAX_FLING_VELOCITY,
28};
29use std::cell::{Cell, RefCell};
30use std::rc::Rc;
31use std::sync::atomic::{AtomicU64, Ordering};
32use web_time::Instant;
33
34#[cfg(feature = "test-helpers")]
39mod test_velocity_tracking {
40 use std::sync::atomic::{AtomicU32, Ordering};
41
42 static LAST_FLING_VELOCITY: AtomicU32 = AtomicU32::new(0);
52
53 pub fn last_fling_velocity() -> f32 {
58 f32::from_bits(LAST_FLING_VELOCITY.load(Ordering::SeqCst))
59 }
60
61 pub fn reset_last_fling_velocity() {
65 LAST_FLING_VELOCITY.store(0.0f32.to_bits(), Ordering::SeqCst);
66 }
67
68 pub(super) fn set_last_fling_velocity(velocity: f32) {
70 LAST_FLING_VELOCITY.store(velocity.to_bits(), Ordering::SeqCst);
71 }
72}
73
74#[cfg(feature = "test-helpers")]
75pub use test_velocity_tracking::{last_fling_velocity, reset_last_fling_velocity};
76
77#[inline]
80fn set_last_fling_velocity(velocity: f32) {
81 #[cfg(feature = "test-helpers")]
82 test_velocity_tracking::set_last_fling_velocity(velocity);
83 #[cfg(not(feature = "test-helpers"))]
84 let _ = velocity; }
86
87struct ScrollGestureState {
93 drag_down_position: Option<Point>,
96
97 last_position: Option<Point>,
100
101 is_dragging: bool,
105
106 velocity_tracker: VelocityTracker1D,
108
109 gesture_start_time: Option<Instant>,
111
112 last_velocity_sample_ms: Option<i64>,
114
115 fling_animation: Option<FlingAnimation>,
117}
118
119impl Default for ScrollGestureState {
120 fn default() -> Self {
121 Self {
122 drag_down_position: None,
123 last_position: None,
124 is_dragging: false,
125 velocity_tracker: VelocityTracker1D::new(),
126 gesture_start_time: None,
127 last_velocity_sample_ms: None,
128 fling_animation: None,
129 }
130 }
131}
132
133#[derive(Clone)]
134struct MotionContextState {
135 inner: Rc<MotionContextStateInner>,
136}
137
138struct MotionContextStateInner {
139 active: Cell<bool>,
140 generation: Cell<u64>,
141 invalidate_callbacks: RefCell<std::collections::HashMap<u64, Box<dyn Fn()>>>,
142 pending_invalidation: Cell<bool>,
143}
144
145impl MotionContextState {
146 fn new() -> Self {
147 Self {
148 inner: Rc::new(MotionContextStateInner {
149 active: Cell::new(false),
150 generation: Cell::new(0),
151 invalidate_callbacks: RefCell::new(std::collections::HashMap::new()),
152 pending_invalidation: Cell::new(false),
153 }),
154 }
155 }
156
157 fn is_active(&self) -> bool {
158 self.inner.active.get()
159 }
160
161 fn set_active(&self, active: bool) {
162 if self.inner.active.replace(active) != active {
163 self.bump_generation();
164 self.invalidate();
165 }
166 }
167
168 fn activate_for_next_frame(&self) {
169 let was_active = self.inner.active.replace(true);
170 let generation = self.bump_generation();
171 if !was_active {
172 self.invalidate();
173 }
174 if let Some(runtime) = current_runtime_handle() {
175 let state = self.clone();
176 let _ = runtime.register_frame_callback(move |_| {
177 state.clear_if_generation(generation);
178 });
179 runtime.schedule();
180 } else {
181 self.clear_if_generation(generation);
182 }
183 }
184
185 fn add_invalidate_callback(&self, callback: Box<dyn Fn()>) -> u64 {
186 static NEXT_CALLBACK_ID: AtomicU64 = AtomicU64::new(1);
187 let id = NEXT_CALLBACK_ID.fetch_add(1, Ordering::Relaxed);
188 self.inner
189 .invalidate_callbacks
190 .borrow_mut()
191 .insert(id, callback);
192 if self.inner.pending_invalidation.replace(false) {
193 if let Some(callback) = self.inner.invalidate_callbacks.borrow().get(&id) {
194 callback();
195 }
196 }
197 id
198 }
199
200 fn remove_invalidate_callback(&self, id: u64) {
201 self.inner.invalidate_callbacks.borrow_mut().remove(&id);
202 }
203
204 fn bump_generation(&self) -> u64 {
205 let next = self.inner.generation.get().wrapping_add(1);
206 self.inner.generation.set(next);
207 next
208 }
209
210 fn clear_if_generation(&self, generation: u64) {
211 if self.inner.generation.get() == generation {
212 self.set_active(false);
213 }
214 }
215
216 fn invalidate(&self) {
217 let callbacks = self.inner.invalidate_callbacks.borrow();
218 if callbacks.is_empty() {
219 self.inner.pending_invalidation.set(true);
220 return;
221 }
222 for callback in callbacks.values() {
223 callback();
224 }
225 }
226}
227
228#[inline]
237fn calculate_total_delta(from: Point, to: Point, is_vertical: bool) -> f32 {
238 if is_vertical {
239 to.y - from.y
240 } else {
241 to.x - from.x
242 }
243}
244
245#[inline]
250fn calculate_incremental_delta(from: Point, to: Point, is_vertical: bool) -> f32 {
251 if is_vertical {
252 to.y - from.y
253 } else {
254 to.x - from.x
255 }
256}
257
258trait ScrollTarget: Clone {
266 fn apply_delta(&self, delta: f32) -> f32;
268
269 fn apply_fling_delta(&self, delta: f32) -> f32;
271
272 fn invalidate(&self);
274
275 fn current_offset(&self) -> f32;
277}
278
279impl ScrollTarget for ScrollState {
280 fn apply_delta(&self, delta: f32) -> f32 {
281 self.dispatch_raw_delta(-delta)
283 }
284
285 fn apply_fling_delta(&self, delta: f32) -> f32 {
286 self.dispatch_raw_delta(delta)
287 }
288
289 fn invalidate(&self) {
290 }
292
293 fn current_offset(&self) -> f32 {
294 self.value()
295 }
296}
297
298impl ScrollTarget for LazyListState {
299 fn apply_delta(&self, delta: f32) -> f32 {
300 self.dispatch_scroll_delta(delta)
304 }
305
306 fn apply_fling_delta(&self, delta: f32) -> f32 {
307 -self.dispatch_scroll_delta(-delta)
308 }
309
310 fn invalidate(&self) {
311 }
316
317 fn current_offset(&self) -> f32 {
318 self.first_visible_item_scroll_offset()
320 }
321}
322
323struct ScrollGestureDetector<S: ScrollTarget> {
329 gesture_state: Rc<RefCell<ScrollGestureState>>,
331
332 scroll_target: S,
334
335 is_vertical: bool,
337
338 reverse_scrolling: bool,
340
341 motion_context: MotionContextState,
343}
344
345impl<S: ScrollTarget + 'static> ScrollGestureDetector<S> {
346 fn new(
348 gesture_state: Rc<RefCell<ScrollGestureState>>,
349 scroll_target: S,
350 is_vertical: bool,
351 reverse_scrolling: bool,
352 motion_context: MotionContextState,
353 ) -> Self {
354 Self {
355 gesture_state,
356 scroll_target,
357 is_vertical,
358 reverse_scrolling,
359 motion_context,
360 }
361 }
362
363 fn on_down(&self, position: Point) -> bool {
372 let mut gs = self.gesture_state.borrow_mut();
373
374 if let Some(fling) = gs.fling_animation.take() {
376 fling.cancel();
377 }
378 self.motion_context.set_active(false);
379
380 gs.drag_down_position = Some(position);
381 gs.last_position = Some(position);
382 gs.is_dragging = false;
383 gs.velocity_tracker.reset();
384 gs.gesture_start_time = Some(Instant::now());
385
386 let pos = if self.is_vertical {
388 position.y
389 } else {
390 position.x
391 };
392 gs.velocity_tracker.add_data_point(0, pos);
393 gs.last_velocity_sample_ms = Some(0);
394
395 false
397 }
398
399 fn on_move(&self, position: Point, buttons: PointerButtons) -> bool {
410 let mut gs = self.gesture_state.borrow_mut();
411
412 if !buttons.contains(PointerButton::Primary) && gs.drag_down_position.is_some() {
414 gs.drag_down_position = None;
415 gs.last_position = None;
416 gs.is_dragging = false;
417 gs.gesture_start_time = None;
418 gs.last_velocity_sample_ms = None;
419 gs.velocity_tracker.reset();
420 self.motion_context.set_active(false);
421 return false;
422 }
423
424 let Some(down_pos) = gs.drag_down_position else {
425 return false;
426 };
427
428 let Some(last_pos) = gs.last_position else {
429 gs.last_position = Some(position);
430 return false;
431 };
432
433 let total_delta = calculate_total_delta(down_pos, position, self.is_vertical);
434 let incremental_delta = calculate_incremental_delta(last_pos, position, self.is_vertical);
435
436 if !gs.is_dragging && total_delta.abs() > DRAG_THRESHOLD {
438 gs.is_dragging = true;
439 self.motion_context.set_active(true);
440 }
441
442 gs.last_position = Some(position);
443
444 if let Some(start_time) = gs.gesture_start_time {
446 let elapsed_ms = start_time.elapsed().as_millis() as i64;
447 let pos = if self.is_vertical {
448 position.y
449 } else {
450 position.x
451 };
452 let sample_ms = match gs.last_velocity_sample_ms {
455 Some(last_sample_ms) => {
456 let mut sample_ms = if elapsed_ms <= last_sample_ms {
457 last_sample_ms + 1
458 } else {
459 elapsed_ms
460 };
461 if sample_ms - last_sample_ms > ASSUME_STOPPED_MS {
463 sample_ms = last_sample_ms + ASSUME_STOPPED_MS;
464 }
465 sample_ms
466 }
467 None => elapsed_ms,
468 };
469 gs.velocity_tracker.add_data_point(sample_ms, pos);
470 gs.last_velocity_sample_ms = Some(sample_ms);
471 }
472
473 if gs.is_dragging {
474 drop(gs); let delta = if self.reverse_scrolling {
476 -incremental_delta
477 } else {
478 incremental_delta
479 };
480 let _ = self.scroll_target.apply_delta(delta);
481 self.scroll_target.invalidate();
482 true } else {
484 false
485 }
486 }
487
488 fn finish_gesture(&self, allow_fling: bool) -> bool {
495 let (was_dragging, velocity, start_fling, existing_fling) = {
496 let mut gs = self.gesture_state.borrow_mut();
497 let was_dragging = gs.is_dragging;
498 let mut velocity = 0.0;
499
500 if allow_fling && was_dragging && gs.gesture_start_time.is_some() {
501 velocity = gs
502 .velocity_tracker
503 .calculate_velocity_with_max(MAX_FLING_VELOCITY);
504 }
505
506 let start_fling = allow_fling && was_dragging && velocity.abs() > MIN_FLING_VELOCITY;
507 let existing_fling = if start_fling {
508 gs.fling_animation.take()
509 } else {
510 None
511 };
512
513 gs.drag_down_position = None;
514 gs.last_position = None;
515 gs.is_dragging = false;
516 gs.gesture_start_time = None;
517 gs.last_velocity_sample_ms = None;
518
519 (was_dragging, velocity, start_fling, existing_fling)
520 };
521
522 if allow_fling && was_dragging {
524 set_last_fling_velocity(velocity);
525 }
526
527 if start_fling {
529 if let Some(old_fling) = existing_fling {
530 old_fling.cancel();
531 }
532
533 if let Some(runtime) = current_runtime_handle() {
535 self.motion_context.set_active(true);
536 let scroll_target = self.scroll_target.clone();
537 let reverse = self.reverse_scrolling;
538 let fling = FlingAnimation::new(runtime);
539 let motion_context = self.motion_context.clone();
540
541 let initial_value = scroll_target.current_offset();
543
544 let adjusted_velocity = if reverse { -velocity } else { velocity };
546 let fling_velocity = -adjusted_velocity;
547
548 let scroll_target_for_fling = scroll_target.clone();
549 let scroll_target_for_end = scroll_target.clone();
550
551 fling.start_fling(
552 initial_value,
553 fling_velocity,
554 current_density(),
555 move |delta| {
556 let consumed = scroll_target_for_fling.apply_fling_delta(delta);
558 scroll_target_for_fling.invalidate();
559 consumed
560 },
561 move || {
562 scroll_target_for_end.invalidate();
564 motion_context.set_active(false);
565 },
566 );
567
568 let mut gs = self.gesture_state.borrow_mut();
569 gs.fling_animation = Some(fling);
570 }
571 } else {
572 self.motion_context.set_active(false);
573 }
574
575 was_dragging
576 }
577
578 fn on_up(&self) -> bool {
585 self.finish_gesture(true)
586 }
587
588 fn on_cancel(&self) -> bool {
592 self.finish_gesture(false)
593 }
594
595 fn on_scroll(&self, axis_delta: f32) -> bool {
599 if axis_delta.abs() <= f32::EPSILON {
600 return false;
601 }
602
603 {
604 let mut gs = self.gesture_state.borrow_mut();
606 if let Some(fling) = gs.fling_animation.take() {
607 fling.cancel();
608 }
609 gs.drag_down_position = None;
610 gs.last_position = None;
611 gs.is_dragging = false;
612 gs.gesture_start_time = None;
613 gs.last_velocity_sample_ms = None;
614 gs.velocity_tracker.reset();
615 }
616
617 self.motion_context.activate_for_next_frame();
618
619 let delta = if self.reverse_scrolling {
620 -axis_delta
621 } else {
622 axis_delta
623 };
624 let consumed = self.scroll_target.apply_delta(delta);
625 if consumed.abs() > 0.001 {
626 self.scroll_target.invalidate();
627 true
628 } else {
629 false
630 }
631 }
632}
633
634pub(crate) struct MotionContextAnimatedNode {
635 state: NodeState,
636 motion_context: MotionContextState,
637 invalidation_callback_id: Option<u64>,
638 node_id: Option<NodeId>,
639}
640
641impl MotionContextAnimatedNode {
642 fn new(motion_context: MotionContextState) -> Self {
643 Self {
644 state: NodeState::new(),
645 motion_context,
646 invalidation_callback_id: None,
647 node_id: None,
648 }
649 }
650
651 pub(crate) fn is_active(&self) -> bool {
652 self.motion_context.is_active()
653 }
654}
655
656pub(crate) struct TranslatedContentContextNode {
657 state: NodeState,
658}
659
660impl TranslatedContentContextNode {
661 fn new() -> Self {
662 Self {
663 state: NodeState::new(),
664 }
665 }
666
667 pub(crate) fn is_active(&self) -> bool {
668 true
669 }
670}
671
672impl DelegatableNode for TranslatedContentContextNode {
673 fn node_state(&self) -> &NodeState {
674 &self.state
675 }
676}
677
678impl ModifierNode for TranslatedContentContextNode {}
679
680impl DelegatableNode for MotionContextAnimatedNode {
681 fn node_state(&self) -> &NodeState {
682 &self.state
683 }
684}
685
686impl ModifierNode for MotionContextAnimatedNode {
687 fn on_attach(&mut self, context: &mut dyn cranpose_foundation::ModifierNodeContext) {
688 let node_id = context.node_id();
689 self.node_id = node_id;
690 if let Some(node_id) = node_id {
691 let callback_id = self
692 .motion_context
693 .add_invalidate_callback(Box::new(move || {
694 schedule_draw_repass(node_id);
695 }));
696 self.invalidation_callback_id = Some(callback_id);
697 }
698 }
699
700 fn on_detach(&mut self) {
701 if let Some(id) = self.invalidation_callback_id.take() {
702 self.motion_context.remove_invalidate_callback(id);
703 }
704 self.node_id = None;
705 }
706}
707
708#[derive(Clone)]
709struct MotionContextAnimatedElement {
710 motion_context: MotionContextState,
711}
712
713impl MotionContextAnimatedElement {
714 fn new(motion_context: MotionContextState) -> Self {
715 Self { motion_context }
716 }
717}
718
719impl std::fmt::Debug for MotionContextAnimatedElement {
720 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721 f.debug_struct("MotionContextAnimatedElement").finish()
722 }
723}
724
725impl PartialEq for MotionContextAnimatedElement {
726 fn eq(&self, other: &Self) -> bool {
727 Rc::ptr_eq(&self.motion_context.inner, &other.motion_context.inner)
728 }
729}
730
731impl Eq for MotionContextAnimatedElement {}
732
733impl std::hash::Hash for MotionContextAnimatedElement {
734 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
735 (Rc::as_ptr(&self.motion_context.inner) as usize).hash(state);
736 }
737}
738
739impl ModifierNodeElement for MotionContextAnimatedElement {
740 type Node = MotionContextAnimatedNode;
741
742 fn create(&self) -> Self::Node {
743 MotionContextAnimatedNode::new(self.motion_context.clone())
744 }
745
746 fn update(&self, node: &mut Self::Node) {
747 if Rc::ptr_eq(&node.motion_context.inner, &self.motion_context.inner) {
748 return;
749 }
750 if let Some(id) = node.invalidation_callback_id.take() {
751 node.motion_context.remove_invalidate_callback(id);
752 }
753 node.motion_context = self.motion_context.clone();
754 if let Some(node_id) = node.node_id {
755 let callback_id = node
756 .motion_context
757 .add_invalidate_callback(Box::new(move || {
758 schedule_draw_repass(node_id);
759 }));
760 node.invalidation_callback_id = Some(callback_id);
761 }
762 }
763
764 fn capabilities(&self) -> NodeCapabilities {
765 NodeCapabilities::LAYOUT
766 }
767}
768
769#[derive(Clone)]
770struct TranslatedContentContextElement {
771 identity: usize,
772}
773
774impl TranslatedContentContextElement {
775 fn new(identity: usize) -> Self {
776 Self { identity }
777 }
778}
779
780impl std::fmt::Debug for TranslatedContentContextElement {
781 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
782 f.debug_struct("TranslatedContentContextElement")
783 .field("identity", &self.identity)
784 .finish()
785 }
786}
787
788impl PartialEq for TranslatedContentContextElement {
789 fn eq(&self, other: &Self) -> bool {
790 self.identity == other.identity
791 }
792}
793
794impl Eq for TranslatedContentContextElement {}
795
796impl std::hash::Hash for TranslatedContentContextElement {
797 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
798 self.identity.hash(state);
799 }
800}
801
802impl ModifierNodeElement for TranslatedContentContextElement {
803 type Node = TranslatedContentContextNode;
804
805 fn create(&self) -> Self::Node {
806 TranslatedContentContextNode::new()
807 }
808
809 fn update(&self, _node: &mut Self::Node) {}
810
811 fn capabilities(&self) -> NodeCapabilities {
812 NodeCapabilities::LAYOUT
813 }
814}
815
816impl Modifier {
821 pub fn horizontal_scroll(self, state: ScrollState, reverse_scrolling: bool) -> Self {
839 self.then(scroll_impl(state, false, reverse_scrolling, None))
840 }
841
842 pub fn vertical_scroll(self, state: ScrollState, reverse_scrolling: bool) -> Self {
851 self.then(scroll_impl(state, true, reverse_scrolling, None))
852 }
853
854 pub fn horizontal_scroll_guarded(
856 self,
857 state: ScrollState,
858 reverse_scrolling: bool,
859 guard: impl Fn() -> bool + 'static,
860 ) -> Self {
861 self.then(scroll_impl(
862 state,
863 false,
864 reverse_scrolling,
865 Some(Rc::new(guard)),
866 ))
867 }
868
869 pub fn vertical_scroll_guarded(
871 self,
872 state: ScrollState,
873 reverse_scrolling: bool,
874 guard: impl Fn() -> bool + 'static,
875 ) -> Self {
876 self.then(scroll_impl(
877 state,
878 true,
879 reverse_scrolling,
880 Some(Rc::new(guard)),
881 ))
882 }
883}
884
885fn scroll_impl(
894 state: ScrollState,
895 is_vertical: bool,
896 reverse_scrolling: bool,
897 guard: Option<Rc<dyn Fn() -> bool>>,
898) -> Modifier {
899 let gesture_state = Rc::new(RefCell::new(ScrollGestureState::default()));
901 let motion_context = MotionContextState::new();
902
903 let scroll_state = state.clone();
905 let pointer_motion_context = motion_context.clone();
906 let key = (state.id(), is_vertical);
907 let pointer_input = Modifier::empty().pointer_input(key, move |scope| {
908 let detector = ScrollGestureDetector::new(
910 gesture_state.clone(),
911 scroll_state.clone(),
912 is_vertical,
913 false, pointer_motion_context.clone(),
915 );
916 let guard = guard.clone();
917
918 async move {
919 scope
920 .await_pointer_event_scope(|await_scope| async move {
921 loop {
923 let event = await_scope.await_pointer_event().await;
924
925 if let Some(ref guard) = guard {
926 if !guard() {
927 if matches!(
928 event.kind,
929 PointerEventKind::Up | PointerEventKind::Cancel
930 ) {
931 detector.on_cancel();
932 }
933 continue;
934 }
935 }
936
937 let should_consume = match event.kind {
939 PointerEventKind::Down => detector.on_down(event.position),
940 PointerEventKind::Move => {
941 detector.on_move(event.position, event.buttons)
942 }
943 PointerEventKind::Up => detector.on_up(),
944 PointerEventKind::Cancel => detector.on_cancel(),
945 PointerEventKind::Scroll => detector.on_scroll(if is_vertical {
946 event.scroll_delta.y
947 } else {
948 event.scroll_delta.x
949 }),
950 PointerEventKind::Enter | PointerEventKind::Exit => false,
951 };
952
953 if should_consume {
954 event.consume();
955 }
956 }
957 })
958 .await;
959 }
960 });
961
962 let element = ScrollElement::new(state.clone(), is_vertical, reverse_scrolling);
964 let layout_modifier =
965 Modifier::with_element(element).with_inspector_metadata(inspector_metadata(
966 if is_vertical {
967 "verticalScroll"
968 } else {
969 "horizontalScroll"
970 },
971 move |info| {
972 info.add_property("isVertical", is_vertical.to_string());
973 info.add_property("reverseScrolling", reverse_scrolling.to_string());
974 },
975 ));
976 let motion_modifier =
977 Modifier::with_element(MotionContextAnimatedElement::new(motion_context.clone()));
978 let translated_content_modifier =
979 Modifier::with_element(TranslatedContentContextElement::new(state.id() as usize));
980
981 pointer_input
983 .then(motion_modifier)
984 .then(translated_content_modifier)
985 .then(layout_modifier)
986 .clip_to_bounds()
987}
988
989use cranpose_foundation::lazy::LazyListState;
994
995impl Modifier {
996 pub fn lazy_vertical_scroll(self, state: LazyListState, reverse_scrolling: bool) -> Self {
1007 self.then(lazy_scroll_impl(state, true, reverse_scrolling))
1008 }
1009
1010 pub fn lazy_horizontal_scroll(self, state: LazyListState, reverse_scrolling: bool) -> Self {
1012 self.then(lazy_scroll_impl(state, false, reverse_scrolling))
1013 }
1014}
1015
1016fn lazy_scroll_impl(state: LazyListState, is_vertical: bool, reverse_scrolling: bool) -> Modifier {
1018 let gesture_state = Rc::new(RefCell::new(ScrollGestureState::default()));
1019 let list_state = state;
1020 let motion_context = MotionContextState::new();
1021
1022 let state_id = std::ptr::addr_of!(*state.inner_ptr()) as usize;
1028 let key = (state_id, is_vertical, reverse_scrolling);
1029 let translated_content_modifier =
1030 Modifier::with_element(TranslatedContentContextElement::new(state_id));
1031
1032 Modifier::with_element(MotionContextAnimatedElement::new(motion_context.clone()))
1033 .then(translated_content_modifier)
1034 .pointer_input(key, move |scope| {
1035 let detector = ScrollGestureDetector::new(
1037 gesture_state.clone(),
1038 list_state,
1039 is_vertical,
1040 reverse_scrolling,
1041 motion_context.clone(),
1042 );
1043
1044 async move {
1045 scope
1046 .await_pointer_event_scope(|await_scope| async move {
1047 loop {
1048 let event = await_scope.await_pointer_event().await;
1049
1050 let should_consume = match event.kind {
1052 PointerEventKind::Down => detector.on_down(event.position),
1053 PointerEventKind::Move => {
1054 detector.on_move(event.position, event.buttons)
1055 }
1056 PointerEventKind::Up => detector.on_up(),
1057 PointerEventKind::Cancel => detector.on_cancel(),
1058 PointerEventKind::Scroll => detector.on_scroll(if is_vertical {
1059 event.scroll_delta.y
1060 } else {
1061 event.scroll_delta.x
1062 }),
1063 PointerEventKind::Enter | PointerEventKind::Exit => false,
1064 };
1065
1066 if should_consume {
1067 event.consume();
1068 }
1069 }
1070 })
1071 .await;
1072 }
1073 })
1074}