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 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
));
146 EdgeEffect::EdgeEffect(scoped_refptr
<cc::Layer
> edge
,
147 scoped_refptr
<cc::Layer
> glow
)
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)
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());
182 void EdgeEffect::Pull(base::TimeTicks current_time
, float delta_distance
) {
183 if (state_
== STATE_PULL_DECAY
&& current_time
- start_time_
< duration_
) {
186 if (state_
!= STATE_PULL
) {
187 glow_scale_y_
= kPullGlowBegin
;
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_
=
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)
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
) {
226 if (state_
!= STATE_PULL
&& state_
!= STATE_PULL_DECAY
)
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
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
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
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
,
280 bool EdgeEffect::Update(base::TimeTicks current_time
) {
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
) {
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
;
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
;
327 case STATE_PULL_DECAY
: {
328 // When receding, we want edge to decrease more slowly
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
;
345 if (state_
== STATE_RECEDE
&& glow_scale_y_
<= 0 && edge_scale_y_
<= 0)
348 return !IsFinished();
351 void EdgeEffect::ApplyToLayers(gfx::SizeF window_size
,
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());
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
);
372 glow_
.get(), edge
, window_size
, offset
, scaled_glow_height
, glow_alpha_
);
375 const int scaled_edge_height
= static_cast<int>(edge_height
* edge_scale_y_
);
377 edge_
.get(), edge
, window_size
, offset
, scaled_edge_height
, edge_alpha_
);
380 } // namespace content