2727
2828namespace Microsoft ::ReactNative::Composition::Experimental {
2929
30- using namespace winrt ::Microsoft::ReactNative::Composition::Experimental;
31-
3230template <typename TSpriteVisual>
3331struct CompositionTypeTraits {};
3432
@@ -711,24 +709,50 @@ struct CompScrollerVisual : winrt::implements<
711709 void IdleStateEntered (
712710 typename TTypeRedirects::InteractionTracker sender,
713711 typename TTypeRedirects::InteractionTrackerIdleStateEnteredArgs args) noexcept {
712+ // If we were in inertia and are now idle, momentum has ended
713+ if (m_outer->m_inertia ) {
714+ m_outer->FireScrollMomentumEnd ({sender.Position ().x , sender.Position ().y });
715+ }
716+
717+ // If we were interacting but never entered inertia (Interacting -> Idle),
718+ // and the interaction was user-driven (requestId == 0), fire end-drag here.
719+ // Note: if the interactionRequestId was non-zero it was caused by a Try* call
720+ // (programmatic), so we should not fire onScrollEndDrag.
721+ if (m_outer->m_interacting && args.RequestId () == 0 ) {
722+ m_outer->FireScrollEndDrag ({sender.Position ().x , sender.Position ().y });
723+ }
724+
725+ // Clear state flags
714726 m_outer->m_custom = false ;
715727 m_outer->m_inertia = false ;
728+ m_outer->m_interacting = false ;
716729 }
717730 void InertiaStateEntered (
718731 typename TTypeRedirects::InteractionTracker sender,
719732 typename TTypeRedirects::InteractionTrackerInertiaStateEnteredArgs args) noexcept {
720733 m_outer->m_custom = false ;
721734 m_outer->m_inertia = true ;
722735 m_outer->m_currentPosition = args.NaturalRestingPosition ();
723- // When the user stops interacting with the object, tracker can go into two paths:
724- // 1. tracker goes into idle state immediately
725- // 2. tracker has just started gliding into Inertia state
726- // Fire ScrollEndDrag
727- m_outer->FireScrollEndDrag ({args.NaturalRestingPosition ().x , args.NaturalRestingPosition ().y });
736+
737+ if (!m_outer->m_interacting && args.RequestId () == 0 ) {
738+ m_outer->FireScrollBeginDrag ({args.NaturalRestingPosition ().x , args.NaturalRestingPosition ().y });
739+ }
740+
741+ // If interaction was user-driven (requestId == 0),
742+ // fire ScrollEndDrag here (Interacting -> Inertia caused by user lift).
743+ if (m_outer->m_interacting && args.RequestId () == 0 ) {
744+ m_outer->FireScrollEndDrag ({args.NaturalRestingPosition ().x , args.NaturalRestingPosition ().y });
745+ }
746+
747+ // Fire momentum scroll begin when we enter inertia (user or programmatic)
748+ m_outer->FireScrollMomentumBegin ({args.NaturalRestingPosition ().x , args.NaturalRestingPosition ().y });
728749 }
729750 void InteractingStateEntered (
730751 typename TTypeRedirects::InteractionTracker sender,
731752 typename TTypeRedirects::InteractionTrackerInteractingStateEnteredArgs args) noexcept {
753+ // Mark that we're now interacting and remember the requestId (user manipulations => 0)
754+ m_outer->m_interacting = true ;
755+
732756 // Fire when the user starts dragging the object
733757 m_outer->FireScrollBeginDrag ({sender.Position ().x , sender.Position ().y });
734758 }
@@ -738,6 +762,10 @@ struct CompScrollerVisual : winrt::implements<
738762 void ValuesChanged (
739763 typename TTypeRedirects::InteractionTracker sender,
740764 typename TTypeRedirects::InteractionTrackerValuesChangedArgs args) noexcept {
765+ if (!m_outer->m_interacting && args.RequestId () == 0 ) {
766+ m_outer->FireScrollBeginDrag ({args.Position ().x , args.Position ().y });
767+ }
768+ m_outer->m_interacting = true ;
741769 m_outer->m_currentPosition = args.Position ();
742770 m_outer->FireScrollPositionChanged ({args.Position ().x , args.Position ().y });
743771 }
@@ -873,11 +901,9 @@ struct CompScrollerVisual : winrt::implements<
873901 void SetSnapPoints (
874902 bool snapToStart,
875903 bool snapToEnd,
876- winrt::Windows::Foundation::Collections::IVectorView<float > const &offsets,
877- SnapAlignment snapToAlignment) noexcept {
904+ winrt::Windows::Foundation::Collections::IVectorView<float > const &offsets) noexcept {
878905 m_snapToStart = snapToStart;
879906 m_snapToEnd = snapToEnd;
880- m_snapToAlignment = snapToAlignment;
881907 m_snapToOffsets.clear ();
882908 if (offsets) {
883909 for (auto const &offset : offsets) {
@@ -985,6 +1011,20 @@ struct CompScrollerVisual : winrt::implements<
9851011 return m_scrollEndDragEvent.add (handler);
9861012 }
9871013
1014+ winrt::event_token ScrollMomentumBegin (
1015+ winrt::Windows::Foundation::EventHandler<
1016+ winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs> const
1017+ &handler) noexcept {
1018+ return m_scrollMomentumBeginEvent.add (handler);
1019+ }
1020+
1021+ winrt::event_token ScrollMomentumEnd (
1022+ winrt::Windows::Foundation::EventHandler<
1023+ winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs> const
1024+ &handler) noexcept {
1025+ return m_scrollMomentumEndEvent.add (handler);
1026+ }
1027+
9881028 void ScrollPositionChanged (winrt::event_token const &token) noexcept {
9891029 m_scrollPositionChangedEvent.remove (token);
9901030 }
@@ -997,6 +1037,14 @@ struct CompScrollerVisual : winrt::implements<
9971037 m_scrollEndDragEvent.remove (token);
9981038 }
9991039
1040+ void ScrollMomentumBegin (winrt::event_token const &token) noexcept {
1041+ m_scrollMomentumBeginEvent.remove (token);
1042+ }
1043+
1044+ void ScrollMomentumEnd (winrt::event_token const &token) noexcept {
1045+ m_scrollMomentumEndEvent.remove (token);
1046+ }
1047+
10001048 void ContentSize (winrt::Windows::Foundation::Numerics::float2 const &size) noexcept {
10011049 m_contentSize = size;
10021050 m_contentVisual.Size (size);
@@ -1075,6 +1123,14 @@ struct CompScrollerVisual : winrt::implements<
10751123 m_scrollEndDragEvent (*this , winrt::make<CompScrollPositionChangedArgs>(position));
10761124 }
10771125
1126+ void FireScrollMomentumBegin (winrt::Windows::Foundation::Numerics::float2 position) noexcept {
1127+ m_scrollMomentumBeginEvent (*this , winrt::make<CompScrollPositionChangedArgs>(position));
1128+ }
1129+
1130+ void FireScrollMomentumEnd (winrt::Windows::Foundation::Numerics::float2 position) noexcept {
1131+ m_scrollMomentumEndEvent (*this , winrt::make<CompScrollPositionChangedArgs>(position));
1132+ }
1133+
10781134 void UpdateMaxPosition () noexcept {
10791135 m_interactionTracker.MaxPosition (
10801136 {std::max<float >(m_contentSize.x - m_visualSize.x , 0 ),
@@ -1104,22 +1160,6 @@ struct CompScrollerVisual : winrt::implements<
11041160 }
11051161
11061162 snapPositions.insert (snapPositions.end (), m_snapToOffsets.begin (), m_snapToOffsets.end ());
1107-
1108- // Adjust snap positions based on alignment
1109- const float viewportSize = m_horizontal ? visualSize.x : visualSize.y ;
1110- if (m_snapToAlignment == SnapAlignment::Center) {
1111- // For center alignment, offset snap positions by half the viewport size
1112- for (auto &position : snapPositions) {
1113- position = std::max (0 .0f , position - viewportSize / 2 .0f );
1114- }
1115- } else if (m_snapToAlignment == SnapAlignment::End) {
1116- // For end alignment, offset snap positions by the full viewport size
1117- for (auto &position : snapPositions) {
1118- position = std::max (0 .0f , position - viewportSize);
1119- }
1120- }
1121- // For Start alignment, no adjustment needed
1122-
11231163 std::sort (snapPositions.begin (), snapPositions.end ());
11241164 snapPositions.erase (std::unique (snapPositions.begin (), snapPositions.end ()), snapPositions.end ());
11251165
@@ -1247,9 +1287,9 @@ struct CompScrollerVisual : winrt::implements<
12471287 bool m_snapToStart{true };
12481288 bool m_snapToEnd{true };
12491289 std::vector<float > m_snapToOffsets;
1250- SnapAlignment m_snapToAlignment{SnapAlignment::Start};
12511290 bool m_inertia{false };
12521291 bool m_custom{false };
1292+ bool m_interacting{false };
12531293 winrt::Windows::Foundation::Numerics::float3 m_targetPosition;
12541294 winrt::Windows::Foundation::Numerics::float3 m_currentPosition;
12551295 winrt::Windows::Foundation::Numerics::float2 m_contentSize{0 };
@@ -1263,6 +1303,12 @@ struct CompScrollerVisual : winrt::implements<
12631303 winrt::event<winrt::Windows::Foundation::EventHandler<
12641304 winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs>>
12651305 m_scrollEndDragEvent;
1306+ winrt::event<winrt::Windows::Foundation::EventHandler<
1307+ winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs>>
1308+ m_scrollMomentumBeginEvent;
1309+ winrt::event<winrt::Windows::Foundation::EventHandler<
1310+ winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs>>
1311+ m_scrollMomentumEndEvent;
12661312 typename TTypeRedirects::SpriteVisual m_visual{nullptr };
12671313 typename TTypeRedirects::SpriteVisual m_contentVisual{nullptr };
12681314 typename TTypeRedirects::InteractionTracker m_interactionTracker{nullptr };
0 commit comments