Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(483)

Side by Side Diff: content/browser/android/edge_effect.cc

Issue 14268004: Add overscroll edge effect animations for Android. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Cleanup and layer attachment fixes Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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/edge_effect.h"
6
7 #include "cc/layers/layer.h"
8 #include "ui/gfx/screen.h"
9 #include "ui/gfx/size_f.h"
10
11 namespace content {
12
13 namespace {
14
15 enum State {
16 STATE_IDLE = 0,
17 STATE_PULL,
18 STATE_ABSORB,
19 STATE_RECEDE,
20 STATE_PULL_DECAY
21 };
22
23 // Time it will take the effect to fully recede in ms
24 static const int RECEDE_TIME = 1000;
25
26 // Time it will take before a pulled glow begins receding in ms
27 static const int PULL_TIME = 167;
28
29 // Time it will take in ms for a pulled glow to decay before release
30 static const int PULL_DECAY_TIME = 1000;
31
32 static const float MAX_ALPHA = 1.f;
33 static const float HELD_EDGE_SCALE_Y = .5f;
34
35 static const float MAX_GLOW_HEIGHT = 4.f;
36
37 // Note: The Android version computes the aspect ratio from the source texture;
38 // because we use rescaled images, this is precomputed from the original Android
39 // textures.
40 static const float GLOW_IMAGE_ASPECT_RATIO_INVERSE = 0.25f;
41
42 static const float PULL_GLOW_BEGIN = 1.f;
43 static const float PULL_EDGE_BEGIN = 0.6f;
44
45 // Minimum velocity that will be absorbed
46 static const float MIN_VELOCITY = 25.f;
47
48 static const float EPSILON = 0.001f;
49
50 // How much dragging should effect the height of the edge image.
51 // Number determined by user testing.
52 static const int PULL_DISTANCE_EDGE_FACTOR = 7;
53
54 // How much dragging should effect the height of the glow image.
55 // Number determined by user testing.
56 static const int PULL_DISTANCE_GLOW_FACTOR = 7;
57 static const float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 1.1f;
58
59 static const int VELOCITY_EDGE_FACTOR = 8;
60 static const int VELOCITY_GLOW_FACTOR = 16;
61
62 template <typename T>
63 static T Lerp(T a, T b, T t) {
64 return a + (b - a) * t;
65 }
66
67 template <typename T>
68 T Clamp(T value, T low, T high) {
69 return value < low ? low : (value > high ? high : value);
70 }
71
72 static float Damp(float input, float factor) {
73 float result;
74 if (factor == 1.0f) {
75 result = (float)(1.0f - (1.0f - input) * (1.0f - input));
76 } else {
77 result = (float)(1.0f - std::pow((1.0f - input), 2 * factor));
78 }
79 return result;
80 }
81
82 int Depth(const cc::Layer* layer) {
83 int count = 0;
84 while (layer != NULL) {
85 ++count;
86 layer = layer->parent();
87 }
88 return count;
89 }
90
91 static void AttachLayer(scoped_refptr<cc::Layer>& child, cc::Layer* parent) {
92 if (!parent || !child)
93 return;
94
95 if (child->parent() != parent) {
96 child->RemoveFromParent();
97 parent->AddChild(child);
98 }
99 }
100
101 static void DetachLayer(scoped_refptr<cc::Layer>& layer) {
102 if (!layer || !layer->parent())
103 return;
104
105 layer->SetIsDrawable(false);
106 layer->RemoveFromParent();
107 }
108
109 } // namespace
110
111 EdgeEffect::EdgeEffect(scoped_refptr<cc::Layer> edge,
112 scoped_refptr<cc::Layer> glow)
113 : edge_(edge)
114 , glow_(glow)
115 , edge_alpha_(0)
116 , edge_scale_y_(0)
117 , glow_alpha_(0)
118 , glow_scale_y_(0)
119 , edge_alpha_start_(0)
120 , edge_alpha_finish_(0)
121 , edge_scale_y_start_(0)
122 , edge_scale_y_finish_(0)
123 , glow_alpha_start_(0)
124 , glow_alpha_finish_(0)
125 , glow_scale_y_start_(0)
126 , glow_scale_y_finish_(0)
127 , state_(STATE_IDLE)
128 , pull_distance_(0)
129 , base_attachment_depth_(0) {
130 DCHECK(edge_);
131 DCHECK(glow_);
132 base_attachment_depth_ = CurrentAttachmentDepth();
133 }
134
135 EdgeEffect::~EdgeEffect() {
136 Finish();
137 }
138
139 bool EdgeEffect::IsFinished() const {
140 return state_ == STATE_IDLE;
141 }
142
143 void EdgeEffect::Finish() {
144 Detach();
145 state_ = STATE_IDLE;
146 }
147
148 void EdgeEffect::Pull(base::TimeTicks current_time, float delta_distance) {
149 if (state_ == STATE_PULL_DECAY && current_time - start_time_ < duration_) {
150 return;
151 }
152
153 if (state_ != STATE_PULL) {
154 glow_scale_y_ = PULL_GLOW_BEGIN;
155 }
156 state_ = STATE_PULL;
157
158 start_time_ = current_time;
159 duration_ = base::TimeDelta::FromMilliseconds(PULL_TIME);
160
161 float abs_delta_distance = std::abs(delta_distance);
162 pull_distance_ += delta_distance;
163 float distance = std::abs(pull_distance_);
164
165 edge_alpha_ = edge_alpha_start_ = Clamp(distance, PULL_EDGE_BEGIN, MAX_ALPHA);
166 edge_scale_y_ = edge_scale_y_start_
167 = Clamp(distance * PULL_DISTANCE_EDGE_FACTOR, HELD_EDGE_SCALE_Y, 1.f);
168
169 glow_alpha_ = glow_alpha_start_
170 = std::min(MAX_ALPHA,
171 glow_alpha_ +
172 abs_delta_distance * PULL_DISTANCE_ALPHA_GLOW_FACTOR);
173
174 float glow_change = abs_delta_distance;
175 if (delta_distance > 0 && pull_distance_ < 0) {
176 glow_change = -glow_change;
177 }
178 if (pull_distance_ == 0) {
179 glow_scale_y_ = 0;
180 }
181
182 // Do not allow glow to get larger than MAX_GLOW_HEIGHT.
183 glow_scale_y_ = glow_scale_y_start_
184 = Clamp(glow_scale_y_ + glow_change * PULL_DISTANCE_GLOW_FACTOR,
185 0.f, MAX_GLOW_HEIGHT);
186
187 edge_alpha_finish_ = edge_alpha_;
188 edge_scale_y_finish_ = edge_scale_y_;
189 glow_alpha_finish_ = glow_alpha_;
190 glow_scale_y_finish_ = glow_scale_y_;
191 }
192
193 void EdgeEffect::Release(base::TimeTicks current_time) {
194 pull_distance_ = 0;
195
196 if (state_ != STATE_PULL && state_ != STATE_PULL_DECAY) {
197 return;
198 }
199
200 state_ = STATE_RECEDE;
201 edge_alpha_start_ = edge_alpha_;
202 edge_scale_y_start_ = edge_scale_y_;
203 glow_alpha_start_ = glow_alpha_;
204 glow_scale_y_start_ = glow_scale_y_;
205
206 edge_alpha_finish_ = 0.f;
207 edge_scale_y_finish_ = 0.f;
208 glow_alpha_finish_ = 0.f;
209 glow_scale_y_finish_ = 0.f;
210
211 start_time_ = current_time;
212 duration_ = base::TimeDelta::FromMilliseconds(RECEDE_TIME);
213 }
214
215 void EdgeEffect::Absorb(base::TimeTicks current_time, float velocity) {
216 state_ = STATE_ABSORB;
217 velocity = std::max(MIN_VELOCITY, std::abs(velocity));
218
219 start_time_ = current_time;
220 // This should never be less than 1 millisecond.
221 duration_ = base::TimeDelta::FromMilliseconds(0.25f + (velocity * 0.03f));
222
223 // The edge should always be at least partially visible, regardless
224 // of velocity.
225 edge_alpha_start_ = 0.f;
226 edge_scale_y_ = edge_scale_y_start_ = 0.f;
227 // The glow depends more on the velocity, and therefore starts out
228 // nearly invisible.
229 glow_alpha_start_ = 0.5f;
230 glow_scale_y_start_ = 0.f;
231
232 // Factor the velocity by 8. Testing on device shows this works best to
233 // reflect the strength of the user's scrolling.
234 edge_alpha_finish_ = Clamp(velocity * VELOCITY_EDGE_FACTOR, 0.f, 1.f);
235 // Edge should never get larger than the size of its asset.
236 edge_scale_y_finish_ = Clamp(velocity * VELOCITY_EDGE_FACTOR,
237 HELD_EDGE_SCALE_Y, 1.f);
238
239 // Growth for the size of the glow should be quadratic to properly
240 // respond
241 // to a user's scrolling speed. The faster the scrolling speed, the more
242 // intense the effect should be for both the size and the saturation.
243 glow_scale_y_finish_
244 = std::min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f);
245 // Alpha should change for the glow as well as size.
246 glow_alpha_finish_
247 = Clamp(glow_alpha_start_,
248 velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA);
249 }
250
251 bool EdgeEffect::Update(base::TimeTicks current_time) {
252 const float dt = (current_time - start_time_).InMilliseconds();
253 const float t = std::min(dt / duration_.InMilliseconds(), 1.f);
254 const float interp = Damp(t, 1.f);
255
256 edge_alpha_ = Lerp(edge_alpha_start_, edge_alpha_finish_, interp);
257 edge_scale_y_ = Lerp(edge_scale_y_start_, edge_scale_y_finish_, interp);
258 glow_alpha_ = Lerp(glow_alpha_start_, glow_alpha_finish_, interp);
259 glow_scale_y_ = Lerp(glow_scale_y_start_, glow_scale_y_finish_, interp);
260
261 if (t >= 1.f - EPSILON) {
262 switch (state_) {
263 case STATE_ABSORB:
264 state_ = STATE_RECEDE;
265 start_time_ = current_time;
266 duration_ = base::TimeDelta::FromMilliseconds(RECEDE_TIME);
267
268 edge_alpha_start_ = edge_alpha_;
269 edge_scale_y_start_ = edge_scale_y_;
270 glow_alpha_start_ = glow_alpha_;
271 glow_scale_y_start_ = glow_scale_y_;
272
273 // After absorb, the glow and edge should fade to nothing.
274 edge_alpha_finish_ = 0.f;
275 edge_scale_y_finish_ = 0.f;
276 glow_alpha_finish_ = 0.f;
277 glow_scale_y_finish_ = 0.f;
278 break;
279 case STATE_PULL:
280 state_ = STATE_PULL_DECAY;
281 start_time_ = current_time;
282 duration_ = base::TimeDelta::FromMilliseconds(PULL_DECAY_TIME);
283
284 edge_alpha_start_ = edge_alpha_;
285 edge_scale_y_start_ = edge_scale_y_;
286 glow_alpha_start_ = glow_alpha_;
287 glow_scale_y_start_ = glow_scale_y_;
288
289 // After pull, the glow and edge should fade to nothing.
290 edge_alpha_finish_ = 0.f;
291 edge_scale_y_finish_ = 0.f;
292 glow_alpha_finish_ = 0.f;
293 glow_scale_y_finish_ = 0.f;
294 break;
295 case STATE_PULL_DECAY:
296 {
297 // When receding, we want edge to decrease more slowly
298 // than the glow.
299 float factor = glow_scale_y_finish_ != 0 ?
300 1 / (glow_scale_y_finish_ * glow_scale_y_finish_) :
301 std::numeric_limits<float>::max();
302 edge_scale_y_ = edge_scale_y_start_ +
303 (edge_scale_y_finish_ - edge_scale_y_start_) * interp * factor;
304 state_ = STATE_RECEDE;
305 }
306 break;
307 case STATE_RECEDE:
308 state_ = STATE_IDLE;
309 break;
310 default:
311 break;
312 }
313 }
314
315 if (state_ == STATE_RECEDE && glow_scale_y_ <= 0 && edge_scale_y_ <= 0) {
316 state_ = STATE_IDLE;
317 }
318
319 return !IsFinished();
320 }
321
322 namespace {
323 static gfx::Transform ComputeTransform(EdgeEffect::Edge edge,
324 const gfx::SizeF& size, int height) {
325 switch (edge) {
326 default:
327 case EdgeEffect::EDGE_TOP:
328 return gfx::Transform(1, 0, 0, 1, 0, 0);
329 case EdgeEffect::EDGE_LEFT:
330 return gfx::Transform(0, 1, -1, 0,
331 (-size.width() + height) / 2 ,
332 (size.width() - height) / 2);
333 case EdgeEffect::EDGE_BOTTOM:
334 return gfx::Transform(-1, 0, 0, -1, 0, size.height() - height);
335 case EdgeEffect::EDGE_RIGHT:
336 return gfx::Transform(0, -1, 1, 0,
337 (-size.width() - height) / 2 + size.height(),
338 (size.width() - height) / 2);
339 };
340 }
341
342 static void UpdateLayer(cc::Layer* layer,
343 EdgeEffect::Edge edge,
344 const gfx::SizeF& size,
345 int height,
346 float opacity) {
347 layer->SetIsDrawable(true);
348 layer->SetTransform(ComputeTransform(edge, size, height));
349 layer->SetBounds(gfx::Size(size.width(), height));
350 layer->SetOpacity(Clamp(opacity, 0.f, 1.f));
351 }
352 }
353
354 void EdgeEffect::Draw(cc::Layer* parent, const gfx::SizeF& size, Edge edge) {
355 DCHECK(parent);
356
357 if (IsFinished()) {
358 Detach();
359 return;
360 }
361
362 // If additional levels have been added to the layer tree after the animation
363 // started, disable the animation to prevent clipping or transparency issues.
364 if (base_attachment_depth_ != CurrentAttachmentDepth()) {
365 Finish();
366 return;
367 }
368
369 Attach(parent);
370
371 const float dpi_scale =
372 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().device_scale_factor();
373
374 // Glow
375 {
376 gfx::Size glow_image_bounds;
377 float dummy_scale_x, dummy_scale_y;
378 glow_->CalculateContentsScale(1.f, false, &dummy_scale_x, &dummy_scale_y,
379 &glow_image_bounds);
380 const int glow_height = glow_image_bounds.height();
381 const int glow_bottom = (int)(std::min(
382 glow_height * glow_scale_y_ * GLOW_IMAGE_ASPECT_RATIO_INVERSE * 0.6f,
383 glow_height * MAX_GLOW_HEIGHT) * dpi_scale + 0.5f);
384 UpdateLayer(glow_.get(), edge, size, glow_bottom, glow_alpha_);
385 }
386
387 // Edge
388 {
389 gfx::Size edge_image_bounds;
390 float dummy_scale_x, dummy_scale_y;
391 edge_->CalculateContentsScale(1.f, false, &dummy_scale_x, &dummy_scale_y,
392 &edge_image_bounds);
393 const int edge_height = edge_image_bounds.height();
394 const int edge_bottom = (int)(edge_height * edge_scale_y_ * dpi_scale);
395 UpdateLayer(edge_.get(), edge, size, edge_bottom, edge_alpha_);
396 }
397 }
398
399 void EdgeEffect::Attach(cc::Layer* parent) {
400 AttachLayer(edge_, parent);
401 AttachLayer(glow_, parent);
402 base_attachment_depth_ = CurrentAttachmentDepth();
403 }
404
405 void EdgeEffect::Detach() {
406 DetachLayer(edge_);
407 DetachLayer(glow_);
408 base_attachment_depth_ = CurrentAttachmentDepth();
409 }
410
411 int EdgeEffect::CurrentAttachmentDepth() const {
412 return Depth(edge_.get());
413 }
414
415
416 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698