blob: 877cacb96f501f3996ed4409a08a72c9abc522f1 [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 "config.h"
#include "cc/page_scale_animation.h"
#include "ui/gfx/point_f.h"
#include "ui/gfx/rect_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(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();
}
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::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
{
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.
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);
gfx::SizeF currentViewportSize = viewportSizeAtScale(pageScaleFactorAt(interp));
return denormalizeToViewport(interpNormalized, currentViewportSize);
}
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