blob: ec6b44d2b3457dbe5b8e644d1b5e192ba0ae7977 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/input/single_scrollbar_animation_controller_thinning.h"
#include <algorithm>
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
#include "cc/base/math_util.h"
#include "cc/input/scrollbar_animation_controller.h"
#include "cc/layers/layer_impl.h"
#include "cc/layers/scrollbar_layer_impl_base.h"
#include "cc/trees/layer_tree_impl.h"
namespace cc {
namespace {
float DistanceToRect(const gfx::PointF& device_viewport_point,
const ScrollbarLayerImplBase& scrollbar,
const gfx::Rect& rect) {
gfx::RectF device_viewport_rect = MathUtil::MapClippedRect(
scrollbar.ScreenSpaceTransform(), gfx::RectF(rect));
return device_viewport_rect.ManhattanDistanceToPoint(device_viewport_point) /
scrollbar.layer_tree_impl()->device_scale_factor();
}
float DistanceToScrollbar(const gfx::PointF& device_viewport_point,
const ScrollbarLayerImplBase& scrollbar) {
return DistanceToRect(device_viewport_point, scrollbar,
gfx::Rect(scrollbar.bounds()));
}
float DistanceToScrollbarThumb(const gfx::PointF& device_viewport_point,
const ScrollbarLayerImplBase& scrollbar) {
return DistanceToRect(device_viewport_point, scrollbar,
scrollbar.ComputeHitTestableExpandedThumbQuadRect());
}
} // namespace
std::unique_ptr<SingleScrollbarAnimationControllerThinning>
SingleScrollbarAnimationControllerThinning::Create(
ElementId scroll_element_id,
ScrollbarOrientation orientation,
ScrollbarAnimationControllerClient* client,
base::TimeDelta thinning_duration,
float idle_thickness_scale) {
return base::WrapUnique(new SingleScrollbarAnimationControllerThinning(
scroll_element_id, orientation, client, thinning_duration,
idle_thickness_scale));
}
SingleScrollbarAnimationControllerThinning::
SingleScrollbarAnimationControllerThinning(
ElementId scroll_element_id,
ScrollbarOrientation orientation,
ScrollbarAnimationControllerClient* client,
base::TimeDelta thinning_duration,
float idle_thickness_scale)
: client_(client),
scroll_element_id_(scroll_element_id),
orientation_(orientation),
thinning_duration_(thinning_duration),
idle_thickness_scale_(idle_thickness_scale) {
ApplyThumbThicknessScale(idle_thickness_scale_);
}
ScrollbarLayerImplBase*
SingleScrollbarAnimationControllerThinning::GetScrollbar() const {
for (ScrollbarLayerImplBase* scrollbar :
client_->ScrollbarsFor(scroll_element_id_)) {
DCHECK(scrollbar->is_overlay_scrollbar());
if (scrollbar->orientation() == orientation_)
return scrollbar;
}
return nullptr;
}
bool SingleScrollbarAnimationControllerThinning::Animate(base::TimeTicks now) {
if (!is_animating_)
return false;
if (last_awaken_time_.is_null())
last_awaken_time_ = now;
float progress = AnimationProgressAtTime(now);
RunAnimationFrame(progress);
return true;
}
float SingleScrollbarAnimationControllerThinning::AnimationProgressAtTime(
base::TimeTicks now) {
// In tests, there may be no duration; snap to the end in such a case.
if (thinning_duration_.is_zero())
return 1.0f;
const base::TimeDelta delta = now - last_awaken_time_;
return std::clamp(static_cast<float>(delta / thinning_duration_), 0.0f, 1.0f);
}
void SingleScrollbarAnimationControllerThinning::RunAnimationFrame(
float progress) {
if (captured_)
return;
ApplyThumbThicknessScale(ThumbThicknessScaleAt(progress));
client_->SetNeedsRedrawForScrollbarAnimation();
if (progress == 1.f) {
StopAnimation();
thickness_change_ = AnimationChange::kNone;
}
}
void SingleScrollbarAnimationControllerThinning::StartAnimation() {
is_animating_ = true;
last_awaken_time_ = base::TimeTicks();
client_->SetNeedsAnimateForScrollbarAnimation();
}
void SingleScrollbarAnimationControllerThinning::StopAnimation() {
is_animating_ = false;
}
void SingleScrollbarAnimationControllerThinning::DidScrollUpdate() {
if (captured_ || !mouse_is_near_scrollbar_) {
return;
}
CalculateThicknessShouldChange(device_viewport_last_pointer_location_);
// If scrolling with the pointer on top of the scrollbar, force the scrollbar
// to expand.
if (thickness_change_ == AnimationChange::kNone) {
UpdateThumbThicknessScale();
}
}
void SingleScrollbarAnimationControllerThinning::DidMouseDown() {
// When invisible, Fluent scrollbars are disabled and their thumb has no
// dimensions, which causes mouse_is_over_scrollbar_thumb_ to always be false.
// This check updates the thumb variable to cover the cases where you mouse
// over the invisible thumb, make it appear by some mechanism (tickmarks,
// scrolling, etc.) and press mouse down without moving your pointer.
if (client_->IsFluentOverlayScrollbar() && !mouse_is_over_scrollbar_thumb_) {
ScrollbarLayerImplBase* scrollbar = GetScrollbar();
if (scrollbar) {
const float distance_to_scrollbar_thumb = DistanceToScrollbarThumb(
device_viewport_last_pointer_location_, *scrollbar);
mouse_is_over_scrollbar_thumb_ = distance_to_scrollbar_thumb == 0.0f;
}
}
if (!mouse_is_over_scrollbar_thumb_)
return;
captured_ = true;
UpdateThumbThicknessScale();
}
void SingleScrollbarAnimationControllerThinning::DidMouseUp() {
if (!captured_)
return;
captured_ = false;
StopAnimation();
// On mouse up, Fluent scrollbars go straight to the scrollbar disappearance
// animation (via ScrollbarAnimationController) without queueing a thinning
// animation.
const bool thickness_should_decrease =
!client_->IsFluentOverlayScrollbar() && !mouse_is_near_scrollbar_thumb_;
if (thickness_should_decrease) {
thickness_change_ = AnimationChange::kDecrease;
StartAnimation();
} else {
thickness_change_ = AnimationChange::kNone;
}
}
void SingleScrollbarAnimationControllerThinning::DidMouseLeave() {
mouse_is_over_scrollbar_thumb_ = false;
mouse_is_near_scrollbar_thumb_ = false;
mouse_is_near_scrollbar_ = false;
if (captured_) {
return;
}
// If fully expanded, Fluent scrollbars don't queue a thinning animation and
// let the ScrollbarAnimationController make the scrollbars disappear.
if (client_->IsFluentOverlayScrollbar() &&
thickness_change_ == AnimationChange::kNone) {
return;
}
thickness_change_ = AnimationChange::kDecrease;
StartAnimation();
}
void SingleScrollbarAnimationControllerThinning::DidMouseMove(
const gfx::PointF& device_viewport_point) {
CalculateThicknessShouldChange(device_viewport_point);
device_viewport_last_pointer_location_ = device_viewport_point;
}
void SingleScrollbarAnimationControllerThinning::CalculateThicknessShouldChange(
const gfx::PointF& device_viewport_point) {
ScrollbarLayerImplBase* scrollbar = GetScrollbar();
if (!scrollbar)
return;
const float distance_to_scrollbar =
DistanceToScrollbar(device_viewport_point, *scrollbar);
const float distance_to_scrollbar_thumb =
DistanceToScrollbarThumb(device_viewport_point, *scrollbar);
const bool mouse_is_near_scrollbar =
distance_to_scrollbar <= MouseMoveDistanceToTriggerFadeIn();
const bool mouse_is_over_scrollbar_thumb =
distance_to_scrollbar_thumb == 0.0f;
const bool mouse_is_near_scrollbar_thumb =
distance_to_scrollbar_thumb <= MouseMoveDistanceToTriggerExpand();
bool thickness_should_change;
if (client_->IsFluentOverlayScrollbar()) {
const bool is_visible = scrollbar->OverlayScrollbarOpacity() > 0.f;
const bool moved_over_scrollbar =
mouse_is_near_scrollbar_ != mouse_is_near_scrollbar;
const bool mouse_far_from_scrollbar =
(!mouse_is_near_scrollbar &&
thickness_change_ == AnimationChange::kNone);
// On mouse move Fluent scrollbars will queue a thinning animation iff the
// scrollbar is visible and either the mouse has moved over the scrollbar
// (increase thickness) or the mouse has moved far away from the scrollbar
// and there is no previously queued animation (decreasse thickness).
// If tickmarks are shown, the scrollbars should be and should remain in
// Full mode.
thickness_should_change =
!tickmarks_showing_ && is_visible &&
(moved_over_scrollbar || mouse_far_from_scrollbar);
} else {
thickness_should_change =
(mouse_is_near_scrollbar_thumb_ != mouse_is_near_scrollbar_thumb);
}
if (!captured_ && thickness_should_change) {
const bool thickness_should_increase = client_->IsFluentOverlayScrollbar()
? mouse_is_near_scrollbar
: mouse_is_near_scrollbar_thumb;
thickness_change_ = thickness_should_increase ? AnimationChange::kIncrease
: AnimationChange::kDecrease;
StartAnimation();
}
mouse_is_near_scrollbar_ = mouse_is_near_scrollbar;
mouse_is_near_scrollbar_thumb_ = mouse_is_near_scrollbar_thumb;
mouse_is_over_scrollbar_thumb_ = mouse_is_over_scrollbar_thumb;
}
float SingleScrollbarAnimationControllerThinning::ThumbThicknessScaleAt(
float progress) const {
CHECK_NE(thickness_change_, AnimationChange::kNone);
float factor = thickness_change_ == AnimationChange::kIncrease
? progress
: (1.f - progress);
return ((1.f - idle_thickness_scale_) * factor) + idle_thickness_scale_;
}
float SingleScrollbarAnimationControllerThinning::AdjustScale(
float new_value,
float current_value,
AnimationChange animation_change,
float min_value,
float max_value) {
float result;
if (animation_change == AnimationChange::kIncrease &&
current_value > new_value) {
result = current_value;
} else if (animation_change == AnimationChange::kDecrease &&
current_value < new_value) {
result = current_value;
} else {
result = new_value;
}
if (result > max_value)
return max_value;
if (result < min_value)
return min_value;
return result;
}
float SingleScrollbarAnimationControllerThinning::
CurrentForcedThumbThicknessScale() const {
bool thumb_should_be_expanded;
if (client_->IsFluentOverlayScrollbar()) {
thumb_should_be_expanded = mouse_is_near_scrollbar_ || tickmarks_showing_;
} else {
thumb_should_be_expanded = mouse_is_near_scrollbar_thumb_;
}
thumb_should_be_expanded |= captured_;
return thumb_should_be_expanded ? 1.f : idle_thickness_scale_;
}
void SingleScrollbarAnimationControllerThinning::UpdateThumbThicknessScale() {
StopAnimation();
ApplyThumbThicknessScale(CurrentForcedThumbThicknessScale());
}
void SingleScrollbarAnimationControllerThinning::DidRequestShow() {
if (thickness_change_ == AnimationChange::kNone) {
UpdateThumbThicknessScale();
}
}
void SingleScrollbarAnimationControllerThinning::ApplyThumbThicknessScale(
float thumb_thickness_scale) {
for (auto* scrollbar : client_->ScrollbarsFor(scroll_element_id_)) {
if (scrollbar->orientation() != orientation_)
continue;
DCHECK(scrollbar->is_overlay_scrollbar());
float scale = AdjustScale(thumb_thickness_scale,
scrollbar->thumb_thickness_scale_factor(),
thickness_change_, idle_thickness_scale_, 1);
scrollbar->SetThumbThicknessScaleFactor(scale);
}
}
void SingleScrollbarAnimationControllerThinning::UpdateTickmarksVisibility(
bool show) {
tickmarks_showing_ = show;
if (show) {
UpdateThumbThicknessScale();
}
}
float SingleScrollbarAnimationControllerThinning::
MouseMoveDistanceToTriggerExpand() {
return client_->IsFluentOverlayScrollbar() ? 0.0f : 25.0f;
}
float SingleScrollbarAnimationControllerThinning::
MouseMoveDistanceToTriggerFadeIn() {
return client_->IsFluentOverlayScrollbar() ? 0.0f : 30.0f;
}
} // namespace cc