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