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
10namespace content {
11
12namespace {
13
14enum State {
15  STATE_IDLE = 0,
16  STATE_PULL,
17  STATE_ABSORB,
18  STATE_RECEDE,
19  STATE_PULL_DECAY
20};
21
22// Time it will take the effect to fully recede in ms
23const int kRecedeTime = 1000;
24
25// Time it will take before a pulled glow begins receding in ms
26const int kPullTime = 167;
27
28// Time it will take in ms for a pulled glow to decay before release
29const int kPullDecayTime = 1000;
30
31const float kMaxAlpha = 1.f;
32const float kHeldEdgeScaleY = .5f;
33
34const float kMaxGlowHeight = 4.f;
35
36const float kPullGlowBegin = 1.f;
37const float kPullEdgeBegin = 0.6f;
38
39// Min/max velocity that will be absorbed
40const float kMinVelocity = 100.f;
41const float kMaxVelocity = 10000.f;
42
43const float kEpsilon = 0.001f;
44
45// How much dragging should effect the height of the edge image.
46// Number determined by user testing.
47const int kPullDistanceEdgeFactor = 7;
48
49// How much dragging should effect the height of the glow image.
50// Number determined by user testing.
51const int kPullDistanceGlowFactor = 7;
52const float kPullDistanceAlphaGlowFactor = 1.1f;
53
54const int kVelocityEdgeFactor = 8;
55const int kVelocityGlowFactor = 12;
56
57template <typename T>
58T Lerp(T a, T b, T t) {
59  return a + (b - a) * t;
60}
61
62template <typename T>
63T Clamp(T value, T low, T high) {
64   return value < low ? low : (value > high ? high : value);
65}
66
67template <typename T>
68T Damp(T input, T factor) {
69  T result;
70  if (factor == 1) {
71    result = 1 - (1 - input) * (1 - input);
72  } else {
73    result = 1 - std::pow(1 - input, 2 * factor);
74  }
75  return result;
76}
77
78gfx::Transform ComputeTransform(EdgeEffect::Edge edge,
79                                gfx::SizeF size, int height) {
80  switch (edge) {
81    default:
82    case EdgeEffect::EDGE_TOP:
83      return gfx::Transform(1, 0, 0, 1, 0, 0);
84    case EdgeEffect::EDGE_LEFT:
85      return gfx::Transform(0, 1, -1, 0,
86                            (-size.width() + height) / 2 ,
87                            (size.width() - height) / 2);
88    case EdgeEffect::EDGE_BOTTOM:
89      return gfx::Transform(-1, 0, 0, -1, 0, size.height() - height);
90    case EdgeEffect::EDGE_RIGHT:
91      return gfx::Transform(0, -1, 1, 0,
92                            (-size.width() - height) / 2 + size.height(),
93                            (size.width() - height) / 2);
94  };
95}
96
97void DisableLayer(cc::Layer* layer) {
98  DCHECK(layer);
99  layer->SetIsDrawable(false);
100  layer->SetTransform(gfx::Transform());
101  layer->SetOpacity(1.f);
102}
103
104void UpdateLayer(cc::Layer* layer,
105                 EdgeEffect::Edge edge,
106                 gfx::SizeF size,
107                 int height,
108                 float opacity) {
109  DCHECK(layer);
110  layer->SetIsDrawable(true);
111  layer->SetTransform(ComputeTransform(edge, size, height));
112  layer->SetBounds(gfx::Size(size.width(), height));
113  layer->SetOpacity(Clamp(opacity, 0.f, 1.f));
114}
115
116} // namespace
117
118EdgeEffect::EdgeEffect(scoped_refptr<cc::Layer> edge,
119                       scoped_refptr<cc::Layer> glow)
120  : edge_(edge)
121  , glow_(glow)
122  , edge_alpha_(0)
123  , edge_scale_y_(0)
124  , glow_alpha_(0)
125  , glow_scale_y_(0)
126  , edge_alpha_start_(0)
127  , edge_alpha_finish_(0)
128  , edge_scale_y_start_(0)
129  , edge_scale_y_finish_(0)
130  , glow_alpha_start_(0)
131  , glow_alpha_finish_(0)
132  , glow_scale_y_start_(0)
133  , glow_scale_y_finish_(0)
134  , state_(STATE_IDLE)
135  , pull_distance_(0)
136  , dpi_scale_(1) {
137  // Prevent the provided layers from drawing until the effect is activated.
138  DisableLayer(edge_.get());
139  DisableLayer(glow_.get());
140
141  dpi_scale_ =
142      gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().device_scale_factor();
143}
144
145EdgeEffect::~EdgeEffect() { }
146
147bool EdgeEffect::IsFinished() const {
148  return state_ == STATE_IDLE;
149}
150
151void EdgeEffect::Finish() {
152  DisableLayer(edge_.get());
153  DisableLayer(glow_.get());
154  pull_distance_ = 0;
155  state_ = STATE_IDLE;
156}
157
158void EdgeEffect::Pull(base::TimeTicks current_time, float delta_distance) {
159  if (state_ == STATE_PULL_DECAY && current_time - start_time_ < duration_) {
160    return;
161  }
162  if (state_ != STATE_PULL) {
163    glow_scale_y_ = kPullGlowBegin;
164  }
165  state_ = STATE_PULL;
166
167  start_time_ = current_time;
168  duration_ = base::TimeDelta::FromMilliseconds(kPullTime);
169
170  delta_distance *= dpi_scale_;
171  float abs_delta_distance = std::abs(delta_distance);
172  pull_distance_ += delta_distance;
173  float distance = std::abs(pull_distance_);
174
175  edge_alpha_ = edge_alpha_start_ = Clamp(distance, kPullEdgeBegin, kMaxAlpha);
176  edge_scale_y_ = edge_scale_y_start_
177      = Clamp(distance * kPullDistanceEdgeFactor, kHeldEdgeScaleY, 1.f);
178
179  glow_alpha_ = glow_alpha_start_ =
180      std::min(kMaxAlpha,
181               glow_alpha_ + abs_delta_distance * kPullDistanceAlphaGlowFactor);
182
183  float glow_change = abs_delta_distance;
184  if (delta_distance > 0 && pull_distance_ < 0)
185    glow_change = -glow_change;
186  if (pull_distance_ == 0)
187    glow_scale_y_ = 0;
188
189  // Do not allow glow to get larger than kMaxGlowHeight.
190  glow_scale_y_ = glow_scale_y_start_ =
191      Clamp(glow_scale_y_ + glow_change * kPullDistanceGlowFactor,
192            0.f, kMaxGlowHeight);
193
194  edge_alpha_finish_ = edge_alpha_;
195  edge_scale_y_finish_ = edge_scale_y_;
196  glow_alpha_finish_ = glow_alpha_;
197  glow_scale_y_finish_ = glow_scale_y_;
198}
199
200void EdgeEffect::Release(base::TimeTicks current_time) {
201  pull_distance_ = 0;
202
203  if (state_ != STATE_PULL && state_ != STATE_PULL_DECAY)
204    return;
205
206  state_ = STATE_RECEDE;
207  edge_alpha_start_ = edge_alpha_;
208  edge_scale_y_start_ = edge_scale_y_;
209  glow_alpha_start_ = glow_alpha_;
210  glow_scale_y_start_ = glow_scale_y_;
211
212  edge_alpha_finish_ = 0.f;
213  edge_scale_y_finish_ = 0.f;
214  glow_alpha_finish_ = 0.f;
215  glow_scale_y_finish_ = 0.f;
216
217  start_time_ = current_time;
218  duration_ = base::TimeDelta::FromMilliseconds(kRecedeTime);
219}
220
221void EdgeEffect::Absorb(base::TimeTicks current_time, float velocity) {
222  state_ = STATE_ABSORB;
223  float scaled_velocity =
224      dpi_scale_ * Clamp(std::abs(velocity), kMinVelocity, kMaxVelocity);
225
226  start_time_ = current_time;
227  // This should never be less than 1 millisecond.
228  duration_ = base::TimeDelta::FromMilliseconds(0.15f + (velocity * 0.02f));
229
230  // The edge should always be at least partially visible, regardless
231  // of velocity.
232  edge_alpha_start_ = 0.f;
233  edge_scale_y_ = edge_scale_y_start_ = 0.f;
234  // The glow depends more on the velocity, and therefore starts out
235  // nearly invisible.
236  glow_alpha_start_ = 0.3f;
237  glow_scale_y_start_ = 0.f;
238
239  // Factor the velocity by 8. Testing on device shows this works best to
240  // reflect the strength of the user's scrolling.
241  edge_alpha_finish_ = Clamp(scaled_velocity * kVelocityEdgeFactor, 0.f, 1.f);
242  // Edge should never get larger than the size of its asset.
243  edge_scale_y_finish_ = Clamp(scaled_velocity * kVelocityEdgeFactor,
244                               kHeldEdgeScaleY, 1.f);
245
246  // Growth for the size of the glow should be quadratic to properly
247  // respond
248  // to a user's scrolling speed. The faster the scrolling speed, the more
249  // intense the effect should be for both the size and the saturation.
250  glow_scale_y_finish_ = std::min(
251      0.025f + (scaled_velocity * (scaled_velocity / 100) * 0.00015f), 1.75f);
252  // Alpha should change for the glow as well as size.
253  glow_alpha_finish_ = Clamp(glow_alpha_start_,
254                             scaled_velocity * kVelocityGlowFactor * .00001f,
255                             kMaxAlpha);
256}
257
258bool EdgeEffect::Update(base::TimeTicks current_time) {
259  if (IsFinished())
260    return false;
261
262  const double dt = (current_time - start_time_).InMilliseconds();
263  const double t = std::min(dt / duration_.InMilliseconds(), 1.);
264  const float interp = static_cast<float>(Damp(t, 1.));
265
266  edge_alpha_ = Lerp(edge_alpha_start_, edge_alpha_finish_, interp);
267  edge_scale_y_ = Lerp(edge_scale_y_start_, edge_scale_y_finish_, interp);
268  glow_alpha_ = Lerp(glow_alpha_start_, glow_alpha_finish_, interp);
269  glow_scale_y_ = Lerp(glow_scale_y_start_, glow_scale_y_finish_, interp);
270
271  if (t >= 1.f - kEpsilon) {
272    switch (state_) {
273      case STATE_ABSORB:
274        state_ = STATE_RECEDE;
275        start_time_ = current_time;
276        duration_ = base::TimeDelta::FromMilliseconds(kRecedeTime);
277
278        edge_alpha_start_ = edge_alpha_;
279        edge_scale_y_start_ = edge_scale_y_;
280        glow_alpha_start_ = glow_alpha_;
281        glow_scale_y_start_ = glow_scale_y_;
282
283        // After absorb, the glow and edge should fade to nothing.
284        edge_alpha_finish_ = 0.f;
285        edge_scale_y_finish_ = 0.f;
286        glow_alpha_finish_ = 0.f;
287        glow_scale_y_finish_ = 0.f;
288        break;
289      case STATE_PULL:
290        state_ = STATE_PULL_DECAY;
291        start_time_ = current_time;
292        duration_ = base::TimeDelta::FromMilliseconds(kPullDecayTime);
293
294        edge_alpha_start_ = edge_alpha_;
295        edge_scale_y_start_ = edge_scale_y_;
296        glow_alpha_start_ = glow_alpha_;
297        glow_scale_y_start_ = glow_scale_y_;
298
299        // After pull, the glow and edge should fade to nothing.
300        edge_alpha_finish_ = 0.f;
301        edge_scale_y_finish_ = 0.f;
302        glow_alpha_finish_ = 0.f;
303        glow_scale_y_finish_ = 0.f;
304        break;
305      case STATE_PULL_DECAY:
306        {
307          // When receding, we want edge to decrease more slowly
308          // than the glow.
309          float factor = glow_scale_y_finish_ != 0 ?
310              1 / (glow_scale_y_finish_ * glow_scale_y_finish_) :
311              std::numeric_limits<float>::max();
312          edge_scale_y_ = edge_scale_y_start_ +
313              (edge_scale_y_finish_ - edge_scale_y_start_) * interp * factor;
314          state_ = STATE_RECEDE;
315        }
316        break;
317      case STATE_RECEDE:
318        Finish();
319        break;
320      default:
321        break;
322    }
323  }
324
325  if (state_ == STATE_RECEDE && glow_scale_y_ <= 0 && edge_scale_y_ <= 0)
326    Finish();
327
328  return !IsFinished();
329}
330
331void EdgeEffect::ApplyToLayers(gfx::SizeF size, Edge edge) {
332  if (IsFinished())
333    return;
334
335  // An empty effect size, while meaningless, is also relatively harmless, and
336  // will simply prevent any drawing of the layers.
337  if (size.IsEmpty()) {
338    DisableLayer(edge_.get());
339    DisableLayer(glow_.get());
340    return;
341  }
342
343  float dummy_scale_x, dummy_scale_y;
344
345  // Glow
346  gfx::Size glow_image_bounds;
347  glow_->CalculateContentsScale(1.f, 1.f, 1.f, false,
348                                &dummy_scale_x, &dummy_scale_y,
349                                &glow_image_bounds);
350  const int glow_height = glow_image_bounds.height();
351  const int glow_width = glow_image_bounds.width();
352  const int glow_bottom = static_cast<int>(std::min(
353      glow_height * glow_scale_y_ * glow_height / glow_width * 0.6f,
354      glow_height * kMaxGlowHeight) * dpi_scale_ + 0.5f);
355  UpdateLayer(glow_.get(), edge, size, glow_bottom, glow_alpha_);
356
357  // Edge
358  gfx::Size edge_image_bounds;
359  edge_->CalculateContentsScale(1.f, 1.f, 1.f, false,
360                                &dummy_scale_x, &dummy_scale_y,
361                                &edge_image_bounds);
362  const int edge_height = edge_image_bounds.height();
363  const int edge_bottom = static_cast<int>(
364      edge_height * edge_scale_y_ * dpi_scale_);
365  UpdateLayer(edge_.get(), edge, size, edge_bottom, edge_alpha_);
366}
367
368} // namespace content
369