diff options
Diffstat (limited to 'cc/page_scale_animation.cc')
-rw-r--r-- | cc/page_scale_animation.cc | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/cc/page_scale_animation.cc b/cc/page_scale_animation.cc new file mode 100644 index 0000000..12dda09 --- /dev/null +++ b/cc/page_scale_animation.cc @@ -0,0 +1,159 @@ +// 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 |