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 #include "ScrollAnimationBezierPhysics.h"
8 #include "mozilla/StaticPrefs_general.h"
9 
10 using namespace mozilla;
11 
ScrollAnimationBezierPhysics(const nsPoint & aStartPos,const ScrollAnimationBezierPhysicsSettings & aSettings)12 ScrollAnimationBezierPhysics::ScrollAnimationBezierPhysics(
13     const nsPoint& aStartPos,
14     const ScrollAnimationBezierPhysicsSettings& aSettings)
15     : mSettings(aSettings), mStartPos(aStartPos), mIsFirstIteration(true) {}
16 
Update(const TimeStamp & aTime,const nsPoint & aDestination,const nsSize & aCurrentVelocity)17 void ScrollAnimationBezierPhysics::Update(const TimeStamp& aTime,
18                                           const nsPoint& aDestination,
19                                           const nsSize& aCurrentVelocity) {
20   if (mIsFirstIteration) {
21     InitializeHistory(aTime);
22   }
23 
24   TimeDuration duration = ComputeDuration(aTime);
25   nsSize currentVelocity = aCurrentVelocity;
26 
27   if (!mIsFirstIteration) {
28     // If an additional event has not changed the destination, then do not let
29     // another minimum duration reset slow things down.  If it would then
30     // instead continue with the existing timing function.
31     if (aDestination == mDestination &&
32         aTime + duration > mStartTime + mDuration) {
33       return;
34     }
35 
36     currentVelocity = VelocityAt(aTime);
37     mStartPos = PositionAt(aTime);
38   }
39 
40   mStartTime = aTime;
41   mDuration = duration;
42   mDestination = aDestination;
43   InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width,
44                      aDestination.x);
45   InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height,
46                      aDestination.y);
47   mIsFirstIteration = false;
48 }
49 
ApplyContentShift(const CSSPoint & aShiftDelta)50 void ScrollAnimationBezierPhysics::ApplyContentShift(
51     const CSSPoint& aShiftDelta) {
52   nsPoint shiftDelta = CSSPoint::ToAppUnits(aShiftDelta);
53   mStartPos += shiftDelta;
54   mDestination += shiftDelta;
55 }
56 
ComputeDuration(const TimeStamp & aTime)57 TimeDuration ScrollAnimationBezierPhysics::ComputeDuration(
58     const TimeStamp& aTime) {
59   // Average last 3 delta durations (rounding errors up to 2ms are negligible
60   // for us)
61   int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3;
62   mPrevEventTime[2] = mPrevEventTime[1];
63   mPrevEventTime[1] = mPrevEventTime[0];
64   mPrevEventTime[0] = aTime;
65 
66   // Modulate duration according to events rate (quicker events -> shorter
67   // durations). The desired effect is to use longer duration when scrolling
68   // slowly, such that it's easier to follow, but reduce the duration to make it
69   // feel more snappy when scrolling quickly. To reduce fluctuations of the
70   // duration, we average event intervals using the recent 4 timestamps (now +
71   // three prev -> 3 intervals).
72   int32_t durationMS =
73       clamped<int32_t>(eventsDeltaMs * mSettings.mIntervalRatio,
74                        mSettings.mMinMS, mSettings.mMaxMS);
75 
76   return TimeDuration::FromMilliseconds(durationMS);
77 }
78 
InitializeHistory(const TimeStamp & aTime)79 void ScrollAnimationBezierPhysics::InitializeHistory(const TimeStamp& aTime) {
80   // Starting a new scroll (i.e. not when extending an existing scroll
81   // animation), create imaginary prev timestamps with maximum relevant
82   // intervals between them.
83 
84   // Longest relevant interval (which results in maximum duration)
85   TimeDuration maxDelta = TimeDuration::FromMilliseconds(
86       mSettings.mMaxMS / mSettings.mIntervalRatio);
87   mPrevEventTime[0] = aTime - maxDelta;
88   mPrevEventTime[1] = mPrevEventTime[0] - maxDelta;
89   mPrevEventTime[2] = mPrevEventTime[1] - maxDelta;
90 }
91 
InitTimingFunction(SMILKeySpline & aTimingFunction,nscoord aCurrentPos,nscoord aCurrentVelocity,nscoord aDestination)92 void ScrollAnimationBezierPhysics::InitTimingFunction(
93     SMILKeySpline& aTimingFunction, nscoord aCurrentPos,
94     nscoord aCurrentVelocity, nscoord aDestination) {
95   if (aDestination == aCurrentPos ||
96       StaticPrefs::general_smoothScroll_currentVelocityWeighting() == 0) {
97     aTimingFunction.Init(
98         0, 0, 1 - StaticPrefs::general_smoothScroll_stopDecelerationWeighting(),
99         1);
100     return;
101   }
102 
103   const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
104   double slope =
105       aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos);
106   double normalization = sqrt(1.0 + slope * slope);
107   double dt = 1.0 / normalization *
108               StaticPrefs::general_smoothScroll_currentVelocityWeighting();
109   double dxy = slope / normalization *
110                StaticPrefs::general_smoothScroll_currentVelocityWeighting();
111   aTimingFunction.Init(
112       dt, dxy,
113       1 - StaticPrefs::general_smoothScroll_stopDecelerationWeighting(), 1);
114 }
115 
PositionAt(const TimeStamp & aTime)116 nsPoint ScrollAnimationBezierPhysics::PositionAt(const TimeStamp& aTime) {
117   if (IsFinished(aTime)) {
118     return mDestination;
119   }
120 
121   double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
122   double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
123   return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x +
124                                 progressX * mDestination.x),
125                  NSToCoordRound((1 - progressY) * mStartPos.y +
126                                 progressY * mDestination.y));
127 }
128 
VelocityAt(const TimeStamp & aTime)129 nsSize ScrollAnimationBezierPhysics::VelocityAt(const TimeStamp& aTime) {
130   if (IsFinished(aTime)) {
131     return nsSize(0, 0);
132   }
133 
134   double timeProgress = ProgressAt(aTime);
135   return nsSize(VelocityComponent(timeProgress, mTimingFunctionX, mStartPos.x,
136                                   mDestination.x),
137                 VelocityComponent(timeProgress, mTimingFunctionY, mStartPos.y,
138                                   mDestination.y));
139 }
140 
VelocityComponent(double aTimeProgress,const SMILKeySpline & aTimingFunction,nscoord aStart,nscoord aDestination) const141 nscoord ScrollAnimationBezierPhysics::VelocityComponent(
142     double aTimeProgress, const SMILKeySpline& aTimingFunction, nscoord aStart,
143     nscoord aDestination) const {
144   double dt, dxy;
145   aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy);
146   if (dt == 0) return dxy >= 0 ? nscoord_MAX : nscoord_MIN;
147 
148   const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
149   double slope = dxy / dt;
150   return NSToCoordRound(slope * (aDestination - aStart) /
151                         (mDuration / oneSecond));
152 }
153