OLD | NEW |
---|---|
(Empty) | |
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. | |
4 | |
5 #include "content/browser/android/overscroll_refresh.h" | |
6 | |
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" | |
11 | |
12 using std::max; | |
13 using std::min; | |
14 | |
15 namespace content { | |
16 namespace { | |
17 | |
18 const ui::SystemUIResourceType kIdleResourceType = ui::OVERSCROLL_REFRESH_IDLE; | |
19 const ui::SystemUIResourceType kActiveResourceType = | |
20 ui::OVERSCROLL_REFRESH_ACTIVE; | |
21 | |
22 // Animation duration after the effect is released without triggering a refresh. | |
23 const int kRecedeTimeMs = 300; | |
24 | |
25 // Animation duration after the effect is released and triggers a refresh. | |
26 const int kActivationTimeMs = 1000; | |
27 | |
28 // Max animation duration after the effect is released and triggers a refresh. | |
29 const int kMaxActivationTimeMs = kActivationTimeMs * 3; | |
30 | |
31 // Animation duration after the refresh activated phase has completed. | |
32 const int kActivationRecedeTimeMs = 300; | |
33 | |
34 // Input threshold required to activate the refresh. | |
35 const float kPullActivationThreshold = .35f; | |
36 | |
37 // Input threshold required to start glowing. | |
38 const float kGlowActivationThreshold = kPullActivationThreshold * 0.85f; | |
39 | |
40 // Useful for avoiding accidental triggering when a scroll janks (is delayed), | |
41 // capping the impulse per event. | |
42 const float kMaxNormalizedDeltaPerPull = kPullActivationThreshold / 4.f; | |
43 | |
44 // Maximum offset of the effect relative to the content size. | |
45 const float kMaxRelativeOffset = .3f; | |
46 | |
47 // Minimum alpha for the effect layer. | |
48 const float kMinAlpha = 0.25f; | |
49 | |
50 // Controls spin velocity. | |
51 const float kPullRotationMultiplier = 180.f * (1.f / kPullActivationThreshold); | |
52 | |
53 // Experimentally determined constant used to allow activation even if touch | |
54 // release results in a small upward fling (quite common during a slow scroll). | |
55 const float kMinFlingVelocityForActivation = -500.f; | |
56 | |
57 const float kEpsilon = 0.005f; | |
58 | |
59 void UpdateLayer(cc::UIResourceLayer* layer, | |
60 cc::Layer* parent, | |
61 cc::UIResourceId res_id, | |
62 const gfx::SizeF& viewport_size, | |
63 float relative_offset, | |
64 float opacity, | |
65 float rotation) { | |
66 // An empty window size, while meaningless, is also relatively harmless, and | |
67 // will simply prevent any drawing of the layers. | |
68 if (viewport_size.IsEmpty()) { | |
69 layer->SetIsDrawable(false); | |
70 return; | |
71 } | |
72 | |
73 if (!res_id) { | |
74 layer->SetIsDrawable(false); | |
75 return; | |
76 } | |
77 | |
78 if (opacity == 0) { | |
79 layer->SetIsDrawable(false); | |
80 layer->SetOpacity(0); | |
81 return; | |
82 } | |
83 | |
84 if (layer->parent() != parent) | |
aelias_OOO_until_Jul13
2014/11/06 22:28:48
Please move these two if statements (reparenting a
jdduke (slow)
2014/11/07 18:33:44
Done.
| |
85 parent->AddChild(layer); | |
86 | |
87 if (!layer->layer_tree_host()) | |
88 return; | |
89 | |
90 gfx::Size image_size = layer->layer_tree_host()->GetUIResourceSize(res_id); | |
91 layer->SetUIResourceId(res_id); | |
92 layer->SetIsDrawable(true); | |
93 layer->SetTransformOrigin( | |
94 gfx::Point3F(image_size.width() * 0.5f, image_size.height() * 0.5f, 0)); | |
95 layer->SetBounds(image_size); | |
96 layer->SetContentsOpaque(false); | |
97 layer->SetOpacity(Clamp(opacity, 0.f, 1.f)); | |
98 | |
99 float min_viewport_size = min(viewport_size.width(), viewport_size.height()); | |
100 float offset_x = (viewport_size.width() - image_size.width()) * 0.5f; | |
101 float offset_y = | |
102 Damp(relative_offset, 1.2f) * min_viewport_size * kMaxRelativeOffset - | |
103 image_size.height(); | |
104 gfx::Transform transform; | |
105 transform.Translate(offset_x, offset_y); | |
106 transform.Rotate(rotation); | |
107 layer->SetTransform(transform); | |
108 } | |
109 | |
110 } // namespace | |
111 | |
112 class OverscrollRefresh::Effect { | |
113 public: | |
114 Effect(ui::SystemUIResourceManager* resource_manager) | |
115 : resource_manager_(resource_manager), | |
116 idle_layer_(cc::UIResourceLayer::Create()), | |
117 active_layer_(cc::UIResourceLayer::Create()), | |
118 idle_alpha_(0), | |
119 active_alpha_(0), | |
120 offset_(0), | |
121 rotation_(0), | |
122 idle_alpha_start_(0), | |
123 idle_alpha_finish_(0), | |
124 active_alpha_start_(0), | |
125 active_alpha_finish_(0), | |
126 offset_start_(0), | |
127 offset_finish_(0), | |
128 rotation_start_(0), | |
129 rotation_finish_(0), | |
130 state_(STATE_IDLE) { | |
131 idle_layer_->SetIsDrawable(false); | |
132 active_layer_->SetIsDrawable(false); | |
133 } | |
134 | |
135 ~Effect() { Detach(); } | |
136 | |
137 void Pull(float normalized_delta) { | |
138 if (state_ != STATE_PULL) | |
139 offset_ = 0; | |
140 | |
141 state_ = STATE_PULL; | |
142 | |
143 normalized_delta = Clamp(normalized_delta, -kMaxNormalizedDeltaPerPull, | |
144 kMaxNormalizedDeltaPerPull); | |
145 | |
146 offset_ += normalized_delta; | |
147 offset_ = Clamp(offset_, 0.f, 1.f); | |
148 | |
149 idle_alpha_ = | |
150 kMinAlpha + (1.f - kMinAlpha) * offset_ / kGlowActivationThreshold; | |
151 active_alpha_ = (offset_ - kGlowActivationThreshold) / | |
152 (kPullActivationThreshold - kGlowActivationThreshold); | |
153 idle_alpha_ = Clamp(idle_alpha_, 0.f, 1.f); | |
154 active_alpha_ = Clamp(active_alpha_, 0.f, 1.f); | |
155 | |
156 rotation_ = kPullRotationMultiplier * Damp(offset_, 1.f); | |
157 } | |
158 | |
159 bool Update(base::TimeTicks current_time, bool is_triggered_refresh_active) { | |
aelias_OOO_until_Jul13
2014/11/06 22:28:48
Please rename this to Animate.
jdduke (slow)
2014/11/07 18:33:44
Done.
| |
160 if (IsFinished()) | |
161 return false; | |
162 | |
163 if (state_ == STATE_PULL) | |
164 return true; | |
165 | |
166 const double dt = (current_time - start_time_).InMilliseconds(); | |
167 const double t = min(dt / duration_.InMilliseconds(), 1.); | |
168 const float interp = static_cast<float>(Damp(t, 1.)); | |
169 | |
170 idle_alpha_ = Lerp(idle_alpha_start_, idle_alpha_finish_, interp); | |
171 active_alpha_ = Lerp(active_alpha_start_, active_alpha_finish_, interp); | |
172 offset_ = Lerp(offset_start_, offset_finish_, interp); | |
173 rotation_ = Lerp(rotation_start_, rotation_finish_, interp); | |
174 | |
175 if (t < 1.f - kEpsilon) | |
176 return true; | |
177 | |
178 switch (state_) { | |
179 case STATE_IDLE: | |
180 case STATE_PULL: | |
181 NOTREACHED() << "Invalidate state for animation."; | |
182 break; | |
183 case STATE_ACTIVATED: | |
184 start_time_ = current_time; | |
185 if (is_triggered_refresh_active && | |
186 (current_time - activated_start_time_ < | |
187 base::TimeDelta::FromMilliseconds(kMaxActivationTimeMs))) { | |
188 offset_start_ = offset_finish_ = offset_; | |
189 rotation_start_ = rotation_; | |
190 rotation_finish_ = rotation_start_ + 360.f; | |
191 break; | |
192 } | |
193 state_ = STATE_ACTIVATED_RECEDE; | |
194 duration_ = base::TimeDelta::FromMilliseconds(kActivationRecedeTimeMs); | |
195 idle_alpha_start_ = idle_alpha_; | |
196 active_alpha_start_ = active_alpha_; | |
197 idle_alpha_finish_ = 0; | |
198 active_alpha_finish_ = 0; | |
199 rotation_start_ = rotation_finish_ = rotation_; | |
200 offset_start_ = offset_finish_ = offset_; | |
201 break; | |
202 case STATE_ACTIVATED_RECEDE: | |
203 Finish(); | |
204 break; | |
205 case STATE_RECEDE: | |
206 Finish(); | |
207 break; | |
208 }; | |
209 | |
210 return !IsFinished(); | |
211 } | |
212 | |
213 bool Release(base::TimeTicks current_time, bool allow_activation) { | |
214 if (state_ != STATE_ACTIVATED && state_ != STATE_PULL) | |
215 return false; | |
216 | |
217 if (state_ == STATE_ACTIVATED && allow_activation) | |
218 return false; | |
219 | |
220 start_time_ = current_time; | |
221 idle_alpha_start_ = idle_alpha_; | |
222 active_alpha_start_ = active_alpha_; | |
223 offset_start_ = offset_; | |
224 rotation_start_ = rotation_; | |
225 | |
226 if (offset_ < kPullActivationThreshold || !allow_activation) { | |
227 state_ = STATE_RECEDE; | |
228 duration_ = base::TimeDelta::FromMilliseconds(kRecedeTimeMs); | |
229 idle_alpha_finish_ = 0; | |
230 active_alpha_finish_ = 0; | |
231 offset_finish_ = 0; | |
232 rotation_finish_ = rotation_start_ - 180.f; | |
233 return false; | |
234 } | |
235 | |
236 state_ = STATE_ACTIVATED; | |
237 duration_ = base::TimeDelta::FromMilliseconds(kActivationTimeMs); | |
238 activated_start_time_ = current_time; | |
239 idle_alpha_finish_ = idle_alpha_start_; | |
240 active_alpha_finish_ = active_alpha_start_; | |
241 offset_finish_ = kPullActivationThreshold; | |
242 rotation_finish_ = rotation_start_ + 360.f; | |
243 return true; | |
244 } | |
245 | |
246 void Finish() { | |
247 Detach(); | |
248 idle_layer_->SetIsDrawable(false); | |
249 active_layer_->SetIsDrawable(false); | |
250 offset_ = 0; | |
251 idle_alpha_ = 0; | |
252 active_alpha_ = 0; | |
253 rotation_ = 0; | |
254 state_ = STATE_IDLE; | |
255 } | |
256 | |
257 void ApplyToLayers(const gfx::SizeF& size, cc::Layer* parent) { | |
258 if (IsFinished()) | |
259 return; | |
260 | |
261 UpdateLayer(idle_layer_.get(), parent, | |
262 resource_manager_->GetUIResourceId(kIdleResourceType), size, | |
Ted C
2014/11/06 19:25:40
I was under the impression that these all did need
jdduke (slow)
2014/11/07 18:33:44
I ran "git cl format" and this is what it gave me,
| |
263 offset_, idle_alpha_, rotation_); | |
264 UpdateLayer(active_layer_.get(), parent, | |
265 resource_manager_->GetUIResourceId(kActiveResourceType), size, | |
266 offset_, active_alpha_, rotation_); | |
267 } | |
268 | |
269 bool IsFinished() const { return state_ == STATE_IDLE; } | |
270 | |
271 private: | |
272 enum State { | |
273 STATE_IDLE = 0, | |
274 STATE_PULL, | |
275 STATE_ACTIVATED, | |
276 STATE_ACTIVATED_RECEDE, | |
277 STATE_RECEDE | |
278 }; | |
279 | |
280 void Detach() { | |
281 idle_layer_->RemoveFromParent(); | |
282 active_layer_->RemoveFromParent(); | |
283 } | |
284 | |
285 ui::SystemUIResourceManager* const resource_manager_; | |
286 | |
287 scoped_refptr<cc::UIResourceLayer> idle_layer_; | |
288 scoped_refptr<cc::UIResourceLayer> active_layer_; | |
289 | |
290 float idle_alpha_; | |
291 float active_alpha_; | |
292 float offset_; | |
293 float rotation_; | |
294 | |
295 float idle_alpha_start_; | |
296 float idle_alpha_finish_; | |
297 float active_alpha_start_; | |
298 float active_alpha_finish_; | |
299 float offset_start_; | |
300 float offset_finish_; | |
301 float rotation_start_; | |
302 float rotation_finish_; | |
303 | |
304 base::TimeTicks start_time_; | |
305 base::TimeTicks activated_start_time_; | |
306 base::TimeDelta duration_; | |
307 | |
308 State state_; | |
309 }; | |
310 | |
311 OverscrollRefresh::OverscrollRefresh( | |
312 ui::SystemUIResourceManager* resource_manager, | |
313 OverscrollRefreshClient* client) | |
314 : client_(client), | |
315 scroll_consumption_state_(DISABLED), | |
316 effect_(new Effect(resource_manager)) { | |
317 DCHECK(client); | |
318 } | |
319 | |
320 OverscrollRefresh::~OverscrollRefresh() { | |
321 } | |
322 | |
323 void OverscrollRefresh::Reset() { | |
324 scroll_consumption_state_ = DISABLED; | |
325 effect_->Finish(); | |
326 } | |
327 | |
328 void OverscrollRefresh::OnScrollBegin() { | |
329 bool allow_activation = false; | |
330 Release(allow_activation); | |
331 if (!scroll_offset_.y()) | |
332 scroll_consumption_state_ = PENDING_SCROLL_UPDATE_ACK; | |
333 } | |
334 | |
335 void OverscrollRefresh::OnScrollEnd(const gfx::Vector2dF& scroll_velocity) { | |
336 bool allow_activation = scroll_velocity.y() > kMinFlingVelocityForActivation; | |
337 Release(allow_activation); | |
338 } | |
339 | |
340 void OverscrollRefresh::OnScrollUpdateAck(ActivationAllowance allowance) { | |
341 if (scroll_consumption_state_ != PENDING_SCROLL_UPDATE_ACK) | |
342 return; | |
343 | |
344 scroll_consumption_state_ = | |
345 allowance == ALLOW_ACTIVATION ? ENABLED : DISABLED; | |
346 } | |
347 | |
348 bool OverscrollRefresh::WillHandleScrollUpdate( | |
349 const gfx::Vector2dF& scroll_delta) { | |
350 if (viewport_size_.IsEmpty()) | |
351 return false; | |
352 | |
353 if (scroll_consumption_state_ == ENABLED) { | |
aelias_OOO_until_Jul13
2014/11/06 22:28:48
I think this would be more readable as a switch st
jdduke (slow)
2014/11/07 18:33:44
Done.
| |
354 float normalized_delta = | |
355 scroll_delta.y() / min(viewport_size_.height(), viewport_size_.width()); | |
356 effect_->Pull(normalized_delta); | |
357 return true; | |
358 } | |
359 | |
360 // If the initial scroll motion is downward, never allow activation. | |
361 if (scroll_consumption_state_ == PENDING_SCROLL_UPDATE_ACK && | |
362 scroll_delta.y() <= 0) { | |
363 scroll_consumption_state_ = DISABLED; | |
364 } | |
365 | |
366 return false; | |
367 } | |
368 | |
369 bool OverscrollRefresh::Animate(base::TimeTicks current_time, | |
370 cc::Layer* parent_layer) { | |
371 DCHECK(parent_layer); | |
372 if (effect_->IsFinished()) | |
373 return false; | |
374 | |
375 bool is_triggered_refresh_active = client_->IsTriggeredRefreshActive(); | |
376 if (effect_->Update(current_time, is_triggered_refresh_active)) | |
377 effect_->ApplyToLayers(viewport_size_, parent_layer); | |
378 | |
379 return !effect_->IsFinished(); | |
380 } | |
381 | |
382 bool OverscrollRefresh::IsActive() const { | |
383 return scroll_consumption_state_ == ENABLED || !effect_->IsFinished(); | |
384 } | |
385 | |
386 bool OverscrollRefresh::IsPendingScrollUpdateAck() const { | |
387 return scroll_consumption_state_ == PENDING_SCROLL_UPDATE_ACK; | |
388 } | |
389 | |
390 void OverscrollRefresh::UpdateDisplay( | |
391 const gfx::SizeF& viewport_size, | |
392 const gfx::Vector2dF& content_scroll_offset) { | |
393 viewport_size_ = viewport_size; | |
394 scroll_offset_ = content_scroll_offset; | |
395 } | |
396 | |
397 void OverscrollRefresh::Release(bool allow_activation) { | |
398 if (scroll_consumption_state_ == ENABLED) { | |
399 if (effect_->Release(base::TimeTicks::Now(), allow_activation)) | |
400 client_->TriggerRefresh(); | |
401 } | |
402 scroll_consumption_state_ = DISABLED; | |
403 } | |
404 | |
405 } // namespace content | |
OLD | NEW |