[WTF] Move currentCPUTime and sleep(Seconds) to CPUTime.h and Seconds.h respectively
[WebKit-https.git] / Source / WebCore / platform / ScrollAnimationSmooth.cpp
1 /*
2  * Copyright (C) 2016 Igalia S.L.
3  * Copyright (C) 2015 Apple Inc. All rights reserved.
4  * Copyright (c) 2011, Google Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
19  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25  * THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "ScrollAnimationSmooth.h"
30
31 #if ENABLE(SMOOTH_SCROLLING)
32
33 #include "FloatPoint.h"
34 #include "ScrollableArea.h"
35
36 namespace WebCore {
37
38 static const double frameRate = 60;
39 static const Seconds tickTime = 1_s / frameRate;
40 static const Seconds minimumTimerInterval { 1_ms };
41
42 ScrollAnimationSmooth::ScrollAnimationSmooth(ScrollableArea& scrollableArea, const FloatPoint& position, WTF::Function<void (FloatPoint&&)>&& notifyPositionChangedFunction)
43     : ScrollAnimation(scrollableArea)
44     , m_notifyPositionChangedFunction(WTFMove(notifyPositionChangedFunction))
45     , m_horizontalData(position.x(), scrollableArea.visibleWidth())
46     , m_verticalData(position.y(), scrollableArea.visibleHeight())
47     , m_animationTimer(*this, &ScrollAnimationSmooth::animationTimerFired)
48 {
49 }
50
51 bool ScrollAnimationSmooth::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
52 {
53     float minScrollPosition;
54     float maxScrollPosition;
55     if (orientation == HorizontalScrollbar) {
56         minScrollPosition = m_scrollableArea.minimumScrollPosition().x();
57         maxScrollPosition = m_scrollableArea.maximumScrollPosition().x();
58     } else {
59         minScrollPosition = m_scrollableArea.minimumScrollPosition().y();
60         maxScrollPosition = m_scrollableArea.maximumScrollPosition().y();
61     }
62     bool needToScroll = updatePerAxisData(orientation == HorizontalScrollbar ? m_horizontalData : m_verticalData, granularity, step * multiplier, minScrollPosition, maxScrollPosition);
63     if (needToScroll && !animationTimerActive()) {
64         m_startTime = orientation == HorizontalScrollbar ? m_horizontalData.startTime : m_verticalData.startTime;
65         animationTimerFired();
66     }
67     return needToScroll;
68 }
69
70 void ScrollAnimationSmooth::stop()
71 {
72     m_animationTimer.stop();
73 }
74
75 void ScrollAnimationSmooth::updateVisibleLengths()
76 {
77     m_horizontalData.visibleLength = m_scrollableArea.visibleWidth();
78     m_verticalData.visibleLength = m_scrollableArea.visibleHeight();
79 }
80
81 void ScrollAnimationSmooth::setCurrentPosition(const FloatPoint& position)
82 {
83     stop();
84     m_horizontalData = PerAxisData(position.x(), m_horizontalData.visibleLength);
85     m_verticalData = PerAxisData(position.y(), m_verticalData.visibleLength);
86 }
87
88 ScrollAnimationSmooth::~ScrollAnimationSmooth() = default;
89
90 static inline double curveAt(ScrollAnimationSmooth::Curve curve, double t)
91 {
92     switch (curve) {
93     case ScrollAnimationSmooth::Curve::Linear:
94         return t;
95     case ScrollAnimationSmooth::Curve::Quadratic:
96         return t * t;
97     case ScrollAnimationSmooth::Curve::Cubic:
98         return t * t * t;
99     case ScrollAnimationSmooth::Curve::Quartic:
100         return t * t * t * t;
101     case ScrollAnimationSmooth::Curve::Bounce:
102         // Time base is chosen to keep the bounce points simpler:
103         // 1 (half bounce coming in) + 1 + .5 + .25
104         static const double timeBase = 2.75;
105         static const double timeBaseSquared = timeBase * timeBase;
106         if (t < 1 / timeBase)
107             return timeBaseSquared * t * t;
108         if (t < 2 / timeBase) {
109             // Invert a [-.5,.5] quadratic parabola, center it in [1,2].
110             double t1 = t - 1.5 / timeBase;
111             const double parabolaAtEdge = 1 - .5 * .5;
112             return timeBaseSquared * t1 * t1 + parabolaAtEdge;
113         }
114         if (t < 2.5 / timeBase) {
115             // Invert a [-.25,.25] quadratic parabola, center it in [2,2.5].
116             double t2 = t - 2.25 / timeBase;
117             const double parabolaAtEdge = 1 - .25 * .25;
118             return timeBaseSquared * t2 * t2 + parabolaAtEdge;
119         }
120         // Invert a [-.125,.125] quadratic parabola, center it in [2.5,2.75].
121         const double parabolaAtEdge = 1 - .125 * .125;
122         t -= 2.625 / timeBase;
123         return timeBaseSquared * t * t + parabolaAtEdge;
124     }
125     ASSERT_NOT_REACHED();
126     return 0;
127 }
128
129 static inline double attackCurve(ScrollAnimationSmooth::Curve curve, double deltaTime, double curveT, double startPosition, double attackPosition)
130 {
131     double t = deltaTime / curveT;
132     double positionFactor = curveAt(curve, t);
133     return startPosition + positionFactor * (attackPosition - startPosition);
134 }
135
136 static inline double releaseCurve(ScrollAnimationSmooth::Curve curve, double deltaTime, double curveT, double releasePosition, double desiredPosition)
137 {
138     double t = deltaTime / curveT;
139     double positionFactor = 1 - curveAt(curve, 1 - t);
140     return releasePosition + (positionFactor * (desiredPosition - releasePosition));
141 }
142
143 static inline double coastCurve(ScrollAnimationSmooth::Curve curve, double factor)
144 {
145     return 1 - curveAt(curve, 1 - factor);
146 }
147
148 static inline double curveIntegralAt(ScrollAnimationSmooth::Curve curve, double t)
149 {
150     switch (curve) {
151     case ScrollAnimationSmooth::Curve::Linear:
152         return t * t / 2;
153     case ScrollAnimationSmooth::Curve::Quadratic:
154         return t * t * t / 3;
155     case ScrollAnimationSmooth::Curve::Cubic:
156         return t * t * t * t / 4;
157     case ScrollAnimationSmooth::Curve::Quartic:
158         return t * t * t * t * t / 5;
159     case ScrollAnimationSmooth::Curve::Bounce:
160         static const double timeBase = 2.75;
161         static const double timeBaseSquared = timeBase * timeBase;
162         static const double timeBaseSquaredOverThree = timeBaseSquared / 3;
163         double area;
164         double t1 = std::min(t, 1 / timeBase);
165         area = timeBaseSquaredOverThree * t1 * t1 * t1;
166         if (t < 1 / timeBase)
167             return area;
168
169         t1 = std::min(t - 1 / timeBase, 1 / timeBase);
170         // The integral of timeBaseSquared * (t1 - .5 / timeBase) * (t1 - .5 / timeBase) + parabolaAtEdge
171         static const double secondInnerOffset = timeBaseSquared * .5 / timeBase;
172         double bounceArea = t1 * (t1 * (timeBaseSquaredOverThree * t1 - secondInnerOffset) + 1);
173         area += bounceArea;
174         if (t < 2 / timeBase)
175             return area;
176
177         t1 = std::min(t - 2 / timeBase, 0.5 / timeBase);
178         // The integral of timeBaseSquared * (t1 - .25 / timeBase) * (t1 - .25 / timeBase) + parabolaAtEdge
179         static const double thirdInnerOffset = timeBaseSquared * .25 / timeBase;
180         bounceArea =  t1 * (t1 * (timeBaseSquaredOverThree * t1 - thirdInnerOffset) + 1);
181         area += bounceArea;
182         if (t < 2.5 / timeBase)
183             return area;
184
185         t1 = t - 2.5 / timeBase;
186         // The integral of timeBaseSquared * (t1 - .125 / timeBase) * (t1 - .125 / timeBase) + parabolaAtEdge
187         static const double fourthInnerOffset = timeBaseSquared * .125 / timeBase;
188         bounceArea = t1 * (t1 * (timeBaseSquaredOverThree * t1 - fourthInnerOffset) + 1);
189         area += bounceArea;
190         return area;
191     }
192     ASSERT_NOT_REACHED();
193     return 0;
194 }
195
196 static inline double attackArea(ScrollAnimationSmooth::Curve curve, double startT, double endT)
197 {
198     double startValue = curveIntegralAt(curve, startT);
199     double endValue = curveIntegralAt(curve, endT);
200     return endValue - startValue;
201 }
202
203 static inline double releaseArea(ScrollAnimationSmooth::Curve curve, double startT, double endT)
204 {
205     double startValue = curveIntegralAt(curve, 1 - endT);
206     double endValue = curveIntegralAt(curve, 1 - startT);
207     return endValue - startValue;
208 }
209
210 static inline void getAnimationParametersForGranularity(ScrollGranularity granularity, Seconds& animationTime, Seconds& repeatMinimumSustainTime, Seconds& attackTime, Seconds& releaseTime, ScrollAnimationSmooth::Curve& coastTimeCurve, Seconds& maximumCoastTime)
211 {
212     switch (granularity) {
213     case ScrollByDocument:
214         animationTime = tickTime * 10;
215         repeatMinimumSustainTime = tickTime * 10;
216         attackTime = tickTime * 10;
217         releaseTime = tickTime * 10;
218         coastTimeCurve = ScrollAnimationSmooth::Curve::Linear;
219         maximumCoastTime = 1_s;
220         break;
221     case ScrollByLine:
222         animationTime = tickTime * 10;
223         repeatMinimumSustainTime = tickTime * 7;
224         attackTime = tickTime * 3;
225         releaseTime = tickTime * 3;
226         coastTimeCurve = ScrollAnimationSmooth::Curve::Linear;
227         maximumCoastTime = 1_s;
228         break;
229     case ScrollByPage:
230         animationTime = tickTime * 15;
231         repeatMinimumSustainTime = tickTime * 10;
232         attackTime = tickTime * 5;
233         releaseTime = tickTime * 5;
234         coastTimeCurve = ScrollAnimationSmooth::Curve::Linear;
235         maximumCoastTime = 1_s;
236         break;
237     case ScrollByPixel:
238         animationTime = tickTime * 11;
239         repeatMinimumSustainTime = tickTime * 2;
240         attackTime = tickTime * 3;
241         releaseTime = tickTime * 3;
242         coastTimeCurve = ScrollAnimationSmooth::Curve::Quadratic;
243         maximumCoastTime = 1250_ms;
244         break;
245     default:
246         ASSERT_NOT_REACHED();
247     }
248 }
249
250 bool ScrollAnimationSmooth::updatePerAxisData(PerAxisData& data, ScrollGranularity granularity, float delta, float minScrollPosition, float maxScrollPosition)
251 {
252     if (!data.startTime || !delta || (delta < 0) != (data.desiredPosition - data.currentPosition < 0)) {
253         data.desiredPosition = data.currentPosition;
254         data.startTime = { };
255     }
256     float newPosition = data.desiredPosition + delta;
257
258     newPosition = std::max(std::min(newPosition, maxScrollPosition), minScrollPosition);
259
260     if (newPosition == data.desiredPosition)
261         return false;
262
263     Seconds animationTime, repeatMinimumSustainTime, attackTime, releaseTime, maximumCoastTime;
264     Curve coastTimeCurve;
265     getAnimationParametersForGranularity(granularity, animationTime, repeatMinimumSustainTime, attackTime, releaseTime, coastTimeCurve, maximumCoastTime);
266
267     data.desiredPosition = newPosition;
268     if (!data.startTime)
269         data.attackTime = attackTime;
270     data.animationTime = animationTime;
271     data.releaseTime = releaseTime;
272
273     // Prioritize our way out of over constraint.
274     if (data.attackTime + data.releaseTime > data.animationTime) {
275         if (data.releaseTime > data.animationTime)
276             data.releaseTime = data.animationTime;
277         data.attackTime = data.animationTime - data.releaseTime;
278     }
279
280     if (!data.startTime) {
281         // FIXME: This should be the time from the event that got us here.
282         data.startTime = MonotonicTime::now() - tickTime / 2.;
283         data.startPosition = data.currentPosition;
284         data.lastAnimationTime = data.startTime;
285     }
286     data.startVelocity = data.currentVelocity;
287
288     double remainingDelta = data.desiredPosition - data.currentPosition;
289     double attackAreaLeft = 0;
290     Seconds deltaTime = data.lastAnimationTime - data.startTime;
291     Seconds attackTimeLeft = std::max(0_s, data.attackTime - deltaTime);
292     Seconds timeLeft = data.animationTime - deltaTime;
293     Seconds minTimeLeft = data.releaseTime + std::min(repeatMinimumSustainTime, data.animationTime - data.releaseTime - attackTimeLeft);
294     if (timeLeft < minTimeLeft) {
295         data.animationTime = deltaTime + minTimeLeft;
296         timeLeft = minTimeLeft;
297     }
298
299     if (maximumCoastTime > (repeatMinimumSustainTime + releaseTime)) {
300         double targetMaxCoastVelocity = data.visibleLength * .25 * frameRate;
301         // This needs to be as minimal as possible while not being intrusive to page up/down.
302         double minCoastDelta = data.visibleLength;
303
304         if (fabs(remainingDelta) > minCoastDelta) {
305             double maxCoastDelta = maximumCoastTime.value() * targetMaxCoastVelocity;
306             double coastFactor = std::min(1., (fabs(remainingDelta) - minCoastDelta) / (maxCoastDelta - minCoastDelta));
307
308             // We could play with the curve here - linear seems a little soft. Initial testing makes me want to feed into the sustain time more aggressively.
309             Seconds coastMinTimeLeft = std::min(maximumCoastTime, minTimeLeft + (maximumCoastTime - minTimeLeft) * coastCurve(coastTimeCurve, coastFactor));
310
311             if (Seconds additionalTime = std::max(0_s, coastMinTimeLeft - minTimeLeft)) {
312                 Seconds additionalReleaseTime = std::min(additionalTime, additionalTime * (releaseTime / (releaseTime + repeatMinimumSustainTime)));
313                 data.releaseTime = releaseTime + additionalReleaseTime;
314                 data.animationTime = deltaTime + coastMinTimeLeft;
315                 timeLeft = coastMinTimeLeft;
316             }
317         }
318     }
319
320     Seconds releaseTimeLeft = std::min(timeLeft, data.releaseTime);
321     Seconds sustainTimeLeft = std::max(0_s, timeLeft - releaseTimeLeft - attackTimeLeft);
322     if (attackTimeLeft) {
323         double attackSpot = deltaTime / data.attackTime;
324         attackAreaLeft = attackArea(Curve::Cubic, attackSpot, 1) * data.attackTime.value();
325     }
326
327     double releaseSpot = (data.releaseTime - releaseTimeLeft) / data.releaseTime;
328     double releaseAreaLeft = releaseArea(Curve::Cubic, releaseSpot, 1) * data.releaseTime.value();
329
330     data.desiredVelocity = remainingDelta / (attackAreaLeft + sustainTimeLeft.value() + releaseAreaLeft);
331     data.releasePosition = data.desiredPosition - data.desiredVelocity * releaseAreaLeft;
332     if (attackAreaLeft)
333         data.attackPosition = data.startPosition + data.desiredVelocity * attackAreaLeft;
334     else
335         data.attackPosition = data.releasePosition - (data.animationTime - data.releaseTime - data.attackTime).value() * data.desiredVelocity;
336
337     if (sustainTimeLeft) {
338         double roundOff = data.releasePosition - ((attackAreaLeft ? data.attackPosition : data.currentPosition) + data.desiredVelocity * sustainTimeLeft.value());
339         data.desiredVelocity += roundOff / sustainTimeLeft.value();
340     }
341
342     return true;
343 }
344
345 bool ScrollAnimationSmooth::animateScroll(PerAxisData& data, MonotonicTime currentTime)
346 {
347     if (!data.startTime)
348         return false;
349
350     Seconds lastScrollInterval = currentTime - data.lastAnimationTime;
351     if (lastScrollInterval < minimumTimerInterval)
352         return true;
353
354     data.lastAnimationTime = currentTime;
355
356     Seconds deltaTime = currentTime - data.startTime;
357     double newPosition = data.currentPosition;
358
359     if (deltaTime > data.animationTime) {
360         data = PerAxisData(data.desiredPosition, data.visibleLength);
361         return false;
362     }
363     if (deltaTime < data.attackTime)
364         newPosition = attackCurve(Curve::Cubic, deltaTime.value(), data.attackTime.value(), data.startPosition, data.attackPosition);
365     else if (deltaTime < (data.animationTime - data.releaseTime))
366         newPosition = data.attackPosition + (deltaTime - data.attackTime).value() * data.desiredVelocity;
367     else {
368         // release is based on targeting the exact final position.
369         Seconds releaseDeltaT = deltaTime - (data.animationTime - data.releaseTime);
370         newPosition = releaseCurve(Curve::Cubic, releaseDeltaT.value(), data.releaseTime.value(), data.releasePosition, data.desiredPosition);
371     }
372
373     // Normalize velocity to a per second amount. Could be used to check for jank.
374     if (lastScrollInterval > 0_s)
375         data.currentVelocity = (newPosition - data.currentPosition) / lastScrollInterval.value();
376     data.currentPosition = newPosition;
377
378     return true;
379 }
380
381 void ScrollAnimationSmooth::animationTimerFired()
382 {
383     MonotonicTime currentTime = MonotonicTime::now();
384     Seconds deltaToNextFrame = 1_s * ceil((currentTime - m_startTime).value() * frameRate) / frameRate - (currentTime - m_startTime);
385     currentTime += deltaToNextFrame;
386
387     bool continueAnimation = false;
388     if (animateScroll(m_horizontalData, currentTime))
389         continueAnimation = true;
390     if (animateScroll(m_verticalData, currentTime))
391         continueAnimation = true;
392
393     if (continueAnimation)
394         startNextTimer(std::max(minimumTimerInterval, deltaToNextFrame));
395
396     m_notifyPositionChangedFunction(FloatPoint(m_horizontalData.currentPosition, m_verticalData.currentPosition));
397 }
398
399 void ScrollAnimationSmooth::startNextTimer(Seconds delay)
400 {
401     m_animationTimer.startOneShot(delay);
402 }
403
404 bool ScrollAnimationSmooth::animationTimerActive() const
405 {
406     return m_animationTimer.isActive();
407 }
408
409 } // namespace WebCore
410
411 #endif // ENABLE(SMOOTH_SCROLLING)