blob: 12dda09f147ee8c47528f43e0133c87876fe3ec1 [file] [log] [blame] [edit]
// 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 "CCPageScaleAnimation.h"
#include "FloatRect.h"
#include "FloatSize.h"
#include <math.h>
namespace cc {
PassOwnPtr<CCPageScaleAnimation> CCPageScaleAnimation::create(const IntSize& scrollStart, float pageScaleStart, const IntSize& windowSize, const IntSize& contentSize, double startTime)
{
return adoptPtr(new CCPageScaleAnimation(scrollStart, pageScaleStart, windowSize, contentSize, startTime));
}
CCPageScaleAnimation::CCPageScaleAnimation(const IntSize& scrollStart, float pageScaleStart, const IntSize& windowSize, const IntSize& contentSize, double startTime)
: m_scrollStart(scrollStart)
, m_pageScaleStart(pageScaleStart)
, m_windowSize(windowSize)
, m_contentSize(contentSize)
, m_anchorMode(false)
, m_scrollEnd(scrollStart)
, m_pageScaleEnd(pageScaleStart)
, m_startTime(startTime)
, m_duration(0)
{
}
void CCPageScaleAnimation::zoomTo(const IntSize& 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.
FloatRect startRect(IntPoint(m_scrollStart), m_windowSize);
FloatRect endRect(IntPoint(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());
IntSize 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 CCPageScaleAnimation::zoomWithAnchor(const IntSize& anchor, float finalPageScale, double duration)
{
m_scrollEnd = m_scrollStart + anchor;
m_scrollEnd.scale(finalPageScale / m_pageScaleStart);
m_scrollEnd -= anchor;
m_scrollEnd.clampNegativeToZero();
FloatSize scaledContentSize(m_contentSize);
scaledContentSize.scale(finalPageScale / m_pageScaleStart);
IntSize maxScrollPosition = roundedIntSize(scaledContentSize - m_windowSize);
m_scrollEnd = m_scrollEnd.shrunkTo(maxScrollPosition);
m_anchor = anchor;
m_pageScaleEnd = finalPageScale;
m_duration = duration;
m_anchorMode = true;
}
IntSize CCPageScaleAnimation::scrollOffsetAtTime(double time) const
{
return scrollOffsetAtRatio(progressRatioForTime(time));
}
float CCPageScaleAnimation::pageScaleAtTime(double time) const
{
return pageScaleAtRatio(progressRatioForTime(time));
}
bool CCPageScaleAnimation::isAnimationCompleteAtTime(double time) const
{
return time >= endTime();
}
float CCPageScaleAnimation::progressRatioForTime(double time) const
{
if (isAnimationCompleteAtTime(time))
return 1;
return (time - m_startTime) / m_duration;
}
IntSize CCPageScaleAnimation::scrollOffsetAtRatio(float ratio) const
{
if (ratio <= 0)
return m_scrollStart;
if (ratio >= 1)
return m_scrollEnd;
float currentPageScale = pageScaleAtRatio(ratio);
IntSize currentScrollOffset;
if (m_anchorMode) {
// Keep the anchor stable on the screen at the current scale.
IntSize documentAnchor = m_scrollStart + m_anchor;
documentAnchor.scale(currentPageScale / m_pageScaleStart);
currentScrollOffset = documentAnchor - m_anchor;
} else {
// First move both scroll offsets to the current coordinate space.
FloatSize scaledStartScroll(m_scrollStart);
scaledStartScroll.scale(currentPageScale / m_pageScaleStart);
FloatSize scaledEndScroll(m_scrollEnd);
scaledEndScroll.scale(currentPageScale / m_pageScaleEnd);
// Linearly interpolate between them.
FloatSize delta = scaledEndScroll - scaledStartScroll;
delta.scale(ratio);
currentScrollOffset = roundedIntSize(scaledStartScroll + delta);
}
return currentScrollOffset;
}
float CCPageScaleAnimation::pageScaleAtRatio(float ratio) const
{
if (ratio <= 0)
return m_pageScaleStart;
if (ratio >= 1)
return m_pageScaleEnd;
// 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 logDiff = log(diff);
logDiff *= ratio;
diff = exp(logDiff);
return m_pageScaleStart * diff;
}
} // namespace cc