1 // Copyright (c) 2013 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.
4
5 #include "content/browser/android/overscroll_glow.h"
6
7 #include "cc/layers/layer.h"
8 #include "content/browser/android/edge_effect_base.h"
9
10 using std::max;
11 using std::min;
12
13 namespace content {
14
15 namespace {
16
17 const float kEpsilon = 1e-3f;
18
IsApproxZero(float value)19 bool IsApproxZero(float value) {
20 return std::abs(value) < kEpsilon;
21 }
22
ZeroSmallComponents(gfx::Vector2dF vector)23 gfx::Vector2dF ZeroSmallComponents(gfx::Vector2dF vector) {
24 if (IsApproxZero(vector.x()))
25 vector.set_x(0);
26 if (IsApproxZero(vector.y()))
27 vector.set_y(0);
28 return vector;
29 }
30
ComputeTransform(OverscrollGlow::Edge edge,const gfx::SizeF & window_size,float offset)31 gfx::Transform ComputeTransform(OverscrollGlow::Edge edge,
32 const gfx::SizeF& window_size,
33 float offset) {
34 // Transforms assume the edge layers are anchored to their *top center point*.
35 switch (edge) {
36 case OverscrollGlow::EDGE_TOP:
37 return gfx::Transform(1, 0, 0, 1, 0, offset);
38 case OverscrollGlow::EDGE_LEFT:
39 return gfx::Transform(0,
40 1,
41 -1,
42 0,
43 -window_size.height() / 2.f + offset,
44 window_size.height() / 2.f);
45 case OverscrollGlow::EDGE_BOTTOM:
46 return gfx::Transform(-1, 0, 0, -1, 0, window_size.height() + offset);
47 case OverscrollGlow::EDGE_RIGHT:
48 return gfx::Transform(
49 0,
50 -1,
51 1,
52 0,
53 -window_size.height() / 2.f + window_size.width() + offset,
54 window_size.height() / 2.f);
55 default:
56 NOTREACHED() << "Invalid edge: " << edge;
57 return gfx::Transform();
58 };
59 }
60
ComputeSize(OverscrollGlow::Edge edge,const gfx::SizeF & window_size)61 gfx::SizeF ComputeSize(OverscrollGlow::Edge edge,
62 const gfx::SizeF& window_size) {
63 switch (edge) {
64 case OverscrollGlow::EDGE_TOP:
65 case OverscrollGlow::EDGE_BOTTOM:
66 return window_size;
67 case OverscrollGlow::EDGE_LEFT:
68 case OverscrollGlow::EDGE_RIGHT:
69 return gfx::SizeF(window_size.height(), window_size.width());
70 default:
71 NOTREACHED() << "Invalid edge: " << edge;
72 return gfx::SizeF();
73 };
74 }
75
76 } // namespace
77
OverscrollGlow(const EdgeEffectProvider & edge_effect_provider)78 OverscrollGlow::OverscrollGlow(const EdgeEffectProvider& edge_effect_provider)
79 : edge_effect_provider_(edge_effect_provider),
80 enabled_(true),
81 initialized_(false) {
82 DCHECK(!edge_effect_provider_.is_null());
83 }
84
~OverscrollGlow()85 OverscrollGlow::~OverscrollGlow() {
86 Detach();
87 }
88
Enable()89 void OverscrollGlow::Enable() {
90 enabled_ = true;
91 }
92
Disable()93 void OverscrollGlow::Disable() {
94 if (!enabled_)
95 return;
96 enabled_ = false;
97 if (!enabled_ && initialized_) {
98 Detach();
99 for (size_t i = 0; i < EDGE_COUNT; ++i)
100 edge_effects_[i]->Finish();
101 }
102 }
103
OnOverscrolled(cc::Layer * overscrolling_layer,base::TimeTicks current_time,gfx::Vector2dF accumulated_overscroll,gfx::Vector2dF overscroll_delta,gfx::Vector2dF velocity,gfx::Vector2dF displacement)104 bool OverscrollGlow::OnOverscrolled(cc::Layer* overscrolling_layer,
105 base::TimeTicks current_time,
106 gfx::Vector2dF accumulated_overscroll,
107 gfx::Vector2dF overscroll_delta,
108 gfx::Vector2dF velocity,
109 gfx::Vector2dF displacement) {
110 DCHECK(overscrolling_layer);
111
112 if (!enabled_)
113 return false;
114
115 // The size of the glow determines the relative effect of the inputs; an
116 // empty-sized effect is effectively disabled.
117 if (display_params_.size.IsEmpty())
118 return false;
119
120 // Ignore sufficiently small values that won't meaningfuly affect animation.
121 overscroll_delta = ZeroSmallComponents(overscroll_delta);
122 if (overscroll_delta.IsZero()) {
123 if (initialized_) {
124 Release(current_time);
125 UpdateLayerAttachment(overscrolling_layer);
126 }
127 return NeedsAnimate();
128 }
129
130 if (!InitializeIfNecessary())
131 return false;
132
133 gfx::Vector2dF old_overscroll = accumulated_overscroll - overscroll_delta;
134 bool x_overscroll_started =
135 !IsApproxZero(overscroll_delta.x()) && IsApproxZero(old_overscroll.x());
136 bool y_overscroll_started =
137 !IsApproxZero(overscroll_delta.y()) && IsApproxZero(old_overscroll.y());
138
139 velocity = ZeroSmallComponents(velocity);
140 if (!velocity.IsZero())
141 Absorb(current_time, velocity, x_overscroll_started, y_overscroll_started);
142 else
143 Pull(current_time, overscroll_delta, displacement);
144
145 UpdateLayerAttachment(overscrolling_layer);
146 return NeedsAnimate();
147 }
148
Animate(base::TimeTicks current_time)149 bool OverscrollGlow::Animate(base::TimeTicks current_time) {
150 if (!NeedsAnimate()) {
151 Detach();
152 return false;
153 }
154
155 for (size_t i = 0; i < EDGE_COUNT; ++i) {
156 if (edge_effects_[i]->Update(current_time)) {
157 Edge edge = static_cast<Edge>(i);
158 edge_effects_[i]->ApplyToLayers(
159 ComputeSize(edge, display_params_.size),
160 ComputeTransform(
161 edge, display_params_.size, display_params_.edge_offsets[i]));
162 }
163 }
164
165 if (!NeedsAnimate()) {
166 Detach();
167 return false;
168 }
169
170 return true;
171 }
172
UpdateDisplayParameters(const DisplayParameters & params)173 void OverscrollGlow::UpdateDisplayParameters(const DisplayParameters& params) {
174 display_params_ = params;
175 }
176
NeedsAnimate() const177 bool OverscrollGlow::NeedsAnimate() const {
178 if (!enabled_ || !initialized_)
179 return false;
180 for (size_t i = 0; i < EDGE_COUNT; ++i) {
181 if (!edge_effects_[i]->IsFinished())
182 return true;
183 }
184 return false;
185 }
186
UpdateLayerAttachment(cc::Layer * parent)187 void OverscrollGlow::UpdateLayerAttachment(cc::Layer* parent) {
188 DCHECK(parent);
189 if (!root_layer_)
190 return;
191
192 if (!NeedsAnimate()) {
193 Detach();
194 return;
195 }
196
197 if (root_layer_->parent() != parent)
198 parent->AddChild(root_layer_);
199
200 for (size_t i = 0; i < EDGE_COUNT; ++i)
201 edge_effects_[i]->SetParent(root_layer_);
202 }
203
Detach()204 void OverscrollGlow::Detach() {
205 if (root_layer_)
206 root_layer_->RemoveFromParent();
207 }
208
InitializeIfNecessary()209 bool OverscrollGlow::InitializeIfNecessary() {
210 DCHECK(enabled_);
211 if (initialized_)
212 return true;
213
214 DCHECK(!root_layer_);
215 root_layer_ = cc::Layer::Create();
216 for (size_t i = 0; i < EDGE_COUNT; ++i) {
217 edge_effects_[i] = edge_effect_provider_.Run();
218 DCHECK(edge_effects_[i]);
219 }
220
221 initialized_ = true;
222 return true;
223 }
224
Pull(base::TimeTicks current_time,const gfx::Vector2dF & overscroll_delta,const gfx::Vector2dF & overscroll_location)225 void OverscrollGlow::Pull(base::TimeTicks current_time,
226 const gfx::Vector2dF& overscroll_delta,
227 const gfx::Vector2dF& overscroll_location) {
228 DCHECK(enabled_ && initialized_);
229 DCHECK(!overscroll_delta.IsZero());
230 const float inv_width = 1.f / display_params_.size.width();
231 const float inv_height = 1.f / display_params_.size.height();
232
233 gfx::Vector2dF overscroll_pull =
234 gfx::ScaleVector2d(overscroll_delta, inv_width, inv_height);
235 const float edge_pull[EDGE_COUNT] = {
236 min(overscroll_pull.y(), 0.f), // Top
237 min(overscroll_pull.x(), 0.f), // Left
238 max(overscroll_pull.y(), 0.f), // Bottom
239 max(overscroll_pull.x(), 0.f) // Right
240 };
241
242 gfx::Vector2dF displacement =
243 gfx::ScaleVector2d(overscroll_location, inv_width, inv_height);
244 displacement.set_x(max(0.f, min(1.f, displacement.x())));
245 displacement.set_y(max(0.f, min(1.f, displacement.y())));
246 const float edge_displacement[EDGE_COUNT] = {
247 1.f - displacement.x(), // Top
248 displacement.y(), // Left
249 displacement.x(), // Bottom
250 1.f - displacement.y() // Right
251 };
252
253 for (size_t i = 0; i < EDGE_COUNT; ++i) {
254 if (!edge_pull[i])
255 continue;
256
257 edge_effects_[i]->Pull(
258 current_time, std::abs(edge_pull[i]), edge_displacement[i]);
259 GetOppositeEdge(i)->Release(current_time);
260 }
261 }
262
Absorb(base::TimeTicks current_time,const gfx::Vector2dF & velocity,bool x_overscroll_started,bool y_overscroll_started)263 void OverscrollGlow::Absorb(base::TimeTicks current_time,
264 const gfx::Vector2dF& velocity,
265 bool x_overscroll_started,
266 bool y_overscroll_started) {
267 DCHECK(enabled_ && initialized_);
268 DCHECK(!velocity.IsZero());
269
270 // Only trigger on initial overscroll at a non-zero velocity
271 const float overscroll_velocities[EDGE_COUNT] = {
272 y_overscroll_started ? min(velocity.y(), 0.f) : 0, // Top
273 x_overscroll_started ? min(velocity.x(), 0.f) : 0, // Left
274 y_overscroll_started ? max(velocity.y(), 0.f) : 0, // Bottom
275 x_overscroll_started ? max(velocity.x(), 0.f) : 0 // Right
276 };
277
278 for (size_t i = 0; i < EDGE_COUNT; ++i) {
279 if (!overscroll_velocities[i])
280 continue;
281
282 edge_effects_[i]->Absorb(current_time, std::abs(overscroll_velocities[i]));
283 GetOppositeEdge(i)->Release(current_time);
284 }
285 }
286
Release(base::TimeTicks current_time)287 void OverscrollGlow::Release(base::TimeTicks current_time) {
288 DCHECK(initialized_);
289 for (size_t i = 0; i < EDGE_COUNT; ++i)
290 edge_effects_[i]->Release(current_time);
291 }
292
GetOppositeEdge(int edge_index)293 EdgeEffectBase* OverscrollGlow::GetOppositeEdge(int edge_index) {
294 DCHECK(initialized_);
295 return edge_effects_[(edge_index + 2) % EDGE_COUNT].get();
296 }
297
DisplayParameters()298 OverscrollGlow::DisplayParameters::DisplayParameters() {
299 edge_offsets[0] = edge_offsets[1] = edge_offsets[2] = edge_offsets[3] = 0.f;
300 }
301
302 } // namespace content
303