1 // Copyright 2014 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/overscroll_refresh.h"
7 #include "cc/animation/timing_function.h"
8 #include "cc/layers/ui_resource_layer.h"
9 #include "cc/trees/layer_tree_host.h"
10 #include "content/browser/android/animation_utils.h"
11 #include "ui/android/resources/resource_manager.h"
12 #include "ui/android/resources/system_ui_resource_type.h"
13 #include "ui/gfx/geometry/size_conversions.h"
22 const ui::SystemUIResourceType kIdleResourceId
= ui::OVERSCROLL_REFRESH_IDLE
;
23 const ui::SystemUIResourceType kActiveResourceId
=
24 ui::OVERSCROLL_REFRESH_ACTIVE
;
26 // Drag movement multiplier between user input and effect translation.
27 const float kDragRate
= .5f
;
29 // Animation duration after the effect is released without triggering a refresh.
30 const int kRecedeTimeMs
= 300;
32 // Animation duration immediately after the effect is released and activated.
33 const int kActivationStartTimeMs
= 150;
35 // Animation duration after the effect is released and triggers a refresh.
36 const int kActivationTimeMs
= 850;
38 // Max animation duration after the effect is released and triggers a refresh.
39 const int kMaxActivationTimeMs
= kActivationTimeMs
* 4;
41 // Animation duration after the refresh activated phase has completed.
42 const int kActivationRecedeTimeMs
= 250;
44 // Input threshold required to start glowing.
45 const float kGlowActivationThreshold
= 0.85f
;
47 // Minimum alpha for the effect layer.
48 const float kMinAlpha
= 0.3f
;
50 // Experimentally determined constant used to allow activation even if touch
51 // release results in a small upward fling (quite common during a slow scroll).
52 const float kMinFlingVelocityForActivation
= -500.f
;
54 const float kEpsilon
= 0.005f
;
56 void UpdateLayer(cc::UIResourceLayer
* layer
,
58 cc::UIResourceId res_id
,
59 const gfx::Size
& size
,
60 const gfx::Vector2dF
& offset
,
66 DCHECK(parent
->layer_tree_host());
67 if (layer
->parent() != parent
)
68 parent
->AddChild(layer
);
71 layer
->SetIsDrawable(false);
76 layer
->SetIsDrawable(false);
81 layer
->SetIsDrawable(false);
86 layer
->SetUIResourceId(res_id
);
87 layer
->SetIsDrawable(true);
88 layer
->SetTransformOrigin(
89 gfx::Point3F(size
.width() * 0.5f
, size
.height() * 0.5f
, 0));
90 layer
->SetBounds(size
);
91 layer
->SetContentsOpaque(false);
92 layer
->SetOpacity(Clamp(opacity
, 0.f
, 1.f
));
94 float offset_x
= offset
.x() - size
.width() * 0.5f
;
95 float offset_y
= offset
.y() - size
.height() * 0.5f
;
96 gfx::Transform transform
;
97 transform
.Translate(offset_x
, offset_y
);
99 transform
.Scale(-1.f
, 1.f
);
100 transform
.Rotate(rotation
);
101 layer
->SetTransform(transform
);
106 class OverscrollRefresh::Effect
{
108 Effect(ui::ResourceManager
* resource_manager
, float target_drag
, bool mirror
)
109 : resource_manager_(resource_manager
),
110 idle_layer_(cc::UIResourceLayer::Create()),
111 active_layer_(cc::UIResourceLayer::Create()),
112 target_drag_(target_drag
),
120 idle_alpha_start_(0),
121 idle_alpha_finish_(0),
122 active_alpha_start_(0),
123 active_alpha_finish_(0),
128 size_scale_start_(1),
129 size_scale_finish_(1),
131 ease_out_(cc::EaseOutTimingFunction::Create()),
132 ease_in_out_(cc::EaseInOutTimingFunction::Create()) {
133 DCHECK(target_drag_
);
134 idle_layer_
->SetIsDrawable(false);
135 active_layer_
->SetIsDrawable(false);
138 ~Effect() { Detach(); }
140 void Pull(float delta
) {
141 if (state_
!= STATE_PULL
)
147 float max_delta
= target_drag_
/ OverscrollRefresh::kMinPullsToActivate
;
148 delta
= Clamp(delta
, -max_delta
, max_delta
);
151 drag_
= Clamp(drag_
, 0.f
, target_drag_
* 3.f
);
153 // The following logic and constants were taken from Android's refresh
154 // effect (see SwipeRefreshLayout.java from v4 of the AppCompat library).
155 float original_drag_percent
= drag_
/ target_drag_
;
156 float drag_percent
= min(1.f
, abs(original_drag_percent
));
157 float adjusted_percent
= max(drag_percent
- .4f
, 0.f
) * 5.f
/ 3.f
;
158 float extra_os
= abs(drag_
) - target_drag_
;
159 float slingshot_dist
= target_drag_
;
160 float tension_slingshot_percent
=
161 max(0.f
, min(extra_os
, slingshot_dist
* 2) / slingshot_dist
);
162 float tension_percent
= ((tension_slingshot_percent
/ 4) -
163 std::pow((tension_slingshot_percent
/ 4), 2.f
)) *
165 float extra_move
= slingshot_dist
* tension_percent
* 2;
167 offset_
= slingshot_dist
* drag_percent
+ extra_move
;
170 360.f
* ((-0.25f
+ .4f
* adjusted_percent
+ tension_percent
* 2) * .5f
);
173 kMinAlpha
+ (1.f
- kMinAlpha
) * drag_percent
/ kGlowActivationThreshold
;
174 active_alpha_
= (drag_percent
- kGlowActivationThreshold
) /
175 (1.f
- kGlowActivationThreshold
);
176 idle_alpha_
= Clamp(idle_alpha_
, 0.f
, 1.f
);
177 active_alpha_
= Clamp(active_alpha_
, 0.f
, 1.f
);
182 bool Animate(base::TimeTicks current_time
, bool still_refreshing
) {
186 if (state_
== STATE_PULL
)
189 const double dt
= (current_time
- start_time_
).InMilliseconds();
190 const double t
= dt
/ duration_
.InMilliseconds();
191 const float interp
= ease_out_
->GetValue(min(t
, 1.));
193 idle_alpha_
= Lerp(idle_alpha_start_
, idle_alpha_finish_
, interp
);
194 active_alpha_
= Lerp(active_alpha_start_
, active_alpha_finish_
, interp
);
195 offset_
= Lerp(offset_start_
, offset_finish_
, interp
);
196 size_scale_
= Lerp(size_scale_start_
, size_scale_finish_
, interp
);
198 if (state_
== STATE_ACTIVATED
|| state_
== STATE_ACTIVATED_RECEDE
) {
199 float adjusted_interp
= ease_in_out_
->GetValue(min(t
, 1.));
200 rotation_
= Lerp(rotation_start_
, rotation_finish_
, adjusted_interp
);
201 // Add a small constant rotational velocity during activation.
202 rotation_
+= dt
* 90.f
/ kActivationTimeMs
;
204 rotation_
= Lerp(rotation_start_
, rotation_finish_
, interp
);
207 if (t
< 1.f
- kEpsilon
)
213 NOTREACHED() << "Invalidate state for animation.";
215 case STATE_ACTIVATED_START
:
216 // Briefly pause the animation after the rapid initial translation.
219 state_
= STATE_ACTIVATED
;
220 start_time_
= current_time
;
221 duration_
= base::TimeDelta::FromMilliseconds(kActivationTimeMs
);
222 activated_start_time_
= current_time
;
223 offset_start_
= offset_finish_
= offset_
;
224 rotation_start_
= rotation_
;
225 rotation_finish_
= rotation_start_
+ 270.f
;
226 size_scale_start_
= size_scale_finish_
= size_scale_
;
228 case STATE_ACTIVATED
:
229 start_time_
= current_time
;
230 if (still_refreshing
&&
231 (current_time
- activated_start_time_
<
232 base::TimeDelta::FromMilliseconds(kMaxActivationTimeMs
))) {
233 offset_start_
= offset_finish_
= offset_
;
234 rotation_start_
= rotation_
;
235 rotation_finish_
= rotation_start_
+ 270.f
;
238 state_
= STATE_ACTIVATED_RECEDE
;
239 duration_
= base::TimeDelta::FromMilliseconds(kActivationRecedeTimeMs
);
240 rotation_start_
= rotation_finish_
= rotation_
;
241 offset_start_
= offset_finish_
= offset_
;
242 size_scale_start_
= size_scale_
;
243 size_scale_finish_
= 0;
245 case STATE_ACTIVATED_RECEDE
:
253 return !IsFinished();
256 bool Release(base::TimeTicks current_time
, bool allow_activation
) {
261 case STATE_ACTIVATED
:
262 case STATE_ACTIVATED_START
:
263 // Avoid redundant activations.
264 if (allow_activation
)
269 case STATE_ACTIVATED_RECEDE
:
271 // These states have already been "released" in some fashion.
275 start_time_
= current_time
;
276 idle_alpha_start_
= idle_alpha_
;
277 active_alpha_start_
= active_alpha_
;
278 offset_start_
= offset_
;
279 rotation_start_
= rotation_
;
280 size_scale_start_
= size_scale_finish_
= size_scale_
;
282 if (drag_
< target_drag_
|| !allow_activation
) {
283 state_
= STATE_RECEDE
;
284 duration_
= base::TimeDelta::FromMilliseconds(kRecedeTimeMs
);
285 idle_alpha_finish_
= 0;
286 active_alpha_finish_
= 0;
288 rotation_finish_
= rotation_start_
- 180.f
;
292 state_
= STATE_ACTIVATED_START
;
293 duration_
= base::TimeDelta::FromMilliseconds(kActivationStartTimeMs
);
294 activated_start_time_
= current_time
;
295 idle_alpha_finish_
= idle_alpha_start_
;
296 active_alpha_finish_
= active_alpha_start_
;
297 offset_finish_
= target_drag_
;
298 rotation_finish_
= rotation_start_
;
304 idle_layer_
->SetIsDrawable(false);
305 active_layer_
->SetIsDrawable(false);
314 void ApplyToLayers(const gfx::SizeF
& viewport_size
, cc::Layer
* parent
) {
318 if (!parent
->layer_tree_host())
321 // An empty window size, while meaningless, is also relatively harmless, and
322 // will simply prevent any drawing of the layers.
323 if (viewport_size
.IsEmpty()) {
324 idle_layer_
->SetIsDrawable(false);
325 active_layer_
->SetIsDrawable(false);
329 cc::UIResourceId idle_resource
= resource_manager_
->GetUIResourceId(
330 ui::ANDROID_RESOURCE_TYPE_SYSTEM
, kIdleResourceId
);
331 cc::UIResourceId active_resource
= resource_manager_
->GetUIResourceId(
332 ui::ANDROID_RESOURCE_TYPE_SYSTEM
, kActiveResourceId
);
334 gfx::Size idle_size
=
335 parent
->layer_tree_host()->GetUIResourceSize(idle_resource
);
336 gfx::Size active_size
=
337 parent
->layer_tree_host()->GetUIResourceSize(active_resource
);
338 gfx::Size scaled_idle_size
=
339 gfx::ToRoundedSize(gfx::ScaleSize(idle_size
, size_scale_
));
340 gfx::Size scaled_active_size
=
341 gfx::ToRoundedSize(gfx::ScaleSize(active_size
, size_scale_
));
343 gfx::Vector2dF
idle_offset(viewport_size
.width() * 0.5f
,
344 offset_
- idle_size
.height() * 0.5f
);
345 gfx::Vector2dF
active_offset(viewport_size
.width() * 0.5f
,
346 offset_
- active_size
.height() * 0.5f
);
348 UpdateLayer(idle_layer_
.get(), parent
, idle_resource
, scaled_idle_size
,
349 idle_offset
, idle_alpha_
, rotation_
, mirror_
);
350 UpdateLayer(active_layer_
.get(), parent
, active_resource
,
351 scaled_active_size
, active_offset
, active_alpha_
, rotation_
,
355 bool IsFinished() const { return state_
== STATE_IDLE
; }
361 STATE_ACTIVATED_START
,
363 STATE_ACTIVATED_RECEDE
,
368 idle_layer_
->RemoveFromParent();
369 active_layer_
->RemoveFromParent();
372 ui::ResourceManager
* const resource_manager_
;
374 scoped_refptr
<cc::UIResourceLayer
> idle_layer_
;
375 scoped_refptr
<cc::UIResourceLayer
> active_layer_
;
377 const float target_drag_
;
386 float idle_alpha_start_
;
387 float idle_alpha_finish_
;
388 float active_alpha_start_
;
389 float active_alpha_finish_
;
391 float offset_finish_
;
392 float rotation_start_
;
393 float rotation_finish_
;
394 float size_scale_start_
;
395 float size_scale_finish_
;
397 base::TimeTicks start_time_
;
398 base::TimeTicks activated_start_time_
;
399 base::TimeDelta duration_
;
403 scoped_ptr
<cc::TimingFunction
> ease_out_
;
404 scoped_ptr
<cc::TimingFunction
> ease_in_out_
;
406 DISALLOW_COPY_AND_ASSIGN(Effect
);
409 OverscrollRefresh::OverscrollRefresh(ui::ResourceManager
* resource_manager
,
410 OverscrollRefreshClient
* client
,
411 float target_drag_offset_pixels
,
414 scrolled_to_top_(true),
415 overflow_y_hidden_(false),
416 scroll_consumption_state_(DISABLED
),
417 effect_(new Effect(resource_manager
, target_drag_offset_pixels
, mirror
)) {
421 OverscrollRefresh::~OverscrollRefresh() {
424 void OverscrollRefresh::Reset() {
425 scroll_consumption_state_
= DISABLED
;
429 void OverscrollRefresh::OnScrollBegin() {
430 ReleaseWithoutActivation();
431 if (scrolled_to_top_
&& !overflow_y_hidden_
)
432 scroll_consumption_state_
= AWAITING_SCROLL_UPDATE_ACK
;
435 void OverscrollRefresh::OnScrollEnd(const gfx::Vector2dF
& scroll_velocity
) {
436 bool allow_activation
= scroll_velocity
.y() > kMinFlingVelocityForActivation
;
437 Release(allow_activation
);
440 void OverscrollRefresh::OnScrollUpdateAck(bool was_consumed
) {
441 if (scroll_consumption_state_
!= AWAITING_SCROLL_UPDATE_ACK
)
444 scroll_consumption_state_
= was_consumed
? DISABLED
: ENABLED
;
447 bool OverscrollRefresh::WillHandleScrollUpdate(
448 const gfx::Vector2dF
& scroll_delta
) {
449 if (viewport_size_
.IsEmpty())
452 switch (scroll_consumption_state_
) {
456 case AWAITING_SCROLL_UPDATE_ACK
:
457 // If the initial scroll motion is downward, never allow activation.
458 if (scroll_delta
.y() <= 0)
459 scroll_consumption_state_
= DISABLED
;
463 effect_
->Pull(scroll_delta
.y());
468 NOTREACHED() << "Invalid overscroll state: " << scroll_consumption_state_
;
472 void OverscrollRefresh::ReleaseWithoutActivation() {
473 bool allow_activation
= false;
474 Release(allow_activation
);
477 bool OverscrollRefresh::Animate(base::TimeTicks current_time
,
478 cc::Layer
* parent_layer
) {
479 DCHECK(parent_layer
);
480 if (effect_
->IsFinished())
483 if (effect_
->Animate(current_time
, client_
->IsStillRefreshing()))
484 effect_
->ApplyToLayers(viewport_size_
, parent_layer
);
486 return !effect_
->IsFinished();
489 bool OverscrollRefresh::IsActive() const {
490 return scroll_consumption_state_
== ENABLED
|| !effect_
->IsFinished();
493 bool OverscrollRefresh::IsAwaitingScrollUpdateAck() const {
494 return scroll_consumption_state_
== AWAITING_SCROLL_UPDATE_ACK
;
497 void OverscrollRefresh::UpdateDisplay(
498 const gfx::SizeF
& viewport_size
,
499 const gfx::Vector2dF
& content_scroll_offset
,
500 bool root_overflow_y_hidden
) {
501 viewport_size_
= viewport_size
;
502 scrolled_to_top_
= content_scroll_offset
.y() == 0;
503 overflow_y_hidden_
= root_overflow_y_hidden
;
506 void OverscrollRefresh::Release(bool allow_activation
) {
507 if (scroll_consumption_state_
== ENABLED
) {
508 if (effect_
->Release(base::TimeTicks::Now(), allow_activation
))
509 client_
->TriggerRefresh();
511 scroll_consumption_state_
= DISABLED
;
514 } // namespace content