/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_layers_GenericFlingAnimation_h_ #define mozilla_layers_GenericFlingAnimation_h_ #include "APZUtils.h" #include "AsyncPanZoomAnimation.h" #include "AsyncPanZoomController.h" #include "FrameMetrics.h" #include "Layers.h" #include "Units.h" #include "OverscrollHandoffState.h" #include "gfxPrefs.h" #include "mozilla/Assertions.h" #include "mozilla/Monitor.h" #include "mozilla/RefPtr.h" #include "mozilla/TimeStamp.h" #include "nsThreadUtils.h" #define FLING_LOG(...) // #define FLING_LOG(...) printf_stderr("FLING: " __VA_ARGS__) namespace mozilla { namespace layers { class GenericFlingAnimation: public AsyncPanZoomAnimation { public: GenericFlingAnimation(AsyncPanZoomController& aApzc, PlatformSpecificStateBase* aPlatformSpecificState, const RefPtr& aOverscrollHandoffChain, bool aFlingIsHandedOff, const RefPtr& aScrolledApzc) : mApzc(aApzc) , mOverscrollHandoffChain(aOverscrollHandoffChain) , mScrolledApzc(aScrolledApzc) { MOZ_ASSERT(mOverscrollHandoffChain); TimeStamp now = aApzc.GetFrameTime(); // Drop any velocity on axes where we don't have room to scroll anyways // (in this APZC, or an APZC further in the handoff chain). // This ensures that we don't take the 'overscroll' path in Sample() // on account of one axis which can't scroll having a velocity. if (!mOverscrollHandoffChain->CanScrollInDirection(&mApzc, Layer::HORIZONTAL)) { ReentrantMonitorAutoEnter lock(mApzc.mMonitor); mApzc.mX.SetVelocity(0); } if (!mOverscrollHandoffChain->CanScrollInDirection(&mApzc, Layer::VERTICAL)) { ReentrantMonitorAutoEnter lock(mApzc.mMonitor); mApzc.mY.SetVelocity(0); } ParentLayerPoint velocity = mApzc.GetVelocityVector(); // If the last fling was very recent and in the same direction as this one, // boost the velocity to be the sum of the two. Check separate axes separately // because we could have two vertical flings with small horizontal components // on the opposite side of zero, and we still want the y-fling to get accelerated. // Note that the acceleration code is only applied on the APZC that initiates // the fling; the accelerated velocities are then handed off using the // normal DispatchFling codepath. // Acceleration is only applied in the APZC that originated the fling, // not in APZCs further down the handoff chain during handoff. bool applyAcceleration = !aFlingIsHandedOff; if (applyAcceleration && !mApzc.mLastFlingTime.IsNull() && (now - mApzc.mLastFlingTime).ToMilliseconds() < gfxPrefs::APZFlingAccelInterval() && velocity.Length() >= gfxPrefs::APZFlingAccelMinVelocity()) { if (SameDirection(velocity.x, mApzc.mLastFlingVelocity.x)) { velocity.x = Accelerate(velocity.x, mApzc.mLastFlingVelocity.x); FLING_LOG("%p Applying fling x-acceleration from %f to %f (delta %f)\n", &mApzc, mApzc.mX.GetVelocity(), velocity.x, mApzc.mLastFlingVelocity.x); mApzc.mX.SetVelocity(velocity.x); } if (SameDirection(velocity.y, mApzc.mLastFlingVelocity.y)) { velocity.y = Accelerate(velocity.y, mApzc.mLastFlingVelocity.y); FLING_LOG("%p Applying fling y-acceleration from %f to %f (delta %f)\n", &mApzc, mApzc.mY.GetVelocity(), velocity.y, mApzc.mLastFlingVelocity.y); mApzc.mY.SetVelocity(velocity.y); } } mApzc.mLastFlingTime = now; mApzc.mLastFlingVelocity = velocity; } /** * Advances a fling by an interpolated amount based on the passed in |aDelta|. * This should be called whenever sampling the content transform for this * frame. Returns true if the fling animation should be advanced by one frame, * or false if there is no fling or the fling has ended. */ virtual bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) override { float friction = gfxPrefs::APZFlingFriction(); float threshold = gfxPrefs::APZFlingStoppedThreshold(); bool shouldContinueFlingX = mApzc.mX.FlingApplyFrictionOrCancel(aDelta, friction, threshold), shouldContinueFlingY = mApzc.mY.FlingApplyFrictionOrCancel(aDelta, friction, threshold); // If we shouldn't continue the fling, let's just stop and repaint. if (!shouldContinueFlingX && !shouldContinueFlingY) { FLING_LOG("%p ending fling animation. overscrolled=%d\n", &mApzc, mApzc.IsOverscrolled()); // This APZC or an APZC further down the handoff chain may be be overscrolled. // Start a snap-back animation on the overscrolled APZC. // Note: // This needs to be a deferred task even though it can safely run // while holding mMonitor, because otherwise, if the overscrolled APZC // is this one, then the SetState(NOTHING) in UpdateAnimation will // stomp on the SetState(SNAP_BACK) it does. mDeferredTasks.AppendElement( NewRunnableMethod(mOverscrollHandoffChain.get(), &OverscrollHandoffChain::SnapBackOverscrolledApzc, &mApzc)); return false; } // AdjustDisplacement() zeroes out the Axis velocity if we're in overscroll. // Since we need to hand off the velocity to the tree manager in such a case, // we save it here. Would be ParentLayerVector instead of ParentLayerPoint // if we had vector classes. ParentLayerPoint velocity = mApzc.GetVelocityVector(); ParentLayerPoint offset = velocity * aDelta.ToMilliseconds(); // Ordinarily we might need to do a ScheduleComposite if either of // the following AdjustDisplacement calls returns true, but this // is already running as part of a FlingAnimation, so we'll be compositing // per frame of animation anyway. ParentLayerPoint overscroll; ParentLayerPoint adjustedOffset; mApzc.mX.AdjustDisplacement(offset.x, adjustedOffset.x, overscroll.x); mApzc.mY.AdjustDisplacement(offset.y, adjustedOffset.y, overscroll.y); aFrameMetrics.ScrollBy(adjustedOffset / aFrameMetrics.GetZoom()); // The fling may have caused us to reach the end of our scroll range. if (!IsZero(overscroll)) { // Hand off the fling to the next APZC in the overscroll handoff chain. // We may have reached the end of the scroll range along one axis but // not the other. In such a case we only want to hand off the relevant // component of the fling. if (FuzzyEqualsAdditive(overscroll.x, 0.0f, COORDINATE_EPSILON)) { velocity.x = 0; } else if (FuzzyEqualsAdditive(overscroll.y, 0.0f, COORDINATE_EPSILON)) { velocity.y = 0; } // To hand off the fling, we attempt to find a target APZC and start a new // fling with the same velocity on that APZC. For simplicity, the actual // overscroll of the current sample is discarded rather than being handed // off. The compositor should sample animations sufficiently frequently // that this is not noticeable. The target APZC is chosen by seeing if // there is an APZC further in the handoff chain which is pannable; if // there isn't, we take the new fling ourselves, entering an overscrolled // state. // Note: APZC is holding mMonitor, so directly calling // HandleFlingOverscroll() (which acquires the tree lock) would violate // the lock ordering. Instead we schedule HandleFlingOverscroll() to be // called after mMonitor is released. FLING_LOG("%p fling went into overscroll, handing off with velocity %s\n", &mApzc, Stringify(velocity).c_str()); mDeferredTasks.AppendElement( NewRunnableMethod, RefPtr>(&mApzc, &AsyncPanZoomController::HandleFlingOverscroll, velocity, mOverscrollHandoffChain, mScrolledApzc)); // If there is a remaining velocity on this APZC, continue this fling // as well. (This fling and the handed-off fling will run concurrently.) // Note that AdjustDisplacement() will have zeroed out the velocity // along the axes where we're overscrolled. return !IsZero(mApzc.GetVelocityVector()); } return true; } private: static bool SameDirection(float aVelocity1, float aVelocity2) { return (aVelocity1 == 0.0f) || (aVelocity2 == 0.0f) || (IsNegative(aVelocity1) == IsNegative(aVelocity2)); } static float Accelerate(float aBase, float aSupplemental) { return (aBase * gfxPrefs::APZFlingAccelBaseMultiplier()) + (aSupplemental * gfxPrefs::APZFlingAccelSupplementalMultiplier()); } AsyncPanZoomController& mApzc; RefPtr mOverscrollHandoffChain; RefPtr mScrolledApzc; }; } // namespace layers } // namespace mozilla #endif // mozilla_layers_GenericFlingAnimation_h_