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 "cc/input/scrollbar_animation_controller.h"
6 
7 #include <algorithm>
8 
9 #include "base/bind.h"
10 #include "base/memory/ptr_util.h"
11 #include "base/numerics/ranges.h"
12 #include "base/time/time.h"
13 #include "cc/trees/layer_tree_impl.h"
14 
15 namespace cc {
16 
17 std::unique_ptr<ScrollbarAnimationController>
CreateScrollbarAnimationControllerAndroid(ElementId scroll_element_id,ScrollbarAnimationControllerClient * client,base::TimeDelta fade_delay,base::TimeDelta fade_duration,float initial_opacity)18 ScrollbarAnimationController::CreateScrollbarAnimationControllerAndroid(
19     ElementId scroll_element_id,
20     ScrollbarAnimationControllerClient* client,
21     base::TimeDelta fade_delay,
22     base::TimeDelta fade_duration,
23     float initial_opacity) {
24   return base::WrapUnique(new ScrollbarAnimationController(
25       scroll_element_id, client, fade_delay, fade_duration, initial_opacity));
26 }
27 
28 std::unique_ptr<ScrollbarAnimationController>
CreateScrollbarAnimationControllerAuraOverlay(ElementId scroll_element_id,ScrollbarAnimationControllerClient * client,base::TimeDelta fade_delay,base::TimeDelta fade_duration,base::TimeDelta thinning_duration,float initial_opacity)29 ScrollbarAnimationController::CreateScrollbarAnimationControllerAuraOverlay(
30     ElementId scroll_element_id,
31     ScrollbarAnimationControllerClient* client,
32     base::TimeDelta fade_delay,
33     base::TimeDelta fade_duration,
34     base::TimeDelta thinning_duration,
35     float initial_opacity) {
36   return base::WrapUnique(new ScrollbarAnimationController(
37       scroll_element_id, client, fade_delay, fade_duration, thinning_duration,
38       initial_opacity));
39 }
40 
ScrollbarAnimationController(ElementId scroll_element_id,ScrollbarAnimationControllerClient * client,base::TimeDelta fade_delay,base::TimeDelta fade_duration,float initial_opacity)41 ScrollbarAnimationController::ScrollbarAnimationController(
42     ElementId scroll_element_id,
43     ScrollbarAnimationControllerClient* client,
44     base::TimeDelta fade_delay,
45     base::TimeDelta fade_duration,
46     float initial_opacity)
47     : client_(client),
48       fade_delay_(fade_delay),
49       fade_duration_(fade_duration),
50       need_trigger_scrollbar_fade_in_(false),
51       is_animating_(false),
52       animation_change_(NONE),
53       scroll_element_id_(scroll_element_id),
54       currently_scrolling_(false),
55       show_in_fast_scroll_(false),
56       opacity_(initial_opacity),
57       show_scrollbars_on_scroll_gesture_(false),
58       need_thinning_animation_(false),
59       is_mouse_down_(false),
60       tickmarks_showing_(false) {}
61 
ScrollbarAnimationController(ElementId scroll_element_id,ScrollbarAnimationControllerClient * client,base::TimeDelta fade_delay,base::TimeDelta fade_duration,base::TimeDelta thinning_duration,float initial_opacity)62 ScrollbarAnimationController::ScrollbarAnimationController(
63     ElementId scroll_element_id,
64     ScrollbarAnimationControllerClient* client,
65     base::TimeDelta fade_delay,
66     base::TimeDelta fade_duration,
67     base::TimeDelta thinning_duration,
68     float initial_opacity)
69     : client_(client),
70       fade_delay_(fade_delay),
71       fade_duration_(fade_duration),
72       need_trigger_scrollbar_fade_in_(false),
73       is_animating_(false),
74       animation_change_(NONE),
75       scroll_element_id_(scroll_element_id),
76       currently_scrolling_(false),
77       show_in_fast_scroll_(false),
78       opacity_(initial_opacity),
79       show_scrollbars_on_scroll_gesture_(true),
80       need_thinning_animation_(true),
81       is_mouse_down_(false),
82       tickmarks_showing_(false) {
83   vertical_controller_ = SingleScrollbarAnimationControllerThinning::Create(
84       scroll_element_id, ScrollbarOrientation::VERTICAL, client,
85       thinning_duration);
86   horizontal_controller_ = SingleScrollbarAnimationControllerThinning::Create(
87       scroll_element_id, ScrollbarOrientation::HORIZONTAL, client,
88       thinning_duration);
89 }
90 
91 ScrollbarAnimationController::~ScrollbarAnimationController() = default;
92 
Scrollbars() const93 ScrollbarSet ScrollbarAnimationController::Scrollbars() const {
94   return client_->ScrollbarsFor(scroll_element_id_);
95 }
96 
97 SingleScrollbarAnimationControllerThinning&
GetScrollbarAnimationController(ScrollbarOrientation orientation) const98 ScrollbarAnimationController::GetScrollbarAnimationController(
99     ScrollbarOrientation orientation) const {
100   DCHECK(need_thinning_animation_);
101   if (orientation == ScrollbarOrientation::VERTICAL)
102     return *(vertical_controller_.get());
103   else
104     return *(horizontal_controller_.get());
105 }
106 
StartAnimation()107 void ScrollbarAnimationController::StartAnimation() {
108   DCHECK(animation_change_ != NONE);
109   delayed_scrollbar_animation_.Cancel();
110   need_trigger_scrollbar_fade_in_ = false;
111   is_animating_ = true;
112   last_awaken_time_ = base::TimeTicks();
113   client_->SetNeedsAnimateForScrollbarAnimation();
114 }
115 
StopAnimation()116 void ScrollbarAnimationController::StopAnimation() {
117   delayed_scrollbar_animation_.Cancel();
118   need_trigger_scrollbar_fade_in_ = false;
119   is_animating_ = false;
120   animation_change_ = NONE;
121 }
122 
PostDelayedAnimation(AnimationChange animation_change)123 void ScrollbarAnimationController::PostDelayedAnimation(
124     AnimationChange animation_change) {
125   animation_change_ = animation_change;
126   delayed_scrollbar_animation_.Cancel();
127   delayed_scrollbar_animation_.Reset(
128       base::BindOnce(&ScrollbarAnimationController::StartAnimation,
129                      weak_factory_.GetWeakPtr()));
130   client_->PostDelayedScrollbarAnimationTask(
131       delayed_scrollbar_animation_.callback(), fade_delay_);
132 }
133 
Animate(base::TimeTicks now)134 bool ScrollbarAnimationController::Animate(base::TimeTicks now) {
135   bool animated = false;
136 
137   for (ScrollbarLayerImplBase* scrollbar : Scrollbars()) {
138     if (!scrollbar->CanScrollOrientation())
139       scrollbar->SetOverlayScrollbarLayerOpacityAnimated(0);
140   }
141 
142   if (is_animating_) {
143     DCHECK(animation_change_ != NONE);
144     if (last_awaken_time_.is_null())
145       last_awaken_time_ = now;
146 
147     float progress = AnimationProgressAtTime(now);
148     RunAnimationFrame(progress);
149 
150     if (is_animating_)
151       client_->SetNeedsAnimateForScrollbarAnimation();
152     animated = true;
153   }
154 
155   if (need_thinning_animation_) {
156     animated |= vertical_controller_->Animate(now);
157     animated |= horizontal_controller_->Animate(now);
158   }
159 
160   return animated;
161 }
162 
AnimationProgressAtTime(base::TimeTicks now)163 float ScrollbarAnimationController::AnimationProgressAtTime(
164     base::TimeTicks now) {
165   base::TimeDelta delta = now - last_awaken_time_;
166   float progress = delta.InSecondsF() / fade_duration_.InSecondsF();
167   return base::ClampToRange(progress, 0.0f, 1.0f);
168 }
169 
RunAnimationFrame(float progress)170 void ScrollbarAnimationController::RunAnimationFrame(float progress) {
171   float opacity;
172 
173   DCHECK(animation_change_ != NONE);
174   if (animation_change_ == FADE_IN) {
175     opacity = std::max(progress, opacity_);
176   } else {
177     opacity = std::min(1.f - progress, opacity_);
178   }
179 
180   ApplyOpacityToScrollbars(opacity);
181   if (progress == 1.f)
182     StopAnimation();
183 }
184 
DidScrollBegin()185 void ScrollbarAnimationController::DidScrollBegin() {
186   currently_scrolling_ = true;
187 }
188 
DidScrollEnd()189 void ScrollbarAnimationController::DidScrollEnd() {
190   bool has_scrolled = show_in_fast_scroll_;
191   show_in_fast_scroll_ = false;
192 
193   currently_scrolling_ = false;
194 
195   // We don't fade out scrollbar if they need thinning animation and mouse is
196   // near.
197   if (need_thinning_animation_ && MouseIsNearAnyScrollbar())
198     return;
199 
200   if (has_scrolled && !tickmarks_showing_)
201     PostDelayedAnimation(FADE_OUT);
202 }
203 
DidScrollUpdate()204 void ScrollbarAnimationController::DidScrollUpdate() {
205   UpdateScrollbarState();
206 }
207 
UpdateScrollbarState()208 void ScrollbarAnimationController::UpdateScrollbarState() {
209   if (need_thinning_animation_ && Captured())
210     return;
211 
212   StopAnimation();
213 
214   Show();
215 
216   // As an optimization, we avoid spamming fade delay tasks during active fast
217   // scrolls.  But if we're not within one, we need to post every scroll update.
218   if (!currently_scrolling_) {
219     // We don't fade out scrollbar if they need thinning animation (Aura
220     // Overlay) and mouse is near or tickmarks show.
221     if (need_thinning_animation_) {
222       if (!MouseIsNearAnyScrollbar() && !tickmarks_showing_)
223         PostDelayedAnimation(FADE_OUT);
224     } else {
225       PostDelayedAnimation(FADE_OUT);
226     }
227   } else {
228     show_in_fast_scroll_ = true;
229   }
230 
231   if (need_thinning_animation_) {
232     vertical_controller_->UpdateThumbThicknessScale();
233     horizontal_controller_->UpdateThumbThicknessScale();
234   }
235 }
236 
WillUpdateScroll()237 void ScrollbarAnimationController::WillUpdateScroll() {
238   if (show_scrollbars_on_scroll_gesture_)
239     UpdateScrollbarState();
240 }
241 
DidRequestShowFromMainThread()242 void ScrollbarAnimationController::DidRequestShowFromMainThread() {
243   UpdateScrollbarState();
244 }
245 
UpdateTickmarksVisibility(bool show)246 void ScrollbarAnimationController::UpdateTickmarksVisibility(bool show) {
247   if (!need_thinning_animation_)
248     return;
249 
250   if (tickmarks_showing_ == show)
251     return;
252 
253   tickmarks_showing_ = show;
254   UpdateScrollbarState();
255 }
256 
DidMouseDown()257 void ScrollbarAnimationController::DidMouseDown() {
258   if (!need_thinning_animation_)
259     return;
260 
261   is_mouse_down_ = true;
262 
263   if (ScrollbarsHidden()) {
264     if (need_trigger_scrollbar_fade_in_) {
265       delayed_scrollbar_animation_.Cancel();
266       need_trigger_scrollbar_fade_in_ = false;
267     }
268     return;
269   }
270 
271   vertical_controller_->DidMouseDown();
272   horizontal_controller_->DidMouseDown();
273 }
274 
DidMouseUp()275 void ScrollbarAnimationController::DidMouseUp() {
276   if (!need_thinning_animation_)
277     return;
278 
279   is_mouse_down_ = false;
280 
281   if (!Captured()) {
282     if (MouseIsNearAnyScrollbar() && ScrollbarsHidden()) {
283       PostDelayedAnimation(FADE_IN);
284       need_trigger_scrollbar_fade_in_ = true;
285     }
286     return;
287   }
288 
289   vertical_controller_->DidMouseUp();
290   horizontal_controller_->DidMouseUp();
291 
292   if (!MouseIsNearAnyScrollbar() && !ScrollbarsHidden() && !tickmarks_showing_)
293     PostDelayedAnimation(FADE_OUT);
294 }
295 
DidMouseLeave()296 void ScrollbarAnimationController::DidMouseLeave() {
297   if (!need_thinning_animation_)
298     return;
299 
300   vertical_controller_->DidMouseLeave();
301   horizontal_controller_->DidMouseLeave();
302 
303   delayed_scrollbar_animation_.Cancel();
304   need_trigger_scrollbar_fade_in_ = false;
305 
306   if (ScrollbarsHidden() || Captured() || tickmarks_showing_)
307     return;
308 
309   PostDelayedAnimation(FADE_OUT);
310 }
311 
DidMouseMove(const gfx::PointF & device_viewport_point)312 void ScrollbarAnimationController::DidMouseMove(
313     const gfx::PointF& device_viewport_point) {
314   if (!need_thinning_animation_)
315     return;
316 
317   bool need_trigger_scrollbar_fade_in_before = need_trigger_scrollbar_fade_in_;
318 
319   vertical_controller_->DidMouseMove(device_viewport_point);
320   horizontal_controller_->DidMouseMove(device_viewport_point);
321 
322   if (Captured() || tickmarks_showing_) {
323     DCHECK(!ScrollbarsHidden());
324     return;
325   }
326 
327   if (ScrollbarsHidden()) {
328     // Do not fade in scrollbar when user interacting with the content below
329     // scrollbar.
330     if (is_mouse_down_)
331       return;
332     need_trigger_scrollbar_fade_in_ = MouseIsNearAnyScrollbar();
333     if (need_trigger_scrollbar_fade_in_before !=
334         need_trigger_scrollbar_fade_in_) {
335       if (need_trigger_scrollbar_fade_in_) {
336         PostDelayedAnimation(FADE_IN);
337       } else {
338         delayed_scrollbar_animation_.Cancel();
339       }
340     }
341   } else {
342     if (MouseIsNearAnyScrollbar()) {
343       Show();
344       StopAnimation();
345     } else if (!is_animating_) {
346       PostDelayedAnimation(FADE_OUT);
347     }
348   }
349 }
350 
MouseIsOverScrollbarThumb(ScrollbarOrientation orientation) const351 bool ScrollbarAnimationController::MouseIsOverScrollbarThumb(
352     ScrollbarOrientation orientation) const {
353   DCHECK(need_thinning_animation_);
354   return GetScrollbarAnimationController(orientation)
355       .mouse_is_over_scrollbar_thumb();
356 }
357 
MouseIsNearScrollbarThumb(ScrollbarOrientation orientation) const358 bool ScrollbarAnimationController::MouseIsNearScrollbarThumb(
359     ScrollbarOrientation orientation) const {
360   DCHECK(need_thinning_animation_);
361   return GetScrollbarAnimationController(orientation)
362       .mouse_is_near_scrollbar_thumb();
363 }
364 
MouseIsNearScrollbar(ScrollbarOrientation orientation) const365 bool ScrollbarAnimationController::MouseIsNearScrollbar(
366     ScrollbarOrientation orientation) const {
367   DCHECK(need_thinning_animation_);
368   return GetScrollbarAnimationController(orientation)
369       .mouse_is_near_scrollbar_track();
370 }
371 
MouseIsNearAnyScrollbar() const372 bool ScrollbarAnimationController::MouseIsNearAnyScrollbar() const {
373   DCHECK(need_thinning_animation_);
374   return vertical_controller_->mouse_is_near_scrollbar_track() ||
375          horizontal_controller_->mouse_is_near_scrollbar_track();
376 }
377 
ScrollbarsHidden() const378 bool ScrollbarAnimationController::ScrollbarsHidden() const {
379   return opacity_ == 0.0f;
380 }
381 
Captured() const382 bool ScrollbarAnimationController::Captured() const {
383   DCHECK(need_thinning_animation_);
384   return GetScrollbarAnimationController(VERTICAL).captured() ||
385          GetScrollbarAnimationController(HORIZONTAL).captured();
386 }
387 
Show()388 void ScrollbarAnimationController::Show() {
389   delayed_scrollbar_animation_.Cancel();
390   ApplyOpacityToScrollbars(1.0f);
391 }
392 
ApplyOpacityToScrollbars(float opacity)393 void ScrollbarAnimationController::ApplyOpacityToScrollbars(float opacity) {
394   for (ScrollbarLayerImplBase* scrollbar : Scrollbars()) {
395     DCHECK(scrollbar->is_overlay_scrollbar());
396     float effective_opacity = scrollbar->CanScrollOrientation() ? opacity : 0;
397     scrollbar->SetOverlayScrollbarLayerOpacityAnimated(effective_opacity);
398   }
399 
400   bool previouslyVisible = opacity_ > 0.0f;
401   bool currentlyVisible = opacity > 0.0f;
402 
403   if (opacity_ != opacity)
404     client_->SetNeedsRedrawForScrollbarAnimation();
405 
406   opacity_ = opacity;
407 
408   if (previouslyVisible != currentlyVisible)
409     client_->DidChangeScrollbarVisibility();
410 }
411 
412 }  // namespace cc
413