blob: b8f1fe6185899e4426bdcdbaf1d406e84684b415 [file] [log] [blame]
// 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 <math.h>
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> 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();
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