1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef mozilla_layers_Overscroll_h
8 #define mozilla_layers_Overscroll_h
9 
10 #include "AsyncPanZoomAnimation.h"
11 #include "AsyncPanZoomController.h"
12 #include "mozilla/TimeStamp.h"
13 #include "nsThreadUtils.h"
14 
15 namespace mozilla {
16 namespace layers {
17 
18 // Animation used by GenericOverscrollEffect.
19 class OverscrollAnimation : public AsyncPanZoomAnimation {
20  public:
OverscrollAnimation(AsyncPanZoomController & aApzc,const ParentLayerPoint & aVelocity,SideBits aOverscrollSideBits)21   OverscrollAnimation(AsyncPanZoomController& aApzc,
22                       const ParentLayerPoint& aVelocity,
23                       SideBits aOverscrollSideBits)
24       : mApzc(aApzc), mOverscrollSideBits(aOverscrollSideBits) {
25     MOZ_ASSERT(
26         (mOverscrollSideBits & SideBits::eTopBottom) != SideBits::eTopBottom &&
27             (mOverscrollSideBits & SideBits::eLeftRight) !=
28                 SideBits::eLeftRight,
29         "Don't allow overscrolling on both sides at the same time");
30     if ((aOverscrollSideBits & SideBits::eLeftRight) != SideBits::eNone) {
31       mApzc.mX.StartOverscrollAnimation(aVelocity.x);
32     }
33     if ((aOverscrollSideBits & SideBits::eTopBottom) != SideBits::eNone) {
34       mApzc.mY.StartOverscrollAnimation(aVelocity.y);
35     }
36   }
~OverscrollAnimation()37   virtual ~OverscrollAnimation() {
38     mApzc.mX.EndOverscrollAnimation();
39     mApzc.mY.EndOverscrollAnimation();
40   }
41 
DoSample(FrameMetrics & aFrameMetrics,const TimeDuration & aDelta)42   virtual bool DoSample(FrameMetrics& aFrameMetrics,
43                         const TimeDuration& aDelta) override {
44     // Can't inline these variables due to short-circuit evaluation.
45     bool continueX = mApzc.mX.IsOverscrollAnimationAlive() &&
46                      mApzc.mX.SampleOverscrollAnimation(
47                          aDelta, mOverscrollSideBits & SideBits::eLeftRight);
48     bool continueY = mApzc.mY.IsOverscrollAnimationAlive() &&
49                      mApzc.mY.SampleOverscrollAnimation(
50                          aDelta, mOverscrollSideBits & SideBits::eTopBottom);
51     if (!continueX && !continueY) {
52       // If we got into overscroll from a fling, that fling did not request a
53       // fling snap to avoid a resulting scrollTo from cancelling the overscroll
54       // animation too early. We do still want to request a fling snap, though,
55       // in case the end of the axis at which we're overscrolled is not a valid
56       // snap point, so we request one now. If there are no snap points, this
57       // will do nothing. If there are snap points, we'll get a scrollTo that
58       // snaps us back to the nearest valid snap point. The scroll snapping is
59       // done in a deferred task, otherwise the state change to NOTHING caused
60       // by the overscroll animation ending would clobber a possible state
61       // change to SMOOTH_SCROLL in ScrollSnap().
62       mDeferredTasks.AppendElement(
63           NewRunnableMethod("layers::AsyncPanZoomController::ScrollSnap",
64                             &mApzc, &AsyncPanZoomController::ScrollSnap));
65       return false;
66     }
67     return true;
68   }
69 
WantsRepaints()70   virtual bool WantsRepaints() override { return false; }
71 
72   // Tell the overscroll animation about the pan momentum event. For each axis,
73   // the overscroll animation may start, stop, or continue managing that axis in
74   // response to the pan momentum event
HandlePanMomentum(const ParentLayerPoint & aDisplacement)75   void HandlePanMomentum(const ParentLayerPoint& aDisplacement) {
76     float xOverscroll = mApzc.mX.GetOverscroll();
77     if ((xOverscroll > 0 && aDisplacement.x > 0) ||
78         (xOverscroll < 0 && aDisplacement.x < 0)) {
79       if (!mApzc.mX.IsOverscrollAnimationRunning()) {
80         // Start a new overscroll animation on this axis, if there is no
81         // overscroll animation running and if the pan momentum displacement
82         // the pan momentum displacement is the same direction of the current
83         // overscroll.
84         mApzc.mX.StartOverscrollAnimation(mApzc.mX.GetVelocity());
85         mOverscrollSideBits |=
86             xOverscroll > 0 ? SideBits::eRight : SideBits::eLeft;
87       }
88     } else if ((xOverscroll > 0 && aDisplacement.x < 0) ||
89                (xOverscroll < 0 && aDisplacement.x > 0)) {
90       // Otherwise, stop the animation in the direction so that it won't clobber
91       // subsequent pan momentum scrolling.
92       mApzc.mX.EndOverscrollAnimation();
93     }
94 
95     // Same as above but for Y axis.
96     float yOverscroll = mApzc.mY.GetOverscroll();
97     if ((yOverscroll > 0 && aDisplacement.y > 0) ||
98         (yOverscroll < 0 && aDisplacement.y < 0)) {
99       if (!mApzc.mY.IsOverscrollAnimationRunning()) {
100         mApzc.mY.StartOverscrollAnimation(mApzc.mY.GetVelocity());
101         mOverscrollSideBits |=
102             yOverscroll > 0 ? SideBits::eBottom : SideBits::eTop;
103       }
104     } else if ((yOverscroll > 0 && aDisplacement.y < 0) ||
105                (yOverscroll < 0 && aDisplacement.y > 0)) {
106       mApzc.mY.EndOverscrollAnimation();
107     }
108   }
109 
GetDirections()110   ScrollDirections GetDirections() const {
111     ScrollDirections directions;
112     if (mApzc.mX.IsOverscrollAnimationRunning()) {
113       directions += ScrollDirection::eHorizontal;
114     }
115     if (mApzc.mY.IsOverscrollAnimationRunning()) {
116       directions += ScrollDirection::eVertical;
117     }
118     return directions;
119   };
120 
AsOverscrollAnimation()121   OverscrollAnimation* AsOverscrollAnimation() override { return this; }
122 
IsManagingXAxis()123   bool IsManagingXAxis() const {
124     return mApzc.mX.IsOverscrollAnimationRunning();
125   }
IsManagingYAxis()126   bool IsManagingYAxis() const {
127     return mApzc.mY.IsOverscrollAnimationRunning();
128   }
129 
130  private:
131   AsyncPanZoomController& mApzc;
132   SideBits mOverscrollSideBits;
133 };
134 
135 // Base class for different overscroll effects;
136 class OverscrollEffectBase {
137  public:
138   virtual ~OverscrollEffectBase() = default;
139   virtual void ConsumeOverscroll(ParentLayerPoint& aOverscroll,
140                                  ScrollDirections aOverscrolableDirections) = 0;
141   virtual void HandleFlingOverscroll(const ParentLayerPoint& aVelocity,
142                                      SideBits aOverscrollSideBits) = 0;
143 };
144 
145 // A generic overscroll effect, implemented by AsyncPanZoomController itself.
146 class GenericOverscrollEffect : public OverscrollEffectBase {
147  public:
GenericOverscrollEffect(AsyncPanZoomController & aApzc)148   explicit GenericOverscrollEffect(AsyncPanZoomController& aApzc)
149       : mApzc(aApzc) {}
150 
ConsumeOverscroll(ParentLayerPoint & aOverscroll,ScrollDirections aOverscrolableDirections)151   void ConsumeOverscroll(ParentLayerPoint& aOverscroll,
152                          ScrollDirections aOverscrolableDirections) override {
153     if (mApzc.mScrollMetadata.PrefersReducedMotion()) {
154       return;
155     }
156 
157     if (aOverscrolableDirections.contains(ScrollDirection::eHorizontal)) {
158       mApzc.mX.OverscrollBy(aOverscroll.x);
159       aOverscroll.x = 0;
160     }
161 
162     if (aOverscrolableDirections.contains(ScrollDirection::eVertical)) {
163       mApzc.mY.OverscrollBy(aOverscroll.y);
164       aOverscroll.y = 0;
165     }
166 
167     if (!aOverscrolableDirections.isEmpty()) {
168       mApzc.ScheduleComposite();
169     }
170   }
171 
HandleFlingOverscroll(const ParentLayerPoint & aVelocity,SideBits aOverscrollSideBits)172   void HandleFlingOverscroll(const ParentLayerPoint& aVelocity,
173                              SideBits aOverscrollSideBits) override {
174     if (mApzc.mScrollMetadata.PrefersReducedMotion()) {
175       return;
176     }
177 
178     mApzc.StartOverscrollAnimation(aVelocity, aOverscrollSideBits);
179   }
180 
181  private:
182   AsyncPanZoomController& mApzc;
183 };
184 
185 // A widget-specific overscroll effect, implemented by the widget via
186 // GeckoContentController.
187 class WidgetOverscrollEffect : public OverscrollEffectBase {
188  public:
WidgetOverscrollEffect(AsyncPanZoomController & aApzc)189   explicit WidgetOverscrollEffect(AsyncPanZoomController& aApzc)
190       : mApzc(aApzc) {}
191 
ConsumeOverscroll(ParentLayerPoint & aOverscroll,ScrollDirections aOverscrolableDirections)192   void ConsumeOverscroll(ParentLayerPoint& aOverscroll,
193                          ScrollDirections aOverscrolableDirections) override {
194     RefPtr<GeckoContentController> controller =
195         mApzc.GetGeckoContentController();
196     if (controller && !aOverscrolableDirections.isEmpty()) {
197       controller->UpdateOverscrollOffset(mApzc.GetGuid(), aOverscroll.x,
198                                          aOverscroll.y, mApzc.IsRootContent());
199       aOverscroll = ParentLayerPoint();
200     }
201   }
202 
HandleFlingOverscroll(const ParentLayerPoint & aVelocity,SideBits aOverscrollSideBits)203   void HandleFlingOverscroll(const ParentLayerPoint& aVelocity,
204                              SideBits aOverscrollSideBits) override {
205     RefPtr<GeckoContentController> controller =
206         mApzc.GetGeckoContentController();
207     if (controller) {
208       controller->UpdateOverscrollVelocity(mApzc.GetGuid(), aVelocity.x,
209                                            aVelocity.y, mApzc.IsRootContent());
210     }
211   }
212 
213  private:
214   AsyncPanZoomController& mApzc;
215 };
216 
217 }  // namespace layers
218 }  // namespace mozilla
219 
220 #endif  // mozilla_layers_Overscroll_h
221