2
* Copyright (c) 2011, Google Inc. All rights reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are
8
* * Redistributions of source code must retain the above copyright
9
* notice, this list of conditions and the following disclaimer.
10
* * Redistributions in binary form must reproduce the above
11
* copyright notice, this list of conditions and the following disclaimer
12
* in the documentation and/or other materials provided with the
14
* * Neither the name of Google Inc. nor the names of its
15
* contributors may be used to endorse or promote products derived from
16
* this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
#if ENABLE(SMOOTH_SCROLLING)
35
#include "ScrollAnimatorNone.h"
37
#include "FloatPoint.h"
38
#include "NotImplemented.h"
39
#include <wtf/OwnArrayPtr.h>
40
#include "PlatformGestureEvent.h"
41
#include "ScrollableArea.h"
42
#include "ScrollbarTheme.h"
44
#include <wtf/CurrentTime.h>
45
#include <wtf/PassOwnPtr.h>
47
#if PLATFORM(CHROMIUM)
48
#include "TraceEvent.h"
55
const double kFrameRate = 60;
56
const double kTickTime = 1 / kFrameRate;
57
const double kMinimumTimerInterval = .001;
58
const double kZoomTicks = 11;
60
#if !(PLATFORM(BLACKBERRY))
61
PassOwnPtr<ScrollAnimator> ScrollAnimator::create(ScrollableArea* scrollableArea)
63
if (scrollableArea && scrollableArea->scrollAnimatorEnabled())
64
return adoptPtr(new ScrollAnimatorNone(scrollableArea));
65
return adoptPtr(new ScrollAnimator(scrollableArea));
69
ScrollAnimatorNone::Parameters::Parameters()
74
ScrollAnimatorNone::Parameters::Parameters(bool isEnabled, double animationTime, double repeatMinimumSustainTime, Curve attackCurve, double attackTime, Curve releaseCurve, double releaseTime, Curve coastTimeCurve, double maximumCoastTime)
75
: m_isEnabled(isEnabled)
76
, m_animationTime(animationTime)
77
, m_repeatMinimumSustainTime(repeatMinimumSustainTime)
78
, m_attackCurve(attackCurve)
79
, m_attackTime(attackTime)
80
, m_releaseCurve(releaseCurve)
81
, m_releaseTime(releaseTime)
82
, m_coastTimeCurve(coastTimeCurve)
83
, m_maximumCoastTime(maximumCoastTime)
87
double ScrollAnimatorNone::PerAxisData::curveAt(Curve curve, double t)
99
// Time base is chosen to keep the bounce points simpler:
100
// 1 (half bounce coming in) + 1 + .5 + .25
101
const double kTimeBase = 2.75;
102
const double kTimeBaseSquared = kTimeBase * kTimeBase;
103
if (t < 1 / kTimeBase)
104
return kTimeBaseSquared * t * t;
105
if (t < 2 / kTimeBase) {
106
// Invert a [-.5,.5] quadratic parabola, center it in [1,2].
107
double t1 = t - 1.5 / kTimeBase;
108
const double kParabolaAtEdge = 1 - .5 * .5;
109
return kTimeBaseSquared * t1 * t1 + kParabolaAtEdge;
111
if (t < 2.5 / kTimeBase) {
112
// Invert a [-.25,.25] quadratic parabola, center it in [2,2.5].
113
double t2 = t - 2.25 / kTimeBase;
114
const double kParabolaAtEdge = 1 - .25 * .25;
115
return kTimeBaseSquared * t2 * t2 + kParabolaAtEdge;
117
// Invert a [-.125,.125] quadratic parabola, center it in [2.5,2.75].
118
const double kParabolaAtEdge = 1 - .125 * .125;
119
t -= 2.625 / kTimeBase;
120
return kTimeBaseSquared * t * t + kParabolaAtEdge;
122
ASSERT_NOT_REACHED();
126
double ScrollAnimatorNone::PerAxisData::attackCurve(Curve curve, double deltaTime, double curveT, double startPosition, double attackPosition)
128
double t = deltaTime / curveT;
129
double positionFactor = curveAt(curve, t);
130
return startPosition + positionFactor * (attackPosition - startPosition);
133
double ScrollAnimatorNone::PerAxisData::releaseCurve(Curve curve, double deltaTime, double curveT, double releasePosition, double desiredPosition)
135
double t = deltaTime / curveT;
136
double positionFactor = 1 - curveAt(curve, 1 - t);
137
return releasePosition + (positionFactor * (desiredPosition - releasePosition));
140
double ScrollAnimatorNone::PerAxisData::coastCurve(Curve curve, double factor)
142
return 1 - curveAt(curve, 1 - factor);
145
double ScrollAnimatorNone::PerAxisData::curveIntegralAt(Curve curve, double t)
151
return t * t * t / 3;
153
return t * t * t * t / 4;
155
return t * t * t * t * t / 5;
157
const double kTimeBase = 2.75;
158
const double kTimeBaseSquared = kTimeBase * kTimeBase;
159
const double kTimeBaseSquaredOverThree = kTimeBaseSquared / 3;
161
double t1 = min(t, 1 / kTimeBase);
162
area = kTimeBaseSquaredOverThree * t1 * t1 * t1;
163
if (t < 1 / kTimeBase)
166
t1 = min(t - 1 / kTimeBase, 1 / kTimeBase);
167
// The integral of kTimeBaseSquared * (t1 - .5 / kTimeBase) * (t1 - .5 / kTimeBase) + kParabolaAtEdge
168
const double kSecondInnerOffset = kTimeBaseSquared * .5 / kTimeBase;
169
double bounceArea = t1 * (t1 * (kTimeBaseSquaredOverThree * t1 - kSecondInnerOffset) + 1);
171
if (t < 2 / kTimeBase)
174
t1 = min(t - 2 / kTimeBase, 0.5 / kTimeBase);
175
// The integral of kTimeBaseSquared * (t1 - .25 / kTimeBase) * (t1 - .25 / kTimeBase) + kParabolaAtEdge
176
const double kThirdInnerOffset = kTimeBaseSquared * .25 / kTimeBase;
177
bounceArea = t1 * (t1 * (kTimeBaseSquaredOverThree * t1 - kThirdInnerOffset) + 1);
179
if (t < 2.5 / kTimeBase)
182
t1 = t - 2.5 / kTimeBase;
183
// The integral of kTimeBaseSquared * (t1 - .125 / kTimeBase) * (t1 - .125 / kTimeBase) + kParabolaAtEdge
184
const double kFourthInnerOffset = kTimeBaseSquared * .125 / kTimeBase;
185
bounceArea = t1 * (t1 * (kTimeBaseSquaredOverThree * t1 - kFourthInnerOffset) + 1);
189
ASSERT_NOT_REACHED();
193
double ScrollAnimatorNone::PerAxisData::attackArea(Curve curve, double startT, double endT)
195
double startValue = curveIntegralAt(curve, startT);
196
double endValue = curveIntegralAt(curve, endT);
197
return endValue - startValue;
200
double ScrollAnimatorNone::PerAxisData::releaseArea(Curve curve, double startT, double endT)
202
double startValue = curveIntegralAt(curve, 1 - endT);
203
double endValue = curveIntegralAt(curve, 1 - startT);
204
return endValue - startValue;
207
ScrollAnimatorNone::PerAxisData::PerAxisData(ScrollAnimatorNone* parent, float* currentPosition, int visibleLength)
208
: m_currentPosition(currentPosition)
209
, m_visibleLength(visibleLength)
214
void ScrollAnimatorNone::PerAxisData::reset()
216
m_currentVelocity = 0;
218
m_desiredPosition = 0;
219
m_desiredVelocity = 0;
226
m_lastAnimationTime = 0;
228
m_attackPosition = 0;
230
m_attackCurve = Quadratic;
232
m_releasePosition = 0;
234
m_releaseCurve = Quadratic;
238
bool ScrollAnimatorNone::PerAxisData::updateDataFromParameters(float step, float multiplier, float scrollableSize, double currentTime, Parameters* parameters)
240
float delta = step * multiplier;
241
if (!m_startTime || !delta || (delta < 0) != (m_desiredPosition - *m_currentPosition < 0)) {
242
m_desiredPosition = *m_currentPosition;
245
float newPosition = m_desiredPosition + delta;
247
if (newPosition < 0 || newPosition > scrollableSize)
248
newPosition = max(min(newPosition, scrollableSize), 0.0f);
250
if (newPosition == m_desiredPosition)
253
m_desiredPosition = newPosition;
256
m_attackTime = parameters->m_attackTime;
257
m_attackCurve = parameters->m_attackCurve;
259
m_animationTime = parameters->m_animationTime;
260
m_releaseTime = parameters->m_releaseTime;
261
m_releaseCurve = parameters->m_releaseCurve;
263
// Prioritize our way out of over constraint.
264
if (m_attackTime + m_releaseTime > m_animationTime) {
265
if (m_releaseTime > m_animationTime)
266
m_releaseTime = m_animationTime;
267
m_attackTime = m_animationTime - m_releaseTime;
271
// FIXME: This should be the time from the event that got us here.
272
m_startTime = currentTime - kTickTime / 2;
273
m_startPosition = *m_currentPosition;
274
m_lastAnimationTime = m_startTime;
276
m_startVelocity = m_currentVelocity;
278
double remainingDelta = m_desiredPosition - *m_currentPosition;
280
double attackAreaLeft = 0;
282
double deltaTime = m_lastAnimationTime - m_startTime;
283
double attackTimeLeft = max(0., m_attackTime - deltaTime);
284
double timeLeft = m_animationTime - deltaTime;
285
double minTimeLeft = m_releaseTime + min(parameters->m_repeatMinimumSustainTime, m_animationTime - m_releaseTime - attackTimeLeft);
286
if (timeLeft < minTimeLeft) {
287
m_animationTime = deltaTime + minTimeLeft;
288
timeLeft = minTimeLeft;
291
if (parameters->m_maximumCoastTime > (parameters->m_repeatMinimumSustainTime + parameters->m_releaseTime)) {
292
double targetMaxCoastVelocity = m_visibleLength * .25 * kFrameRate;
293
// This needs to be as minimal as possible while not being intrusive to page up/down.
294
double minCoastDelta = m_visibleLength;
296
if (fabs(remainingDelta) > minCoastDelta) {
297
double maxCoastDelta = parameters->m_maximumCoastTime * targetMaxCoastVelocity;
298
double coastFactor = min(1., (fabs(remainingDelta) - minCoastDelta) / (maxCoastDelta - minCoastDelta));
300
// 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.
301
double coastMinTimeLeft = min(parameters->m_maximumCoastTime, minTimeLeft + coastCurve(parameters->m_coastTimeCurve, coastFactor) * (parameters->m_maximumCoastTime - minTimeLeft));
303
double additionalTime = max(0., coastMinTimeLeft - minTimeLeft);
304
if (additionalTime) {
305
double additionalReleaseTime = min(additionalTime, parameters->m_releaseTime / (parameters->m_releaseTime + parameters->m_repeatMinimumSustainTime) * additionalTime);
306
m_releaseTime = parameters->m_releaseTime + additionalReleaseTime;
307
m_animationTime = deltaTime + coastMinTimeLeft;
308
timeLeft = coastMinTimeLeft;
313
double releaseTimeLeft = min(timeLeft, m_releaseTime);
314
double sustainTimeLeft = max(0., timeLeft - releaseTimeLeft - attackTimeLeft);
316
if (attackTimeLeft) {
317
double attackSpot = deltaTime / m_attackTime;
318
attackAreaLeft = attackArea(m_attackCurve, attackSpot, 1) * m_attackTime;
321
double releaseSpot = (m_releaseTime - releaseTimeLeft) / m_releaseTime;
322
double releaseAreaLeft = releaseArea(m_releaseCurve, releaseSpot, 1) * m_releaseTime;
324
m_desiredVelocity = remainingDelta / (attackAreaLeft + sustainTimeLeft + releaseAreaLeft);
325
m_releasePosition = m_desiredPosition - m_desiredVelocity * releaseAreaLeft;
327
m_attackPosition = m_startPosition + m_desiredVelocity * attackAreaLeft;
329
m_attackPosition = m_releasePosition - (m_animationTime - m_releaseTime - m_attackTime) * m_desiredVelocity;
331
if (sustainTimeLeft) {
332
double roundOff = m_releasePosition - ((attackAreaLeft ? m_attackPosition : *m_currentPosition) + m_desiredVelocity * sustainTimeLeft);
333
m_desiredVelocity += roundOff / sustainTimeLeft;
339
// FIXME: Add in jank detection trace events into this function.
340
bool ScrollAnimatorNone::PerAxisData::animateScroll(double currentTime)
342
double lastScrollInterval = currentTime - m_lastAnimationTime;
343
if (lastScrollInterval < kMinimumTimerInterval)
346
m_lastAnimationTime = currentTime;
348
double deltaTime = currentTime - m_startTime;
349
double newPosition = *m_currentPosition;
351
if (deltaTime > m_animationTime) {
352
*m_currentPosition = m_desiredPosition;
356
if (deltaTime < m_attackTime)
357
newPosition = attackCurve(m_attackCurve, deltaTime, m_attackTime, m_startPosition, m_attackPosition);
358
else if (deltaTime < (m_animationTime - m_releaseTime))
359
newPosition = m_attackPosition + (deltaTime - m_attackTime) * m_desiredVelocity;
361
// release is based on targeting the exact final position.
362
double releaseDeltaT = deltaTime - (m_animationTime - m_releaseTime);
363
newPosition = releaseCurve(m_releaseCurve, releaseDeltaT, m_releaseTime, m_releasePosition, m_desiredPosition);
366
// Normalize velocity to a per second amount. Could be used to check for jank.
367
if (lastScrollInterval > 0)
368
m_currentVelocity = (newPosition - *m_currentPosition) / lastScrollInterval;
369
*m_currentPosition = newPosition;
374
void ScrollAnimatorNone::PerAxisData::updateVisibleLength(int visibleLength)
376
m_visibleLength = visibleLength;
379
ScrollAnimatorNone::ScrollAnimatorNone(ScrollableArea* scrollableArea)
380
: ScrollAnimator(scrollableArea)
381
, m_horizontalData(this, &m_currentPosX, scrollableArea->visibleWidth())
382
, m_verticalData(this, &m_currentPosY, scrollableArea->visibleHeight())
384
#if USE(REQUEST_ANIMATION_FRAME_TIMER)
385
, m_animationTimer(this, &ScrollAnimatorNone::animationTimerFired)
387
, m_animationActive(false)
392
ScrollAnimatorNone::~ScrollAnimatorNone()
394
stopAnimationTimerIfNeeded();
397
ScrollAnimatorNone::Parameters ScrollAnimatorNone::parametersForScrollGranularity(ScrollGranularity granularity) const
400
switch (granularity) {
401
case ScrollByDocument:
402
return Parameters(true, 20 * kTickTime, 10 * kTickTime, Cubic, 10 * kTickTime, Cubic, 10 * kTickTime, Linear, 1);
404
return Parameters(true, 10 * kTickTime, 7 * kTickTime, Cubic, 3 * kTickTime, Cubic, 3 * kTickTime, Linear, 1);
406
return Parameters(true, 15 * kTickTime, 10 * kTickTime, Cubic, 5 * kTickTime, Cubic, 5 * kTickTime, Linear, 1);
408
return Parameters(true, 11 * kTickTime, 2 * kTickTime, Cubic, 3 * kTickTime, Cubic, 3 * kTickTime, Quadratic, 1.25);
410
ASSERT_NOT_REACHED();
413
// This is a slightly different strategy for the animation with a steep attack curve and natural release curve.
414
// The fast acceleration makes the animation look more responsive to user input.
415
switch (granularity) {
416
case ScrollByDocument:
417
return Parameters(true, 20 * kTickTime, 10 * kTickTime, Cubic, 6 * kTickTime, Quadratic, 10 * kTickTime, Quadratic, 22 * kTickTime);
419
return Parameters(true, 6 * kTickTime, 5 * kTickTime, Cubic, 1 * kTickTime, Quadratic, 4 * kTickTime, Linear, 1);
421
return Parameters(true, 12 * kTickTime, 10 * kTickTime, Cubic, 3 * kTickTime, Quadratic, 6 * kTickTime, Linear, 1);
423
return Parameters(true, 8 * kTickTime, 3 * kTickTime, Cubic, 2 * kTickTime, Quadratic, 5 * kTickTime, Quadratic, 1.25);
425
ASSERT_NOT_REACHED();
431
bool ScrollAnimatorNone::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
433
if (!m_scrollableArea->scrollAnimatorEnabled())
434
return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
436
#if PLATFORM(CHROMIUM)
437
TRACE_EVENT0("webkit", "ScrollAnimatorNone::scroll");
440
// FIXME: get the type passed in. MouseWheel could also be by line, but should still have different
441
// animation parameters than the keyboard.
442
Parameters parameters;
443
switch (granularity) {
444
case ScrollByDocument:
448
parameters = parametersForScrollGranularity(granularity);
450
case ScrollByPrecisePixel:
451
return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
454
// If the individual input setting is disabled, bail.
455
if (!parameters.m_isEnabled)
456
return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
458
// This is an animatable scroll. Set the animation in motion using the appropriate parameters.
459
float scrollableSize = static_cast<float>(m_scrollableArea->scrollSize(orientation));
461
PerAxisData& data = (orientation == VerticalScrollbar) ? m_verticalData : m_horizontalData;
462
bool needToScroll = data.updateDataFromParameters(step, multiplier, scrollableSize, WTF::monotonicallyIncreasingTime(), ¶meters);
463
if (needToScroll && !animationTimerActive()) {
464
m_startTime = data.m_startTime;
465
animationWillStart();
466
animationTimerFired();
471
void ScrollAnimatorNone::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
473
stopAnimationTimerIfNeeded();
475
m_horizontalData.reset();
476
*m_horizontalData.m_currentPosition = offset.x();
477
m_horizontalData.m_desiredPosition = offset.x();
479
m_verticalData.reset();
480
*m_verticalData.m_currentPosition = offset.y();
481
m_verticalData.m_desiredPosition = offset.y();
483
notifyPositionChanged();
486
#if !USE(REQUEST_ANIMATION_FRAME_TIMER)
487
void ScrollAnimatorNone::cancelAnimations()
489
m_animationActive = false;
492
void ScrollAnimatorNone::serviceScrollAnimations()
494
if (m_animationActive)
495
animationTimerFired();
499
void ScrollAnimatorNone::willEndLiveResize()
501
updateVisibleLengths();
504
void ScrollAnimatorNone::didAddVerticalScrollbar(Scrollbar*)
506
updateVisibleLengths();
509
void ScrollAnimatorNone::didAddHorizontalScrollbar(Scrollbar*)
511
updateVisibleLengths();
514
void ScrollAnimatorNone::updateVisibleLengths()
516
m_horizontalData.updateVisibleLength(scrollableArea()->visibleWidth());
517
m_verticalData.updateVisibleLength(scrollableArea()->visibleHeight());
520
#if USE(REQUEST_ANIMATION_FRAME_TIMER)
521
void ScrollAnimatorNone::animationTimerFired(Timer<ScrollAnimatorNone>* timer)
523
animationTimerFired();
527
void ScrollAnimatorNone::animationTimerFired()
529
#if PLATFORM(CHROMIUM)
530
TRACE_EVENT0("webkit", "ScrollAnimatorNone::animationTimerFired");
533
double currentTime = WTF::monotonicallyIncreasingTime();
534
double deltaToNextFrame = ceil((currentTime - m_startTime) * kFrameRate) / kFrameRate - (currentTime - m_startTime);
535
currentTime += deltaToNextFrame;
537
bool continueAnimation = false;
538
if (m_horizontalData.m_startTime && m_horizontalData.animateScroll(currentTime))
539
continueAnimation = true;
540
if (m_verticalData.m_startTime && m_verticalData.animateScroll(currentTime))
541
continueAnimation = true;
543
if (continueAnimation)
544
#if USE(REQUEST_ANIMATION_FRAME_TIMER)
545
startNextTimer(max(kMinimumTimerInterval, deltaToNextFrame));
549
m_animationActive = false;
552
#if PLATFORM(CHROMIUM)
553
TRACE_EVENT0("webkit", "ScrollAnimatorNone::notifyPositionChanged");
555
notifyPositionChanged();
557
if (!continueAnimation)
558
animationDidFinish();
561
#if USE(REQUEST_ANIMATION_FRAME_TIMER)
562
void ScrollAnimatorNone::startNextTimer(double delay)
564
m_animationTimer.startOneShot(delay);
567
void ScrollAnimatorNone::startNextTimer()
569
if (scrollableArea()->scheduleAnimation())
570
m_animationActive = true;
574
bool ScrollAnimatorNone::animationTimerActive()
576
#if USE(REQUEST_ANIMATION_FRAME_TIMER)
577
return m_animationTimer.isActive();
579
return m_animationActive;
583
void ScrollAnimatorNone::stopAnimationTimerIfNeeded()
585
if (animationTimerActive())
586
#if USE(REQUEST_ANIMATION_FRAME_TIMER)
587
m_animationTimer.stop();
589
m_animationActive = false;
593
} // namespace WebCore
595
#endif // ENABLE(SMOOTH_SCROLLING)