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 #include "platform/scroll/ScrollAnimatorNone.h"
36 #include "platform/scroll/ScrollableArea.h"
37 #include "wtf/CurrentTime.h"
38 #include "wtf/PassRefPtr.h"
40 #include "platform/TraceEvent.h"
44 const double kFrameRate
= 60;
45 const double kTickTime
= 1 / kFrameRate
;
46 const double kMinimumTimerInterval
= .001;
48 PassOwnPtr
<ScrollAnimator
> ScrollAnimator::create(ScrollableArea
* scrollableArea
)
50 if (scrollableArea
&& scrollableArea
->scrollAnimatorEnabled())
51 return adoptPtr(new ScrollAnimatorNone(scrollableArea
));
52 return adoptPtr(new ScrollAnimator(scrollableArea
));
55 ScrollAnimatorNone::Parameters::Parameters()
60 ScrollAnimatorNone::Parameters::Parameters(bool isEnabled
, double animationTime
, double repeatMinimumSustainTime
, Curve attackCurve
, double attackTime
, Curve releaseCurve
, double releaseTime
, Curve coastTimeCurve
, double maximumCoastTime
)
61 : m_isEnabled(isEnabled
)
62 , m_animationTime(animationTime
)
63 , m_repeatMinimumSustainTime(repeatMinimumSustainTime
)
64 , m_attackCurve(attackCurve
)
65 , m_attackTime(attackTime
)
66 , m_releaseCurve(releaseCurve
)
67 , m_releaseTime(releaseTime
)
68 , m_coastTimeCurve(coastTimeCurve
)
69 , m_maximumCoastTime(maximumCoastTime
)
73 double ScrollAnimatorNone::PerAxisData::curveAt(Curve curve
, double t
)
85 // Time base is chosen to keep the bounce points simpler:
86 // 1 (half bounce coming in) + 1 + .5 + .25
87 const double kTimeBase
= 2.75;
88 const double kTimeBaseSquared
= kTimeBase
* kTimeBase
;
89 if (t
< 1 / kTimeBase
)
90 return kTimeBaseSquared
* t
* t
;
91 if (t
< 2 / kTimeBase
) {
92 // Invert a [-.5,.5] quadratic parabola, center it in [1,2].
93 double t1
= t
- 1.5 / kTimeBase
;
94 const double kParabolaAtEdge
= 1 - .5 * .5;
95 return kTimeBaseSquared
* t1
* t1
+ kParabolaAtEdge
;
97 if (t
< 2.5 / kTimeBase
) {
98 // Invert a [-.25,.25] quadratic parabola, center it in [2,2.5].
99 double t2
= t
- 2.25 / kTimeBase
;
100 const double kParabolaAtEdge
= 1 - .25 * .25;
101 return kTimeBaseSquared
* t2
* t2
+ kParabolaAtEdge
;
103 // Invert a [-.125,.125] quadratic parabola, center it in [2.5,2.75].
104 const double kParabolaAtEdge
= 1 - .125 * .125;
105 t
-= 2.625 / kTimeBase
;
106 return kTimeBaseSquared
* t
* t
+ kParabolaAtEdge
;
108 ASSERT_NOT_REACHED();
112 double ScrollAnimatorNone::PerAxisData::attackCurve(Curve curve
, double deltaTime
, double curveT
, double startPosition
, double attackPosition
)
114 double t
= deltaTime
/ curveT
;
115 double positionFactor
= curveAt(curve
, t
);
116 return startPosition
+ positionFactor
* (attackPosition
- startPosition
);
119 double ScrollAnimatorNone::PerAxisData::releaseCurve(Curve curve
, double deltaTime
, double curveT
, double releasePosition
, double desiredPosition
)
121 double t
= deltaTime
/ curveT
;
122 double positionFactor
= 1 - curveAt(curve
, 1 - t
);
123 return releasePosition
+ (positionFactor
* (desiredPosition
- releasePosition
));
126 double ScrollAnimatorNone::PerAxisData::coastCurve(Curve curve
, double factor
)
128 return 1 - curveAt(curve
, 1 - factor
);
131 double ScrollAnimatorNone::PerAxisData::curveIntegralAt(Curve curve
, double t
)
137 return t
* t
* t
/ 3;
139 return t
* t
* t
* t
/ 4;
141 return t
* t
* t
* t
* t
/ 5;
143 const double kTimeBase
= 2.75;
144 const double kTimeBaseSquared
= kTimeBase
* kTimeBase
;
145 const double kTimeBaseSquaredOverThree
= kTimeBaseSquared
/ 3;
147 double t1
= std::min(t
, 1 / kTimeBase
);
148 area
= kTimeBaseSquaredOverThree
* t1
* t1
* t1
;
149 if (t
< 1 / kTimeBase
)
152 t1
= std::min(t
- 1 / kTimeBase
, 1 / kTimeBase
);
153 // The integral of kTimeBaseSquared * (t1 - .5 / kTimeBase) * (t1 - .5 / kTimeBase) + kParabolaAtEdge
154 const double kSecondInnerOffset
= kTimeBaseSquared
* .5 / kTimeBase
;
155 double bounceArea
= t1
* (t1
* (kTimeBaseSquaredOverThree
* t1
- kSecondInnerOffset
) + 1);
157 if (t
< 2 / kTimeBase
)
160 t1
= std::min(t
- 2 / kTimeBase
, 0.5 / kTimeBase
);
161 // The integral of kTimeBaseSquared * (t1 - .25 / kTimeBase) * (t1 - .25 / kTimeBase) + kParabolaAtEdge
162 const double kThirdInnerOffset
= kTimeBaseSquared
* .25 / kTimeBase
;
163 bounceArea
= t1
* (t1
* (kTimeBaseSquaredOverThree
* t1
- kThirdInnerOffset
) + 1);
165 if (t
< 2.5 / kTimeBase
)
168 t1
= t
- 2.5 / kTimeBase
;
169 // The integral of kTimeBaseSquared * (t1 - .125 / kTimeBase) * (t1 - .125 / kTimeBase) + kParabolaAtEdge
170 const double kFourthInnerOffset
= kTimeBaseSquared
* .125 / kTimeBase
;
171 bounceArea
= t1
* (t1
* (kTimeBaseSquaredOverThree
* t1
- kFourthInnerOffset
) + 1);
175 ASSERT_NOT_REACHED();
179 double ScrollAnimatorNone::PerAxisData::attackArea(Curve curve
, double startT
, double endT
)
181 double startValue
= curveIntegralAt(curve
, startT
);
182 double endValue
= curveIntegralAt(curve
, endT
);
183 return endValue
- startValue
;
186 double ScrollAnimatorNone::PerAxisData::releaseArea(Curve curve
, double startT
, double endT
)
188 double startValue
= curveIntegralAt(curve
, 1 - endT
);
189 double endValue
= curveIntegralAt(curve
, 1 - startT
);
190 return endValue
- startValue
;
193 ScrollAnimatorNone::PerAxisData::PerAxisData(float* currentPosition
, int visibleLength
)
194 : m_currentPosition(currentPosition
)
195 , m_visibleLength(visibleLength
)
200 void ScrollAnimatorNone::PerAxisData::reset()
202 m_currentVelocity
= 0;
204 m_desiredPosition
= 0;
205 m_desiredVelocity
= 0;
212 m_lastAnimationTime
= 0;
214 m_attackPosition
= 0;
216 m_attackCurve
= Quadratic
;
218 m_releasePosition
= 0;
220 m_releaseCurve
= Quadratic
;
224 bool ScrollAnimatorNone::PerAxisData::updateDataFromParameters(float step
, float delta
, float scrollableSize
, double currentTime
, Parameters
* parameters
)
226 float pixelDelta
= step
* delta
;
227 if (!m_startTime
|| !pixelDelta
|| (pixelDelta
< 0) != (m_desiredPosition
- *m_currentPosition
< 0)) {
228 m_desiredPosition
= *m_currentPosition
;
231 float newPosition
= m_desiredPosition
+ pixelDelta
;
233 if (newPosition
< 0 || newPosition
> scrollableSize
)
234 newPosition
= std::max(std::min(newPosition
, scrollableSize
), 0.0f
);
236 if (newPosition
== m_desiredPosition
)
239 m_desiredPosition
= newPosition
;
242 m_attackTime
= parameters
->m_attackTime
;
243 m_attackCurve
= parameters
->m_attackCurve
;
245 m_animationTime
= parameters
->m_animationTime
;
246 m_releaseTime
= parameters
->m_releaseTime
;
247 m_releaseCurve
= parameters
->m_releaseCurve
;
249 // Prioritize our way out of over constraint.
250 if (m_attackTime
+ m_releaseTime
> m_animationTime
) {
251 if (m_releaseTime
> m_animationTime
)
252 m_releaseTime
= m_animationTime
;
253 m_attackTime
= m_animationTime
- m_releaseTime
;
257 // FIXME: This should be the time from the event that got us here.
258 m_startTime
= currentTime
- kTickTime
/ 2;
259 m_startPosition
= *m_currentPosition
;
260 m_lastAnimationTime
= m_startTime
;
262 m_startVelocity
= m_currentVelocity
;
264 double remainingDelta
= m_desiredPosition
- *m_currentPosition
;
266 double attackAreaLeft
= 0;
268 double deltaTime
= m_lastAnimationTime
- m_startTime
;
269 double attackTimeLeft
= std::max(0., m_attackTime
- deltaTime
);
270 double timeLeft
= m_animationTime
- deltaTime
;
271 double minTimeLeft
= m_releaseTime
+ std::min(parameters
->m_repeatMinimumSustainTime
, m_animationTime
- m_releaseTime
- attackTimeLeft
);
272 if (timeLeft
< minTimeLeft
) {
273 m_animationTime
= deltaTime
+ minTimeLeft
;
274 timeLeft
= minTimeLeft
;
277 if (parameters
->m_maximumCoastTime
> (parameters
->m_repeatMinimumSustainTime
+ parameters
->m_releaseTime
)) {
278 double targetMaxCoastVelocity
= m_visibleLength
* .25 * kFrameRate
;
279 // This needs to be as minimal as possible while not being intrusive to page up/down.
280 double minCoastDelta
= m_visibleLength
;
282 if (fabs(remainingDelta
) > minCoastDelta
) {
283 double maxCoastDelta
= parameters
->m_maximumCoastTime
* targetMaxCoastVelocity
;
284 double coastFactor
= std::min(1., (fabs(remainingDelta
) - minCoastDelta
) / (maxCoastDelta
- minCoastDelta
));
286 // 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.
287 double coastMinTimeLeft
= std::min(parameters
->m_maximumCoastTime
, minTimeLeft
+ coastCurve(parameters
->m_coastTimeCurve
, coastFactor
) * (parameters
->m_maximumCoastTime
- minTimeLeft
));
289 double additionalTime
= std::max(0., coastMinTimeLeft
- minTimeLeft
);
290 if (additionalTime
) {
291 double additionalReleaseTime
= std::min(additionalTime
, parameters
->m_releaseTime
/ (parameters
->m_releaseTime
+ parameters
->m_repeatMinimumSustainTime
) * additionalTime
);
292 m_releaseTime
= parameters
->m_releaseTime
+ additionalReleaseTime
;
293 m_animationTime
= deltaTime
+ coastMinTimeLeft
;
294 timeLeft
= coastMinTimeLeft
;
299 double releaseTimeLeft
= std::min(timeLeft
, m_releaseTime
);
300 double sustainTimeLeft
= std::max(0., timeLeft
- releaseTimeLeft
- attackTimeLeft
);
302 if (attackTimeLeft
) {
303 double attackSpot
= deltaTime
/ m_attackTime
;
304 attackAreaLeft
= attackArea(m_attackCurve
, attackSpot
, 1) * m_attackTime
;
307 double releaseSpot
= (m_releaseTime
- releaseTimeLeft
) / m_releaseTime
;
308 double releaseAreaLeft
= releaseArea(m_releaseCurve
, releaseSpot
, 1) * m_releaseTime
;
310 m_desiredVelocity
= remainingDelta
/ (attackAreaLeft
+ sustainTimeLeft
+ releaseAreaLeft
);
311 m_releasePosition
= m_desiredPosition
- m_desiredVelocity
* releaseAreaLeft
;
313 m_attackPosition
= m_startPosition
+ m_desiredVelocity
* attackAreaLeft
;
315 m_attackPosition
= m_releasePosition
- (m_animationTime
- m_releaseTime
- m_attackTime
) * m_desiredVelocity
;
317 if (sustainTimeLeft
) {
318 double roundOff
= m_releasePosition
- ((attackAreaLeft
? m_attackPosition
: *m_currentPosition
) + m_desiredVelocity
* sustainTimeLeft
);
319 m_desiredVelocity
+= roundOff
/ sustainTimeLeft
;
325 inline double ScrollAnimatorNone::PerAxisData::newScrollAnimationPosition(double deltaTime
)
327 if (deltaTime
< m_attackTime
)
328 return attackCurve(m_attackCurve
, deltaTime
, m_attackTime
, m_startPosition
, m_attackPosition
);
329 if (deltaTime
< (m_animationTime
- m_releaseTime
))
330 return m_attackPosition
+ (deltaTime
- m_attackTime
) * m_desiredVelocity
;
331 // release is based on targeting the exact final position.
332 double releaseDeltaT
= deltaTime
- (m_animationTime
- m_releaseTime
);
333 return releaseCurve(m_releaseCurve
, releaseDeltaT
, m_releaseTime
, m_releasePosition
, m_desiredPosition
);
336 // FIXME: Add in jank detection trace events into this function.
337 bool ScrollAnimatorNone::PerAxisData::animateScroll(double currentTime
)
339 double lastScrollInterval
= currentTime
- m_lastAnimationTime
;
340 if (lastScrollInterval
< kMinimumTimerInterval
)
343 m_lastAnimationTime
= currentTime
;
345 double deltaTime
= currentTime
- m_startTime
;
347 if (deltaTime
> m_animationTime
) {
348 *m_currentPosition
= m_desiredPosition
;
352 double newPosition
= newScrollAnimationPosition(deltaTime
);
353 // Normalize velocity to a per second amount. Could be used to check for jank.
354 if (lastScrollInterval
> 0)
355 m_currentVelocity
= (newPosition
- *m_currentPosition
) / lastScrollInterval
;
356 *m_currentPosition
= newPosition
;
361 void ScrollAnimatorNone::PerAxisData::updateVisibleLength(int visibleLength
)
363 m_visibleLength
= visibleLength
;
366 ScrollAnimatorNone::ScrollAnimatorNone(ScrollableArea
* scrollableArea
)
367 : ScrollAnimator(scrollableArea
)
368 , m_horizontalData(&m_currentPosX
, scrollableArea
->visibleWidth())
369 , m_verticalData(&m_currentPosY
, scrollableArea
->visibleHeight())
371 , m_animationActive(false)
375 ScrollAnimatorNone::~ScrollAnimatorNone()
377 stopAnimationTimerIfNeeded();
380 ScrollAnimatorNone::Parameters
ScrollAnimatorNone::parametersForScrollGranularity(ScrollGranularity granularity
) const
382 switch (granularity
) {
383 case ScrollByDocument
:
384 return Parameters(true, 20 * kTickTime
, 10 * kTickTime
, Cubic
, 10 * kTickTime
, Cubic
, 10 * kTickTime
, Linear
, 1);
386 return Parameters(true, 10 * kTickTime
, 7 * kTickTime
, Cubic
, 3 * kTickTime
, Cubic
, 3 * kTickTime
, Linear
, 1);
388 return Parameters(true, 15 * kTickTime
, 10 * kTickTime
, Cubic
, 5 * kTickTime
, Cubic
, 5 * kTickTime
, Linear
, 1);
390 return Parameters(true, 11 * kTickTime
, 2 * kTickTime
, Cubic
, 3 * kTickTime
, Cubic
, 3 * kTickTime
, Quadratic
, 1.25);
392 ASSERT_NOT_REACHED();
397 ScrollResultOneDimensional
ScrollAnimatorNone::userScroll(ScrollbarOrientation orientation
, ScrollGranularity granularity
, float step
, float delta
)
399 if (!m_scrollableArea
->scrollAnimatorEnabled())
400 return ScrollAnimator::userScroll(orientation
, granularity
, step
, delta
);
402 TRACE_EVENT0("blink", "ScrollAnimatorNone::scroll");
404 // FIXME: get the type passed in. MouseWheel could also be by line, but should still have different
405 // animation parameters than the keyboard.
406 Parameters parameters
;
407 switch (granularity
) {
408 case ScrollByDocument
:
412 parameters
= parametersForScrollGranularity(granularity
);
414 case ScrollByPrecisePixel
:
415 return ScrollAnimator::userScroll(orientation
, granularity
, step
, delta
);
418 // If the individual input setting is disabled, bail.
419 if (!parameters
.m_isEnabled
)
420 return ScrollAnimator::userScroll(orientation
, granularity
, step
, delta
);
422 // This is an animatable scroll. Set the animation in motion using the appropriate parameters.
423 float scrollableSize
= static_cast<float>(m_scrollableArea
->scrollSize(orientation
));
425 PerAxisData
& data
= (orientation
== VerticalScrollbar
) ? m_verticalData
: m_horizontalData
;
426 bool needToScroll
= data
.updateDataFromParameters(step
, delta
, scrollableSize
, WTF::monotonicallyIncreasingTime(), ¶meters
);
427 float unusedDelta
= needToScroll
? delta
- (data
.m_desiredPosition
- *data
.m_currentPosition
) : delta
;
428 if (needToScroll
&& !animationTimerActive()) {
429 m_startTime
= data
.m_startTime
;
430 animationWillStart();
431 animationTimerFired();
432 scrollableArea()->registerForAnimation();
434 return ScrollResultOneDimensional(needToScroll
, unusedDelta
);
437 void ScrollAnimatorNone::scrollToOffsetWithoutAnimation(const FloatPoint
& offset
)
439 m_currentPosX
= offset
.x();
440 m_currentPosY
= offset
.y();
442 // Must be called after setting the position since canceling the animation resets
443 // the desired position to the current.
445 notifyPositionChanged();
448 void ScrollAnimatorNone::cancelAnimations()
450 m_animationActive
= false;
452 m_horizontalData
.reset();
453 m_verticalData
.reset();
454 m_horizontalData
.m_desiredPosition
= m_currentPosX
;
455 m_verticalData
.m_desiredPosition
= m_currentPosY
;
458 void ScrollAnimatorNone::serviceScrollAnimations()
460 if (m_animationActive
)
461 animationTimerFired();
464 bool ScrollAnimatorNone::hasRunningAnimation() const
466 return m_animationActive
;
469 void ScrollAnimatorNone::updateAfterLayout()
471 updateVisibleLengths();
474 void ScrollAnimatorNone::willEndLiveResize()
476 updateVisibleLengths();
479 void ScrollAnimatorNone::didAddVerticalScrollbar(Scrollbar
*)
481 updateVisibleLengths();
484 void ScrollAnimatorNone::didAddHorizontalScrollbar(Scrollbar
*)
486 updateVisibleLengths();
489 void ScrollAnimatorNone::updateVisibleLengths()
491 m_horizontalData
.updateVisibleLength(scrollableArea()->visibleWidth());
492 m_verticalData
.updateVisibleLength(scrollableArea()->visibleHeight());
495 void ScrollAnimatorNone::animationTimerFired()
497 TRACE_EVENT0("blink", "ScrollAnimatorNone::animationTimerFired");
499 double currentTime
= WTF::monotonicallyIncreasingTime();
501 bool continueAnimation
= false;
502 if (m_horizontalData
.m_startTime
&& m_horizontalData
.animateScroll(currentTime
))
503 continueAnimation
= true;
504 if (m_verticalData
.m_startTime
&& m_verticalData
.animateScroll(currentTime
))
505 continueAnimation
= true;
507 if (continueAnimation
)
510 m_animationActive
= false;
512 TRACE_EVENT0("blink", "ScrollAnimatorNone::notifyPositionChanged");
513 notifyPositionChanged();
515 if (!continueAnimation
)
516 animationDidFinish();
519 void ScrollAnimatorNone::startNextTimer()
521 if (scrollableArea()->scheduleAnimation())
522 m_animationActive
= true;
525 bool ScrollAnimatorNone::animationTimerActive()
527 return m_animationActive
;
530 void ScrollAnimatorNone::stopAnimationTimerIfNeeded()
532 if (animationTimerActive())
533 m_animationActive
= false;