1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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
|