// 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 namespace cc { PassOwnPtr 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