| // 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/time/time.h" |
| #include "cc/trees/layer_tree_impl.h" |
| |
| namespace cc { |
| |
| std::unique_ptr<ScrollbarAnimationController> |
| ScrollbarAnimationController::CreateScrollbarAnimationControllerAndroid( |
| int scroll_layer_id, |
| ScrollbarAnimationControllerClient* client, |
| base::TimeDelta fade_out_delay, |
| base::TimeDelta fade_out_resize_delay, |
| base::TimeDelta fade_out_duration) { |
| return base::WrapUnique(new ScrollbarAnimationController( |
| scroll_layer_id, client, fade_out_delay, fade_out_resize_delay, |
| fade_out_duration)); |
| } |
| |
| std::unique_ptr<ScrollbarAnimationController> |
| ScrollbarAnimationController::CreateScrollbarAnimationControllerAuraOverlay( |
| int scroll_layer_id, |
| ScrollbarAnimationControllerClient* client, |
| base::TimeDelta show_delay, |
| base::TimeDelta fade_out_delay, |
| base::TimeDelta fade_out_resize_delay, |
| base::TimeDelta fade_out_duration, |
| base::TimeDelta thinning_duration) { |
| return base::WrapUnique(new ScrollbarAnimationController( |
| scroll_layer_id, client, show_delay, fade_out_delay, |
| fade_out_resize_delay, fade_out_duration, thinning_duration)); |
| } |
| |
| ScrollbarAnimationController::ScrollbarAnimationController( |
| int scroll_layer_id, |
| ScrollbarAnimationControllerClient* client, |
| base::TimeDelta fade_out_delay, |
| base::TimeDelta fade_out_resize_delay, |
| base::TimeDelta fade_out_duration) |
| : client_(client), |
| fade_out_delay_(fade_out_delay), |
| fade_out_resize_delay_(fade_out_resize_delay), |
| need_trigger_scrollbar_show_(false), |
| is_animating_(false), |
| scroll_layer_id_(scroll_layer_id), |
| currently_scrolling_(false), |
| scroll_gesture_has_scrolled_(false), |
| opacity_(0.0f), |
| fade_out_duration_(fade_out_duration), |
| need_thinning_animation_(false), |
| weak_factory_(this) { |
| ApplyOpacityToScrollbars(0.0f); |
| } |
| |
| ScrollbarAnimationController::ScrollbarAnimationController( |
| int scroll_layer_id, |
| ScrollbarAnimationControllerClient* client, |
| base::TimeDelta show_delay, |
| base::TimeDelta fade_out_delay, |
| base::TimeDelta fade_out_resize_delay, |
| base::TimeDelta fade_out_duration, |
| base::TimeDelta thinning_duration) |
| : client_(client), |
| show_delay_(show_delay), |
| fade_out_delay_(fade_out_delay), |
| fade_out_resize_delay_(fade_out_resize_delay), |
| need_trigger_scrollbar_show_(false), |
| is_animating_(false), |
| scroll_layer_id_(scroll_layer_id), |
| currently_scrolling_(false), |
| scroll_gesture_has_scrolled_(false), |
| opacity_(0.0f), |
| fade_out_duration_(fade_out_duration), |
| need_thinning_animation_(true), |
| weak_factory_(this) { |
| vertical_controller_ = SingleScrollbarAnimationControllerThinning::Create( |
| scroll_layer_id, ScrollbarOrientation::VERTICAL, client, |
| thinning_duration); |
| horizontal_controller_ = SingleScrollbarAnimationControllerThinning::Create( |
| scroll_layer_id, ScrollbarOrientation::HORIZONTAL, client, |
| thinning_duration); |
| ApplyOpacityToScrollbars(0.0f); |
| } |
| |
| ScrollbarAnimationController::~ScrollbarAnimationController() {} |
| |
| ScrollbarSet ScrollbarAnimationController::Scrollbars() const { |
| return client_->ScrollbarsFor(scroll_layer_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() { |
| delayed_scrollbar_show_.Cancel(); |
| delayed_scrollbar_fade_out_.Cancel(); |
| is_animating_ = true; |
| last_awaken_time_ = base::TimeTicks(); |
| client_->SetNeedsAnimateForScrollbarAnimation(); |
| } |
| |
| void ScrollbarAnimationController::StopAnimation() { |
| delayed_scrollbar_show_.Cancel(); |
| delayed_scrollbar_fade_out_.Cancel(); |
| is_animating_ = false; |
| } |
| |
| void ScrollbarAnimationController::PostDelayedShow() { |
| DCHECK(delayed_scrollbar_fade_out_.IsCancelled()); |
| delayed_scrollbar_show_.Reset(base::Bind(&ScrollbarAnimationController::Show, |
| weak_factory_.GetWeakPtr())); |
| client_->PostDelayedScrollbarAnimationTask(delayed_scrollbar_show_.callback(), |
| show_delay_); |
| } |
| |
| void ScrollbarAnimationController::PostDelayedFadeOut(bool on_resize) { |
| DCHECK(delayed_scrollbar_show_.IsCancelled()); |
| base::TimeDelta delay = on_resize ? fade_out_resize_delay_ : fade_out_delay_; |
| delayed_scrollbar_fade_out_.Reset( |
| base::Bind(&ScrollbarAnimationController::StartAnimation, |
| weak_factory_.GetWeakPtr())); |
| client_->PostDelayedScrollbarAnimationTask( |
| delayed_scrollbar_fade_out_.callback(), delay); |
| } |
| |
| bool ScrollbarAnimationController::Animate(base::TimeTicks now) { |
| bool animated = false; |
| |
| if (is_animating_) { |
| 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_out_duration_.InSecondsF(); |
| return std::max(std::min(progress, 1.f), 0.f); |
| } |
| |
| void ScrollbarAnimationController::DidScrollBegin() { |
| currently_scrolling_ = true; |
| } |
| |
| void ScrollbarAnimationController::RunAnimationFrame(float progress) { |
| ApplyOpacityToScrollbars(1.f - progress); |
| client_->SetNeedsRedrawForScrollbarAnimation(); |
| if (progress == 1.f) |
| StopAnimation(); |
| } |
| |
| void ScrollbarAnimationController::DidScrollUpdate(bool on_resize) { |
| if (need_thinning_animation_ && Captured()) |
| return; |
| |
| StopAnimation(); |
| |
| // 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 and mouse is |
| // near. |
| if (!need_thinning_animation_ || !MouseIsNearAnyScrollbar()) |
| PostDelayedFadeOut(on_resize); |
| } else { |
| scroll_gesture_has_scrolled_ = true; |
| } |
| |
| Show(); |
| |
| if (need_thinning_animation_) { |
| vertical_controller_->UpdateThumbThicknessScale(); |
| horizontal_controller_->UpdateThumbThicknessScale(); |
| } |
| } |
| |
| void ScrollbarAnimationController::DidScrollEnd() { |
| bool has_scrolled = scroll_gesture_has_scrolled_; |
| scroll_gesture_has_scrolled_ = 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) |
| PostDelayedFadeOut(false); |
| } |
| |
| void ScrollbarAnimationController::DidMouseDown() { |
| if (!need_thinning_animation_ || ScrollbarsHidden()) |
| return; |
| |
| vertical_controller_->DidMouseDown(); |
| horizontal_controller_->DidMouseDown(); |
| } |
| |
| void ScrollbarAnimationController::DidMouseUp() { |
| if (!need_thinning_animation_ || !Captured()) |
| return; |
| |
| vertical_controller_->DidMouseUp(); |
| horizontal_controller_->DidMouseUp(); |
| |
| if (!MouseIsNearAnyScrollbar()) |
| PostDelayedFadeOut(false); |
| } |
| |
| void ScrollbarAnimationController::DidMouseLeave() { |
| if (!need_thinning_animation_) |
| return; |
| |
| vertical_controller_->DidMouseLeave(); |
| horizontal_controller_->DidMouseLeave(); |
| |
| delayed_scrollbar_show_.Cancel(); |
| |
| if (ScrollbarsHidden() || Captured()) |
| return; |
| |
| PostDelayedFadeOut(false); |
| } |
| |
| void ScrollbarAnimationController::DidMouseMoveNear( |
| ScrollbarOrientation orientation, |
| float distance) { |
| if (!need_thinning_animation_) |
| return; |
| |
| bool need_trigger_scrollbar_show_before = need_trigger_scrollbar_show_; |
| |
| GetScrollbarAnimationController(orientation).DidMouseMoveNear(distance); |
| |
| need_trigger_scrollbar_show_ = |
| CalcNeedTriggerScrollbarShow(orientation, distance); |
| |
| if (Captured()) |
| return; |
| |
| if (ScrollbarsHidden()) { |
| if (need_trigger_scrollbar_show_before != need_trigger_scrollbar_show_) { |
| if (need_trigger_scrollbar_show_) { |
| PostDelayedShow(); |
| } else { |
| delayed_scrollbar_show_.Cancel(); |
| } |
| } |
| } else { |
| if (MouseIsNearAnyScrollbar()) { |
| Show(); |
| StopAnimation(); |
| } else if (!is_animating_) { |
| PostDelayedFadeOut(false); |
| } |
| } |
| } |
| |
| bool ScrollbarAnimationController::CalcNeedTriggerScrollbarShow( |
| ScrollbarOrientation orientation, |
| float distance) const { |
| DCHECK(need_thinning_animation_); |
| |
| if (vertical_controller_->mouse_is_over_scrollbar() || |
| horizontal_controller_->mouse_is_over_scrollbar()) |
| return true; |
| |
| for (ScrollbarLayerImplBase* scrollbar : Scrollbars()) { |
| if (scrollbar->orientation() != orientation) |
| continue; |
| |
| if (distance < |
| (kMouseMoveDistanceToTriggerShow - scrollbar->ThumbThickness())) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ScrollbarAnimationController::MouseIsOverScrollbar( |
| ScrollbarOrientation orientation) const { |
| DCHECK(need_thinning_animation_); |
| return GetScrollbarAnimationController(orientation).mouse_is_over_scrollbar(); |
| } |
| |
| bool ScrollbarAnimationController::MouseIsNearScrollbar( |
| ScrollbarOrientation orientation) const { |
| DCHECK(need_thinning_animation_); |
| return GetScrollbarAnimationController(orientation).mouse_is_near_scrollbar(); |
| } |
| |
| bool ScrollbarAnimationController::MouseIsNearAnyScrollbar() const { |
| DCHECK(need_thinning_animation_); |
| return vertical_controller_->mouse_is_near_scrollbar() || |
| horizontal_controller_->mouse_is_near_scrollbar(); |
| } |
| |
| bool ScrollbarAnimationController::ScrollbarsHidden() const { |
| return opacity_ == 0.0f; |
| } |
| |
| bool ScrollbarAnimationController::Captured() const { |
| DCHECK(need_thinning_animation_); |
| return vertical_controller_->captured() || horizontal_controller_->captured(); |
| } |
| |
| void ScrollbarAnimationController::Show() { |
| delayed_scrollbar_show_.Cancel(); |
| ApplyOpacityToScrollbars(1.0f); |
| } |
| |
| void ScrollbarAnimationController::ApplyOpacityToScrollbars(float opacity) { |
| for (ScrollbarLayerImplBase* scrollbar : Scrollbars()) { |
| if (!scrollbar->is_overlay_scrollbar()) |
| continue; |
| float effective_opacity = scrollbar->CanScrollOrientation() ? opacity : 0; |
| PropertyTrees* property_trees = |
| scrollbar->layer_tree_impl()->property_trees(); |
| // If this method is called during LayerImpl::PushPropertiesTo, we may not |
| // yet have valid layer_id_to_effect_node_index entries as property trees |
| // are pushed after layers during activation. We can skip updating opacity |
| // in that case as we are only registering a scrollbar and because opacity |
| // will be overwritten anyway when property trees are pushed. |
| if (property_trees->IsInIdToIndexMap(PropertyTrees::TreeType::EFFECT, |
| scrollbar->id())) { |
| property_trees->effect_tree.OnOpacityAnimated( |
| effective_opacity, |
| property_trees->layer_id_to_effect_node_index[scrollbar->id()], |
| scrollbar->layer_tree_impl()); |
| } |
| } |
| |
| bool previouslyVisible = opacity_ > 0.0f; |
| bool currentlyVisible = opacity > 0.0f; |
| |
| opacity_ = opacity; |
| |
| if (previouslyVisible != currentlyVisible) |
| client_->DidChangeScrollbarVisibility(); |
| } |
| |
| } // namespace cc |