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/layers/ui_resource_layer.h"
8 #include "cc/trees/layer_tree_host.h"
9 #include "content/browser/android/animation_utils.h"
10 #include "ui/base/android/system_ui_resource_manager.h"
19 const ui::SystemUIResourceType kIdleResourceType
= ui::OVERSCROLL_REFRESH_IDLE
;
20 const ui::SystemUIResourceType kActiveResourceType
=
21 ui::OVERSCROLL_REFRESH_ACTIVE
;
23 // Drag movement multiplier between user input and effect translation.
24 const float kDragRate
= .5f
;
26 // Animation duration after the effect is released without triggering a refresh.
27 const int kRecedeTimeMs
= 300;
29 // Animation duration immediately after the effect is released and activated.
30 const int kActivationStartTimeMs
= 150;
32 // Animation duration after the effect is released and triggers a refresh.
33 const int kActivationTimeMs
= 1000;
35 // Max animation duration after the effect is released and triggers a refresh.
36 const int kMaxActivationTimeMs
= kActivationTimeMs
* 3;
38 // Animation duration after the refresh activated phase has completed.
39 const int kActivationRecedeTimeMs
= 300;
41 // Input threshold required to start glowing.
42 const float kGlowActivationThreshold
= 0.85f
;
44 // Useful for avoiding accidental triggering when a scroll janks (is delayed),
45 // capping the impulse per event.
46 const int kMinPullsToActivate
= 4;
48 // Minimum alpha for the effect layer.
49 const float kMinAlpha
= 0.3f
;
51 // Experimentally determined constant used to allow activation even if touch
52 // release results in a small upward fling (quite common during a slow scroll).
53 const float kMinFlingVelocityForActivation
= -500.f
;
55 const float kEpsilon
= 0.005f
;
57 void UpdateLayer(cc::UIResourceLayer
* layer
,
59 cc::UIResourceId res_id
,
60 const gfx::SizeF
& viewport_size
,
64 if (layer
->parent() != parent
)
65 parent
->AddChild(layer
);
67 if (!layer
->layer_tree_host())
70 // An empty window size, while meaningless, is also relatively harmless, and
71 // will simply prevent any drawing of the layers.
72 if (viewport_size
.IsEmpty()) {
73 layer
->SetIsDrawable(false);
78 layer
->SetIsDrawable(false);
83 layer
->SetIsDrawable(false);
88 gfx::Size image_size
= layer
->layer_tree_host()->GetUIResourceSize(res_id
);
89 layer
->SetUIResourceId(res_id
);
90 layer
->SetIsDrawable(true);
91 layer
->SetTransformOrigin(
92 gfx::Point3F(image_size
.width() * 0.5f
, image_size
.height() * 0.5f
, 0));
93 layer
->SetBounds(image_size
);
94 layer
->SetContentsOpaque(false);
95 layer
->SetOpacity(Clamp(opacity
, 0.f
, 1.f
));
97 float offset_x
= (viewport_size
.width() - image_size
.width()) * 0.5f
;
98 float offset_y
= offset
- image_size
.height();
99 gfx::Transform transform
;
100 transform
.Translate(offset_x
, offset_y
);
101 transform
.Rotate(rotation
);
102 layer
->SetTransform(transform
);
107 class OverscrollRefresh::Effect
{
109 Effect(ui::SystemUIResourceManager
* resource_manager
, float target_drag
)
110 : resource_manager_(resource_manager
),
111 idle_layer_(cc::UIResourceLayer::Create()),
112 active_layer_(cc::UIResourceLayer::Create()),
113 target_drag_(target_drag
),
119 idle_alpha_start_(0),
120 idle_alpha_finish_(0),
121 active_alpha_start_(0),
122 active_alpha_finish_(0),
128 DCHECK(target_drag_
);
129 idle_layer_
->SetIsDrawable(false);
130 active_layer_
->SetIsDrawable(false);
133 ~Effect() { Detach(); }
135 void Pull(float delta
) {
136 if (state_
!= STATE_PULL
)
142 float max_delta
= target_drag_
/ kMinPullsToActivate
;
143 delta
= Clamp(delta
, -max_delta
, max_delta
);
146 drag_
= Clamp(drag_
, 0.f
, target_drag_
* 3.f
);
148 // The following logic and constants were taken from Android's refresh
149 // effect (see SwipeRefreshLayout.java from v4 of the AppCompat library).
150 float original_drag_percent
= drag_
/ target_drag_
;
151 float drag_percent
= min(1.f
, abs(original_drag_percent
));
152 float adjusted_percent
= max(drag_percent
- .4f
, 0.f
) * 5.f
/ 3.f
;
153 float extra_os
= abs(drag_
) - target_drag_
;
154 float slingshot_dist
= target_drag_
;
155 float tension_slingshot_percent
=
156 max(0.f
, min(extra_os
, slingshot_dist
* 2) / slingshot_dist
);
157 float tension_percent
= ((tension_slingshot_percent
/ 4) -
158 std::pow((tension_slingshot_percent
/ 4), 2.f
)) *
160 float extra_move
= slingshot_dist
* tension_percent
* 2;
162 offset_
= slingshot_dist
* drag_percent
+ extra_move
;
165 360.f
* ((-0.25f
+ .4f
* adjusted_percent
+ tension_percent
* 2) * .5f
);
168 kMinAlpha
+ (1.f
- kMinAlpha
) * drag_percent
/ kGlowActivationThreshold
;
169 active_alpha_
= (drag_percent
- kGlowActivationThreshold
) /
170 (1.f
- kGlowActivationThreshold
);
171 idle_alpha_
= Clamp(idle_alpha_
, 0.f
, 1.f
);
172 active_alpha_
= Clamp(active_alpha_
, 0.f
, 1.f
);
175 bool Animate(base::TimeTicks current_time
, bool still_refreshing
) {
179 if (state_
== STATE_PULL
)
182 const double dt
= (current_time
- start_time_
).InMilliseconds();
183 const double t
= dt
/ duration_
.InMilliseconds();
184 const float interp
= static_cast<float>(Damp(min(t
, 1.), 1.));
186 idle_alpha_
= Lerp(idle_alpha_start_
, idle_alpha_finish_
, interp
);
187 active_alpha_
= Lerp(active_alpha_start_
, active_alpha_finish_
, interp
);
188 offset_
= Lerp(offset_start_
, offset_finish_
, interp
);
189 rotation_
= Lerp(rotation_start_
, rotation_finish_
, interp
);
191 if (t
< 1.f
- kEpsilon
)
197 NOTREACHED() << "Invalidate state for animation.";
199 case STATE_ACTIVATED_START
:
200 // Briefly pause the animation after the rapid initial translation.
203 state_
= STATE_ACTIVATED
;
204 start_time_
= current_time
;
205 duration_
= base::TimeDelta::FromMilliseconds(kActivationTimeMs
);
206 activated_start_time_
= current_time
;
207 offset_start_
= offset_finish_
= offset_
;
208 rotation_start_
= rotation_
;
209 rotation_finish_
= rotation_start_
+ 360.f
;
211 case STATE_ACTIVATED
:
212 start_time_
= current_time
;
213 if (still_refreshing
&&
214 (current_time
- activated_start_time_
<
215 base::TimeDelta::FromMilliseconds(kMaxActivationTimeMs
))) {
216 offset_start_
= offset_finish_
= offset_
;
217 rotation_start_
= rotation_
;
218 rotation_finish_
= rotation_start_
+ 360.f
;
221 state_
= STATE_ACTIVATED_RECEDE
;
222 duration_
= base::TimeDelta::FromMilliseconds(kActivationRecedeTimeMs
);
223 idle_alpha_start_
= idle_alpha_
;
224 active_alpha_start_
= active_alpha_
;
225 idle_alpha_finish_
= 0;
226 active_alpha_finish_
= 0;
227 rotation_start_
= rotation_finish_
= rotation_
;
228 offset_start_
= offset_finish_
= offset_
;
230 case STATE_ACTIVATED_RECEDE
:
238 return !IsFinished();
241 bool Release(base::TimeTicks current_time
, bool allow_activation
) {
246 case STATE_ACTIVATED
:
247 case STATE_ACTIVATED_START
:
248 // Avoid redundant activations.
249 if (allow_activation
)
254 case STATE_ACTIVATED_RECEDE
:
256 // These states have already been "released" in some fashion.
260 start_time_
= current_time
;
261 idle_alpha_start_
= idle_alpha_
;
262 active_alpha_start_
= active_alpha_
;
263 offset_start_
= offset_
;
264 rotation_start_
= rotation_
;
266 if (drag_
< target_drag_
|| !allow_activation
) {
267 state_
= STATE_RECEDE
;
268 duration_
= base::TimeDelta::FromMilliseconds(kRecedeTimeMs
);
269 idle_alpha_finish_
= 0;
270 active_alpha_finish_
= 0;
272 rotation_finish_
= rotation_start_
- 180.f
;
276 state_
= STATE_ACTIVATED_START
;
277 duration_
= base::TimeDelta::FromMilliseconds(kActivationStartTimeMs
);
278 activated_start_time_
= current_time
;
279 idle_alpha_finish_
= idle_alpha_start_
;
280 active_alpha_finish_
= active_alpha_start_
;
281 offset_finish_
= target_drag_
;
282 rotation_finish_
= rotation_start_
;
288 idle_layer_
->SetIsDrawable(false);
289 active_layer_
->SetIsDrawable(false);
297 void ApplyToLayers(const gfx::SizeF
& size
, cc::Layer
* parent
) {
301 UpdateLayer(idle_layer_
.get(), parent
,
302 resource_manager_
->GetUIResourceId(kIdleResourceType
), size
,
303 offset_
, idle_alpha_
, rotation_
);
304 UpdateLayer(active_layer_
.get(), parent
,
305 resource_manager_
->GetUIResourceId(kActiveResourceType
), size
,
306 offset_
, active_alpha_
, rotation_
);
309 bool IsFinished() const { return state_
== STATE_IDLE
; }
315 STATE_ACTIVATED_START
,
317 STATE_ACTIVATED_RECEDE
,
322 idle_layer_
->RemoveFromParent();
323 active_layer_
->RemoveFromParent();
326 ui::SystemUIResourceManager
* const resource_manager_
;
328 scoped_refptr
<cc::UIResourceLayer
> idle_layer_
;
329 scoped_refptr
<cc::UIResourceLayer
> active_layer_
;
331 const float target_drag_
;
338 float idle_alpha_start_
;
339 float idle_alpha_finish_
;
340 float active_alpha_start_
;
341 float active_alpha_finish_
;
343 float offset_finish_
;
344 float rotation_start_
;
345 float rotation_finish_
;
347 base::TimeTicks start_time_
;
348 base::TimeTicks activated_start_time_
;
349 base::TimeDelta duration_
;
354 OverscrollRefresh::OverscrollRefresh(
355 ui::SystemUIResourceManager
* resource_manager
,
356 OverscrollRefreshClient
* client
,
357 float target_drag_offset_pixels
)
359 scrolled_to_top_(true),
360 scroll_consumption_state_(DISABLED
),
361 effect_(new Effect(resource_manager
, target_drag_offset_pixels
)) {
365 OverscrollRefresh::~OverscrollRefresh() {
368 void OverscrollRefresh::Reset() {
369 scroll_consumption_state_
= DISABLED
;
373 void OverscrollRefresh::OnScrollBegin() {
374 bool allow_activation
= false;
375 Release(allow_activation
);
376 if (scrolled_to_top_
)
377 scroll_consumption_state_
= AWAITING_SCROLL_UPDATE_ACK
;
380 void OverscrollRefresh::OnScrollEnd(const gfx::Vector2dF
& scroll_velocity
) {
381 bool allow_activation
= scroll_velocity
.y() > kMinFlingVelocityForActivation
;
382 Release(allow_activation
);
385 void OverscrollRefresh::OnScrollUpdateAck(bool was_consumed
) {
386 if (scroll_consumption_state_
!= AWAITING_SCROLL_UPDATE_ACK
)
389 scroll_consumption_state_
= was_consumed
? DISABLED
: ENABLED
;
392 bool OverscrollRefresh::WillHandleScrollUpdate(
393 const gfx::Vector2dF
& scroll_delta
) {
394 if (viewport_size_
.IsEmpty())
397 switch (scroll_consumption_state_
) {
401 case AWAITING_SCROLL_UPDATE_ACK
:
402 // If the initial scroll motion is downward, never allow activation.
403 if (scroll_delta
.y() <= 0)
404 scroll_consumption_state_
= DISABLED
;
408 effect_
->Pull(scroll_delta
.y());
413 NOTREACHED() << "Invalid overscroll state: " << scroll_consumption_state_
;
417 bool OverscrollRefresh::Animate(base::TimeTicks current_time
,
418 cc::Layer
* parent_layer
) {
419 DCHECK(parent_layer
);
420 if (effect_
->IsFinished())
423 if (effect_
->Animate(current_time
, client_
->IsStillRefreshing()))
424 effect_
->ApplyToLayers(viewport_size_
, parent_layer
);
426 return !effect_
->IsFinished();
429 bool OverscrollRefresh::IsActive() const {
430 return scroll_consumption_state_
== ENABLED
|| !effect_
->IsFinished();
433 bool OverscrollRefresh::IsAwaitingScrollUpdateAck() const {
434 return scroll_consumption_state_
== AWAITING_SCROLL_UPDATE_ACK
;
437 void OverscrollRefresh::UpdateDisplay(
438 const gfx::SizeF
& viewport_size
,
439 const gfx::Vector2dF
& content_scroll_offset
) {
440 viewport_size_
= viewport_size
;
441 scrolled_to_top_
= content_scroll_offset
.y() == 0;
444 void OverscrollRefresh::Release(bool allow_activation
) {
445 if (scroll_consumption_state_
== ENABLED
) {
446 if (effect_
->Release(base::TimeTicks::Now(), allow_activation
))
447 client_
->TriggerRefresh();
449 scroll_consumption_state_
= DISABLED
;
452 } // namespace content