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"
8 #include "ui/gfx/screen.h"
22 // Time it will take the effect to fully recede in ms
23 const int kRecedeTime
= 1000;
25 // Time it will take before a pulled glow begins receding in ms
26 const int kPullTime
= 167;
28 // Time it will take in ms for a pulled glow to decay before release
29 const int kPullDecayTime
= 1000;
31 const float kMaxAlpha
= 1.f
;
32 const float kHeldEdgeScaleY
= .5f
;
34 const float kMaxGlowHeight
= 4.f
;
36 const float kPullGlowBegin
= 1.f
;
37 const float kPullEdgeBegin
= 0.6f
;
39 // Minimum velocity that will be absorbed
40 const float kMinVelocity
= 100.f
;
42 const float kEpsilon
= 0.001f
;
44 // How much dragging should effect the height of the edge image.
45 // Number determined by user testing.
46 const int kPullDistanceEdgeFactor
= 7;
48 // How much dragging should effect the height of the glow image.
49 // Number determined by user testing.
50 const int kPullDistanceGlowFactor
= 7;
51 const float kPullDistanceAlphaGlowFactor
= 1.1f
;
53 const int kVelocityEdgeFactor
= 8;
54 const int kVelocityGlowFactor
= 16;
57 T
Lerp(T a
, T b
, T t
) {
58 return a
+ (b
- a
) * t
;
62 T
Clamp(T value
, T low
, T high
) {
63 return value
< low
? low
: (value
> high
? high
: value
);
67 T
Damp(T input
, T factor
) {
70 result
= 1 - (1 - input
) * (1 - input
);
72 result
= 1 - std::pow(1 - input
, 2 * factor
);
77 gfx::Transform
ComputeTransform(EdgeEffect::Edge edge
,
78 gfx::SizeF size
, int height
) {
81 case EdgeEffect::EDGE_TOP
:
82 return gfx::Transform(1, 0, 0, 1, 0, 0);
83 case EdgeEffect::EDGE_LEFT
:
84 return gfx::Transform(0, 1, -1, 0,
85 (-size
.width() + height
) / 2 ,
86 (size
.width() - height
) / 2);
87 case EdgeEffect::EDGE_BOTTOM
:
88 return gfx::Transform(-1, 0, 0, -1, 0, size
.height() - height
);
89 case EdgeEffect::EDGE_RIGHT
:
90 return gfx::Transform(0, -1, 1, 0,
91 (-size
.width() - height
) / 2 + size
.height(),
92 (size
.width() - height
) / 2);
96 void DisableLayer(cc::Layer
* layer
) {
98 layer
->SetIsDrawable(false);
99 layer
->SetTransform(gfx::Transform());
100 layer
->SetOpacity(1.f
);
103 void UpdateLayer(cc::Layer
* layer
,
104 EdgeEffect::Edge edge
,
109 layer
->SetIsDrawable(true);
110 layer
->SetTransform(ComputeTransform(edge
, size
, height
));
111 layer
->SetBounds(gfx::Size(size
.width(), height
));
112 layer
->SetOpacity(Clamp(opacity
, 0.f
, 1.f
));
117 EdgeEffect::EdgeEffect(scoped_refptr
<cc::Layer
> edge
,
118 scoped_refptr
<cc::Layer
> glow
)
125 , edge_alpha_start_(0)
126 , edge_alpha_finish_(0)
127 , edge_scale_y_start_(0)
128 , edge_scale_y_finish_(0)
129 , glow_alpha_start_(0)
130 , glow_alpha_finish_(0)
131 , glow_scale_y_start_(0)
132 , glow_scale_y_finish_(0)
136 // Prevent the provided layers from drawing until the effect is activated.
137 DisableLayer(edge_
.get());
138 DisableLayer(glow_
.get());
141 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().device_scale_factor();
144 EdgeEffect::~EdgeEffect() { }
146 bool EdgeEffect::IsFinished() const {
147 return state_
== STATE_IDLE
;
150 void EdgeEffect::Finish() {
151 DisableLayer(edge_
.get());
152 DisableLayer(glow_
.get());
157 void EdgeEffect::Pull(base::TimeTicks current_time
, float delta_distance
) {
158 if (state_
== STATE_PULL_DECAY
&& current_time
- start_time_
< duration_
) {
161 if (state_
!= STATE_PULL
) {
162 glow_scale_y_
= kPullGlowBegin
;
166 start_time_
= current_time
;
167 duration_
= base::TimeDelta::FromMilliseconds(kPullTime
);
169 delta_distance
*= dpi_scale_
;
170 float abs_delta_distance
= std::abs(delta_distance
);
171 pull_distance_
+= delta_distance
;
172 float distance
= std::abs(pull_distance_
);
174 edge_alpha_
= edge_alpha_start_
= Clamp(distance
, kPullEdgeBegin
, kMaxAlpha
);
175 edge_scale_y_
= edge_scale_y_start_
176 = Clamp(distance
* kPullDistanceEdgeFactor
, kHeldEdgeScaleY
, 1.f
);
178 glow_alpha_
= glow_alpha_start_
=
180 glow_alpha_
+ abs_delta_distance
* kPullDistanceAlphaGlowFactor
);
182 float glow_change
= abs_delta_distance
;
183 if (delta_distance
> 0 && pull_distance_
< 0)
184 glow_change
= -glow_change
;
185 if (pull_distance_
== 0)
188 // Do not allow glow to get larger than kMaxGlowHeight.
189 glow_scale_y_
= glow_scale_y_start_
=
190 Clamp(glow_scale_y_
+ glow_change
* kPullDistanceGlowFactor
,
191 0.f
, kMaxGlowHeight
);
193 edge_alpha_finish_
= edge_alpha_
;
194 edge_scale_y_finish_
= edge_scale_y_
;
195 glow_alpha_finish_
= glow_alpha_
;
196 glow_scale_y_finish_
= glow_scale_y_
;
199 void EdgeEffect::Release(base::TimeTicks current_time
) {
202 if (state_
!= STATE_PULL
&& state_
!= STATE_PULL_DECAY
)
205 state_
= STATE_RECEDE
;
206 edge_alpha_start_
= edge_alpha_
;
207 edge_scale_y_start_
= edge_scale_y_
;
208 glow_alpha_start_
= glow_alpha_
;
209 glow_scale_y_start_
= glow_scale_y_
;
211 edge_alpha_finish_
= 0.f
;
212 edge_scale_y_finish_
= 0.f
;
213 glow_alpha_finish_
= 0.f
;
214 glow_scale_y_finish_
= 0.f
;
216 start_time_
= current_time
;
217 duration_
= base::TimeDelta::FromMilliseconds(kRecedeTime
);
220 void EdgeEffect::Absorb(base::TimeTicks current_time
, float velocity
) {
221 state_
= STATE_ABSORB
;
222 float scaled_velocity
=
223 dpi_scale_
* std::max(kMinVelocity
, std::abs(velocity
));
225 start_time_
= current_time
;
226 // This should never be less than 1 millisecond.
227 duration_
= base::TimeDelta::FromMilliseconds(0.1f
+ (velocity
* 0.03f
));
229 // The edge should always be at least partially visible, regardless
231 edge_alpha_start_
= 0.f
;
232 edge_scale_y_
= edge_scale_y_start_
= 0.f
;
233 // The glow depends more on the velocity, and therefore starts out
235 glow_alpha_start_
= 0.5f
;
236 glow_scale_y_start_
= 0.f
;
238 // Factor the velocity by 8. Testing on device shows this works best to
239 // reflect the strength of the user's scrolling.
240 edge_alpha_finish_
= Clamp(scaled_velocity
* kVelocityEdgeFactor
, 0.f
, 1.f
);
241 // Edge should never get larger than the size of its asset.
242 edge_scale_y_finish_
= Clamp(scaled_velocity
* kVelocityEdgeFactor
,
243 kHeldEdgeScaleY
, 1.f
);
245 // Growth for the size of the glow should be quadratic to properly
247 // to a user's scrolling speed. The faster the scrolling speed, the more
248 // intense the effect should be for both the size and the saturation.
249 glow_scale_y_finish_
= std::min(
250 0.025f
+ (scaled_velocity
* (scaled_velocity
/ 100) * 0.00015f
), 1.75f
);
251 // Alpha should change for the glow as well as size.
252 glow_alpha_finish_
= Clamp(glow_alpha_start_
,
253 scaled_velocity
* kVelocityGlowFactor
* .00001f
,
257 bool EdgeEffect::Update(base::TimeTicks current_time
) {
261 const double dt
= (current_time
- start_time_
).InMilliseconds();
262 const double t
= std::min(dt
/ duration_
.InMilliseconds(), 1.);
263 const float interp
= static_cast<float>(Damp(t
, 1.));
265 edge_alpha_
= Lerp(edge_alpha_start_
, edge_alpha_finish_
, interp
);
266 edge_scale_y_
= Lerp(edge_scale_y_start_
, edge_scale_y_finish_
, interp
);
267 glow_alpha_
= Lerp(glow_alpha_start_
, glow_alpha_finish_
, interp
);
268 glow_scale_y_
= Lerp(glow_scale_y_start_
, glow_scale_y_finish_
, interp
);
270 if (t
>= 1.f
- kEpsilon
) {
273 state_
= STATE_RECEDE
;
274 start_time_
= current_time
;
275 duration_
= base::TimeDelta::FromMilliseconds(kRecedeTime
);
277 edge_alpha_start_
= edge_alpha_
;
278 edge_scale_y_start_
= edge_scale_y_
;
279 glow_alpha_start_
= glow_alpha_
;
280 glow_scale_y_start_
= glow_scale_y_
;
282 // After absorb, the glow and edge should fade to nothing.
283 edge_alpha_finish_
= 0.f
;
284 edge_scale_y_finish_
= 0.f
;
285 glow_alpha_finish_
= 0.f
;
286 glow_scale_y_finish_
= 0.f
;
289 state_
= STATE_PULL_DECAY
;
290 start_time_
= current_time
;
291 duration_
= base::TimeDelta::FromMilliseconds(kPullDecayTime
);
293 edge_alpha_start_
= edge_alpha_
;
294 edge_scale_y_start_
= edge_scale_y_
;
295 glow_alpha_start_
= glow_alpha_
;
296 glow_scale_y_start_
= glow_scale_y_
;
298 // After pull, the glow and edge should fade to nothing.
299 edge_alpha_finish_
= 0.f
;
300 edge_scale_y_finish_
= 0.f
;
301 glow_alpha_finish_
= 0.f
;
302 glow_scale_y_finish_
= 0.f
;
304 case STATE_PULL_DECAY
:
306 // When receding, we want edge to decrease more slowly
308 float factor
= glow_scale_y_finish_
!= 0 ?
309 1 / (glow_scale_y_finish_
* glow_scale_y_finish_
) :
310 std::numeric_limits
<float>::max();
311 edge_scale_y_
= edge_scale_y_start_
+
312 (edge_scale_y_finish_
- edge_scale_y_start_
) * interp
* factor
;
313 state_
= STATE_RECEDE
;
324 if (state_
== STATE_RECEDE
&& glow_scale_y_
<= 0 && edge_scale_y_
<= 0)
327 return !IsFinished();
330 void EdgeEffect::ApplyToLayers(gfx::SizeF size
, Edge edge
) {
334 // An empty effect size, while meaningless, is also relatively harmless, and
335 // will simply prevent any drawing of the layers.
336 if (size
.IsEmpty()) {
337 DisableLayer(edge_
.get());
338 DisableLayer(glow_
.get());
342 float dummy_scale_x
, dummy_scale_y
;
345 gfx::Size glow_image_bounds
;
346 glow_
->CalculateContentsScale(1.f
, 1.f
, 1.f
, false,
347 &dummy_scale_x
, &dummy_scale_y
,
349 const int glow_height
= glow_image_bounds
.height();
350 const int glow_width
= glow_image_bounds
.width();
351 const int glow_bottom
= static_cast<int>(std::min(
352 glow_height
* glow_scale_y_
* glow_height
/ glow_width
* 0.6f
,
353 glow_height
* kMaxGlowHeight
) * dpi_scale_
+ 0.5f
);
354 UpdateLayer(glow_
.get(), edge
, size
, glow_bottom
, glow_alpha_
);
357 gfx::Size edge_image_bounds
;
358 edge_
->CalculateContentsScale(1.f
, 1.f
, 1.f
, false,
359 &dummy_scale_x
, &dummy_scale_y
,
361 const int edge_height
= edge_image_bounds
.height();
362 const int edge_bottom
= static_cast<int>(
363 edge_height
* edge_scale_y_
* dpi_scale_
);
364 UpdateLayer(edge_
.get(), edge
, size
, edge_bottom
, edge_alpha_
);
367 } // namespace content