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