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 // Min/max velocity that will be absorbed
40 const float kMinVelocity
= 100.f
;
41 const float kMaxVelocity
= 10000.f
;
43 const float kEpsilon
= 0.001f
;
45 // How much dragging should effect the height of the edge image.
46 // Number determined by user testing.
47 const int kPullDistanceEdgeFactor
= 7;
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
;
54 const int kVelocityEdgeFactor
= 8;
55 const int kVelocityGlowFactor
= 12;
58 T
Lerp(T a
, T b
, T t
) {
59 return a
+ (b
- a
) * t
;
63 T
Clamp(T value
, T low
, T high
) {
64 return value
< low
? low
: (value
> high
? high
: value
);
68 T
Damp(T input
, T factor
) {
71 result
= 1 - (1 - input
) * (1 - input
);
73 result
= 1 - std::pow(1 - input
, 2 * factor
);
78 gfx::Transform
ComputeTransform(EdgeEffect::Edge edge
,
79 gfx::SizeF size
, int height
) {
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);
97 void DisableLayer(cc::Layer
* layer
) {
99 layer
->SetIsDrawable(false);
100 layer
->SetTransform(gfx::Transform());
101 layer
->SetOpacity(1.f
);
104 void UpdateLayer(cc::Layer
* layer
,
105 EdgeEffect::Edge edge
,
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
));
118 EdgeEffect::EdgeEffect(scoped_refptr
<cc::Layer
> edge
,
119 scoped_refptr
<cc::Layer
> glow
)
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)
137 // Prevent the provided layers from drawing until the effect is activated.
138 DisableLayer(edge_
.get());
139 DisableLayer(glow_
.get());
142 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().device_scale_factor();
145 EdgeEffect::~EdgeEffect() { }
147 bool EdgeEffect::IsFinished() const {
148 return state_
== STATE_IDLE
;
151 void EdgeEffect::Finish() {
152 DisableLayer(edge_
.get());
153 DisableLayer(glow_
.get());
158 void EdgeEffect::Pull(base::TimeTicks current_time
, float delta_distance
) {
159 if (state_
== STATE_PULL_DECAY
&& current_time
- start_time_
< duration_
) {
162 if (state_
!= STATE_PULL
) {
163 glow_scale_y_
= kPullGlowBegin
;
167 start_time_
= current_time
;
168 duration_
= base::TimeDelta::FromMilliseconds(kPullTime
);
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_
);
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
);
179 glow_alpha_
= glow_alpha_start_
=
181 glow_alpha_
+ abs_delta_distance
* kPullDistanceAlphaGlowFactor
);
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)
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
);
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_
;
200 void EdgeEffect::Release(base::TimeTicks current_time
) {
203 if (state_
!= STATE_PULL
&& state_
!= STATE_PULL_DECAY
)
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_
;
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
;
217 start_time_
= current_time
;
218 duration_
= base::TimeDelta::FromMilliseconds(kRecedeTime
);
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
);
226 start_time_
= current_time
;
227 // This should never be less than 1 millisecond.
228 duration_
= base::TimeDelta::FromMilliseconds(0.15f
+ (velocity
* 0.02f
));
230 // The edge should always be at least partially visible, regardless
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
236 glow_alpha_start_
= 0.3f
;
237 glow_scale_y_start_
= 0.f
;
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
);
246 // Growth for the size of the glow should be quadratic to properly
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
,
258 bool EdgeEffect::Update(base::TimeTicks current_time
) {
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.));
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
);
271 if (t
>= 1.f
- kEpsilon
) {
274 state_
= STATE_RECEDE
;
275 start_time_
= current_time
;
276 duration_
= base::TimeDelta::FromMilliseconds(kRecedeTime
);
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_
;
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
;
290 state_
= STATE_PULL_DECAY
;
291 start_time_
= current_time
;
292 duration_
= base::TimeDelta::FromMilliseconds(kPullDecayTime
);
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_
;
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
;
305 case STATE_PULL_DECAY
:
307 // When receding, we want edge to decrease more slowly
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
;
325 if (state_
== STATE_RECEDE
&& glow_scale_y_
<= 0 && edge_scale_y_
<= 0)
328 return !IsFinished();
331 void EdgeEffect::ApplyToLayers(gfx::SizeF size
, Edge edge
) {
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());
343 float dummy_scale_x
, dummy_scale_y
;
346 gfx::Size glow_image_bounds
;
347 glow_
->CalculateContentsScale(1.f
, 1.f
, 1.f
, false,
348 &dummy_scale_x
, &dummy_scale_y
,
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_
);
358 gfx::Size edge_image_bounds
;
359 edge_
->CalculateContentsScale(1.f
, 1.f
, 1.f
, false,
360 &dummy_scale_x
, &dummy_scale_y
,
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_
);
368 } // namespace content