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
10using std::max;
11using std::min;
12
13namespace content {
14
15namespace {
16
17const float kEpsilon = 1e-3f;
18
19bool IsApproxZero(float value) {
20  return std::abs(value) < kEpsilon;
21}
22
23gfx::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
31gfx::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
61gfx::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
78OverscrollGlow::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
85OverscrollGlow::~OverscrollGlow() {
86  Detach();
87}
88
89void OverscrollGlow::Enable() {
90  enabled_ = true;
91}
92
93void 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
104bool 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
149bool 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
173void OverscrollGlow::UpdateDisplayParameters(const DisplayParameters& params) {
174  display_params_ = params;
175}
176
177bool 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
187void 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
204void OverscrollGlow::Detach() {
205  if (root_layer_)
206    root_layer_->RemoveFromParent();
207}
208
209bool 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
225void 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
263void 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
287void 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
293EdgeEffectBase* OverscrollGlow::GetOppositeEdge(int edge_index) {
294  DCHECK(initialized_);
295  return edge_effects_[(edge_index + 2) % EDGE_COUNT].get();
296}
297
298OverscrollGlow::DisplayParameters::DisplayParameters() {
299  edge_offsets[0] = edge_offsets[1] = edge_offsets[2] = edge_offsets[3] = 0.f;
300}
301
302}  // namespace content
303