| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cc/input/scrollbar_animation_controller.h" |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/time/time.h" |
| #include "cc/trees/layer_tree_impl.h" |
| |
| namespace cc { |
| |
| std::unique_ptr<ScrollbarAnimationController> |
| ScrollbarAnimationController::CreateScrollbarAnimationControllerAndroid( |
| ElementId scroll_element_id, |
| ScrollbarAnimationControllerClient* client, |
| base::TimeDelta fade_delay, |
| base::TimeDelta fade_duration, |
| float initial_opacity) { |
| return base::WrapUnique(new ScrollbarAnimationController( |
| scroll_element_id, client, fade_delay, fade_duration, initial_opacity)); |
| } |
| |
| std::unique_ptr<ScrollbarAnimationController> |
| ScrollbarAnimationController::CreateScrollbarAnimationControllerAuraOverlay( |
| ElementId scroll_element_id, |
| ScrollbarAnimationControllerClient* client, |
| base::TimeDelta fade_delay, |
| base::TimeDelta fade_duration, |
| base::TimeDelta thinning_duration, |
| float initial_opacity) { |
| return base::WrapUnique(new ScrollbarAnimationController( |
| scroll_element_id, client, fade_delay, fade_duration, thinning_duration, |
| initial_opacity)); |
| } |
| |
| ScrollbarAnimationController::ScrollbarAnimationController( |
| ElementId scroll_element_id, |
| ScrollbarAnimationControllerClient* client, |
| base::TimeDelta fade_delay, |
| base::TimeDelta fade_duration, |
| float initial_opacity) |
| : client_(client), |
| fade_delay_(fade_delay), |
| fade_duration_(fade_duration), |
| need_trigger_scrollbar_fade_in_(false), |
| is_animating_(false), |
| animation_change_(NONE), |
| scroll_element_id_(scroll_element_id), |
| currently_scrolling_(false), |
| show_in_fast_scroll_(false), |
| opacity_(initial_opacity), |
| show_scrollbars_on_scroll_gesture_(false), |
| need_thinning_animation_(false), |
| is_mouse_down_(false), |
| tickmarks_showing_(false), |
| weak_factory_(this) {} |
| |
| ScrollbarAnimationController::ScrollbarAnimationController( |
| ElementId scroll_element_id, |
| ScrollbarAnimationControllerClient* client, |
| base::TimeDelta fade_delay, |
| base::TimeDelta fade_duration, |
| base::TimeDelta thinning_duration, |
| float initial_opacity) |
| : client_(client), |
| fade_delay_(fade_delay), |
| fade_duration_(fade_duration), |
| need_trigger_scrollbar_fade_in_(false), |
| is_animating_(false), |
| animation_change_(NONE), |
| scroll_element_id_(scroll_element_id), |
| currently_scrolling_(false), |
| show_in_fast_scroll_(false), |
| opacity_(initial_opacity), |
| show_scrollbars_on_scroll_gesture_(true), |
| need_thinning_animation_(true), |
| is_mouse_down_(false), |
| tickmarks_showing_(false), |
| weak_factory_(this) { |
| vertical_controller_ = SingleScrollbarAnimationControllerThinning::Create( |
| scroll_element_id, ScrollbarOrientation::VERTICAL, client, |
| thinning_duration); |
| horizontal_controller_ = SingleScrollbarAnimationControllerThinning::Create( |
| scroll_element_id, ScrollbarOrientation::HORIZONTAL, client, |
| thinning_duration); |
| } |
| |
| ScrollbarAnimationController::~ScrollbarAnimationController() = default; |
| |
| ScrollbarSet ScrollbarAnimationController::Scrollbars() const { |
| return client_->ScrollbarsFor(scroll_element_id_); |
| } |
| |
| SingleScrollbarAnimationControllerThinning& |
| ScrollbarAnimationController::GetScrollbarAnimationController( |
| ScrollbarOrientation orientation) const { |
| DCHECK(need_thinning_animation_); |
| if (orientation == ScrollbarOrientation::VERTICAL) |
| return *(vertical_controller_.get()); |
| else |
| return *(horizontal_controller_.get()); |
| } |
| |
| void ScrollbarAnimationController::StartAnimation() { |
| DCHECK(animation_change_ != NONE); |
| delayed_scrollbar_animation_.Cancel(); |
| need_trigger_scrollbar_fade_in_ = false; |
| is_animating_ = true; |
| last_awaken_time_ = base::TimeTicks(); |
| client_->SetNeedsAnimateForScrollbarAnimation(); |
| } |
| |
| void ScrollbarAnimationController::StopAnimation() { |
| delayed_scrollbar_animation_.Cancel(); |
| need_trigger_scrollbar_fade_in_ = false; |
| is_animating_ = false; |
| animation_change_ = NONE; |
| } |
| |
| void ScrollbarAnimationController::PostDelayedAnimation( |
| AnimationChange animation_change) { |
| animation_change_ = animation_change; |
| delayed_scrollbar_animation_.Cancel(); |
| delayed_scrollbar_animation_.Reset( |
| base::BindOnce(&ScrollbarAnimationController::StartAnimation, |
| weak_factory_.GetWeakPtr())); |
| client_->PostDelayedScrollbarAnimationTask( |
| delayed_scrollbar_animation_.callback(), fade_delay_); |
| } |
| |
| bool ScrollbarAnimationController::Animate(base::TimeTicks now) { |
| bool animated = false; |
| |
| for (ScrollbarLayerImplBase* scrollbar : Scrollbars()) { |
| if (!scrollbar->CanScrollOrientation()) |
| scrollbar->SetOverlayScrollbarLayerOpacityAnimated(0); |
| } |
| |
| if (is_animating_) { |
| DCHECK(animation_change_ != NONE); |
| if (last_awaken_time_.is_null()) |
| last_awaken_time_ = now; |
| |
| float progress = AnimationProgressAtTime(now); |
| RunAnimationFrame(progress); |
| |
| if (is_animating_) |
| client_->SetNeedsAnimateForScrollbarAnimation(); |
| animated = true; |
| } |
| |
| if (need_thinning_animation_) { |
| animated |= vertical_controller_->Animate(now); |
| animated |= horizontal_controller_->Animate(now); |
| } |
| |
| return animated; |
| } |
| |
| float ScrollbarAnimationController::AnimationProgressAtTime( |
| base::TimeTicks now) { |
| base::TimeDelta delta = now - last_awaken_time_; |
| float progress = delta.InSecondsF() / fade_duration_.InSecondsF(); |
| return std::max(std::min(progress, 1.f), 0.f); |
| } |
| |
| void ScrollbarAnimationController::RunAnimationFrame(float progress) { |
| float opacity; |
| |
| DCHECK(animation_change_ != NONE); |
| if (animation_change_ == FADE_IN) { |
| opacity = std::max(progress, opacity_); |
| } else { |
| opacity = std::min(1.f - progress, opacity_); |
| } |
| |
| ApplyOpacityToScrollbars(opacity); |
| if (progress == 1.f) |
| StopAnimation(); |
| } |
| |
| void ScrollbarAnimationController::DidScrollBegin() { |
| currently_scrolling_ = true; |
| } |
| |
| void ScrollbarAnimationController::DidScrollEnd() { |
| bool has_scrolled = show_in_fast_scroll_; |
| show_in_fast_scroll_ = false; |
| |
| currently_scrolling_ = false; |
| |
| // We don't fade out scrollbar if they need thinning animation and mouse is |
| // near. |
| if (need_thinning_animation_ && MouseIsNearAnyScrollbar()) |
| return; |
| |
| if (has_scrolled && !tickmarks_showing_) |
| PostDelayedAnimation(FADE_OUT); |
| } |
| |
| void ScrollbarAnimationController::DidScrollUpdate() { |
| UpdateScrollbarState(); |
| } |
| |
| void ScrollbarAnimationController::UpdateScrollbarState() { |
| if (need_thinning_animation_ && Captured()) |
| return; |
| |
| StopAnimation(); |
| |
| Show(); |
| |
| // As an optimization, we avoid spamming fade delay tasks during active fast |
| // scrolls. But if we're not within one, we need to post every scroll update. |
| if (!currently_scrolling_) { |
| // We don't fade out scrollbar if they need thinning animation (Aura |
| // Overlay) and mouse is near or tickmarks show. |
| if (need_thinning_animation_) { |
| if (!MouseIsNearAnyScrollbar() && !tickmarks_showing_) |
| PostDelayedAnimation(FADE_OUT); |
| } else { |
| PostDelayedAnimation(FADE_OUT); |
| } |
| } else { |
| show_in_fast_scroll_ = true; |
| } |
| |
| if (need_thinning_animation_) { |
| vertical_controller_->UpdateThumbThicknessScale(); |
| horizontal_controller_->UpdateThumbThicknessScale(); |
| } |
| } |
| |
| void ScrollbarAnimationController::WillUpdateScroll() { |
| if (show_scrollbars_on_scroll_gesture_) |
| UpdateScrollbarState(); |
| } |
| |
| void ScrollbarAnimationController::DidRequestShowFromMainThread() { |
| UpdateScrollbarState(); |
| } |
| |
| void ScrollbarAnimationController::UpdateTickmarksVisibility(bool show) { |
| if (!need_thinning_animation_) |
| return; |
| |
| if (tickmarks_showing_ == show) |
| return; |
| |
| tickmarks_showing_ = show; |
| UpdateScrollbarState(); |
| } |
| |
| void ScrollbarAnimationController::DidMouseDown() { |
| if (!need_thinning_animation_) |
| return; |
| |
| is_mouse_down_ = true; |
| |
| if (ScrollbarsHidden()) { |
| if (need_trigger_scrollbar_fade_in_) { |
| delayed_scrollbar_animation_.Cancel(); |
| need_trigger_scrollbar_fade_in_ = false; |
| } |
| return; |
| } |
| |
| vertical_controller_->DidMouseDown(); |
| horizontal_controller_->DidMouseDown(); |
| } |
| |
| void ScrollbarAnimationController::DidMouseUp() { |
| if (!need_thinning_animation_) |
| return; |
| |
| is_mouse_down_ = false; |
| |
| if (!Captured()) { |
| if (MouseIsNearAnyScrollbar() && ScrollbarsHidden()) { |
| PostDelayedAnimation(FADE_IN); |
| need_trigger_scrollbar_fade_in_ = true; |
| } |
| return; |
| } |
| |
| vertical_controller_->DidMouseUp(); |
| horizontal_controller_->DidMouseUp(); |
| |
| if (!MouseIsNearAnyScrollbar() && !ScrollbarsHidden() && !tickmarks_showing_) |
| PostDelayedAnimation(FADE_OUT); |
| } |
| |
| void ScrollbarAnimationController::DidMouseLeave() { |
| if (!need_thinning_animation_) |
| return; |
| |
| vertical_controller_->DidMouseLeave(); |
| horizontal_controller_->DidMouseLeave(); |
| |
| delayed_scrollbar_animation_.Cancel(); |
| need_trigger_scrollbar_fade_in_ = false; |
| |
| if (ScrollbarsHidden() || Captured() || tickmarks_showing_) |
| return; |
| |
| PostDelayedAnimation(FADE_OUT); |
| } |
| |
| void ScrollbarAnimationController::DidMouseMove( |
| const gfx::PointF& device_viewport_point) { |
| if (!need_thinning_animation_) |
| return; |
| |
| bool need_trigger_scrollbar_fade_in_before = need_trigger_scrollbar_fade_in_; |
| |
| vertical_controller_->DidMouseMove(device_viewport_point); |
| horizontal_controller_->DidMouseMove(device_viewport_point); |
| |
| if (Captured() || tickmarks_showing_) { |
| DCHECK(!ScrollbarsHidden()); |
| return; |
| } |
| |
| if (ScrollbarsHidden()) { |
| // Do not fade in scrollbar when user interacting with the content below |
| // scrollbar. |
| if (is_mouse_down_) |
| return; |
| need_trigger_scrollbar_fade_in_ = MouseIsNearAnyScrollbar(); |
| if (need_trigger_scrollbar_fade_in_before != |
| need_trigger_scrollbar_fade_in_) { |
| if (need_trigger_scrollbar_fade_in_) { |
| PostDelayedAnimation(FADE_IN); |
| } else { |
| delayed_scrollbar_animation_.Cancel(); |
| } |
| } |
| } else { |
| if (MouseIsNearAnyScrollbar()) { |
| Show(); |
| StopAnimation(); |
| } else if (!is_animating_) { |
| PostDelayedAnimation(FADE_OUT); |
| } |
| } |
| } |
| |
| bool ScrollbarAnimationController::MouseIsOverScrollbarThumb( |
| ScrollbarOrientation orientation) const { |
| DCHECK(need_thinning_animation_); |
| return GetScrollbarAnimationController(orientation) |
| .mouse_is_over_scrollbar_thumb(); |
| } |
| |
| bool ScrollbarAnimationController::MouseIsNearScrollbarThumb( |
| ScrollbarOrientation orientation) const { |
| DCHECK(need_thinning_animation_); |
| return GetScrollbarAnimationController(orientation) |
| .mouse_is_near_scrollbar_thumb(); |
| } |
| |
| bool ScrollbarAnimationController::MouseIsNearScrollbar( |
| ScrollbarOrientation orientation) const { |
| DCHECK(need_thinning_animation_); |
| return GetScrollbarAnimationController(orientation) |
| .mouse_is_near_scrollbar_track(); |
| } |
| |
| bool ScrollbarAnimationController::MouseIsNearAnyScrollbar() const { |
| DCHECK(need_thinning_animation_); |
| return vertical_controller_->mouse_is_near_scrollbar_track() || |
| horizontal_controller_->mouse_is_near_scrollbar_track(); |
| } |
| |
| bool ScrollbarAnimationController::ScrollbarsHidden() const { |
| return opacity_ == 0.0f; |
| } |
| |
| bool ScrollbarAnimationController::Captured() const { |
| DCHECK(need_thinning_animation_); |
| return GetScrollbarAnimationController(VERTICAL).captured() || |
| GetScrollbarAnimationController(HORIZONTAL).captured(); |
| } |
| |
| void ScrollbarAnimationController::Show() { |
| delayed_scrollbar_animation_.Cancel(); |
| ApplyOpacityToScrollbars(1.0f); |
| } |
| |
| void ScrollbarAnimationController::ApplyOpacityToScrollbars(float opacity) { |
| for (ScrollbarLayerImplBase* scrollbar : Scrollbars()) { |
| DCHECK(scrollbar->is_overlay_scrollbar()); |
| float effective_opacity = scrollbar->CanScrollOrientation() ? opacity : 0; |
| scrollbar->SetOverlayScrollbarLayerOpacityAnimated(effective_opacity); |
| } |
| |
| bool previouslyVisible = opacity_ > 0.0f; |
| bool currentlyVisible = opacity > 0.0f; |
| |
| if (opacity_ != opacity) |
| client_->SetNeedsRedrawForScrollbarAnimation(); |
| |
| opacity_ = opacity; |
| |
| if (previouslyVisible != currentlyVisible) |
| client_->DidChangeScrollbarVisibility(); |
| } |
| |
| } // namespace cc |