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