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 // Useful for avoiding accidental triggering when a scroll janks (is delayed), | |
35 // capping the impulse per event. | |
36 const float kMaxNormalizedDeltaPerPull = .05f; | |
37 | |
38 // Input threshold required to activate the refresh. | |
39 const float kPullActivationThreshold = .25f; | |
Ted C
2014/10/31 23:52:05
What is this a percentage of? Screen size? If so
jdduke (slow)
2014/11/06 00:10:37
I think it's important to be at least somewhat cou
Ted C
2014/11/06 19:25:40
In general, we should match the refresh widget use
jdduke (slow)
2014/11/07 18:33:44
Yeah, they use a threshold invariant to the orient
| |
40 | |
41 // Input threshold required to start glowing. | |
42 const float kGlowActivationThreshold = kPullActivationThreshold * 0.85f; | |
43 | |
44 // Maximum height of the effect relative to the content height. | |
45 const float kMaxRelativeHeight = .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 const float kEpsilon = 0.005f; | |
54 | |
55 void UpdateLayer(cc::UIResourceLayer* layer, | |
56 cc::Layer* parent, | |
57 cc::UIResourceId res_id, | |
58 const gfx::SizeF& viewport_size, | |
59 float relative_offset, | |
60 float opacity, | |
61 float rotation) { | |
62 // An empty window size, while meaningless, is also relatively harmless, and | |
63 // will simply prevent any drawing of the layers. | |
64 if (viewport_size.IsEmpty()) { | |
65 layer->SetIsDrawable(false); | |
66 return; | |
67 } | |
68 | |
69 if (!res_id) { | |
70 layer->SetIsDrawable(false); | |
71 return; | |
72 } | |
73 | |
74 if (opacity == 0) { | |
75 layer->SetIsDrawable(false); | |
76 layer->SetOpacity(0); | |
77 return; | |
78 } | |
79 | |
80 if (layer->parent() != parent) | |
81 parent->AddChild(layer); | |
82 | |
83 if (!layer->layer_tree_host()) | |
84 return; | |
85 | |
86 gfx::Size image_size = layer->layer_tree_host()->GetUIResourceSize(res_id); | |
87 layer->SetUIResourceId(res_id); | |
88 layer->SetIsDrawable(true); | |
89 layer->SetTransformOrigin( | |
90 gfx::Point3F(image_size.width() * 0.5f, image_size.height() * 0.5f, 0)); | |
91 layer->SetBounds(image_size); | |
92 layer->SetContentsOpaque(false); | |
93 layer->SetOpacity(Clamp(opacity, 0.f, 1.f)); | |
94 | |
95 float offset_x = (viewport_size.width() - image_size.width()) * 0.5f; | |
96 float offset_y = | |
97 Damp(relative_offset, 1.f) * viewport_size.height() * kMaxRelativeHeight - | |
98 image_size.height(); | |
99 gfx::Transform transform; | |
100 transform.Translate(offset_x, offset_y); | |
101 transform.Rotate(rotation); | |
102 layer->SetTransform(transform); | |
103 } | |
104 | |
105 } // namespace | |
106 | |
107 class OverscrollRefresh::Effect { | |
108 public: | |
109 Effect(ui::SystemUIResourceManager* resource_manager) | |
110 : resource_manager_(resource_manager), | |
111 idle_layer_(cc::UIResourceLayer::Create()), | |
112 active_layer_(cc::UIResourceLayer::Create()), | |
113 idle_alpha_(0), | |
114 active_alpha_(0), | |
115 offset_(0), | |
116 rotation_(0), | |
117 idle_alpha_start_(0), | |
118 idle_alpha_finish_(0), | |
119 active_alpha_start_(0), | |
120 active_alpha_finish_(0), | |
121 offset_start_(0), | |
122 offset_finish_(0), | |
123 rotation_start_(0), | |
124 rotation_finish_(0), | |
125 state_(STATE_IDLE) { | |
126 idle_layer_->SetIsDrawable(false); | |
127 active_layer_->SetIsDrawable(false); | |
128 } | |
129 | |
130 ~Effect() { Detach(); } | |
131 | |
132 void Pull(float normalized_delta) { | |
133 if (state_ != STATE_PULL) | |
134 offset_ = 0; | |
135 | |
136 state_ = STATE_PULL; | |
137 | |
138 normalized_delta = Clamp(normalized_delta, | |
139 -kMaxNormalizedDeltaPerPull, | |
140 kMaxNormalizedDeltaPerPull); | |
141 | |
142 offset_ += normalized_delta; | |
143 offset_ = Clamp(offset_, 0.f, 1.f); | |
144 | |
145 idle_alpha_ = | |
146 kMinAlpha + (1.f - kMinAlpha) * offset_ / kGlowActivationThreshold; | |
147 active_alpha_ = (offset_ - kGlowActivationThreshold) / | |
148 (kPullActivationThreshold - kGlowActivationThreshold); | |
149 idle_alpha_ = Clamp(idle_alpha_, 0.f, 1.f); | |
150 active_alpha_ = Clamp(active_alpha_, 0.f, 1.f); | |
151 | |
152 rotation_ = kPullRotationMultiplier * offset_; | |
153 } | |
154 | |
155 bool Update(base::TimeTicks current_time, bool is_triggered_refresh_active) { | |
156 if (IsFinished()) | |
157 return false; | |
158 | |
159 if (state_ == STATE_PULL) | |
160 return true; | |
161 | |
162 const double dt = (current_time - start_time_).InMilliseconds(); | |
163 const double t = min(dt / duration_.InMilliseconds(), 1.); | |
164 const float interp = static_cast<float>(Damp(t, 1.)); | |
165 | |
166 idle_alpha_ = Lerp(idle_alpha_start_, idle_alpha_finish_, interp); | |
167 active_alpha_ = Lerp(active_alpha_start_, active_alpha_finish_, interp); | |
168 offset_ = Lerp(offset_start_, offset_finish_, interp); | |
169 rotation_ = Lerp(rotation_start_, rotation_finish_, interp); | |
170 | |
171 if (t < 1.f - kEpsilon) | |
172 return true; | |
173 | |
174 switch (state_) { | |
175 case STATE_IDLE: | |
176 case STATE_PULL: | |
177 NOTREACHED() << "Invalidate state for animation."; | |
178 break; | |
179 case STATE_ACTIVATED: | |
180 start_time_ = current_time; | |
181 if (is_triggered_refresh_active && | |
182 (current_time - activated_start_time_ < | |
183 base::TimeDelta::FromMilliseconds(kMaxActivationTimeMs))) { | |
184 offset_start_ = offset_finish_ = offset_; | |
185 rotation_start_ = rotation_; | |
186 rotation_finish_ = rotation_start_ + 360.f; | |
187 break; | |
188 } | |
189 state_ = STATE_ACTIVATED_RECEDE; | |
190 duration_ = base::TimeDelta::FromMilliseconds(kActivationRecedeTimeMs); | |
191 idle_alpha_start_ = idle_alpha_; | |
192 active_alpha_start_ = active_alpha_; | |
193 idle_alpha_finish_ = 0; | |
194 active_alpha_finish_ = 0; | |
195 rotation_start_ = rotation_finish_ = rotation_; | |
196 offset_start_ = offset_finish_ = offset_; | |
197 break; | |
198 case STATE_ACTIVATED_RECEDE: | |
199 Finish(); | |
200 break; | |
201 case STATE_RECEDE: | |
202 Finish(); | |
203 break; | |
204 }; | |
205 | |
206 return !IsFinished(); | |
207 } | |
208 | |
209 bool Release(base::TimeTicks current_time, bool allow_activation) { | |
210 if (state_ != STATE_ACTIVATED && state_ != STATE_PULL) | |
211 return false; | |
212 | |
213 if (state_ == STATE_ACTIVATED && allow_activation) | |
214 return false; | |
215 | |
216 start_time_ = current_time; | |
217 idle_alpha_start_ = idle_alpha_; | |
218 active_alpha_start_ = active_alpha_; | |
219 offset_start_ = offset_; | |
220 rotation_start_ = rotation_; | |
221 | |
222 if (offset_ < kPullActivationThreshold || !allow_activation) { | |
223 state_ = STATE_RECEDE; | |
224 duration_ = base::TimeDelta::FromMilliseconds(kRecedeTimeMs); | |
225 idle_alpha_finish_ = 0; | |
226 active_alpha_finish_ = 0; | |
227 offset_finish_ = 0; | |
228 rotation_finish_ = 0; | |
229 return false; | |
230 } | |
231 | |
232 state_ = STATE_ACTIVATED; | |
233 duration_ = base::TimeDelta::FromMilliseconds(kActivationTimeMs); | |
234 activated_start_time_ = current_time; | |
235 idle_alpha_finish_ = idle_alpha_start_; | |
236 active_alpha_finish_ = active_alpha_start_; | |
237 offset_finish_ = kPullActivationThreshold; | |
238 rotation_finish_ = rotation_start_ + 360.f; | |
239 return true; | |
240 } | |
241 | |
242 void Finish() { | |
243 Detach(); | |
244 idle_layer_->SetIsDrawable(false); | |
245 active_layer_->SetIsDrawable(false); | |
246 offset_ = 0; | |
247 idle_alpha_ = 0; | |
248 active_alpha_ = 0; | |
249 rotation_ = 0; | |
250 state_ = STATE_IDLE; | |
251 } | |
252 | |
253 void ApplyToLayers(const gfx::SizeF& size, cc::Layer* parent) { | |
254 if (IsFinished()) | |
255 return; | |
256 | |
257 UpdateLayer(idle_layer_.get(), | |
258 parent, | |
259 resource_manager_->GetUIResourceId(kIdleResourceType), | |
260 size, | |
261 offset_, | |
262 idle_alpha_, | |
263 rotation_); | |
264 UpdateLayer(active_layer_.get(), | |
265 parent, | |
266 resource_manager_->GetUIResourceId(kActiveResourceType), | |
267 size, | |
268 offset_, | |
269 active_alpha_, | |
270 rotation_); | |
271 } | |
272 | |
273 bool IsFinished() const { return state_ == STATE_IDLE; } | |
274 | |
275 private: | |
276 enum State { | |
277 STATE_IDLE = 0, | |
278 STATE_PULL, | |
279 STATE_ACTIVATED, | |
280 STATE_ACTIVATED_RECEDE, | |
281 STATE_RECEDE | |
282 }; | |
283 | |
284 void Detach() { | |
285 idle_layer_->RemoveFromParent(); | |
286 active_layer_->RemoveFromParent(); | |
287 } | |
288 | |
289 ui::SystemUIResourceManager* const resource_manager_; | |
290 | |
291 scoped_refptr<cc::UIResourceLayer> idle_layer_; | |
292 scoped_refptr<cc::UIResourceLayer> active_layer_; | |
293 | |
294 float idle_alpha_; | |
295 float active_alpha_; | |
296 float offset_; | |
297 float rotation_; | |
298 | |
299 float idle_alpha_start_; | |
300 float idle_alpha_finish_; | |
301 float active_alpha_start_; | |
302 float active_alpha_finish_; | |
303 float offset_start_; | |
304 float offset_finish_; | |
305 float rotation_start_; | |
306 float rotation_finish_; | |
307 | |
308 base::TimeTicks start_time_; | |
309 base::TimeTicks activated_start_time_; | |
310 base::TimeDelta duration_; | |
311 | |
312 State state_; | |
313 }; | |
314 | |
315 OverscrollRefresh::OverscrollRefresh( | |
316 ui::SystemUIResourceManager* resource_manager, | |
317 OverscrollRefreshClient* client) | |
318 : client_(client), | |
319 scroll_consumption_state_(DISABLED), | |
320 effect_(new Effect(resource_manager)) { | |
321 DCHECK(client); | |
322 } | |
323 | |
324 OverscrollRefresh::~OverscrollRefresh() { | |
325 } | |
326 | |
327 void OverscrollRefresh::Reset() { | |
328 scroll_consumption_state_ = DISABLED; | |
329 effect_->Finish(); | |
330 } | |
331 | |
332 void OverscrollRefresh::OnScrollBegin() { | |
333 bool allow_activation = false; | |
334 Release(allow_activation); | |
335 if (!scroll_offset_.y()) | |
336 scroll_consumption_state_ = PENDING_SCROLL_UPDATE_ACK; | |
337 } | |
338 | |
339 void OverscrollRefresh::OnScrollUpdateAck(bool consumed) { | |
340 // Intentionally skip the first scroll update to avoid issues | |
341 if (scroll_consumption_state_ == PENDING_SCROLL_UPDATE_ACK) | |
342 scroll_consumption_state_ = consumed ? DISABLED : ENABLED; | |
343 } | |
344 | |
345 void OverscrollRefresh::OnScrollEnd(const gfx::Vector2dF& scroll_velocity) { | |
346 bool allow_activation = scroll_velocity.y() > -500; | |
Ted C
2014/10/31 23:52:05
where does -500 come from? should it be a constan
jdduke (slow)
2014/11/06 00:10:37
I'll pull it out as a constant, it was from trial
| |
347 Release(allow_activation); | |
348 } | |
349 | |
350 bool OverscrollRefresh::WillHandleScrollUpdate( | |
351 const gfx::Vector2dF& scroll_delta) { | |
352 if (!viewport_size_.height()) | |
353 return false; | |
354 | |
355 if (scroll_consumption_state_ == ENABLED) { | |
356 float normalized_delta = scroll_delta.y() / viewport_size_.height(); | |
357 effect_->Pull(normalized_delta); | |
358 return true; | |
359 } | |
360 | |
361 if (scroll_consumption_state_ == PENDING_SCROLL_UPDATE_ACK && | |
362 scroll_delta.y() < 0) { | |
aelias_OOO_until_Jul13
2014/10/30 03:53:22
I'm having trouble following what this is for, cou
jdduke (slow)
2014/11/06 00:10:37
Done.
| |
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 |