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