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/. */
7 #include "ScrollAnimationBezierPhysics.h"
8 #include "mozilla/StaticPrefs_general.h"
10 using namespace mozilla
;
12 ScrollAnimationBezierPhysics::ScrollAnimationBezierPhysics(
13 const nsPoint
& aStartPos
,
14 const ScrollAnimationBezierPhysicsSettings
& aSettings
)
15 : mSettings(aSettings
), mStartPos(aStartPos
), mIsFirstIteration(true) {}
17 void ScrollAnimationBezierPhysics::Update(const TimeStamp
& aTime
,
18 const nsPoint
& aDestination
,
19 const nsSize
& aCurrentVelocity
) {
20 if (mIsFirstIteration
) {
21 InitializeHistory(aTime
);
24 TimeDuration duration
= ComputeDuration(aTime
);
25 nsSize currentVelocity
= aCurrentVelocity
;
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
) {
36 currentVelocity
= VelocityAt(aTime
);
37 mStartPos
= PositionAt(aTime
);
42 mDestination
= aDestination
;
43 InitTimingFunction(mTimingFunctionX
, mStartPos
.x
, currentVelocity
.width
,
45 InitTimingFunction(mTimingFunctionY
, mStartPos
.y
, currentVelocity
.height
,
47 mIsFirstIteration
= false;
50 void ScrollAnimationBezierPhysics::ApplyContentShift(
51 const CSSPoint
& aShiftDelta
) {
52 nsPoint shiftDelta
= CSSPoint::ToAppUnits(aShiftDelta
);
53 mStartPos
+= shiftDelta
;
54 mDestination
+= shiftDelta
;
57 TimeDuration
ScrollAnimationBezierPhysics::ComputeDuration(
58 const TimeStamp
& aTime
) {
59 // Average last 3 delta durations (rounding errors up to 2ms are negligible
61 int32_t eventsDeltaMs
= (aTime
- mPrevEventTime
[2]).ToMilliseconds() / 3;
62 mPrevEventTime
[2] = mPrevEventTime
[1];
63 mPrevEventTime
[1] = mPrevEventTime
[0];
64 mPrevEventTime
[0] = aTime
;
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).
73 clamped
<int32_t>(eventsDeltaMs
* mSettings
.mIntervalRatio
,
74 mSettings
.mMinMS
, mSettings
.mMaxMS
);
76 return TimeDuration::FromMilliseconds(durationMS
);
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.
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
;
92 void ScrollAnimationBezierPhysics::InitTimingFunction(
93 SMILKeySpline
& aTimingFunction
, nscoord aCurrentPos
,
94 nscoord aCurrentVelocity
, nscoord aDestination
) {
95 if (aDestination
== aCurrentPos
||
96 StaticPrefs::general_smoothScroll_currentVelocityWeighting() == 0) {
98 0, 0, 1 - StaticPrefs::general_smoothScroll_stopDecelerationWeighting(),
103 const TimeDuration oneSecond
= TimeDuration::FromSeconds(1);
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(
113 1 - StaticPrefs::general_smoothScroll_stopDecelerationWeighting(), 1);
116 nsPoint
ScrollAnimationBezierPhysics::PositionAt(const TimeStamp
& aTime
) {
117 if (IsFinished(aTime
)) {
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
));
129 nsSize
ScrollAnimationBezierPhysics::VelocityAt(const TimeStamp
& aTime
) {
130 if (IsFinished(aTime
)) {
134 double timeProgress
= ProgressAt(aTime
);
135 return nsSize(VelocityComponent(timeProgress
, mTimingFunctionX
, mStartPos
.x
,
137 VelocityComponent(timeProgress
, mTimingFunctionY
, mStartPos
.y
,
141 nscoord
ScrollAnimationBezierPhysics::VelocityComponent(
142 double aTimeProgress
, const SMILKeySpline
& aTimingFunction
, nscoord aStart
,
143 nscoord aDestination
) const {
145 aTimingFunction
.GetSplineDerivativeValues(aTimeProgress
, dt
, dxy
);
146 if (dt
== 0) return dxy
>= 0 ? nscoord_MAX
: nscoord_MIN
;
148 const TimeDuration oneSecond
= TimeDuration::FromSeconds(1);
149 double slope
= dxy
/ dt
;
150 return NSToCoordRound(slope
* (aDestination
- aStart
) /
151 (mDuration
/ oneSecond
));