diff options
author | aelias@chromium.org <aelias@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-09 04:46:56 +0000 |
---|---|---|
committer | aelias@chromium.org <aelias@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-09 04:46:56 +0000 |
commit | f6250742aacb4083c9d05979eb04221b97486647 (patch) | |
tree | 7e5063ab8522cb045b7f6c9e8bb9d82e01b6097e | |
parent | 191af5694ac0cd7cffe848ac3bcf23f4c00c9474 (diff) | |
download | chromium_src-f6250742aacb4083c9d05979eb04221b97486647.zip chromium_src-f6250742aacb4083c9d05979eb04221b97486647.tar.gz chromium_src-f6250742aacb4083c9d05979eb04221b97486647.tar.bz2 |
cc: Rewrite PageScaleAnimation for new coordinate system.
- I moved PageScaleAnimation to work entirely at root content layer
scale, to be consistent with the new pinch zoom code. This avoids the
need for any scaling of scroll offsets. However, everything must now
be in floating point, and the complexity is moved into viewport size
calculation instead.
- I also took the opportunity to add support for two anchors instead of
just one, introducing an additional level of interpolation. This
makes the animation appear less jarring when we clamp it to document
edge, and it also removes the need for special-casing translation-only
animations.
BUG=152505
Review URL: https://chromiumcodereview.appspot.com/11090062
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@166855 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | cc/layer_tree_host_impl.cc | 56 | ||||
-rw-r--r-- | cc/layer_tree_host_impl.h | 2 | ||||
-rw-r--r-- | cc/layer_tree_host_impl_unittest.cc | 27 | ||||
-rw-r--r-- | cc/page_scale_animation.cc | 261 | ||||
-rw-r--r-- | cc/page_scale_animation.h | 70 |
5 files changed, 245 insertions, 171 deletions
diff --git a/cc/layer_tree_host_impl.cc b/cc/layer_tree_host_impl.cc index fb4eb59..cdd6995 100644 --- a/cc/layer_tree_host_impl.cc +++ b/cc/layer_tree_host_impl.cc @@ -294,26 +294,34 @@ void LayerTreeHostImpl::animate(base::TimeTicks monotonicTime, base::Time wallCl animateScrollbars(monotonicTime); } -void LayerTreeHostImpl::startPageScaleAnimation(gfx::Vector2d targetOffset, bool anchorPoint, float pageScale, base::TimeTicks startTime, base::TimeDelta duration) +void LayerTreeHostImpl::startPageScaleAnimation(gfx::Vector2d targetPosition, bool anchorPoint, float pageScale, base::TimeTicks startTime, base::TimeDelta duration) { if (!m_rootScrollLayerImpl) return; gfx::Vector2dF scrollTotal = m_rootScrollLayerImpl->scrollOffset() + m_rootScrollLayerImpl->scrollDelta(); - scrollTotal.Scale(m_pinchZoomViewport.pageScaleDelta()); - float scaleTotal = m_pinchZoomViewport.totalPageScaleFactor(); - gfx::Size scaledContentSize = gfx::ToFlooredSize(contentSize().Scale(m_pinchZoomViewport.pageScaleDelta())); + gfx::SizeF scaledContentSize = contentSize(); + if (!Settings::pageScalePinchZoomEnabled()) { + scrollTotal.Scale(1 / m_pinchZoomViewport.pageScaleFactor()); + scaledContentSize = scaledContentSize.Scale(1 / m_pinchZoomViewport.pageScaleFactor()); + } + gfx::SizeF viewportSize = m_deviceViewportSize; + viewportSize = viewportSize.Scale(1 / m_deviceScaleFactor); double startTimeSeconds = (startTime - base::TimeTicks()).InSecondsF(); - m_pageScaleAnimation = PageScaleAnimation::create(gfx::ToFlooredVector2d(scrollTotal), scaleTotal, m_deviceViewportSize, scaledContentSize, startTimeSeconds); + m_pageScaleAnimation = PageScaleAnimation::create(scrollTotal, m_pinchZoomViewport.totalPageScaleFactor(), viewportSize, scaledContentSize, startTimeSeconds); if (anchorPoint) { - gfx::Vector2dF windowAnchor = targetOffset; - windowAnchor.Scale(scaleTotal / pageScale); - windowAnchor -= scrollTotal; - m_pageScaleAnimation->zoomWithAnchor(gfx::ToFlooredVector2d(windowAnchor), pageScale, duration.InSecondsF()); - } else - m_pageScaleAnimation->zoomTo(targetOffset, pageScale, duration.InSecondsF()); + gfx::Vector2dF anchor(targetPosition); + if (!Settings::pageScalePinchZoomEnabled()) + anchor.Scale(1 / pageScale); + m_pageScaleAnimation->zoomWithAnchor(anchor, pageScale, duration.InSecondsF()); + } else { + gfx::Vector2dF scaledTargetPosition = targetPosition; + if (!Settings::pageScalePinchZoomEnabled()) + scaledTargetPosition.Scale(1 / pageScale); + m_pageScaleAnimation->zoomTo(scaledTargetPosition, pageScale, duration.InSecondsF()); + } m_client->setNeedsRedrawOnImplThread(); m_client->setNeedsCommitOnImplThread(); @@ -1262,10 +1270,10 @@ void LayerTreeHostImpl::pinchGestureEnd() void LayerTreeHostImpl::computeDoubleTapZoomDeltas(ScrollAndScaleSet* scrollInfo) { - float pageScale = m_pageScaleAnimation->finalPageScale(); - gfx::Vector2dF scrollOffset = m_pageScaleAnimation->finalScrollOffset(); - scrollOffset.Scale(m_pinchZoomViewport.pageScaleFactor() / pageScale); - makeScrollAndScaleSet(scrollInfo, gfx::ToRoundedVector2d(scrollOffset), pageScale); + gfx::Vector2dF scaledScrollOffset = m_pageScaleAnimation->targetScrollOffset(); + if (!Settings::pageScalePinchZoomEnabled()) + scaledScrollOffset.Scale(m_pinchZoomViewport.pageScaleFactor()); + makeScrollAndScaleSet(scrollInfo, ToFlooredVector2d(scaledScrollOffset), m_pageScaleAnimation->targetPageScaleFactor()); } void LayerTreeHostImpl::computePinchZoomDeltas(ScrollAndScaleSet* scrollInfo) @@ -1339,14 +1347,12 @@ scoped_ptr<ScrollAndScaleSet> LayerTreeHostImpl::processScrollDeltas() if (m_pinchGestureActive || m_pageScaleAnimation) { scrollInfo->pageScaleDelta = 1; m_pinchZoomViewport.setSentPageScaleDelta(1); - // FIXME(aelias): Make these painting optimizations compatible with + // FIXME(aelias): Make pinch-zoom painting optimization compatible with // compositor-side scaling. - if (!Settings::pageScalePinchZoomEnabled()) { - if (m_pinchGestureActive) - computePinchZoomDeltas(scrollInfo.get()); - else if (m_pageScaleAnimation.get()) - computeDoubleTapZoomDeltas(scrollInfo.get()); - } + if (!Settings::pageScalePinchZoomEnabled() && m_pinchGestureActive) + computePinchZoomDeltas(scrollInfo.get()); + else if (m_pageScaleAnimation.get()) + computeDoubleTapZoomDeltas(scrollInfo.get()); return scrollInfo.Pass(); } @@ -1379,9 +1385,11 @@ void LayerTreeHostImpl::animatePageScale(base::TimeTicks time) double monotonicTime = (time - base::TimeTicks()).InSecondsF(); gfx::Vector2dF scrollTotal = m_rootScrollLayerImpl->scrollOffset() + m_rootScrollLayerImpl->scrollDelta(); - setPageScaleDelta(m_pageScaleAnimation->pageScaleAtTime(monotonicTime) / m_pinchZoomViewport.pageScaleFactor()); + setPageScaleDelta(m_pageScaleAnimation->pageScaleFactorAtTime(monotonicTime) / m_pinchZoomViewport.pageScaleFactor()); gfx::Vector2dF nextScroll = m_pageScaleAnimation->scrollOffsetAtTime(monotonicTime); - nextScroll.Scale(1 / m_pinchZoomViewport.pageScaleDelta()); + + if (!Settings::pageScalePinchZoomEnabled()) + nextScroll.Scale(m_pinchZoomViewport.pageScaleFactor()); m_rootScrollLayerImpl->scrollBy(nextScroll - scrollTotal); m_client->setNeedsRedrawOnImplThread(); diff --git a/cc/layer_tree_host_impl.h b/cc/layer_tree_host_impl.h index 10020c8..3c9dba7 100644 --- a/cc/layer_tree_host_impl.h +++ b/cc/layer_tree_host_impl.h @@ -116,7 +116,7 @@ public: virtual void pinchGestureBegin() OVERRIDE; virtual void pinchGestureUpdate(float, gfx::Point) OVERRIDE; virtual void pinchGestureEnd() OVERRIDE; - virtual void startPageScaleAnimation(gfx::Vector2d targetOffset, bool anchorPoint, float pageScale, base::TimeTicks startTime, base::TimeDelta duration) OVERRIDE; + virtual void startPageScaleAnimation(gfx::Vector2d targetPosition, bool anchorPoint, float pageScale, base::TimeTicks startTime, base::TimeDelta duration) OVERRIDE; virtual void scheduleAnimation() OVERRIDE; struct CC_EXPORT FrameData : public RenderPassSink { diff --git a/cc/layer_tree_host_impl_unittest.cc b/cc/layer_tree_host_impl_unittest.cc index ee2e15a..ed7ef13 100644 --- a/cc/layer_tree_host_impl_unittest.cc +++ b/cc/layer_tree_host_impl_unittest.cc @@ -651,7 +651,7 @@ TEST_P(LayerTreeHostImplTest, pageScaleAnimation) LayerImpl* scrollLayer = m_hostImpl->rootScrollLayer(); DCHECK(scrollLayer); - const float minPageScale = Settings::pageScalePinchZoomEnabled() ? 1 : 0.5; + const float minPageScale = 0.5; const float maxPageScale = 4; const base::TimeTicks startTime = base::TimeTicks() + base::TimeDelta::FromSeconds(1); const base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100); @@ -772,33 +772,32 @@ TEST_P(LayerTreeHostImplTest, inhibitScrollAndPageScaleUpdatesWhileAnimatingPage LayerImpl* scrollLayer = m_hostImpl->rootScrollLayer(); DCHECK(scrollLayer); - const float minPageScale = Settings::pageScalePinchZoomEnabled() ? 1 : 0.5; + const float minPageScale = 0.5; const float maxPageScale = 4; const base::TimeTicks startTime = base::TimeTicks() + base::TimeDelta::FromSeconds(1); const base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100); const base::TimeTicks halfwayThroughAnimation = startTime + duration / 2; const base::TimeTicks endTime = startTime + duration; - // Start a page scale animation. + const float pageScaleDelta = 2; + gfx::Vector2d target(25, 25); + gfx::Vector2d scaledTarget = target; + if (!Settings::pageScalePinchZoomEnabled()) + scaledTarget = gfx::Vector2d(12, 12); + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); - m_hostImpl->startPageScaleAnimation(gfx::Vector2d(50, 50), false, pageScaleDelta, startTime, duration); + m_hostImpl->startPageScaleAnimation(target, false, pageScaleDelta, startTime, duration); // We should immediately get the final zoom and scroll values for the // animation. m_hostImpl->animate(halfwayThroughAnimation, base::Time()); scoped_ptr<ScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); - - if (!Settings::pageScalePinchZoomEnabled()) { - EXPECT_EQ(scrollInfo->pageScaleDelta, pageScaleDelta); - expectContains(*scrollInfo, scrollLayer->id(), gfx::Vector2d(25, 25)); - } else { - EXPECT_EQ(scrollInfo->pageScaleDelta, 1); - EXPECT_TRUE(scrollInfo->scrolls.empty()); - } + EXPECT_EQ(scrollInfo->pageScaleDelta, pageScaleDelta); + expectContains(*scrollInfo, scrollLayer->id(), scaledTarget); // Scrolling during the animation is ignored. const gfx::Vector2d scrollDelta(0, 10); - EXPECT_EQ(m_hostImpl->scrollBegin(gfx::Point(25, 25), InputHandlerClient::Wheel), InputHandlerClient::ScrollStarted); + EXPECT_EQ(m_hostImpl->scrollBegin(gfx::Point(target.x(), target.y()), InputHandlerClient::Wheel), InputHandlerClient::ScrollStarted); m_hostImpl->scrollBy(gfx::Point(), scrollDelta); m_hostImpl->scrollEnd(); @@ -807,7 +806,7 @@ TEST_P(LayerTreeHostImplTest, inhibitScrollAndPageScaleUpdatesWhileAnimatingPage m_hostImpl->animate(endTime, base::Time()); scrollInfo = m_hostImpl->processScrollDeltas(); EXPECT_EQ(scrollInfo->pageScaleDelta, pageScaleDelta); - expectContains(*scrollInfo, scrollLayer->id(), gfx::Vector2d(25, 25)); + expectContains(*scrollInfo, scrollLayer->id(), scaledTarget); } class DidDrawCheckLayer : public TiledLayerImpl { diff --git a/cc/page_scale_animation.cc b/cc/page_scale_animation.cc index 49948ab..877cacb 100644 --- a/cc/page_scale_animation.cc +++ b/cc/page_scale_animation.cc @@ -5,29 +5,60 @@ #include "config.h" #include "cc/page_scale_animation.h" - -#include "cc/geometry.h" +#include "ui/gfx/point_f.h" #include "ui/gfx/rect_f.h" -#include "ui/gfx/vector2d_conversions.h" -#include "ui/gfx/vector2d_f.h" +#include <algorithm> #include <math.h> +namespace { + +gfx::PointF toPointF(const gfx::Vector2dF& vector) +{ + return gfx::PointF(vector.x(), vector.y()); +} + +// This takes a viewport-relative vector and returns a vector whose values are +// between 0 and 1, representing the percentage position within the viewport. +gfx::Vector2dF normalizeFromViewport(const gfx::Vector2dF& denormalized, const gfx::SizeF& viewportSize) +{ + gfx::Vector2dF normalized(denormalized); + normalized.Scale(1 / viewportSize.width(), 1 / viewportSize.height()); + return normalized; +} + +gfx::Vector2dF denormalizeToViewport(const gfx::Vector2dF& normalized, const gfx::SizeF& viewportSize) +{ + gfx::Vector2dF denormalized(normalized); + denormalized.Scale(viewportSize.width(), viewportSize.height()); + return denormalized; +} + +gfx::Vector2dF interpolateBetween(const gfx::Vector2dF& start, const gfx::Vector2dF end, float interp) +{ + gfx::Vector2dF result; + result.set_x(start.x() + interp * (end.x() - start.x())); + result.set_y(start.y() + interp * (end.y() - start.y())); + return result; +} + +} + namespace cc { -scoped_ptr<PageScaleAnimation> PageScaleAnimation::create(gfx::Vector2d scrollStart, float pageScaleStart, const gfx::Size& windowSize, const gfx::Size& contentSize, double startTime) +scoped_ptr<PageScaleAnimation> PageScaleAnimation::create(const gfx::Vector2dF& startScrollOffset, float startPageScaleFactor, const gfx::SizeF& viewportSize, const gfx::SizeF& rootLayerSize, double startTime) { - return make_scoped_ptr(new PageScaleAnimation(scrollStart, pageScaleStart, windowSize, contentSize, startTime)); + return make_scoped_ptr(new PageScaleAnimation(startScrollOffset, startPageScaleFactor, viewportSize, rootLayerSize, startTime)); } -PageScaleAnimation::PageScaleAnimation(gfx::Vector2d scrollStart, float pageScaleStart, const gfx::Size& windowSize, const gfx::Size& contentSize, double startTime) - : m_scrollStart(scrollStart) - , m_pageScaleStart(pageScaleStart) - , m_windowSize(windowSize) - , m_contentSize(contentSize) - , m_anchorMode(false) - , m_scrollEnd(scrollStart) - , m_pageScaleEnd(pageScaleStart) +PageScaleAnimation::PageScaleAnimation(const gfx::Vector2dF& startScrollOffset, float startPageScaleFactor, const gfx::SizeF& viewportSize, const gfx::SizeF& rootLayerSize, double startTime) + : m_startPageScaleFactor(startPageScaleFactor) + , m_targetPageScaleFactor(0) + , m_startScrollOffset(startScrollOffset) + , m_startAnchor() + , m_targetAnchor() + , m_viewportSize(viewportSize) + , m_rootLayerSize(rootLayerSize) , m_startTime(startTime) , m_duration(0) { @@ -37,67 +68,93 @@ PageScaleAnimation::~PageScaleAnimation() { } -void PageScaleAnimation::zoomTo(gfx::Vector2d finalScroll, float finalPageScale, double duration) -{ - if (m_pageScaleStart != finalPageScale) { - // For uniform-looking zooming, infer the anchor (point that remains in - // place throughout the zoom) from the start and end rects. - gfx::RectF startRect(gfx::PointAtOffsetFromOrigin(m_scrollStart), m_windowSize); - gfx::RectF endRect(gfx::PointAtOffsetFromOrigin(finalScroll), m_windowSize); - endRect.Scale(m_pageScaleStart / finalPageScale); - - // The anchor is the point which is at the same ratio of the sides of - // both startRect and endRect. For example, a zoom-in double-tap to a - // perfectly centered rect will have anchor ratios (0.5, 0.5), while one - // to a rect touching the bottom-right of the screen will have anchor - // ratios (1.0, 1.0). In other words, it obeys the equations: - // anchorX = start_width * ratioX + start_x - // anchorX = end_width * ratioX + end_x - // anchorY = start_height * ratioY + start_y - // anchorY = end_height * ratioY + end_y - // where both anchor{x,y} and ratio{x,y} begin as unknowns. Solving - // for the ratios, we get the following formulas: - float ratioX = (startRect.x() - endRect.x()) / (endRect.width() - startRect.width()); - float ratioY = (startRect.y() - endRect.y()) / (endRect.height() - startRect.height()); - - gfx::Vector2d anchor(m_windowSize.width() * ratioX, m_windowSize.height() * ratioY); - zoomWithAnchor(anchor, finalPageScale, duration); - } else { - // If this is a pure translation, then there exists no anchor. Linearly - // interpolate the scroll offset instead. - m_scrollEnd = finalScroll; - m_pageScaleEnd = finalPageScale; - m_duration = duration; - m_anchorMode = false; +void PageScaleAnimation::zoomTo(const gfx::Vector2dF& targetScrollOffset, float targetPageScaleFactor, double duration) +{ + m_targetPageScaleFactor = targetPageScaleFactor; + m_targetScrollOffset = targetScrollOffset; + clampTargetScrollOffset(); + m_duration = duration; + + if (m_startPageScaleFactor == targetPageScaleFactor) { + m_startAnchor = m_startScrollOffset; + m_targetAnchor = targetScrollOffset; + return; } + + // For uniform-looking zooming, infer an anchor from the start and target + // viewport rects. + inferTargetAnchorFromScrollOffsets(); + m_startAnchor = m_targetAnchor; } -void PageScaleAnimation::zoomWithAnchor(gfx::Vector2d anchor, float finalPageScale, double duration) +void PageScaleAnimation::zoomWithAnchor(const gfx::Vector2dF& anchor, float targetPageScaleFactor, double duration) { - m_scrollEnd = m_scrollStart + anchor; - m_scrollEnd = gfx::ToFlooredVector2d(cc::ScaleVector2d(m_scrollEnd, finalPageScale / m_pageScaleStart)); - m_scrollEnd -= anchor; + m_startAnchor = anchor; + m_targetPageScaleFactor = targetPageScaleFactor; + m_duration = duration; - m_scrollEnd = ClampFromBelow(m_scrollEnd, gfx::Vector2d()); - gfx::SizeF scaledContentSize = m_contentSize.Scale(finalPageScale / m_pageScaleStart); - gfx::Vector2d maxScrollOffset = gfx::ToRoundedVector2d(BottomRight(gfx::RectF(scaledContentSize)) - BottomRight(gfx::Rect(m_windowSize))); - m_scrollEnd = m_scrollEnd; - m_scrollEnd = ClampFromAbove(m_scrollEnd, maxScrollOffset); + // We start zooming out from the anchor tapped by the user. But if + // the target scale is impossible to attain without hitting the root layer + // edges, then infer an anchor that doesn't collide with the edges. + // We will interpolate between the two anchors during the animation. + inferTargetScrollOffsetFromStartAnchor(); + clampTargetScrollOffset(); + inferTargetAnchorFromScrollOffsets(); +} - m_anchor = anchor; - m_pageScaleEnd = finalPageScale; - m_duration = duration; - m_anchorMode = true; +gfx::SizeF PageScaleAnimation::viewportSizeAtScale(float pageScaleFactor) const +{ + return gfx::SizeF(m_viewportSize.width() / pageScaleFactor, m_viewportSize.height() / pageScaleFactor); +} + +void PageScaleAnimation::inferTargetScrollOffsetFromStartAnchor() +{ + gfx::Vector2dF anchorRelativeToStartViewport = m_startAnchor - m_startScrollOffset; + gfx::Vector2dF normalized = normalizeFromViewport(anchorRelativeToStartViewport, viewportSizeAtScale(m_startPageScaleFactor)); + m_targetScrollOffset = m_startAnchor - denormalizeToViewport(normalized, viewportSizeAtScale(m_targetPageScaleFactor)); +} + +void PageScaleAnimation::inferTargetAnchorFromScrollOffsets() +{ + gfx::RectF startRect(toPointF(m_startScrollOffset), viewportSizeAtScale(m_startPageScaleFactor)); + gfx::RectF targetRect(toPointF(m_targetScrollOffset), viewportSizeAtScale(m_targetPageScaleFactor)); + + // The anchor is the point which is at the same normalized relative position + // within both startRect and endRect. For example, a zoom-in double-tap to a + // perfectly centered rect will have normalized anchor (0.5, 0.5), while one + // to a rect touching the bottom-right of the screen will have normalized + // anchor (1.0, 1.0). In other words, it obeys the equations: + // anchorX = start_width * normalizedX + start_x + // anchorX = target_width * normalizedX + target_x + // anchorY = start_height * normalizedY + start_y + // anchorY = target_height * normalizedY + target_y + // where both anchor{x,y} and normalized{x,y} begin as unknowns. Solving + // for the normalized, we get the following formulas: + gfx::Vector2dF normalized; + normalized.set_x((startRect.x() - targetRect.x()) / (targetRect.width() - startRect.width())); + normalized.set_y((startRect.y() - targetRect.y()) / (targetRect.height() - startRect.height())); + m_targetAnchor = m_targetScrollOffset + denormalizeToViewport(normalized, viewportSizeAtScale(m_targetPageScaleFactor)); +} + +void PageScaleAnimation::clampTargetScrollOffset() +{ + gfx::Vector2dF maxScrollPosition; + maxScrollPosition.set_x(m_rootLayerSize.width() - viewportSizeAtScale(m_targetPageScaleFactor).width()); + maxScrollPosition.set_y(m_rootLayerSize.height() - viewportSizeAtScale(m_targetPageScaleFactor).height()); + m_targetScrollOffset.set_x(std::max<float>(0, m_targetScrollOffset.x())); + m_targetScrollOffset.set_y(std::max<float>(0, m_targetScrollOffset.y())); + m_targetScrollOffset.set_x(std::min<float>(maxScrollPosition.x(), m_targetScrollOffset.x())); + m_targetScrollOffset.set_y(std::min<float>(maxScrollPosition.y(), m_targetScrollOffset.y())); } -gfx::Vector2d PageScaleAnimation::scrollOffsetAtTime(double time) const +gfx::Vector2dF PageScaleAnimation::scrollOffsetAtTime(double time) const { - return scrollOffsetAtRatio(progressRatioForTime(time)); + return scrollOffsetAt(interpAtTime(time)); } -float PageScaleAnimation::pageScaleAtTime(double time) const +float PageScaleAnimation::pageScaleFactorAtTime(double time) const { - return pageScaleAtRatio(progressRatioForTime(time)); + return pageScaleFactorAt(interpAtTime(time)); } bool PageScaleAnimation::isAnimationCompleteAtTime(double time) const @@ -105,7 +162,7 @@ bool PageScaleAnimation::isAnimationCompleteAtTime(double time) const return time >= endTime(); } -float PageScaleAnimation::progressRatioForTime(double time) const +float PageScaleAnimation::interpAtTime(double time) const { if (isAnimationCompleteAtTime(time)) return 1; @@ -113,52 +170,52 @@ float PageScaleAnimation::progressRatioForTime(double time) const return (time - m_startTime) / m_duration; } -gfx::Vector2d PageScaleAnimation::scrollOffsetAtRatio(float ratio) const -{ - if (ratio <= 0) - return m_scrollStart; - if (ratio >= 1) - return m_scrollEnd; - - float currentPageScale = pageScaleAtRatio(ratio); - gfx::Vector2d currentScrollOffset; - if (m_anchorMode) { - // Keep the anchor stable on the screen at the current scale. - gfx::Vector2dF documentAnchor = m_scrollStart + m_anchor; - documentAnchor.Scale(currentPageScale / m_pageScaleStart); - currentScrollOffset = gfx::ToRoundedVector2d(documentAnchor - m_anchor); - } else { - // First move both scroll offsets to the current coordinate space. - gfx::Vector2dF scaledStartScroll(m_scrollStart); - scaledStartScroll.Scale(currentPageScale / m_pageScaleStart); - gfx::Vector2dF scaledEndScroll(m_scrollEnd); - scaledEndScroll.Scale(currentPageScale / m_pageScaleEnd); - - // Linearly interpolate between them. - gfx::Vector2dF delta = scaledEndScroll - scaledStartScroll; - delta.Scale(ratio); - currentScrollOffset = gfx::ToRoundedVector2d(scaledStartScroll + delta); - } +gfx::Vector2dF PageScaleAnimation::scrollOffsetAt(float interp) const +{ + if (interp <= 0) + return m_startScrollOffset; + if (interp >= 1) + return m_targetScrollOffset; + + return anchorAt(interp) - viewportRelativeAnchorAt(interp); +} + +gfx::Vector2dF PageScaleAnimation::anchorAt(float interp) const +{ + // Interpolate from start to target anchor in absolute space. + gfx::Vector2dF delta = m_targetAnchor - m_startAnchor; + delta.Scale(interp); + return m_startAnchor + delta; +} + +gfx::Vector2dF PageScaleAnimation::viewportRelativeAnchorAt(float interp) const +{ + // Interpolate from start to target anchor in the space relative to the + // viewport at its current scale level. + gfx::Vector2dF anchorRelativeToStartViewport = m_startAnchor - m_startScrollOffset; + gfx::Vector2dF anchorRelativeToTargetViewport = m_targetAnchor - m_targetScrollOffset; + + gfx::Vector2dF startNormalized = normalizeFromViewport(anchorRelativeToStartViewport, viewportSizeAtScale(m_startPageScaleFactor)); + gfx::Vector2dF targetNormalized = normalizeFromViewport(anchorRelativeToTargetViewport, viewportSizeAtScale(m_targetPageScaleFactor)); + gfx::Vector2dF interpNormalized = interpolateBetween(startNormalized, targetNormalized, interp); - return currentScrollOffset; + gfx::SizeF currentViewportSize = viewportSizeAtScale(pageScaleFactorAt(interp)); + return denormalizeToViewport(interpNormalized, currentViewportSize); } -float PageScaleAnimation::pageScaleAtRatio(float ratio) const +float PageScaleAnimation::pageScaleFactorAt(float interp) const { - if (ratio <= 0) - return m_pageScaleStart; - if (ratio >= 1) - return m_pageScaleEnd; + if (interp <= 0) + return m_startPageScaleFactor; + if (interp >= 1) + return m_targetPageScaleFactor; // Linearly interpolate the magnitude in log scale. - // Log scale is needed to maintain the appearance of uniform zoom. For - // example, if we zoom from 0.5 to 4.0 in 3 seconds, then we should - // be zooming by 2x every second. - float diff = m_pageScaleEnd / m_pageScaleStart; + float diff = m_targetPageScaleFactor / m_startPageScaleFactor; float logDiff = log(diff); - logDiff *= ratio; + logDiff *= interp; diff = exp(logDiff); - return m_pageScaleStart * diff; + return m_startPageScaleFactor * diff; } } // namespace cc diff --git a/cc/page_scale_animation.h b/cc/page_scale_animation.h index c4c2940..2d8f6d5 100644 --- a/cc/page_scale_animation.h +++ b/cc/page_scale_animation.h @@ -7,7 +7,7 @@ #include "base/memory/scoped_ptr.h" #include "ui/gfx/size.h" -#include "ui/gfx/vector2d.h" +#include "ui/gfx/vector2d_f.h" namespace cc { @@ -15,30 +15,32 @@ namespace cc { // double-tap zoom. Initialize it with starting and ending scroll/page scale // positions and an animation length time, then call ...AtTime() at every frame // to obtain the current interpolated position. +// +// All sizes and vectors in this class's public methods are in the root scroll +// layer's coordinate space. class PageScaleAnimation { public: - // Construct with the starting page scale and scroll offset (which is in - // pageScaleStart space). The window size is the user-viewable area - // in pixels. - static scoped_ptr<PageScaleAnimation> create(gfx::Vector2d scrollStart, float pageScaleStart, const gfx::Size& windowSize, const gfx::Size& contentSize, double startTime); + // Construct with the state at the beginning of the animation. + static scoped_ptr<PageScaleAnimation> create(const gfx::Vector2dF& startScrollOffset, float startPageScaleFactor, const gfx::SizeF& viewportSize, const gfx::SizeF& rootLayerSize, double startTime); + ~PageScaleAnimation(); // The following methods initialize the animation. Call one of them // immediately after construction to set the final scroll and page scale. - // Zoom while explicitly specifying the top-left scroll position. The - // scroll offset is in finalPageScale coordinates. - void zoomTo(gfx::Vector2d finalScroll, float finalPageScale, double duration); + // Zoom while explicitly specifying the top-left scroll position. + void zoomTo(const gfx::Vector2dF& targetScrollOffset, float targetPageScaleFactor, double duration); - // Zoom based on a specified onscreen anchor, which will remain at the same - // position on the screen throughout the animation. The anchor is in local - // space relative to scrollStart. - void zoomWithAnchor(gfx::Vector2d anchor, float finalPageScale, double duration); + // Zoom based on a specified anchor. The animator will attempt to keep it + // at the same position on the physical display throughout the animation, + // unless the edges of the root layer are hit. The anchor is specified + // as an offset from the content layer. + void zoomWithAnchor(const gfx::Vector2dF& anchor, float targetPageScaleFactor, double duration); // Call these functions while the animation is in progress to output the // current state. - gfx::Vector2d scrollOffsetAtTime(double time) const; - float pageScaleAtTime(double time) const; + gfx::Vector2dF scrollOffsetAtTime(double time) const; + float pageScaleFactorAtTime(double time) const; bool isAnimationCompleteAtTime(double time) const; // The following methods return state which is invariant throughout the @@ -46,26 +48,34 @@ public: double startTime() const { return m_startTime; } double duration() const { return m_duration; } double endTime() const { return m_startTime + m_duration; } - gfx::Vector2d finalScrollOffset() const { return m_scrollEnd; } - float finalPageScale() const { return m_pageScaleEnd; } + const gfx::Vector2dF& targetScrollOffset() const { return m_targetScrollOffset; } + float targetPageScaleFactor() const { return m_targetPageScaleFactor; } protected: - PageScaleAnimation(gfx::Vector2d scrollStart, float pageScaleStart, const gfx::Size& windowSize, const gfx::Size& contentSize, double startTime); + PageScaleAnimation(const gfx::Vector2dF& startScrollOffset, float startPageScaleFactor, const gfx::SizeF& viewportSize, const gfx::SizeF& rootLayerSize, double startTime); private: - float progressRatioForTime(double time) const; - gfx::Vector2d scrollOffsetAtRatio(float ratio) const; - float pageScaleAtRatio(float ratio) const; - - gfx::Vector2d m_scrollStart; - float m_pageScaleStart; - gfx::Size m_windowSize; - gfx::Size m_contentSize; - - bool m_anchorMode; - gfx::Vector2d m_anchor; - gfx::Vector2d m_scrollEnd; - float m_pageScaleEnd; + void clampTargetScrollOffset(); + void inferTargetScrollOffsetFromStartAnchor(); + void inferTargetAnchorFromScrollOffsets(); + gfx::SizeF viewportSizeAtScale(float pageScaleFactor) const; + + float interpAtTime(double time) const; + gfx::Vector2dF scrollOffsetAt(float interp) const; + gfx::Vector2dF anchorAt(float interp) const; + gfx::Vector2dF viewportRelativeAnchorAt(float interp) const; + float pageScaleFactorAt(float interp) const; + + float m_startPageScaleFactor; + float m_targetPageScaleFactor; + gfx::Vector2dF m_startScrollOffset; + gfx::Vector2dF m_targetScrollOffset; + + gfx::Vector2dF m_startAnchor; + gfx::Vector2dF m_targetAnchor; + + gfx::SizeF m_viewportSize; + gfx::SizeF m_rootLayerSize; double m_startTime; double m_duration; |