summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/platform/ScrollAnimatorWin.cpp
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-06 11:45:16 +0100
committerSteve Block <steveblock@google.com>2011-05-12 13:44:10 +0100
commitcad810f21b803229eb11403f9209855525a25d57 (patch)
tree29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/platform/ScrollAnimatorWin.cpp
parent121b0cf4517156d0ac5111caf9830c51b69bae8f (diff)
downloadexternal_webkit-cad810f21b803229eb11403f9209855525a25d57.zip
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/platform/ScrollAnimatorWin.cpp')
-rw-r--r--Source/WebCore/platform/ScrollAnimatorWin.cpp301
1 files changed, 301 insertions, 0 deletions
diff --git a/Source/WebCore/platform/ScrollAnimatorWin.cpp b/Source/WebCore/platform/ScrollAnimatorWin.cpp
new file mode 100644
index 0000000..025aa71
--- /dev/null
+++ b/Source/WebCore/platform/ScrollAnimatorWin.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2010, Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if ENABLE(SMOOTH_SCROLLING)
+
+#include "ScrollAnimatorWin.h"
+
+#include "ScrollbarClient.h"
+#include "ScrollbarTheme.h"
+#include <algorithm>
+#include <wtf/CurrentTime.h>
+
+namespace WebCore {
+
+// static
+ScrollAnimator* ScrollAnimator::create(ScrollbarClient* client)
+{
+ return new ScrollAnimatorWin(client);
+}
+
+const double ScrollAnimatorWin::animationTimerDelay = 0.01;
+
+ScrollAnimatorWin::PerAxisData::PerAxisData(ScrollAnimatorWin* parent, float* currentPos)
+ : m_currentPos(currentPos)
+ , m_desiredPos(0)
+ , m_currentVelocity(0)
+ , m_desiredVelocity(0)
+ , m_lastAnimationTime(0)
+ , m_animationTimer(parent, &ScrollAnimatorWin::animationTimerFired)
+{
+}
+
+
+ScrollAnimatorWin::ScrollAnimatorWin(ScrollbarClient* client)
+ : ScrollAnimator(client)
+ , m_horizontalData(this, &m_currentPosX)
+ , m_verticalData(this, &m_currentPosY)
+{
+}
+
+ScrollAnimatorWin::~ScrollAnimatorWin()
+{
+ stopAnimationTimerIfNeeded(&m_horizontalData);
+ stopAnimationTimerIfNeeded(&m_verticalData);
+}
+
+bool ScrollAnimatorWin::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
+{
+ // Don't animate jumping to the beginning or end of the document.
+ if (granularity == ScrollByDocument)
+ return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
+
+ // This is an animatable scroll. Calculate the scroll delta.
+ PerAxisData* data = (orientation == VerticalScrollbar) ? &m_verticalData : &m_horizontalData;
+ float newPos = std::max(std::min(data->m_desiredPos + (step * multiplier), static_cast<float>(m_client->scrollSize(orientation))), 0.0f);
+ if (newPos == data->m_desiredPos)
+ return false;
+ data->m_desiredPos = newPos;
+
+ // Calculate the animation velocity.
+ if (*data->m_currentPos == data->m_desiredPos)
+ return false;
+ bool alreadyAnimating = data->m_animationTimer.isActive();
+ // There are a number of different sources of scroll requests. We want to
+ // make both keyboard and wheel-generated scroll requests (which can come at
+ // unpredictable rates) and autoscrolling from holding down the mouse button
+ // on a scrollbar part (where the request rate can be obtained from the
+ // scrollbar theme) feel smooth, responsive, and similar.
+ //
+ // When autoscrolling, the scrollbar's autoscroll timer will call us to
+ // increment the desired position by |step| (with |multiplier| == 1) every
+ // ScrollbarTheme::nativeTheme()->autoscrollTimerDelay() seconds. If we set
+ // the desired velocity to exactly this rate, smooth scrolling will neither
+ // race ahead (and then have to slow down) nor increasingly lag behind, but
+ // will be smooth and synchronized.
+ //
+ // Note that because of the acceleration period, the current position in
+ // this case would lag the desired one by a small, constant amount (see
+ // comments on animateScroll()); the exact amount is given by
+ // lag = |step| - v(0.5tA + tD)
+ // Where
+ // v = The steady-state velocity,
+ // |step| / ScrollbarTheme::nativeTheme()->autoscrollTimerDelay()
+ // tA = accelerationTime()
+ // tD = The time we pretend has already passed when starting to scroll,
+ // |animationTimerDelay|
+ //
+ // This lag provides some buffer against timer jitter so we're less likely
+ // to hit the desired position and stop (and thus have to re-accelerate,
+ // causing a visible hitch) while waiting for the next autoscroll increment.
+ //
+ // Thus, for autoscroll-timer-triggered requests, the ideal steady-state
+ // distance to travel in each time interval is:
+ // float animationStep = step;
+ // Note that when we're not already animating, this is exactly the same as
+ // the distance to the target position. We'll return to that in a moment.
+ //
+ // For keyboard and wheel scrolls, we don't know when the next increment
+ // will be requested. If we set the target velocity based on how far away
+ // from the target position we are, then for keyboard/wheel events that come
+ // faster than the autoscroll delay, we'll asymptotically approach the
+ // velocity needed to stay smoothly in sync with the user's actions; for
+ // events that come slower, we'll scroll one increment and then pause until
+ // the next event fires.
+ float animationStep = fabs(newPos - *data->m_currentPos);
+ // If a key is held down (or the wheel continually spun), then once we have
+ // reached a velocity close to the steady-state velocity, we're likely to
+ // hit the desired position at around the same time we'd expect the next
+ // increment to occur -- bad because it leads to hitching as described above
+ // (if autoscroll-based requests didn't result in a small amount of constant
+ // lag). So if we're called again while already animating, we want to trim
+ // the animationStep slightly to maintain lag like what's described above.
+ // (I say "maintain" since we'll already be lagged due to the acceleration
+ // during the first scroll period.)
+ //
+ // Remember that trimming won't cause us to fall steadily further behind
+ // here, because the further behind we are, the larger the base step value
+ // above. Given the scrolling algorithm in animateScroll(), the practical
+ // effect will actually be that, assuming a constant trim factor, we'll lag
+ // by a constant amount depending on the rate at which increments occur
+ // compared to the autoscroll timer delay. The exact lag is given by
+ // lag = |step| * ((r / k) - 1)
+ // Where
+ // r = The ratio of the autoscroll repeat delay,
+ // ScrollbarTheme::nativeTheme()->autoscrollTimerDelay(), to the
+ // key/wheel repeat delay (i.e. > 1 when keys repeat faster)
+ // k = The velocity trim constant given below
+ //
+ // We want to choose the trim factor such that for calls that come at the
+ // autoscroll timer rate, we'll wind up with the same lag as in the
+ // "perfect" case described above (or, to put it another way, we'll end up
+ // with |animationStep| == |step| * |multiplier| despite the actual distance
+ // calculated above being larger than that). This will result in "perfect"
+ // behavior for autoscrolling without having to special-case it.
+ if (alreadyAnimating)
+ animationStep /= (2.0 - ((1.0 / ScrollbarTheme::nativeTheme()->autoscrollTimerDelay()) * (0.5 * accelerationTime() + animationTimerDelay)));
+ // The result of all this is that single keypresses or wheel flicks will
+ // scroll in the same time period as single presses of scrollbar elements;
+ // holding the mouse down on a scrollbar part will scroll as fast as
+ // possible without hitching; and other repeated scroll events will also
+ // scroll with the same time lag as holding down the mouse on a scrollbar
+ // part.
+ data->m_desiredVelocity = animationStep / ScrollbarTheme::nativeTheme()->autoscrollTimerDelay();
+
+ // If we're not already scrolling, start.
+ if (!alreadyAnimating)
+ animateScroll(data);
+ return true;
+}
+
+void ScrollAnimatorWin::setScrollPositionAndStopAnimation(ScrollbarOrientation orientation, float pos)
+{
+ PerAxisData* data = (orientation == HorizontalScrollbar) ? &m_horizontalData : &m_verticalData;
+ stopAnimationTimerIfNeeded(data);
+ *data->m_currentPos = pos;
+ data->m_desiredPos = pos;
+ data->m_currentVelocity = 0;
+ data->m_desiredVelocity = 0;
+}
+
+// static
+double ScrollAnimatorWin::accelerationTime()
+{
+ // We elect to use ScrollbarTheme::nativeTheme()->autoscrollTimerDelay() as
+ // the length of time we'll take to accelerate from 0 to our target
+ // velocity. Choosing a larger value would produce a more pronounced
+ // acceleration effect.
+ return ScrollbarTheme::nativeTheme()->autoscrollTimerDelay();
+}
+
+void ScrollAnimatorWin::animationTimerFired(Timer<ScrollAnimatorWin>* timer)
+{
+ animateScroll((timer == &m_horizontalData.m_animationTimer) ? &m_horizontalData : &m_verticalData);
+}
+
+void ScrollAnimatorWin::stopAnimationTimerIfNeeded(PerAxisData* data)
+{
+ if (data->m_animationTimer.isActive())
+ data->m_animationTimer.stop();
+}
+
+void ScrollAnimatorWin::animateScroll(PerAxisData* data)
+{
+ // Note on smooth scrolling perf versus non-smooth scrolling perf:
+ // The total time to perform a complete scroll is given by
+ // t = t0 + 0.5tA - tD + tS
+ // Where
+ // t0 = The time to perform the scroll without smooth scrolling
+ // tA = The acceleration time,
+ // ScrollbarTheme::nativeTheme()->autoscrollTimerDelay() (see below)
+ // tD = |animationTimerDelay|
+ // tS = A value less than or equal to the time required to perform a
+ // single scroll increment, i.e. the work done due to calling
+ // client()->valueChanged() (~0 for simple pages, larger for complex
+ // pages).
+ //
+ // Because tA and tD are fairly small, the total lag (as users perceive it)
+ // is negligible for simple pages and roughly tS for complex pages. Without
+ // knowing in advance how large tS is it's hard to do better than this.
+ // Perhaps we could try to remember previous values and forward-compensate.
+
+
+ // We want to update the scroll position based on the time it's been since
+ // our last update. This may be longer than our ideal time, especially if
+ // the page is complex or the system is slow.
+ //
+ // To avoid feeling laggy, if we've just started smooth scrolling we pretend
+ // we've already accelerated for one ideal interval, so that we'll scroll at
+ // least some distance immediately.
+ double lastScrollInterval = data->m_currentVelocity ? (WTF::currentTime() - data->m_lastAnimationTime) : animationTimerDelay;
+
+ // Figure out how far we've actually traveled and update our current
+ // velocity.
+ float distanceTraveled;
+ if (data->m_currentVelocity < data->m_desiredVelocity) {
+ // We accelerate at a constant rate until we reach the desired velocity.
+ float accelerationRate = data->m_desiredVelocity / accelerationTime();
+
+ // Figure out whether contant acceleration has caused us to reach our
+ // target velocity.
+ float potentialVelocityChange = accelerationRate * lastScrollInterval;
+ float potentialNewVelocity = data->m_currentVelocity + potentialVelocityChange;
+ if (potentialNewVelocity > data->m_desiredVelocity) {
+ // We reached the target velocity at some point between our last
+ // update and now. The distance traveled can be calculated in two
+ // pieces: the distance traveled while accelerating, and the
+ // distance traveled after reaching the target velocity.
+ float actualVelocityChange = data->m_desiredVelocity - data->m_currentVelocity;
+ float accelerationInterval = actualVelocityChange / accelerationRate;
+ // The distance traveled under constant acceleration is the area
+ // under a line segment with a constant rising slope. Break this
+ // into a triangular portion atop a rectangular portion and sum.
+ distanceTraveled = ((data->m_currentVelocity + (actualVelocityChange / 2)) * accelerationInterval);
+ // The distance traveled at the target velocity is simply
+ // (target velocity) * (remaining time after accelerating).
+ distanceTraveled += (data->m_desiredVelocity * (lastScrollInterval - accelerationInterval));
+ data->m_currentVelocity = data->m_desiredVelocity;
+ } else {
+ // Constant acceleration through the entire time interval.
+ distanceTraveled = (data->m_currentVelocity + (potentialVelocityChange / 2)) * lastScrollInterval;
+ data->m_currentVelocity = potentialNewVelocity;
+ }
+ } else {
+ // We've already reached the target velocity, so the distance we've
+ // traveled is simply (current velocity) * (elapsed time).
+ distanceTraveled = data->m_currentVelocity * lastScrollInterval;
+ // If our desired velocity has decreased, drop the current velocity too.
+ data->m_currentVelocity = data->m_desiredVelocity;
+ }
+
+ // Now update the scroll position based on the distance traveled.
+ if (distanceTraveled >= fabs(data->m_desiredPos - *data->m_currentPos)) {
+ // We've traveled far enough to reach the desired position. Stop smooth
+ // scrolling.
+ *data->m_currentPos = data->m_desiredPos;
+ data->m_currentVelocity = 0;
+ data->m_desiredVelocity = 0;
+ } else {
+ // Not yet at the target position. Travel towards it and set up the
+ // next update.
+ if (*data->m_currentPos > data->m_desiredPos)
+ distanceTraveled = -distanceTraveled;
+ *data->m_currentPos += distanceTraveled;
+ data->m_animationTimer.startOneShot(animationTimerDelay);
+ data->m_lastAnimationTime = WTF::currentTime();
+ }
+ m_client->setScrollOffsetFromAnimation(IntPoint(*m_horizontalData.m_currentPos, *m_verticalData.m_currentPos));
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(SMOOTH_SCROLLING)