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