When enabling new profile management programmatically, make sure to set the
[chromium-blink-merge.git] / content / browser / android / edge_effect.cc
blob94e5b512e20505bf5ad12c59f9258e88f9fa41b1
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.
5 #include "content/browser/android/edge_effect.h"
7 #include "cc/layers/layer.h"
9 namespace content {
11 namespace {
13 enum State {
14 STATE_IDLE = 0,
15 STATE_PULL,
16 STATE_ABSORB,
17 STATE_RECEDE,
18 STATE_PULL_DECAY
21 // Time it will take the effect to fully recede in ms
22 const int kRecedeTime = 1000;
24 // Time it will take before a pulled glow begins receding in ms
25 const int kPullTime = 167;
27 // Time it will take in ms for a pulled glow to decay before release
28 const int kPullDecayTime = 1000;
30 const float kMaxAlpha = 1.f;
31 const float kHeldEdgeScaleY = .5f;
33 const float kMaxGlowHeight = 4.f;
35 const float kPullGlowBegin = 1.f;
36 const float kPullEdgeBegin = 0.6f;
38 // Min/max velocity that will be absorbed
39 const float kMinVelocity = 100.f;
40 const float kMaxVelocity = 10000.f;
42 const float kEpsilon = 0.001f;
44 const float kGlowHeightToWidthRatio = 0.25f;
46 // How much dragging should effect the height of the edge image.
47 // Number determined by user testing.
48 const int kPullDistanceEdgeFactor = 7;
50 // How much dragging should effect the height of the glow image.
51 // Number determined by user testing.
52 const int kPullDistanceGlowFactor = 7;
53 const float kPullDistanceAlphaGlowFactor = 1.1f;
55 const int kVelocityEdgeFactor = 8;
56 const int kVelocityGlowFactor = 12;
58 template <typename T>
59 T Lerp(T a, T b, T t) {
60 return a + (b - a) * t;
63 template <typename T>
64 T Clamp(T value, T low, T high) {
65 return value < low ? low : (value > high ? high : value);
68 template <typename T>
69 T Damp(T input, T factor) {
70 T result;
71 if (factor == 1) {
72 result = 1 - (1 - input) * (1 - input);
73 } else {
74 result = 1 - std::pow(1 - input, 2 * factor);
76 return result;
79 gfx::Transform ComputeTransform(EdgeEffect::Edge edge,
80 const gfx::SizeF& window_size,
81 int offset,
82 int height) {
83 // Edge effects that require rotation are translated to the center about which
84 // the layer should be rotated to align with the corresponding edge.
85 switch (edge) {
86 case EdgeEffect::EDGE_TOP:
87 return gfx::Transform(1, 0, 0, 1, 0, offset);
88 case EdgeEffect::EDGE_LEFT:
89 return gfx::Transform(0, 1, -1, 0,
90 (-window_size.height() + height) / 2.f + offset,
91 (window_size.height() - height) / 2.f);
92 case EdgeEffect::EDGE_BOTTOM:
93 return gfx::Transform(-1, 0, 0, -1,
94 0, window_size.height() - height + offset);
95 case EdgeEffect::EDGE_RIGHT:
96 return gfx::Transform(0, -1, 1, 0,
97 (-window_size.height() - height) / 2.f + window_size.width() + offset,
98 (window_size.height() - height) / 2.f);
99 default:
100 NOTREACHED() << "Invalid edge: " << edge;
101 return gfx::Transform();
105 gfx::Size ComputeBounds(EdgeEffect::Edge edge,
106 const gfx::SizeF& window_size,
107 int height) {
108 switch (edge) {
109 case EdgeEffect::EDGE_TOP:
110 case EdgeEffect::EDGE_BOTTOM:
111 return gfx::Size(window_size.width(), height);
112 case EdgeEffect::EDGE_LEFT:
113 case EdgeEffect::EDGE_RIGHT:
114 return gfx::Size(window_size.height(), height);
115 default:
116 NOTREACHED() << "Invalid edge: " << edge;
117 return gfx::Size();
121 void DisableLayer(cc::Layer* layer) {
122 DCHECK(layer);
123 layer->SetIsDrawable(false);
124 layer->SetTransform(gfx::Transform());
125 layer->SetOpacity(1.f);
128 void UpdateLayer(cc::Layer* layer,
129 EdgeEffect::Edge edge,
130 const gfx::SizeF& window_size,
131 int offset,
132 int height,
133 float opacity) {
134 DCHECK(layer);
135 layer->SetIsDrawable(true);
136 gfx::Size bounds = ComputeBounds(edge, window_size, height);
137 layer->SetTransformOrigin(
138 gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0));
139 layer->SetTransform(ComputeTransform(edge, window_size, offset, height));
140 layer->SetBounds(bounds);
141 layer->SetOpacity(Clamp(opacity, 0.f, 1.f));
144 } // namespace
146 EdgeEffect::EdgeEffect(scoped_refptr<cc::Layer> edge,
147 scoped_refptr<cc::Layer> glow)
148 : edge_(edge)
149 , glow_(glow)
150 , edge_alpha_(0)
151 , edge_scale_y_(0)
152 , glow_alpha_(0)
153 , glow_scale_y_(0)
154 , edge_alpha_start_(0)
155 , edge_alpha_finish_(0)
156 , edge_scale_y_start_(0)
157 , edge_scale_y_finish_(0)
158 , glow_alpha_start_(0)
159 , glow_alpha_finish_(0)
160 , glow_scale_y_start_(0)
161 , glow_scale_y_finish_(0)
162 , state_(STATE_IDLE)
163 , pull_distance_(0) {
164 // Prevent the provided layers from drawing until the effect is activated.
165 DisableLayer(edge_.get());
166 DisableLayer(glow_.get());
169 EdgeEffect::~EdgeEffect() { }
171 bool EdgeEffect::IsFinished() const {
172 return state_ == STATE_IDLE;
175 void EdgeEffect::Finish() {
176 DisableLayer(edge_.get());
177 DisableLayer(glow_.get());
178 pull_distance_ = 0;
179 state_ = STATE_IDLE;
182 void EdgeEffect::Pull(base::TimeTicks current_time, float delta_distance) {
183 if (state_ == STATE_PULL_DECAY && current_time - start_time_ < duration_) {
184 return;
186 if (state_ != STATE_PULL) {
187 glow_scale_y_ = kPullGlowBegin;
189 state_ = STATE_PULL;
191 start_time_ = current_time;
192 duration_ = base::TimeDelta::FromMilliseconds(kPullTime);
194 float abs_delta_distance = std::abs(delta_distance);
195 pull_distance_ += delta_distance;
196 float distance = std::abs(pull_distance_);
198 edge_alpha_ = edge_alpha_start_ = Clamp(distance, kPullEdgeBegin, kMaxAlpha);
199 edge_scale_y_ = edge_scale_y_start_
200 = Clamp(distance * kPullDistanceEdgeFactor, kHeldEdgeScaleY, 1.f);
202 glow_alpha_ = glow_alpha_start_ =
203 std::min(kMaxAlpha,
204 glow_alpha_ + abs_delta_distance * kPullDistanceAlphaGlowFactor);
206 float glow_change = abs_delta_distance;
207 if (delta_distance > 0 && pull_distance_ < 0)
208 glow_change = -glow_change;
209 if (pull_distance_ == 0)
210 glow_scale_y_ = 0;
212 // Do not allow glow to get larger than kMaxGlowHeight.
213 glow_scale_y_ = glow_scale_y_start_ =
214 Clamp(glow_scale_y_ + glow_change * kPullDistanceGlowFactor,
215 0.f, kMaxGlowHeight);
217 edge_alpha_finish_ = edge_alpha_;
218 edge_scale_y_finish_ = edge_scale_y_;
219 glow_alpha_finish_ = glow_alpha_;
220 glow_scale_y_finish_ = glow_scale_y_;
223 void EdgeEffect::Release(base::TimeTicks current_time) {
224 pull_distance_ = 0;
226 if (state_ != STATE_PULL && state_ != STATE_PULL_DECAY)
227 return;
229 state_ = STATE_RECEDE;
230 edge_alpha_start_ = edge_alpha_;
231 edge_scale_y_start_ = edge_scale_y_;
232 glow_alpha_start_ = glow_alpha_;
233 glow_scale_y_start_ = glow_scale_y_;
235 edge_alpha_finish_ = 0.f;
236 edge_scale_y_finish_ = 0.f;
237 glow_alpha_finish_ = 0.f;
238 glow_scale_y_finish_ = 0.f;
240 start_time_ = current_time;
241 duration_ = base::TimeDelta::FromMilliseconds(kRecedeTime);
244 void EdgeEffect::Absorb(base::TimeTicks current_time, float velocity) {
245 state_ = STATE_ABSORB;
246 velocity = Clamp(std::abs(velocity), kMinVelocity, kMaxVelocity);
248 start_time_ = current_time;
249 // This should never be less than 1 millisecond.
250 duration_ = base::TimeDelta::FromMilliseconds(0.15f + (velocity * 0.02f));
252 // The edge should always be at least partially visible, regardless
253 // of velocity.
254 edge_alpha_start_ = 0.f;
255 edge_scale_y_ = edge_scale_y_start_ = 0.f;
256 // The glow depends more on the velocity, and therefore starts out
257 // nearly invisible.
258 glow_alpha_start_ = 0.3f;
259 glow_scale_y_start_ = 0.f;
261 // Factor the velocity by 8. Testing on device shows this works best to
262 // reflect the strength of the user's scrolling.
263 edge_alpha_finish_ = Clamp(velocity * kVelocityEdgeFactor, 0.f, 1.f);
264 // Edge should never get larger than the size of its asset.
265 edge_scale_y_finish_ = Clamp(velocity * kVelocityEdgeFactor,
266 kHeldEdgeScaleY, 1.f);
268 // Growth for the size of the glow should be quadratic to properly
269 // respond
270 // to a user's scrolling speed. The faster the scrolling speed, the more
271 // intense the effect should be for both the size and the saturation.
272 glow_scale_y_finish_ = std::min(
273 0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f);
274 // Alpha should change for the glow as well as size.
275 glow_alpha_finish_ = Clamp(glow_alpha_start_,
276 velocity * kVelocityGlowFactor * .00001f,
277 kMaxAlpha);
280 bool EdgeEffect::Update(base::TimeTicks current_time) {
281 if (IsFinished())
282 return false;
284 const double dt = (current_time - start_time_).InMilliseconds();
285 const double t = std::min(dt / duration_.InMilliseconds(), 1.);
286 const float interp = static_cast<float>(Damp(t, 1.));
288 edge_alpha_ = Lerp(edge_alpha_start_, edge_alpha_finish_, interp);
289 edge_scale_y_ = Lerp(edge_scale_y_start_, edge_scale_y_finish_, interp);
290 glow_alpha_ = Lerp(glow_alpha_start_, glow_alpha_finish_, interp);
291 glow_scale_y_ = Lerp(glow_scale_y_start_, glow_scale_y_finish_, interp);
293 if (t >= 1.f - kEpsilon) {
294 switch (state_) {
295 case STATE_ABSORB:
296 state_ = STATE_RECEDE;
297 start_time_ = current_time;
298 duration_ = base::TimeDelta::FromMilliseconds(kRecedeTime);
300 edge_alpha_start_ = edge_alpha_;
301 edge_scale_y_start_ = edge_scale_y_;
302 glow_alpha_start_ = glow_alpha_;
303 glow_scale_y_start_ = glow_scale_y_;
305 // After absorb, the glow and edge should fade to nothing.
306 edge_alpha_finish_ = 0.f;
307 edge_scale_y_finish_ = 0.f;
308 glow_alpha_finish_ = 0.f;
309 glow_scale_y_finish_ = 0.f;
310 break;
311 case STATE_PULL:
312 state_ = STATE_PULL_DECAY;
313 start_time_ = current_time;
314 duration_ = base::TimeDelta::FromMilliseconds(kPullDecayTime);
316 edge_alpha_start_ = edge_alpha_;
317 edge_scale_y_start_ = edge_scale_y_;
318 glow_alpha_start_ = glow_alpha_;
319 glow_scale_y_start_ = glow_scale_y_;
321 // After pull, the glow and edge should fade to nothing.
322 edge_alpha_finish_ = 0.f;
323 edge_scale_y_finish_ = 0.f;
324 glow_alpha_finish_ = 0.f;
325 glow_scale_y_finish_ = 0.f;
326 break;
327 case STATE_PULL_DECAY: {
328 // When receding, we want edge to decrease more slowly
329 // than the glow.
330 const float factor = glow_scale_y_finish_ != 0 ?
331 1 / (glow_scale_y_finish_ * glow_scale_y_finish_) :
332 std::numeric_limits<float>::max();
333 edge_scale_y_ = edge_scale_y_start_ +
334 (edge_scale_y_finish_ - edge_scale_y_start_) * interp * factor;
335 state_ = STATE_RECEDE;
336 } break;
337 case STATE_RECEDE:
338 Finish();
339 break;
340 default:
341 break;
345 if (state_ == STATE_RECEDE && glow_scale_y_ <= 0 && edge_scale_y_ <= 0)
346 Finish();
348 return !IsFinished();
351 void EdgeEffect::ApplyToLayers(gfx::SizeF window_size,
352 Edge edge,
353 float edge_height,
354 float glow_height,
355 float offset) {
356 if (IsFinished())
357 return;
359 // An empty window size, while meaningless, is also relatively harmless, and
360 // will simply prevent any drawing of the layers.
361 if (window_size.IsEmpty()) {
362 DisableLayer(edge_.get());
363 DisableLayer(glow_.get());
364 return;
367 // Glow
368 const int scaled_glow_height = static_cast<int>(
369 std::min(glow_height * glow_scale_y_ * kGlowHeightToWidthRatio * 0.6f,
370 glow_height * kMaxGlowHeight) + 0.5f);
371 UpdateLayer(
372 glow_.get(), edge, window_size, offset, scaled_glow_height, glow_alpha_);
374 // Edge
375 const int scaled_edge_height = static_cast<int>(edge_height * edge_scale_y_);
376 UpdateLayer(
377 edge_.get(), edge, window_size, offset, scaled_edge_height, edge_alpha_);
380 } // namespace content