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"
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;
59 T
Lerp(T a
, T b
, T t
) {
60 return a
+ (b
- a
) * t
;
64 T
Clamp(T value
, T low
, T high
) {
65 return value
< low
? low
: (value
> high
? high
: value
);
69 T
Damp(T input
, T factor
) {
72 result
= 1 - (1 - input
) * (1 - input
);
74 result
= 1 - std::pow(1 - input
, 2 * factor
);
79 gfx::Transform
ComputeTransform(EdgeEffect::Edge edge
,
80 const gfx::SizeF
& window_size
,
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.
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
);
100 NOTREACHED() << "Invalid edge: " << edge
;
101 return gfx::Transform();
105 gfx::Size
ComputeBounds(EdgeEffect::Edge edge
,
106 const gfx::SizeF
& window_size
,
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
);
116 NOTREACHED() << "Invalid edge: " << edge
;
121 void DisableLayer(cc::Layer
* 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
,
135 layer
->SetIsDrawable(true);
136 layer
->SetTransform(ComputeTransform(edge
, window_size
, offset
, height
));
137 layer
->SetBounds(ComputeBounds(edge
, window_size
, height
));
138 layer
->SetOpacity(Clamp(opacity
, 0.f
, 1.f
));
143 EdgeEffect::EdgeEffect(scoped_refptr
<cc::Layer
> edge
,
144 scoped_refptr
<cc::Layer
> glow
)
151 , edge_alpha_start_(0)
152 , edge_alpha_finish_(0)
153 , edge_scale_y_start_(0)
154 , edge_scale_y_finish_(0)
155 , glow_alpha_start_(0)
156 , glow_alpha_finish_(0)
157 , glow_scale_y_start_(0)
158 , glow_scale_y_finish_(0)
160 , pull_distance_(0) {
161 // Prevent the provided layers from drawing until the effect is activated.
162 DisableLayer(edge_
.get());
163 DisableLayer(glow_
.get());
166 EdgeEffect::~EdgeEffect() { }
168 bool EdgeEffect::IsFinished() const {
169 return state_
== STATE_IDLE
;
172 void EdgeEffect::Finish() {
173 DisableLayer(edge_
.get());
174 DisableLayer(glow_
.get());
179 void EdgeEffect::Pull(base::TimeTicks current_time
, float delta_distance
) {
180 if (state_
== STATE_PULL_DECAY
&& current_time
- start_time_
< duration_
) {
183 if (state_
!= STATE_PULL
) {
184 glow_scale_y_
= kPullGlowBegin
;
188 start_time_
= current_time
;
189 duration_
= base::TimeDelta::FromMilliseconds(kPullTime
);
191 float abs_delta_distance
= std::abs(delta_distance
);
192 pull_distance_
+= delta_distance
;
193 float distance
= std::abs(pull_distance_
);
195 edge_alpha_
= edge_alpha_start_
= Clamp(distance
, kPullEdgeBegin
, kMaxAlpha
);
196 edge_scale_y_
= edge_scale_y_start_
197 = Clamp(distance
* kPullDistanceEdgeFactor
, kHeldEdgeScaleY
, 1.f
);
199 glow_alpha_
= glow_alpha_start_
=
201 glow_alpha_
+ abs_delta_distance
* kPullDistanceAlphaGlowFactor
);
203 float glow_change
= abs_delta_distance
;
204 if (delta_distance
> 0 && pull_distance_
< 0)
205 glow_change
= -glow_change
;
206 if (pull_distance_
== 0)
209 // Do not allow glow to get larger than kMaxGlowHeight.
210 glow_scale_y_
= glow_scale_y_start_
=
211 Clamp(glow_scale_y_
+ glow_change
* kPullDistanceGlowFactor
,
212 0.f
, kMaxGlowHeight
);
214 edge_alpha_finish_
= edge_alpha_
;
215 edge_scale_y_finish_
= edge_scale_y_
;
216 glow_alpha_finish_
= glow_alpha_
;
217 glow_scale_y_finish_
= glow_scale_y_
;
220 void EdgeEffect::Release(base::TimeTicks current_time
) {
223 if (state_
!= STATE_PULL
&& state_
!= STATE_PULL_DECAY
)
226 state_
= STATE_RECEDE
;
227 edge_alpha_start_
= edge_alpha_
;
228 edge_scale_y_start_
= edge_scale_y_
;
229 glow_alpha_start_
= glow_alpha_
;
230 glow_scale_y_start_
= glow_scale_y_
;
232 edge_alpha_finish_
= 0.f
;
233 edge_scale_y_finish_
= 0.f
;
234 glow_alpha_finish_
= 0.f
;
235 glow_scale_y_finish_
= 0.f
;
237 start_time_
= current_time
;
238 duration_
= base::TimeDelta::FromMilliseconds(kRecedeTime
);
241 void EdgeEffect::Absorb(base::TimeTicks current_time
, float velocity
) {
242 state_
= STATE_ABSORB
;
243 velocity
= Clamp(std::abs(velocity
), kMinVelocity
, kMaxVelocity
);
245 start_time_
= current_time
;
246 // This should never be less than 1 millisecond.
247 duration_
= base::TimeDelta::FromMilliseconds(0.15f
+ (velocity
* 0.02f
));
249 // The edge should always be at least partially visible, regardless
251 edge_alpha_start_
= 0.f
;
252 edge_scale_y_
= edge_scale_y_start_
= 0.f
;
253 // The glow depends more on the velocity, and therefore starts out
255 glow_alpha_start_
= 0.3f
;
256 glow_scale_y_start_
= 0.f
;
258 // Factor the velocity by 8. Testing on device shows this works best to
259 // reflect the strength of the user's scrolling.
260 edge_alpha_finish_
= Clamp(velocity
* kVelocityEdgeFactor
, 0.f
, 1.f
);
261 // Edge should never get larger than the size of its asset.
262 edge_scale_y_finish_
= Clamp(velocity
* kVelocityEdgeFactor
,
263 kHeldEdgeScaleY
, 1.f
);
265 // Growth for the size of the glow should be quadratic to properly
267 // to a user's scrolling speed. The faster the scrolling speed, the more
268 // intense the effect should be for both the size and the saturation.
269 glow_scale_y_finish_
= std::min(
270 0.025f
+ (velocity
* (velocity
/ 100) * 0.00015f
), 1.75f
);
271 // Alpha should change for the glow as well as size.
272 glow_alpha_finish_
= Clamp(glow_alpha_start_
,
273 velocity
* kVelocityGlowFactor
* .00001f
,
277 bool EdgeEffect::Update(base::TimeTicks current_time
) {
281 const double dt
= (current_time
- start_time_
).InMilliseconds();
282 const double t
= std::min(dt
/ duration_
.InMilliseconds(), 1.);
283 const float interp
= static_cast<float>(Damp(t
, 1.));
285 edge_alpha_
= Lerp(edge_alpha_start_
, edge_alpha_finish_
, interp
);
286 edge_scale_y_
= Lerp(edge_scale_y_start_
, edge_scale_y_finish_
, interp
);
287 glow_alpha_
= Lerp(glow_alpha_start_
, glow_alpha_finish_
, interp
);
288 glow_scale_y_
= Lerp(glow_scale_y_start_
, glow_scale_y_finish_
, interp
);
290 if (t
>= 1.f
- kEpsilon
) {
293 state_
= STATE_RECEDE
;
294 start_time_
= current_time
;
295 duration_
= base::TimeDelta::FromMilliseconds(kRecedeTime
);
297 edge_alpha_start_
= edge_alpha_
;
298 edge_scale_y_start_
= edge_scale_y_
;
299 glow_alpha_start_
= glow_alpha_
;
300 glow_scale_y_start_
= glow_scale_y_
;
302 // After absorb, the glow and edge should fade to nothing.
303 edge_alpha_finish_
= 0.f
;
304 edge_scale_y_finish_
= 0.f
;
305 glow_alpha_finish_
= 0.f
;
306 glow_scale_y_finish_
= 0.f
;
309 state_
= STATE_PULL_DECAY
;
310 start_time_
= current_time
;
311 duration_
= base::TimeDelta::FromMilliseconds(kPullDecayTime
);
313 edge_alpha_start_
= edge_alpha_
;
314 edge_scale_y_start_
= edge_scale_y_
;
315 glow_alpha_start_
= glow_alpha_
;
316 glow_scale_y_start_
= glow_scale_y_
;
318 // After pull, the glow and edge should fade to nothing.
319 edge_alpha_finish_
= 0.f
;
320 edge_scale_y_finish_
= 0.f
;
321 glow_alpha_finish_
= 0.f
;
322 glow_scale_y_finish_
= 0.f
;
324 case STATE_PULL_DECAY
: {
325 // When receding, we want edge to decrease more slowly
327 const float factor
= glow_scale_y_finish_
!= 0 ?
328 1 / (glow_scale_y_finish_
* glow_scale_y_finish_
) :
329 std::numeric_limits
<float>::max();
330 edge_scale_y_
= edge_scale_y_start_
+
331 (edge_scale_y_finish_
- edge_scale_y_start_
) * interp
* factor
;
332 state_
= STATE_RECEDE
;
342 if (state_
== STATE_RECEDE
&& glow_scale_y_
<= 0 && edge_scale_y_
<= 0)
345 return !IsFinished();
348 void EdgeEffect::ApplyToLayers(gfx::SizeF window_size
,
356 // An empty window size, while meaningless, is also relatively harmless, and
357 // will simply prevent any drawing of the layers.
358 if (window_size
.IsEmpty()) {
359 DisableLayer(edge_
.get());
360 DisableLayer(glow_
.get());
365 const int scaled_glow_height
= static_cast<int>(
366 std::min(glow_height
* glow_scale_y_
* kGlowHeightToWidthRatio
* 0.6f
,
367 glow_height
* kMaxGlowHeight
) + 0.5f
);
369 glow_
.get(), edge
, window_size
, offset
, scaled_glow_height
, glow_alpha_
);
372 const int scaled_edge_height
= static_cast<int>(edge_height
* edge_scale_y_
);
374 edge_
.get(), edge
, window_size
, offset
, scaled_edge_height
, edge_alpha_
);
377 } // namespace content