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_GenericFlingAnimation_h_
8 #define mozilla_layers_GenericFlingAnimation_h_
9 
10 #include "APZUtils.h"
11 #include "AsyncPanZoomAnimation.h"
12 #include "AsyncPanZoomController.h"
13 #include "FrameMetrics.h"
14 #include "Layers.h"
15 #include "Units.h"
16 #include "OverscrollHandoffState.h"
17 #include "gfxPrefs.h"
18 #include "mozilla/Assertions.h"
19 #include "mozilla/Monitor.h"
20 #include "mozilla/RefPtr.h"
21 #include "mozilla/TimeStamp.h"
22 #include "nsThreadUtils.h"
23 
24 #define FLING_LOG(...)
25 // #define FLING_LOG(...) printf_stderr("FLING: " __VA_ARGS__)
26 
27 namespace mozilla {
28 namespace layers {
29 
30 class GenericFlingAnimation : public AsyncPanZoomAnimation {
31  public:
GenericFlingAnimation(AsyncPanZoomController & aApzc,PlatformSpecificStateBase * aPlatformSpecificState,const RefPtr<const OverscrollHandoffChain> & aOverscrollHandoffChain,bool aFlingIsHandedOff,const RefPtr<const AsyncPanZoomController> & aScrolledApzc)32   GenericFlingAnimation(
33       AsyncPanZoomController& aApzc,
34       PlatformSpecificStateBase* aPlatformSpecificState,
35       const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
36       bool aFlingIsHandedOff,
37       const RefPtr<const AsyncPanZoomController>& aScrolledApzc)
38       : mApzc(aApzc),
39         mOverscrollHandoffChain(aOverscrollHandoffChain),
40         mScrolledApzc(aScrolledApzc) {
41     MOZ_ASSERT(mOverscrollHandoffChain);
42     TimeStamp now = aApzc.GetFrameTime();
43 
44     // Drop any velocity on axes where we don't have room to scroll anyways
45     // (in this APZC, or an APZC further in the handoff chain).
46     // This ensures that we don't take the 'overscroll' path in Sample()
47     // on account of one axis which can't scroll having a velocity.
48     if (!mOverscrollHandoffChain->CanScrollInDirection(
49             &mApzc, ScrollDirection::eHorizontal)) {
50       RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex);
51       mApzc.mX.SetVelocity(0);
52     }
53     if (!mOverscrollHandoffChain->CanScrollInDirection(
54             &mApzc, ScrollDirection::eVertical)) {
55       RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex);
56       mApzc.mY.SetVelocity(0);
57     }
58 
59     ParentLayerPoint velocity = mApzc.GetVelocityVector();
60 
61     // If the last fling was very recent and in the same direction as this one,
62     // boost the velocity to be the sum of the two. Check separate axes
63     // separately because we could have two vertical flings with small
64     // horizontal components on the opposite side of zero, and we still want the
65     // y-fling to get accelerated. Note that the acceleration code is only
66     // applied on the APZC that initiates the fling; the accelerated velocities
67     // are then handed off using the normal DispatchFling codepath. Acceleration
68     // is only applied in the APZC that originated the fling, not in APZCs
69     // further down the handoff chain during handoff.
70     bool applyAcceleration = !aFlingIsHandedOff;
71     if (applyAcceleration && !mApzc.mLastFlingTime.IsNull() &&
72         (now - mApzc.mLastFlingTime).ToMilliseconds() <
73             gfxPrefs::APZFlingAccelInterval() &&
74         velocity.Length() >= gfxPrefs::APZFlingAccelMinVelocity()) {
75       if (SameDirection(velocity.x, mApzc.mLastFlingVelocity.x)) {
76         velocity.x = Accelerate(velocity.x, mApzc.mLastFlingVelocity.x);
77         FLING_LOG("%p Applying fling x-acceleration from %f to %f (delta %f)\n",
78                   &mApzc, mApzc.mX.GetVelocity(), velocity.x,
79                   mApzc.mLastFlingVelocity.x);
80         mApzc.mX.SetVelocity(velocity.x);
81       }
82       if (SameDirection(velocity.y, mApzc.mLastFlingVelocity.y)) {
83         velocity.y = Accelerate(velocity.y, mApzc.mLastFlingVelocity.y);
84         FLING_LOG("%p Applying fling y-acceleration from %f to %f (delta %f)\n",
85                   &mApzc, mApzc.mY.GetVelocity(), velocity.y,
86                   mApzc.mLastFlingVelocity.y);
87         mApzc.mY.SetVelocity(velocity.y);
88       }
89     }
90 
91     mApzc.mLastFlingTime = now;
92     mApzc.mLastFlingVelocity = velocity;
93   }
94 
95   /**
96    * Advances a fling by an interpolated amount based on the passed in |aDelta|.
97    * This should be called whenever sampling the content transform for this
98    * frame. Returns true if the fling animation should be advanced by one frame,
99    * or false if there is no fling or the fling has ended.
100    */
DoSample(FrameMetrics & aFrameMetrics,const TimeDuration & aDelta)101   virtual bool DoSample(FrameMetrics& aFrameMetrics,
102                         const TimeDuration& aDelta) override {
103     float friction = gfxPrefs::APZFlingFriction();
104     float threshold = gfxPrefs::APZFlingStoppedThreshold();
105 
106     bool shouldContinueFlingX =
107              mApzc.mX.FlingApplyFrictionOrCancel(aDelta, friction, threshold),
108          shouldContinueFlingY =
109              mApzc.mY.FlingApplyFrictionOrCancel(aDelta, friction, threshold);
110     // If we shouldn't continue the fling, let's just stop and repaint.
111     if (!shouldContinueFlingX && !shouldContinueFlingY) {
112       FLING_LOG("%p ending fling animation. overscrolled=%d\n", &mApzc,
113                 mApzc.IsOverscrolled());
114       // This APZC or an APZC further down the handoff chain may be be
115       // overscrolled. Start a snap-back animation on the overscrolled APZC.
116       // Note:
117       //   This needs to be a deferred task even though it can safely run
118       //   while holding mRecursiveMutex, because otherwise, if the overscrolled
119       //   APZC is this one, then the SetState(NOTHING) in UpdateAnimation will
120       //   stomp on the SetState(SNAP_BACK) it does.
121       mDeferredTasks.AppendElement(NewRunnableMethod<AsyncPanZoomController*>(
122           "layers::OverscrollHandoffChain::SnapBackOverscrolledApzc",
123           mOverscrollHandoffChain.get(),
124           &OverscrollHandoffChain::SnapBackOverscrolledApzc, &mApzc));
125       return false;
126     }
127 
128     // AdjustDisplacement() zeroes out the Axis velocity if we're in overscroll.
129     // Since we need to hand off the velocity to the tree manager in such a
130     // case, we save it here. Would be ParentLayerVector instead of
131     // ParentLayerPoint if we had vector classes.
132     ParentLayerPoint velocity = mApzc.GetVelocityVector();
133 
134     ParentLayerPoint offset = velocity * aDelta.ToMilliseconds();
135 
136     // Ordinarily we might need to do a ScheduleComposite if either of
137     // the following AdjustDisplacement calls returns true, but this
138     // is already running as part of a FlingAnimation, so we'll be compositing
139     // per frame of animation anyway.
140     ParentLayerPoint overscroll;
141     ParentLayerPoint adjustedOffset;
142     mApzc.mX.AdjustDisplacement(offset.x, adjustedOffset.x, overscroll.x);
143     mApzc.mY.AdjustDisplacement(offset.y, adjustedOffset.y, overscroll.y);
144 
145     aFrameMetrics.ScrollBy(adjustedOffset / aFrameMetrics.GetZoom());
146 
147     // The fling may have caused us to reach the end of our scroll range.
148     if (!IsZero(overscroll)) {
149       // Hand off the fling to the next APZC in the overscroll handoff chain.
150 
151       // We may have reached the end of the scroll range along one axis but
152       // not the other. In such a case we only want to hand off the relevant
153       // component of the fling.
154       if (FuzzyEqualsAdditive(overscroll.x, 0.0f, COORDINATE_EPSILON)) {
155         velocity.x = 0;
156       } else if (FuzzyEqualsAdditive(overscroll.y, 0.0f, COORDINATE_EPSILON)) {
157         velocity.y = 0;
158       }
159 
160       // To hand off the fling, we attempt to find a target APZC and start a new
161       // fling with the same velocity on that APZC. For simplicity, the actual
162       // overscroll of the current sample is discarded rather than being handed
163       // off. The compositor should sample animations sufficiently frequently
164       // that this is not noticeable. The target APZC is chosen by seeing if
165       // there is an APZC further in the handoff chain which is pannable; if
166       // there isn't, we take the new fling ourselves, entering an overscrolled
167       // state.
168       // Note: APZC is holding mRecursiveMutex, so directly calling
169       // HandleFlingOverscroll() (which acquires the tree lock) would violate
170       // the lock ordering. Instead we schedule HandleFlingOverscroll() to be
171       // called after mRecursiveMutex is released.
172       FLING_LOG("%p fling went into overscroll, handing off with velocity %s\n",
173                 &mApzc, Stringify(velocity).c_str());
174       mDeferredTasks.AppendElement(
175           NewRunnableMethod<ParentLayerPoint,
176                             RefPtr<const OverscrollHandoffChain>,
177                             RefPtr<const AsyncPanZoomController>>(
178               "layers::AsyncPanZoomController::HandleFlingOverscroll", &mApzc,
179               &AsyncPanZoomController::HandleFlingOverscroll, velocity,
180               mOverscrollHandoffChain, mScrolledApzc));
181 
182       // If there is a remaining velocity on this APZC, continue this fling
183       // as well. (This fling and the handed-off fling will run concurrently.)
184       // Note that AdjustDisplacement() will have zeroed out the velocity
185       // along the axes where we're overscrolled.
186       return !IsZero(mApzc.GetVelocityVector());
187     }
188 
189     return true;
190   }
191 
192  private:
SameDirection(float aVelocity1,float aVelocity2)193   static bool SameDirection(float aVelocity1, float aVelocity2) {
194     return (aVelocity1 == 0.0f) || (aVelocity2 == 0.0f) ||
195            (IsNegative(aVelocity1) == IsNegative(aVelocity2));
196   }
197 
Accelerate(float aBase,float aSupplemental)198   static float Accelerate(float aBase, float aSupplemental) {
199     return (aBase * gfxPrefs::APZFlingAccelBaseMultiplier()) +
200            (aSupplemental * gfxPrefs::APZFlingAccelSupplementalMultiplier());
201   }
202 
203   AsyncPanZoomController& mApzc;
204   RefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
205   RefPtr<const AsyncPanZoomController> mScrolledApzc;
206 };
207 
208 }  // namespace layers
209 }  // namespace mozilla
210 
211 #endif  // mozilla_layers_GenericFlingAnimation_h_
212