- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / android / edge_effect.cc
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
10 namespace content {
11
12 namespace {
13
14 enum 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
23 const int kRecedeTime = 1000;
24
25 // Time it will take before a pulled glow begins receding in ms
26 const int kPullTime = 167;
27
28 // Time it will take in ms for a pulled glow to decay before release
29 const int kPullDecayTime = 1000;
30
31 const float kMaxAlpha = 1.f;
32 const float kHeldEdgeScaleY = .5f;
33
34 const float kMaxGlowHeight = 4.f;
35
36 const float kPullGlowBegin = 1.f;
37 const float kPullEdgeBegin = 0.6f;
38
39 // Min/max velocity that will be absorbed
40 const float kMinVelocity = 100.f;
41 const float kMaxVelocity = 10000.f;
42
43 const float kEpsilon = 0.001f;
44
45 // How much dragging should effect the height of the edge image.
46 // Number determined by user testing.
47 const int kPullDistanceEdgeFactor = 7;
48
49 // How much dragging should effect the height of the glow image.
50 // Number determined by user testing.
51 const int kPullDistanceGlowFactor = 7;
52 const float kPullDistanceAlphaGlowFactor = 1.1f;
53
54 const int kVelocityEdgeFactor = 8;
55 const int kVelocityGlowFactor = 12;
56
57 template <typename T>
58 T Lerp(T a, T b, T t) {
59   return a + (b - a) * t;
60 }
61
62 template <typename T>
63 T Clamp(T value, T low, T high) {
64    return value < low ? low : (value > high ? high : value);
65 }
66
67 template <typename T>
68 T 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
78 gfx::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
97 void DisableLayer(cc::Layer* layer) {
98   DCHECK(layer);
99   layer->SetIsDrawable(false);
100   layer->SetTransform(gfx::Transform());
101   layer->SetOpacity(1.f);
102 }
103
104 void 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
118 EdgeEffect::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
145 EdgeEffect::~EdgeEffect() { }
146
147 bool EdgeEffect::IsFinished() const {
148   return state_ == STATE_IDLE;
149 }
150
151 void EdgeEffect::Finish() {
152   DisableLayer(edge_.get());
153   DisableLayer(glow_.get());
154   pull_distance_ = 0;
155   state_ = STATE_IDLE;
156 }
157
158 void 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
200 void 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
221 void 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
258 bool 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
331 void 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