1 /* 2 * Copyright (c) 2011, Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 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 13 * distribution. 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. 17 * 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. 29 */ 30 31 #include "config.h" 32 33 #include "platform/scroll/ScrollAnimatorNone.h" 34 35 #include <algorithm> 36 #include "platform/scroll/ScrollableArea.h" 37 #include "wtf/CurrentTime.h" 38 #include "wtf/PassOwnPtr.h" 39 40 #include "platform/TraceEvent.h" 41 42 namespace blink { 43 44 const double kFrameRate = 60; 45 const double kTickTime = 1 / kFrameRate; 46 const double kMinimumTimerInterval = .001; 47 48 PassOwnPtr<ScrollAnimator> ScrollAnimator::create(ScrollableArea* scrollableArea) 49 { 50 if (scrollableArea && scrollableArea->scrollAnimatorEnabled()) 51 return adoptPtr(new ScrollAnimatorNone(scrollableArea)); 52 return adoptPtr(new ScrollAnimator(scrollableArea)); 53 } 54 55 ScrollAnimatorNone::Parameters::Parameters() 56 : m_isEnabled(false) 57 { 58 } 59 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) 70 { 71 } 72 73 double ScrollAnimatorNone::PerAxisData::curveAt(Curve curve, double t) 74 { 75 switch (curve) { 76 case Linear: 77 return t; 78 case Quadratic: 79 return t * t; 80 case Cubic: 81 return t * t * t; 82 case Quartic: 83 return t * t * t * t; 84 case Bounce: 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; 96 } 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; 102 } 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; 107 } 108 ASSERT_NOT_REACHED(); 109 return 0; 110 } 111 112 double ScrollAnimatorNone::PerAxisData::attackCurve(Curve curve, double deltaTime, double curveT, double startPosition, double attackPosition) 113 { 114 double t = deltaTime / curveT; 115 double positionFactor = curveAt(curve, t); 116 return startPosition + positionFactor * (attackPosition - startPosition); 117 } 118 119 double ScrollAnimatorNone::PerAxisData::releaseCurve(Curve curve, double deltaTime, double curveT, double releasePosition, double desiredPosition) 120 { 121 double t = deltaTime / curveT; 122 double positionFactor = 1 - curveAt(curve, 1 - t); 123 return releasePosition + (positionFactor * (desiredPosition - releasePosition)); 124 } 125 126 double ScrollAnimatorNone::PerAxisData::coastCurve(Curve curve, double factor) 127 { 128 return 1 - curveAt(curve, 1 - factor); 129 } 130 131 double ScrollAnimatorNone::PerAxisData::curveIntegralAt(Curve curve, double t) 132 { 133 switch (curve) { 134 case Linear: 135 return t * t / 2; 136 case Quadratic: 137 return t * t * t / 3; 138 case Cubic: 139 return t * t * t * t / 4; 140 case Quartic: 141 return t * t * t * t * t / 5; 142 case Bounce: 143 const double kTimeBase = 2.75; 144 const double kTimeBaseSquared = kTimeBase * kTimeBase; 145 const double kTimeBaseSquaredOverThree = kTimeBaseSquared / 3; 146 double area; 147 double t1 = std::min(t, 1 / kTimeBase); 148 area = kTimeBaseSquaredOverThree * t1 * t1 * t1; 149 if (t < 1 / kTimeBase) 150 return area; 151 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); 156 area += bounceArea; 157 if (t < 2 / kTimeBase) 158 return area; 159 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); 164 area += bounceArea; 165 if (t < 2.5 / kTimeBase) 166 return area; 167 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); 172 area += bounceArea; 173 return area; 174 } 175 ASSERT_NOT_REACHED(); 176 return 0; 177 } 178 179 double ScrollAnimatorNone::PerAxisData::attackArea(Curve curve, double startT, double endT) 180 { 181 double startValue = curveIntegralAt(curve, startT); 182 double endValue = curveIntegralAt(curve, endT); 183 return endValue - startValue; 184 } 185 186 double ScrollAnimatorNone::PerAxisData::releaseArea(Curve curve, double startT, double endT) 187 { 188 double startValue = curveIntegralAt(curve, 1 - endT); 189 double endValue = curveIntegralAt(curve, 1 - startT); 190 return endValue - startValue; 191 } 192 193 ScrollAnimatorNone::PerAxisData::PerAxisData(ScrollAnimatorNone* parent, float* currentPosition, int visibleLength) 194 : m_currentPosition(currentPosition) 195 , m_visibleLength(visibleLength) 196 { 197 reset(); 198 } 199 200 void ScrollAnimatorNone::PerAxisData::reset() 201 { 202 m_currentVelocity = 0; 203 204 m_desiredPosition = 0; 205 m_desiredVelocity = 0; 206 207 m_startPosition = 0; 208 m_startTime = 0; 209 m_startVelocity = 0; 210 211 m_animationTime = 0; 212 m_lastAnimationTime = 0; 213 214 m_attackPosition = 0; 215 m_attackTime = 0; 216 m_attackCurve = Quadratic; 217 218 m_releasePosition = 0; 219 m_releaseTime = 0; 220 m_releaseCurve = Quadratic; 221 } 222 223 224 bool ScrollAnimatorNone::PerAxisData::updateDataFromParameters(float step, float delta, float scrollableSize, double currentTime, Parameters* parameters) 225 { 226 float pixelDelta = step * delta; 227 if (!m_startTime || !pixelDelta || (pixelDelta < 0) != (m_desiredPosition - *m_currentPosition < 0)) { 228 m_desiredPosition = *m_currentPosition; 229 m_startTime = 0; 230 } 231 float newPosition = m_desiredPosition + pixelDelta; 232 233 if (newPosition < 0 || newPosition > scrollableSize) 234 newPosition = std::max(std::min(newPosition, scrollableSize), 0.0f); 235 236 if (newPosition == m_desiredPosition) 237 return false; 238 239 m_desiredPosition = newPosition; 240 241 if (!m_startTime) { 242 m_attackTime = parameters->m_attackTime; 243 m_attackCurve = parameters->m_attackCurve; 244 } 245 m_animationTime = parameters->m_animationTime; 246 m_releaseTime = parameters->m_releaseTime; 247 m_releaseCurve = parameters->m_releaseCurve; 248 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; 254 } 255 256 if (!m_startTime) { 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; 261 } 262 m_startVelocity = m_currentVelocity; 263 264 double remainingDelta = m_desiredPosition - *m_currentPosition; 265 266 double attackAreaLeft = 0; 267 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; 275 } 276 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; 281 282 if (fabs(remainingDelta) > minCoastDelta) { 283 double maxCoastDelta = parameters->m_maximumCoastTime * targetMaxCoastVelocity; 284 double coastFactor = std::min(1., (fabs(remainingDelta) - minCoastDelta) / (maxCoastDelta - minCoastDelta)); 285 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)); 288 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; 295 } 296 } 297 } 298 299 double releaseTimeLeft = std::min(timeLeft, m_releaseTime); 300 double sustainTimeLeft = std::max(0., timeLeft - releaseTimeLeft - attackTimeLeft); 301 302 if (attackTimeLeft) { 303 double attackSpot = deltaTime / m_attackTime; 304 attackAreaLeft = attackArea(m_attackCurve, attackSpot, 1) * m_attackTime; 305 } 306 307 double releaseSpot = (m_releaseTime - releaseTimeLeft) / m_releaseTime; 308 double releaseAreaLeft = releaseArea(m_releaseCurve, releaseSpot, 1) * m_releaseTime; 309 310 m_desiredVelocity = remainingDelta / (attackAreaLeft + sustainTimeLeft + releaseAreaLeft); 311 m_releasePosition = m_desiredPosition - m_desiredVelocity * releaseAreaLeft; 312 if (attackAreaLeft) 313 m_attackPosition = m_startPosition + m_desiredVelocity * attackAreaLeft; 314 else 315 m_attackPosition = m_releasePosition - (m_animationTime - m_releaseTime - m_attackTime) * m_desiredVelocity; 316 317 if (sustainTimeLeft) { 318 double roundOff = m_releasePosition - ((attackAreaLeft ? m_attackPosition : *m_currentPosition) + m_desiredVelocity * sustainTimeLeft); 319 m_desiredVelocity += roundOff / sustainTimeLeft; 320 } 321 322 return true; 323 } 324 325 inline double ScrollAnimatorNone::PerAxisData::newScrollAnimationPosition(double deltaTime) 326 { 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); 334 } 335 336 // FIXME: Add in jank detection trace events into this function. 337 bool ScrollAnimatorNone::PerAxisData::animateScroll(double currentTime) 338 { 339 double lastScrollInterval = currentTime - m_lastAnimationTime; 340 if (lastScrollInterval < kMinimumTimerInterval) 341 return true; 342 343 m_lastAnimationTime = currentTime; 344 345 double deltaTime = currentTime - m_startTime; 346 347 if (deltaTime > m_animationTime) { 348 *m_currentPosition = m_desiredPosition; 349 reset(); 350 return false; 351 } 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; 357 358 return true; 359 } 360 361 void ScrollAnimatorNone::PerAxisData::updateVisibleLength(int visibleLength) 362 { 363 m_visibleLength = visibleLength; 364 } 365 366 ScrollAnimatorNone::ScrollAnimatorNone(ScrollableArea* scrollableArea) 367 : ScrollAnimator(scrollableArea) 368 , m_horizontalData(this, &m_currentPosX, scrollableArea->visibleWidth()) 369 , m_verticalData(this, &m_currentPosY, scrollableArea->visibleHeight()) 370 , m_startTime(0) 371 , m_animationActive(false) 372 { 373 } 374 375 ScrollAnimatorNone::~ScrollAnimatorNone() 376 { 377 stopAnimationTimerIfNeeded(); 378 } 379 380 ScrollAnimatorNone::Parameters ScrollAnimatorNone::parametersForScrollGranularity(ScrollGranularity granularity) const 381 { 382 switch (granularity) { 383 case ScrollByDocument: 384 return Parameters(true, 20 * kTickTime, 10 * kTickTime, Cubic, 10 * kTickTime, Cubic, 10 * kTickTime, Linear, 1); 385 case ScrollByLine: 386 return Parameters(true, 10 * kTickTime, 7 * kTickTime, Cubic, 3 * kTickTime, Cubic, 3 * kTickTime, Linear, 1); 387 case ScrollByPage: 388 return Parameters(true, 15 * kTickTime, 10 * kTickTime, Cubic, 5 * kTickTime, Cubic, 5 * kTickTime, Linear, 1); 389 case ScrollByPixel: 390 return Parameters(true, 11 * kTickTime, 2 * kTickTime, Cubic, 3 * kTickTime, Cubic, 3 * kTickTime, Quadratic, 1.25); 391 default: 392 ASSERT_NOT_REACHED(); 393 } 394 return Parameters(); 395 } 396 397 bool ScrollAnimatorNone::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float delta) 398 { 399 if (!m_scrollableArea->scrollAnimatorEnabled()) 400 return ScrollAnimator::scroll(orientation, granularity, step, delta); 401 402 TRACE_EVENT0("blink", "ScrollAnimatorNone::scroll"); 403 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: 409 case ScrollByLine: 410 case ScrollByPage: 411 case ScrollByPixel: 412 parameters = parametersForScrollGranularity(granularity); 413 break; 414 case ScrollByPrecisePixel: 415 return ScrollAnimator::scroll(orientation, granularity, step, delta); 416 } 417 418 // If the individual input setting is disabled, bail. 419 if (!parameters.m_isEnabled) 420 return ScrollAnimator::scroll(orientation, granularity, step, delta); 421 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)); 424 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(); 431 } 432 return needToScroll; 433 } 434 435 void ScrollAnimatorNone::scrollToOffsetWithoutAnimation(const FloatPoint& offset) 436 { 437 stopAnimationTimerIfNeeded(); 438 439 m_horizontalData.reset(); 440 *m_horizontalData.m_currentPosition = offset.x(); 441 m_horizontalData.m_desiredPosition = offset.x(); 442 m_currentPosX = offset.x(); 443 444 m_verticalData.reset(); 445 *m_verticalData.m_currentPosition = offset.y(); 446 m_verticalData.m_desiredPosition = offset.y(); 447 m_currentPosY = offset.y(); 448 449 notifyPositionChanged(); 450 } 451 452 void ScrollAnimatorNone::cancelAnimations() 453 { 454 m_animationActive = false; 455 } 456 457 void ScrollAnimatorNone::serviceScrollAnimations() 458 { 459 if (m_animationActive) 460 animationTimerFired(); 461 } 462 463 void ScrollAnimatorNone::willEndLiveResize() 464 { 465 updateVisibleLengths(); 466 } 467 468 void ScrollAnimatorNone::didAddVerticalScrollbar(Scrollbar*) 469 { 470 updateVisibleLengths(); 471 } 472 473 void ScrollAnimatorNone::didAddHorizontalScrollbar(Scrollbar*) 474 { 475 updateVisibleLengths(); 476 } 477 478 void ScrollAnimatorNone::updateVisibleLengths() 479 { 480 m_horizontalData.updateVisibleLength(scrollableArea()->visibleWidth()); 481 m_verticalData.updateVisibleLength(scrollableArea()->visibleHeight()); 482 } 483 484 void ScrollAnimatorNone::animationTimerFired() 485 { 486 TRACE_EVENT0("blink", "ScrollAnimatorNone::animationTimerFired"); 487 488 double currentTime = WTF::monotonicallyIncreasingTime(); 489 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; 495 496 if (continueAnimation) 497 startNextTimer(); 498 else 499 m_animationActive = false; 500 501 TRACE_EVENT0("blink", "ScrollAnimatorNone::notifyPositionChanged"); 502 notifyPositionChanged(); 503 504 if (!continueAnimation) 505 animationDidFinish(); 506 } 507 508 void ScrollAnimatorNone::startNextTimer() 509 { 510 if (scrollableArea()->scheduleAnimation()) 511 m_animationActive = true; 512 } 513 514 bool ScrollAnimatorNone::animationTimerActive() 515 { 516 return m_animationActive; 517 } 518 519 void ScrollAnimatorNone::stopAnimationTimerIfNeeded() 520 { 521 if (animationTimerActive()) 522 m_animationActive = false; 523 } 524 525 } // namespace blink 526