Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / android / overscroll_refresh.cc
blob58962f6356ecfbe02fcd43984416ae843daffaca
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"
15 using std::abs;
16 using std::max;
17 using std::min;
19 namespace content {
20 namespace {
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,
57 cc::Layer* parent,
58 cc::UIResourceId res_id,
59 const gfx::Size& size,
60 const gfx::Vector2dF& offset,
61 float opacity,
62 float rotation,
63 bool mirror) {
64 DCHECK(layer);
65 DCHECK(parent);
66 DCHECK(parent->layer_tree_host());
67 if (layer->parent() != parent)
68 parent->AddChild(layer);
70 if (size.IsEmpty()) {
71 layer->SetIsDrawable(false);
72 return;
75 if (!res_id) {
76 layer->SetIsDrawable(false);
77 return;
80 if (opacity == 0) {
81 layer->SetIsDrawable(false);
82 layer->SetOpacity(0);
83 return;
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);
98 if (mirror)
99 transform.Scale(-1.f, 1.f);
100 transform.Rotate(rotation);
101 layer->SetTransform(transform);
104 } // namespace
106 class OverscrollRefresh::Effect {
107 public:
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),
113 mirror_(mirror),
114 drag_(0),
115 idle_alpha_(0),
116 active_alpha_(0),
117 offset_(0),
118 rotation_(0),
119 size_scale_(1),
120 idle_alpha_start_(0),
121 idle_alpha_finish_(0),
122 active_alpha_start_(0),
123 active_alpha_finish_(0),
124 offset_start_(0),
125 offset_finish_(0),
126 rotation_start_(0),
127 rotation_finish_(0),
128 size_scale_start_(1),
129 size_scale_finish_(1),
130 state_(STATE_IDLE),
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)
142 drag_ = 0;
144 state_ = STATE_PULL;
146 delta *= kDragRate;
147 float max_delta = target_drag_ / OverscrollRefresh::kMinPullsToActivate;
148 delta = Clamp(delta, -max_delta, max_delta);
150 drag_ += 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)) *
164 2.f;
165 float extra_move = slingshot_dist * tension_percent * 2;
167 offset_ = slingshot_dist * drag_percent + extra_move;
169 rotation_ =
170 360.f * ((-0.25f + .4f * adjusted_percent + tension_percent * 2) * .5f);
172 idle_alpha_ =
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);
179 size_scale_ = 1;
182 bool Animate(base::TimeTicks current_time, bool still_refreshing) {
183 if (IsFinished())
184 return false;
186 if (state_ == STATE_PULL)
187 return true;
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;
203 } else {
204 rotation_ = Lerp(rotation_start_, rotation_finish_, interp);
207 if (t < 1.f - kEpsilon)
208 return true;
210 switch (state_) {
211 case STATE_IDLE:
212 case STATE_PULL:
213 NOTREACHED() << "Invalidate state for animation.";
214 break;
215 case STATE_ACTIVATED_START:
216 // Briefly pause the animation after the rapid initial translation.
217 if (t < 1.5f)
218 break;
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_;
227 break;
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;
236 break;
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;
244 break;
245 case STATE_ACTIVATED_RECEDE:
246 Finish();
247 break;
248 case STATE_RECEDE:
249 Finish();
250 break;
253 return !IsFinished();
256 bool Release(base::TimeTicks current_time, bool allow_activation) {
257 switch (state_) {
258 case STATE_PULL:
259 break;
261 case STATE_ACTIVATED:
262 case STATE_ACTIVATED_START:
263 // Avoid redundant activations.
264 if (allow_activation)
265 return false;
266 break;
268 case STATE_IDLE:
269 case STATE_ACTIVATED_RECEDE:
270 case STATE_RECEDE:
271 // These states have already been "released" in some fashion.
272 return false;
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;
287 offset_finish_ = 0;
288 rotation_finish_ = rotation_start_ - 180.f;
289 return false;
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_;
299 return true;
302 void Finish() {
303 Detach();
304 idle_layer_->SetIsDrawable(false);
305 active_layer_->SetIsDrawable(false);
306 offset_ = 0;
307 idle_alpha_ = 0;
308 active_alpha_ = 0;
309 rotation_ = 0;
310 size_scale_ = 1;
311 state_ = STATE_IDLE;
314 void ApplyToLayers(const gfx::SizeF& viewport_size, cc::Layer* parent) {
315 if (IsFinished())
316 return;
318 if (!parent->layer_tree_host())
319 return;
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);
326 return;
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_,
352 mirror_);
355 bool IsFinished() const { return state_ == STATE_IDLE; }
357 private:
358 enum State {
359 STATE_IDLE = 0,
360 STATE_PULL,
361 STATE_ACTIVATED_START,
362 STATE_ACTIVATED,
363 STATE_ACTIVATED_RECEDE,
364 STATE_RECEDE
367 void Detach() {
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_;
378 const bool mirror_;
379 float drag_;
380 float idle_alpha_;
381 float active_alpha_;
382 float offset_;
383 float rotation_;
384 float size_scale_;
386 float idle_alpha_start_;
387 float idle_alpha_finish_;
388 float active_alpha_start_;
389 float active_alpha_finish_;
390 float offset_start_;
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_;
401 State state_;
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,
412 bool mirror)
413 : client_(client),
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)) {
418 DCHECK(client);
421 OverscrollRefresh::~OverscrollRefresh() {
424 void OverscrollRefresh::Reset() {
425 scroll_consumption_state_ = DISABLED;
426 effect_->Finish();
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)
442 return;
444 scroll_consumption_state_ = was_consumed ? DISABLED : ENABLED;
447 bool OverscrollRefresh::WillHandleScrollUpdate(
448 const gfx::Vector2dF& scroll_delta) {
449 if (viewport_size_.IsEmpty())
450 return false;
452 switch (scroll_consumption_state_) {
453 case DISABLED:
454 return false;
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;
460 return false;
462 case ENABLED: {
463 effect_->Pull(scroll_delta.y());
464 return true;
468 NOTREACHED() << "Invalid overscroll state: " << scroll_consumption_state_;
469 return false;
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())
481 return false;
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