Chromium Code Reviews| Index: cc/page_scale_animation.cc |
| diff --git a/cc/page_scale_animation.cc b/cc/page_scale_animation.cc |
| index 49948abd333e9ce8aa72f83e6db51e143e58ebe2..2ee2c2f1628ed95c962cefcd35083817118e60be 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" |
|
danakj
2012/11/09 05:06:49
keep this. see below.
|
| +#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) |
|
danakj
2012/11/09 05:06:49
use gfx::PointAtOffsetFromOrigin instead.
|
| +{ |
| + 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); |
|
danakj
2012/11/09 05:06:49
You could use ScaleVector2d() from cc/geometry.h t
|
| + 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); |
|
danakj
2012/11/09 05:06:49
same
|
| + 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; |
|
danakj
2012/11/09 05:06:49
return start + (end - start).Scale(interp);
|
| +} |
| + |
| +} |
| + |
| 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,95 @@ 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 |
| +{ |
| + gfx::SizeF scaledViewportSize = m_viewportSize; |
|
danakj
2012/11/09 05:06:49
return m_viewportSize.Scale(1 / pageScaleFactor);
|
| + scaledViewportSize.Scale(1 / pageScaleFactor); |
| + return scaledViewportSize; |
| +} |
| + |
| +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)); |
|
danakj
2012/11/09 05:06:49
Don't make rects, just store the sizes. See below.
|
| + 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; |
|
danakj
2012/11/09 05:06:49
float widthScale = targetSize.width() - startSize.
danakj
2012/11/09 05:08:27
I mean:
float widthScale = targetSize.width() -
|
| + 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; |
|
danakj
2012/11/09 05:06:49
maxScrollOffset
Position is for Points.
|
| + 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())); |
|
danakj
2012/11/09 05:06:49
Use cc/geometry.h methods, which will move to ui/g
|
| + m_targetScrollOffset.set_y(std::max<float>(0, m_targetScrollOffset.y())); |
| + m_targetScrollOffset.set_x(std::min<float>(maxScrollPosition.x(), m_targetScrollOffset.x())); |
|
danakj
2012/11/09 05:06:49
m_targetScrollOffset = ClampFromAbove(m_targetScro
|
| + 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 +164,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 +172,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); |
|
danakj
2012/11/09 05:06:49
you could use ScaleVector2d() from cc/geometry.h t
|
| + 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 |