summaryrefslogtreecommitdiffstats
path: root/layout/generic/ScrollAnimationBezierPhysics.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/generic/ScrollAnimationBezierPhysics.cpp')
-rw-r--r--layout/generic/ScrollAnimationBezierPhysics.cpp152
1 files changed, 152 insertions, 0 deletions
diff --git a/layout/generic/ScrollAnimationBezierPhysics.cpp b/layout/generic/ScrollAnimationBezierPhysics.cpp
new file mode 100644
index 0000000000..ffc69a9fa4
--- /dev/null
+++ b/layout/generic/ScrollAnimationBezierPhysics.cpp
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#include "ScrollAnimationBezierPhysics.h"
+#include "mozilla/StaticPrefs_general.h"
+
+using namespace mozilla;
+
+ScrollAnimationBezierPhysics::ScrollAnimationBezierPhysics(
+ const nsPoint& aStartPos,
+ const ScrollAnimationBezierPhysicsSettings& aSettings)
+ : mSettings(aSettings), mStartPos(aStartPos), mIsFirstIteration(true) {}
+
+void ScrollAnimationBezierPhysics::Update(const TimeStamp& aTime,
+ const nsPoint& aDestination,
+ const nsSize& aCurrentVelocity) {
+ if (mIsFirstIteration) {
+ InitializeHistory(aTime);
+ }
+
+ 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;
+}
+
+void ScrollAnimationBezierPhysics::ApplyContentShift(
+ const CSSPoint& aShiftDelta) {
+ nsPoint shiftDelta = CSSPoint::ToAppUnits(aShiftDelta);
+ mStartPos += shiftDelta;
+ mDestination += shiftDelta;
+}
+
+TimeDuration ScrollAnimationBezierPhysics::ComputeDuration(
+ const 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 * mSettings.mIntervalRatio,
+ mSettings.mMinMS, mSettings.mMaxMS);
+
+ return TimeDuration::FromMilliseconds(durationMS);
+}
+
+void ScrollAnimationBezierPhysics::InitializeHistory(const 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(
+ mSettings.mMaxMS / mSettings.mIntervalRatio);
+ mPrevEventTime[0] = aTime - maxDelta;
+ mPrevEventTime[1] = mPrevEventTime[0] - maxDelta;
+ mPrevEventTime[2] = mPrevEventTime[1] - maxDelta;
+}
+
+void ScrollAnimationBezierPhysics::InitTimingFunction(
+ SMILKeySpline& aTimingFunction, nscoord aCurrentPos,
+ nscoord aCurrentVelocity, nscoord aDestination) {
+ if (aDestination == aCurrentPos ||
+ StaticPrefs::general_smoothScroll_currentVelocityWeighting() == 0) {
+ aTimingFunction.Init(
+ 0, 0, 1 - StaticPrefs::general_smoothScroll_stopDecelerationWeighting(),
+ 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 *
+ StaticPrefs::general_smoothScroll_currentVelocityWeighting();
+ double dxy = slope / normalization *
+ StaticPrefs::general_smoothScroll_currentVelocityWeighting();
+ aTimingFunction.Init(
+ dt, dxy,
+ 1 - StaticPrefs::general_smoothScroll_stopDecelerationWeighting(), 1);
+}
+
+nsPoint ScrollAnimationBezierPhysics::PositionAt(const TimeStamp& aTime) {
+ if (IsFinished(aTime)) {
+ return mDestination;
+ }
+
+ 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 ScrollAnimationBezierPhysics::VelocityAt(const TimeStamp& aTime) {
+ if (IsFinished(aTime)) {
+ return nsSize(0, 0);
+ }
+
+ double timeProgress = ProgressAt(aTime);
+ return nsSize(VelocityComponent(timeProgress, mTimingFunctionX, mStartPos.x,
+ mDestination.x),
+ VelocityComponent(timeProgress, mTimingFunctionY, mStartPos.y,
+ mDestination.y));
+}
+
+nscoord ScrollAnimationBezierPhysics::VelocityComponent(
+ double aTimeProgress, const SMILKeySpline& 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));
+}