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/PassOwnPtr.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(ScrollAnimatorNone* parent, 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(this, &m_currentPosX, scrollableArea->visibleWidth())
369 , m_verticalData(this, &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 bool ScrollAnimatorNone::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float delta)
399 if (!m_scrollableArea->scrollAnimatorEnabled())
400 return ScrollAnimator::scroll(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::scroll(orientation, granularity, step, delta);
418 // If the individual input setting is disabled, bail.
419 if (!parameters.m_isEnabled)
420 return ScrollAnimator::scroll(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 if (needToScroll && !animationTimerActive()) {
428 m_startTime = data.m_startTime;
429 animationWillStart();
430 animationTimerFired();
435 void ScrollAnimatorNone::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
437 stopAnimationTimerIfNeeded();
439 m_horizontalData.reset();
440 *m_horizontalData.m_currentPosition = offset.x();
441 m_horizontalData.m_desiredPosition = offset.x();
442 m_currentPosX = offset.x();
444 m_verticalData.reset();
445 *m_verticalData.m_currentPosition = offset.y();
446 m_verticalData.m_desiredPosition = offset.y();
447 m_currentPosY = offset.y();
449 notifyPositionChanged();
452 void ScrollAnimatorNone::cancelAnimations()
454 m_animationActive = false;
457 void ScrollAnimatorNone::serviceScrollAnimations()
459 if (m_animationActive)
460 animationTimerFired();
463 void ScrollAnimatorNone::willEndLiveResize()
465 updateVisibleLengths();
468 void ScrollAnimatorNone::didAddVerticalScrollbar(Scrollbar*)
470 updateVisibleLengths();
473 void ScrollAnimatorNone::didAddHorizontalScrollbar(Scrollbar*)
475 updateVisibleLengths();
478 void ScrollAnimatorNone::updateVisibleLengths()
480 m_horizontalData.updateVisibleLength(scrollableArea()->visibleWidth());
481 m_verticalData.updateVisibleLength(scrollableArea()->visibleHeight());
484 void ScrollAnimatorNone::animationTimerFired()
486 TRACE_EVENT0("blink", "ScrollAnimatorNone::animationTimerFired");
488 double currentTime = WTF::monotonicallyIncreasingTime();
490 bool continueAnimation = false;
491 if (m_horizontalData.m_startTime && m_horizontalData.animateScroll(currentTime))
492 continueAnimation = true;
493 if (m_verticalData.m_startTime && m_verticalData.animateScroll(currentTime))
494 continueAnimation = true;
496 if (continueAnimation)
499 m_animationActive = false;
501 TRACE_EVENT0("blink", "ScrollAnimatorNone::notifyPositionChanged");
502 notifyPositionChanged();
504 if (!continueAnimation)
505 animationDidFinish();
508 void ScrollAnimatorNone::startNextTimer()
510 if (scrollableArea()->scheduleAnimation())
511 m_animationActive = true;
514 bool ScrollAnimatorNone::animationTimerActive()
516 return m_animationActive;
519 void ScrollAnimatorNone::stopAnimationTimerIfNeeded()
521 if (animationTimerActive())
522 m_animationActive = false;