Blink roll 25b6bd3a7a131ffe68d809546ad1a20707915cdc:3a503f41ae42e5b79cfcd2ff10e65afde...
[chromium-blink-merge.git] / content / browser / android / overscroll_refresh.cc
blobc57a657b59fcfe096e276e9e4e43a5c978d68281
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"
12 using std::abs;
13 using std::max;
14 using std::min;
16 namespace content {
17 namespace {
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,
58 cc::Layer* parent,
59 cc::UIResourceId res_id,
60 const gfx::SizeF& viewport_size,
61 float offset,
62 float opacity,
63 float rotation) {
64 if (layer->parent() != parent)
65 parent->AddChild(layer);
67 if (!layer->layer_tree_host())
68 return;
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);
74 return;
77 if (!res_id) {
78 layer->SetIsDrawable(false);
79 return;
82 if (opacity == 0) {
83 layer->SetIsDrawable(false);
84 layer->SetOpacity(0);
85 return;
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);
105 } // namespace
107 class OverscrollRefresh::Effect {
108 public:
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),
114 drag_(0),
115 idle_alpha_(0),
116 active_alpha_(0),
117 offset_(0),
118 rotation_(0),
119 idle_alpha_start_(0),
120 idle_alpha_finish_(0),
121 active_alpha_start_(0),
122 active_alpha_finish_(0),
123 offset_start_(0),
124 offset_finish_(0),
125 rotation_start_(0),
126 rotation_finish_(0),
127 state_(STATE_IDLE) {
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)
137 drag_ = 0;
139 state_ = STATE_PULL;
141 delta *= kDragRate;
142 float max_delta = target_drag_ / kMinPullsToActivate;
143 delta = Clamp(delta, -max_delta, max_delta);
145 drag_ += 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)) *
159 2.f;
160 float extra_move = slingshot_dist * tension_percent * 2;
162 offset_ = slingshot_dist * drag_percent + extra_move;
164 rotation_ =
165 360.f * ((-0.25f + .4f * adjusted_percent + tension_percent * 2) * .5f);
167 idle_alpha_ =
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) {
176 if (IsFinished())
177 return false;
179 if (state_ == STATE_PULL)
180 return true;
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)
192 return true;
194 switch (state_) {
195 case STATE_IDLE:
196 case STATE_PULL:
197 NOTREACHED() << "Invalidate state for animation.";
198 break;
199 case STATE_ACTIVATED_START:
200 // Briefly pause the animation after the rapid initial translation.
201 if (t < 1.5f)
202 break;
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;
210 break;
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;
219 break;
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_;
229 break;
230 case STATE_ACTIVATED_RECEDE:
231 Finish();
232 break;
233 case STATE_RECEDE:
234 Finish();
235 break;
238 return !IsFinished();
241 bool Release(base::TimeTicks current_time, bool allow_activation) {
242 switch (state_) {
243 case STATE_PULL:
244 break;
246 case STATE_ACTIVATED:
247 case STATE_ACTIVATED_START:
248 // Avoid redundant activations.
249 if (allow_activation)
250 return false;
251 break;
253 case STATE_IDLE:
254 case STATE_ACTIVATED_RECEDE:
255 case STATE_RECEDE:
256 // These states have already been "released" in some fashion.
257 return false;
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;
271 offset_finish_ = 0;
272 rotation_finish_ = rotation_start_ - 180.f;
273 return false;
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_;
283 return true;
286 void Finish() {
287 Detach();
288 idle_layer_->SetIsDrawable(false);
289 active_layer_->SetIsDrawable(false);
290 offset_ = 0;
291 idle_alpha_ = 0;
292 active_alpha_ = 0;
293 rotation_ = 0;
294 state_ = STATE_IDLE;
297 void ApplyToLayers(const gfx::SizeF& size, cc::Layer* parent) {
298 if (IsFinished())
299 return;
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; }
311 private:
312 enum State {
313 STATE_IDLE = 0,
314 STATE_PULL,
315 STATE_ACTIVATED_START,
316 STATE_ACTIVATED,
317 STATE_ACTIVATED_RECEDE,
318 STATE_RECEDE
321 void Detach() {
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_;
332 float drag_;
333 float idle_alpha_;
334 float active_alpha_;
335 float offset_;
336 float rotation_;
338 float idle_alpha_start_;
339 float idle_alpha_finish_;
340 float active_alpha_start_;
341 float active_alpha_finish_;
342 float offset_start_;
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_;
351 State state_;
354 OverscrollRefresh::OverscrollRefresh(
355 ui::SystemUIResourceManager* resource_manager,
356 OverscrollRefreshClient* client,
357 float target_drag_offset_pixels)
358 : client_(client),
359 scrolled_to_top_(true),
360 scroll_consumption_state_(DISABLED),
361 effect_(new Effect(resource_manager, target_drag_offset_pixels)) {
362 DCHECK(client);
365 OverscrollRefresh::~OverscrollRefresh() {
368 void OverscrollRefresh::Reset() {
369 scroll_consumption_state_ = DISABLED;
370 effect_->Finish();
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)
387 return;
389 scroll_consumption_state_ = was_consumed ? DISABLED : ENABLED;
392 bool OverscrollRefresh::WillHandleScrollUpdate(
393 const gfx::Vector2dF& scroll_delta) {
394 if (viewport_size_.IsEmpty())
395 return false;
397 switch (scroll_consumption_state_) {
398 case DISABLED:
399 return false;
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;
405 return false;
407 case ENABLED: {
408 effect_->Pull(scroll_delta.y());
409 return true;
413 NOTREACHED() << "Invalid overscroll state: " << scroll_consumption_state_;
414 return false;
417 bool OverscrollRefresh::Animate(base::TimeTicks current_time,
418 cc::Layer* parent_layer) {
419 DCHECK(parent_layer);
420 if (effect_->IsFinished())
421 return false;
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