1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "cc/input/page_scale_animation.h"
9 #include "base/logging.h"
10 #include "cc/animation/timing_function.h"
11 #include "ui/gfx/point_f.h"
12 #include "ui/gfx/rect_f.h"
13 #include "ui/gfx/vector2d_conversions.h"
17 // This takes a viewport-relative vector and returns a vector whose values are
18 // between 0 and 1, representing the percentage position within the viewport.
19 gfx::Vector2dF
NormalizeFromViewport(const gfx::Vector2dF
& denormalized
,
20 const gfx::SizeF
& viewport_size
) {
21 return gfx::ScaleVector2d(denormalized
,
22 1.f
/ viewport_size
.width(),
23 1.f
/ viewport_size
.height());
26 gfx::Vector2dF
DenormalizeToViewport(const gfx::Vector2dF
& normalized
,
27 const gfx::SizeF
& viewport_size
) {
28 return gfx::ScaleVector2d(normalized
,
29 viewport_size
.width(),
30 viewport_size
.height());
33 gfx::Vector2dF
InterpolateBetween(const gfx::Vector2dF
& start
,
34 const gfx::Vector2dF
& end
,
36 return start
+ gfx::ScaleVector2d(end
- start
, interp
);
43 using base::TimeTicks
;
44 using base::TimeDelta
;
46 scoped_ptr
<PageScaleAnimation
> PageScaleAnimation::Create(
47 const gfx::Vector2dF
& start_scroll_offset
,
48 float start_page_scale_factor
,
49 const gfx::SizeF
& viewport_size
,
50 const gfx::SizeF
& root_layer_size
,
51 scoped_ptr
<TimingFunction
> timing_function
) {
52 return make_scoped_ptr(new PageScaleAnimation(start_scroll_offset
,
53 start_page_scale_factor
,
56 timing_function
.Pass()));
59 PageScaleAnimation::PageScaleAnimation(
60 const gfx::Vector2dF
& start_scroll_offset
,
61 float start_page_scale_factor
,
62 const gfx::SizeF
& viewport_size
,
63 const gfx::SizeF
& root_layer_size
,
64 scoped_ptr
<TimingFunction
> timing_function
)
65 : start_page_scale_factor_(start_page_scale_factor
),
66 target_page_scale_factor_(0.f
),
67 start_scroll_offset_(start_scroll_offset
),
70 viewport_size_(viewport_size
),
71 root_layer_size_(root_layer_size
),
72 timing_function_(timing_function
.Pass()) {
75 PageScaleAnimation::~PageScaleAnimation() {}
77 void PageScaleAnimation::ZoomTo(const gfx::Vector2dF
& target_scroll_offset
,
78 float target_page_scale_factor
,
80 target_page_scale_factor_
= target_page_scale_factor
;
81 target_scroll_offset_
= target_scroll_offset
;
82 ClampTargetScrollOffset();
83 duration_
= TimeDelta::FromSecondsD(duration
);
85 if (start_page_scale_factor_
== target_page_scale_factor
) {
86 start_anchor_
= start_scroll_offset_
;
87 target_anchor_
= target_scroll_offset
;
91 // For uniform-looking zooming, infer an anchor from the start and target
93 InferTargetAnchorFromScrollOffsets();
94 start_anchor_
= target_anchor_
;
97 void PageScaleAnimation::ZoomWithAnchor(const gfx::Vector2dF
& anchor
,
98 float target_page_scale_factor
,
100 start_anchor_
= anchor
;
101 target_page_scale_factor_
= target_page_scale_factor
;
102 duration_
= TimeDelta::FromSecondsD(duration
);
104 // We start zooming out from the anchor tapped by the user. But if
105 // the target scale is impossible to attain without hitting the root layer
106 // edges, then infer an anchor that doesn't collide with the edges.
107 // We will interpolate between the two anchors during the animation.
108 InferTargetScrollOffsetFromStartAnchor();
109 ClampTargetScrollOffset();
111 if (start_page_scale_factor_
== target_page_scale_factor_
) {
112 target_anchor_
= start_anchor_
;
115 InferTargetAnchorFromScrollOffsets();
118 void PageScaleAnimation::InferTargetScrollOffsetFromStartAnchor() {
119 gfx::Vector2dF normalized
= NormalizeFromViewport(
120 start_anchor_
- start_scroll_offset_
, StartViewportSize());
121 target_scroll_offset_
=
122 start_anchor_
- DenormalizeToViewport(normalized
, TargetViewportSize());
125 void PageScaleAnimation::InferTargetAnchorFromScrollOffsets() {
126 // The anchor is the point which is at the same normalized relative position
127 // within both start viewport rect and target viewport rect. For example, a
128 // zoom-in double-tap to a perfectly centered rect will have normalized
129 // anchor (0.5, 0.5), while one to a rect touching the bottom-right of the
130 // screen will have normalized anchor (1.0, 1.0). In other words, it obeys
132 // anchor = start_size * normalized + start_offset
133 // anchor = target_size * normalized + target_offset
134 // where both anchor and normalized begin as unknowns. Solving
135 // for the normalized, we get the following:
137 1.f
/ (TargetViewportSize().width() - StartViewportSize().width());
139 1.f
/ (TargetViewportSize().height() - StartViewportSize().height());
140 gfx::Vector2dF normalized
= gfx::ScaleVector2d(
141 start_scroll_offset_
- target_scroll_offset_
, width_scale
, height_scale
);
143 target_scroll_offset_
+ DenormalizeToViewport(normalized
,
144 TargetViewportSize());
147 void PageScaleAnimation::ClampTargetScrollOffset() {
148 gfx::Vector2dF max_scroll_offset
=
149 gfx::RectF(root_layer_size_
).bottom_right() -
150 gfx::RectF(TargetViewportSize()).bottom_right();
151 target_scroll_offset_
.SetToMax(gfx::Vector2dF());
152 target_scroll_offset_
.SetToMin(max_scroll_offset
);
155 gfx::SizeF
PageScaleAnimation::StartViewportSize() const {
156 return gfx::ScaleSize(viewport_size_
, 1.f
/ start_page_scale_factor_
);
159 gfx::SizeF
PageScaleAnimation::TargetViewportSize() const {
160 return gfx::ScaleSize(viewport_size_
, 1.f
/ target_page_scale_factor_
);
163 gfx::SizeF
PageScaleAnimation::ViewportSizeAt(float interp
) const {
164 return gfx::ScaleSize(viewport_size_
, 1.f
/ PageScaleFactorAt(interp
));
167 bool PageScaleAnimation::IsAnimationStarted() const {
168 return start_time_
> base::TimeTicks();
171 void PageScaleAnimation::StartAnimation(base::TimeTicks time
) {
172 DCHECK(start_time_
.is_null());
176 gfx::Vector2dF
PageScaleAnimation::ScrollOffsetAtTime(
177 base::TimeTicks time
) const {
178 DCHECK(!start_time_
.is_null());
179 return ScrollOffsetAt(InterpAtTime(time
));
182 float PageScaleAnimation::PageScaleFactorAtTime(base::TimeTicks time
) const {
183 DCHECK(!start_time_
.is_null());
184 return PageScaleFactorAt(InterpAtTime(time
));
187 bool PageScaleAnimation::IsAnimationCompleteAtTime(base::TimeTicks time
) const {
188 DCHECK(!start_time_
.is_null());
189 return time
>= end_time();
192 float PageScaleAnimation::InterpAtTime(base::TimeTicks monotonic_time
) const {
193 DCHECK(!start_time_
.is_null());
194 DCHECK(monotonic_time
>= start_time_
);
195 if (IsAnimationCompleteAtTime(monotonic_time
))
197 const double normalized_time
=
198 (monotonic_time
- start_time_
).InSecondsF() / duration_
.InSecondsF();
199 return timing_function_
->GetValue(normalized_time
);
202 gfx::Vector2dF
PageScaleAnimation::ScrollOffsetAt(float interp
) const {
204 return start_scroll_offset_
;
206 return target_scroll_offset_
;
208 return AnchorAt(interp
) - ViewportRelativeAnchorAt(interp
);
211 gfx::Vector2dF
PageScaleAnimation::AnchorAt(float interp
) const {
212 // Interpolate from start to target anchor in absolute space.
213 return InterpolateBetween(start_anchor_
, target_anchor_
, interp
);
216 gfx::Vector2dF
PageScaleAnimation::ViewportRelativeAnchorAt(
217 float interp
) const {
218 // Interpolate from start to target anchor in normalized space.
219 gfx::Vector2dF start_normalized
=
220 NormalizeFromViewport(start_anchor_
- start_scroll_offset_
,
221 StartViewportSize());
222 gfx::Vector2dF target_normalized
=
223 NormalizeFromViewport(target_anchor_
- target_scroll_offset_
,
224 TargetViewportSize());
225 gfx::Vector2dF interp_normalized
=
226 InterpolateBetween(start_normalized
, target_normalized
, interp
);
228 return DenormalizeToViewport(interp_normalized
, ViewportSizeAt(interp
));
231 float PageScaleAnimation::PageScaleFactorAt(float interp
) const {
233 return start_page_scale_factor_
;
235 return target_page_scale_factor_
;
237 // Linearly interpolate the magnitude in log scale.
238 float diff
= target_page_scale_factor_
/ start_page_scale_factor_
;
239 float log_diff
= log(diff
);
241 diff
= exp(log_diff
);
242 return start_page_scale_factor_
* diff
;