summaryrefslogtreecommitdiffstats
path: root/layout/generic/AsyncScrollBase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/generic/AsyncScrollBase.cpp')
-rw-r--r--layout/generic/AsyncScrollBase.cpp133
1 files changed, 133 insertions, 0 deletions
diff --git a/layout/generic/AsyncScrollBase.cpp b/layout/generic/AsyncScrollBase.cpp
new file mode 100644
index 000000000..e85444f0f
--- /dev/null
+++ b/layout/generic/AsyncScrollBase.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "AsyncScrollBase.h"
+#include "gfxPrefs.h"
+
+using namespace mozilla;
+
+AsyncScrollBase::AsyncScrollBase(nsPoint aStartPos)
+ : mIsFirstIteration(true)
+ , mStartPos(aStartPos)
+{
+}
+
+void
+AsyncScrollBase::Update(TimeStamp aTime,
+ nsPoint aDestination,
+ const nsSize& aCurrentVelocity)
+{
+ TimeDuration duration = ComputeDuration(aTime);
+ nsSize currentVelocity = aCurrentVelocity;
+
+ if (!mIsFirstIteration) {
+ // If an additional event has not changed the destination, then do not let
+ // another minimum duration reset slow things down. If it would then
+ // instead continue with the existing timing function.
+ if (aDestination == mDestination &&
+ aTime + duration > mStartTime + mDuration)
+ {
+ return;
+ }
+
+ currentVelocity = VelocityAt(aTime);
+ mStartPos = PositionAt(aTime);
+ }
+
+ mStartTime = aTime;
+ mDuration = duration;
+ mDestination = aDestination;
+ InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width,
+ aDestination.x);
+ InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height,
+ aDestination.y);
+ mIsFirstIteration = false;
+}
+
+TimeDuration
+AsyncScrollBase::ComputeDuration(TimeStamp aTime)
+{
+ // Average last 3 delta durations (rounding errors up to 2ms are negligible for us)
+ int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3;
+ mPrevEventTime[2] = mPrevEventTime[1];
+ mPrevEventTime[1] = mPrevEventTime[0];
+ mPrevEventTime[0] = aTime;
+
+ // Modulate duration according to events rate (quicker events -> shorter durations).
+ // The desired effect is to use longer duration when scrolling slowly, such that
+ // it's easier to follow, but reduce the duration to make it feel more snappy when
+ // scrolling quickly. To reduce fluctuations of the duration, we average event
+ // intervals using the recent 4 timestamps (now + three prev -> 3 intervals).
+ int32_t durationMS = clamped<int32_t>(eventsDeltaMs * mIntervalRatio, mOriginMinMS, mOriginMaxMS);
+
+ return TimeDuration::FromMilliseconds(durationMS);
+}
+
+void
+AsyncScrollBase::InitializeHistory(TimeStamp aTime)
+{
+ // Starting a new scroll (i.e. not when extending an existing scroll animation),
+ // create imaginary prev timestamps with maximum relevant intervals between them.
+
+ // Longest relevant interval (which results in maximum duration)
+ TimeDuration maxDelta = TimeDuration::FromMilliseconds(mOriginMaxMS / mIntervalRatio);
+ mPrevEventTime[0] = aTime - maxDelta;
+ mPrevEventTime[1] = mPrevEventTime[0] - maxDelta;
+ mPrevEventTime[2] = mPrevEventTime[1] - maxDelta;
+}
+
+void
+AsyncScrollBase::InitTimingFunction(nsSMILKeySpline& aTimingFunction,
+ nscoord aCurrentPos,
+ nscoord aCurrentVelocity,
+ nscoord aDestination)
+{
+ if (aDestination == aCurrentPos || gfxPrefs::SmoothScrollCurrentVelocityWeighting() == 0) {
+ aTimingFunction.Init(0, 0, 1 - gfxPrefs::SmoothScrollStopDecelerationWeighting(), 1);
+ return;
+ }
+
+ const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
+ double slope = aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos);
+ double normalization = sqrt(1.0 + slope * slope);
+ double dt = 1.0 / normalization * gfxPrefs::SmoothScrollCurrentVelocityWeighting();
+ double dxy = slope / normalization * gfxPrefs::SmoothScrollCurrentVelocityWeighting();
+ aTimingFunction.Init(dt, dxy, 1 - gfxPrefs::SmoothScrollStopDecelerationWeighting(), 1);
+}
+
+nsPoint
+AsyncScrollBase::PositionAt(TimeStamp aTime) const
+{
+ double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
+ double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
+ return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x + progressX * mDestination.x),
+ NSToCoordRound((1 - progressY) * mStartPos.y + progressY * mDestination.y));
+}
+
+nsSize
+AsyncScrollBase::VelocityAt(TimeStamp aTime) const
+{
+ double timeProgress = ProgressAt(aTime);
+ return nsSize(VelocityComponent(timeProgress, mTimingFunctionX,
+ mStartPos.x, mDestination.x),
+ VelocityComponent(timeProgress, mTimingFunctionY,
+ mStartPos.y, mDestination.y));
+}
+
+nscoord
+AsyncScrollBase::VelocityComponent(double aTimeProgress,
+ const nsSMILKeySpline& aTimingFunction,
+ nscoord aStart,
+ nscoord aDestination) const
+{
+ double dt, dxy;
+ aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy);
+ if (dt == 0)
+ return dxy >= 0 ? nscoord_MAX : nscoord_MIN;
+
+ const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
+ double slope = dxy / dt;
+ return NSToCoordRound(slope * (aDestination - aStart) / (mDuration / oneSecond));
+}