// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "cc/page_scale_animation.h" #include "base/logging.h" #include "ui/gfx/point_f.h" #include "ui/gfx/rect_f.h" #include "ui/gfx/vector2d_conversions.h" #include namespace { // 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) { return gfx::ScaleVector2d(denormalized, 1 / viewportSize.width(), 1 / viewportSize.height()); } gfx::Vector2dF denormalizeToViewport(const gfx::Vector2dF& normalized, const gfx::SizeF& viewportSize) { return gfx::ScaleVector2d(normalized, viewportSize.width(), viewportSize.height()); } gfx::Vector2dF interpolateBetween(const gfx::Vector2dF& start, const gfx::Vector2dF end, float interp) { return start + gfx::ScaleVector2d(end - start, interp); } } namespace cc { scoped_ptr PageScaleAnimation::create(const gfx::Vector2dF& startScrollOffset, float startPageScaleFactor, const gfx::SizeF& viewportSize, const gfx::SizeF& rootLayerSize, double startTime) { return make_scoped_ptr(new PageScaleAnimation(startScrollOffset, startPageScaleFactor, viewportSize, rootLayerSize, startTime)); } 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) { } PageScaleAnimation::~PageScaleAnimation() { } 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(const gfx::Vector2dF& anchor, float targetPageScaleFactor, double duration) { m_startAnchor = anchor; m_targetPageScaleFactor = targetPageScaleFactor; m_duration = duration; // 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(); if (m_startPageScaleFactor == m_targetPageScaleFactor) { m_targetAnchor = m_startAnchor; return; } inferTargetAnchorFromScrollOffsets(); } void PageScaleAnimation::inferTargetScrollOffsetFromStartAnchor() { gfx::Vector2dF normalized = normalizeFromViewport(m_startAnchor - m_startScrollOffset, startViewportSize()); m_targetScrollOffset = m_startAnchor - denormalizeToViewport(normalized, targetViewportSize()); } void PageScaleAnimation::inferTargetAnchorFromScrollOffsets() { // The anchor is the point which is at the same normalized relative position // within both start viewport rect and target viewport rect. 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: // anchor = start_size * normalized + start_offset // anchor = target_size * normalized + target_offset // where both anchor and normalized begin as unknowns. Solving // for the normalized, we get the following: float widthScale = 1 / (targetViewportSize().width() - startViewportSize().width()); float heightScale = 1 / (targetViewportSize().height() - startViewportSize().height()); gfx::Vector2dF normalized = gfx::ScaleVector2d(m_startScrollOffset - m_targetScrollOffset, widthScale, heightScale); m_targetAnchor = m_targetScrollOffset + denormalizeToViewport(normalized, targetViewportSize()); } void PageScaleAnimation::clampTargetScrollOffset() { gfx::Vector2dF maxScrollOffset = gfx::RectF(m_rootLayerSize).bottom_right() - gfx::RectF(targetViewportSize()).bottom_right(); m_targetScrollOffset.ClampToMin(gfx::Vector2dF()); m_targetScrollOffset.ClampToMax(maxScrollOffset); } gfx::SizeF PageScaleAnimation::startViewportSize() const { return gfx::ScaleSize(m_viewportSize, 1 / m_startPageScaleFactor); } gfx::SizeF PageScaleAnimation::targetViewportSize() const { return gfx::ScaleSize(m_viewportSize, 1 / m_targetPageScaleFactor); } gfx::SizeF PageScaleAnimation::viewportSizeAt(float interp) const { return gfx::ScaleSize(m_viewportSize, 1 / pageScaleFactorAt(interp)); } gfx::Vector2dF PageScaleAnimation::scrollOffsetAtTime(double time) const { return scrollOffsetAt(interpAtTime(time)); } float PageScaleAnimation::pageScaleFactorAtTime(double time) const { return pageScaleFactorAt(interpAtTime(time)); } bool PageScaleAnimation::isAnimationCompleteAtTime(double time) const { return time >= endTime(); } float PageScaleAnimation::interpAtTime(double time) const { DCHECK_GE(time, m_startTime); if (isAnimationCompleteAtTime(time)) return 1; return (time - m_startTime) / m_duration; } 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. return interpolateBetween(m_startAnchor, m_targetAnchor, interp); } gfx::Vector2dF PageScaleAnimation::viewportRelativeAnchorAt(float interp) const { // Interpolate from start to target anchor in normalized space. gfx::Vector2dF startNormalized = normalizeFromViewport(m_startAnchor - m_startScrollOffset, startViewportSize()); gfx::Vector2dF targetNormalized = normalizeFromViewport(m_targetAnchor - m_targetScrollOffset, targetViewportSize()); gfx::Vector2dF interpNormalized = interpolateBetween(startNormalized, targetNormalized, interp); return denormalizeToViewport(interpNormalized, viewportSizeAt(interp)); } float PageScaleAnimation::pageScaleFactorAt(float interp) const { if (interp <= 0) return m_startPageScaleFactor; if (interp >= 1) return m_targetPageScaleFactor; // Linearly interpolate the magnitude in log scale. float diff = m_targetPageScaleFactor / m_startPageScaleFactor; float logDiff = log(diff); logDiff *= interp; diff = exp(logDiff); return m_startPageScaleFactor * diff; } } // namespace cc