blob: 7351ce298e9af1aa4ff2e61063248165a08b2e75 [file] [log] [blame]
// Copyright 2021 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 "ash/controls/rounded_scroll_bar.h"
#include <limits>
#include "ash/public/cpp/style/color_provider.h"
#include "base/bind.h"
#include "base/numerics/ranges.h"
#include "base/time/time.h"
#include "cc/paint/paint_flags.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/canvas.h"
#include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
namespace ash {
namespace {
// Thickness of scroll bar thumb.
constexpr int kScrollThumbThicknessDp = 8;
// Radius of the scroll bar thumb.
constexpr int kScrollThumbRadiusDp = 4;
// How long for the scrollbar to hide after no scroll events have been received?
constexpr base::TimeDelta kScrollThumbHideTimeout = base::Milliseconds(500);
// How long for the scrollbar to fade away?
constexpr base::TimeDelta kScrollThumbFadeDuration = base::Milliseconds(240);
// Opacity values from go/semantic-color-system for "Scrollbar".
constexpr float kDefaultOpacity = 0.38f;
constexpr float kActiveOpacity = 1.0f;
} // namespace
// A scroll bar "thumb" that paints itself with rounded ends.
class RoundedScrollBar::Thumb : public views::BaseScrollBarThumb {
public:
explicit Thumb(RoundedScrollBar* scroll_bar)
: BaseScrollBarThumb(scroll_bar), scroll_bar_(scroll_bar) {}
Thumb(const Thumb&) = delete;
Thumb& operator=(const Thumb&) = delete;
~Thumb() override = default;
// views::BaseScrollBarThumb:
gfx::Size CalculatePreferredSize() const override {
return gfx::Size(kScrollThumbThicknessDp, kScrollThumbThicknessDp);
}
void OnPaint(gfx::Canvas* canvas) override {
cc::PaintFlags fill_flags;
fill_flags.setStyle(cc::PaintFlags::kFill_Style);
fill_flags.setAntiAlias(true);
// May be null in tests.
if (auto* color_provider = ColorProvider::Get()) {
fill_flags.setColor(color_provider->GetContentLayerColor(
ColorProvider::ContentLayerType::kScrollBarColor));
}
canvas->DrawRoundRect(GetLocalBounds(), kScrollThumbRadiusDp, fill_flags);
}
void OnStateChanged() override { scroll_bar_->OnThumbStateChanged(); }
private:
RoundedScrollBar* const scroll_bar_;
};
RoundedScrollBar::RoundedScrollBar(bool horizontal)
: ScrollBar(horizontal),
hide_scrollbar_timer_(
FROM_HERE,
kScrollThumbHideTimeout,
base::BindRepeating(&RoundedScrollBar::HideScrollBar,
base::Unretained(this))) {
// Moving the mouse directly into the thumb will also notify this view.
SetNotifyEnterExitOnChild(true);
auto* thumb = new Thumb(this); // Owned by views hierarchy.
SetThumb(thumb);
thumb->SetPaintToLayer();
thumb->layer()->SetFillsBoundsOpaquely(false);
// The thumb is hidden by default.
thumb->layer()->SetOpacity(0.f);
}
RoundedScrollBar::~RoundedScrollBar() = default;
void RoundedScrollBar::SetInsets(const gfx::Insets& insets) {
insets_ = insets;
}
gfx::Rect RoundedScrollBar::GetTrackBounds() const {
gfx::Rect bounds = GetLocalBounds();
bounds.Inset(insets_);
return bounds;
}
bool RoundedScrollBar::OverlapsContent() const {
return true;
}
int RoundedScrollBar::GetThickness() const {
// Extend the thickness by the insets on the sides of the bar.
const int sides = IsHorizontal() ? insets_.top() + insets_.bottom()
: insets_.left() + insets_.right();
return kScrollThumbThicknessDp + sides;
}
void RoundedScrollBar::OnMouseEntered(const ui::MouseEvent& event) {
ShowScrollbar();
}
void RoundedScrollBar::OnMouseExited(const ui::MouseEvent& event) {
if (!hide_scrollbar_timer_.IsRunning())
hide_scrollbar_timer_.Reset();
}
void RoundedScrollBar::ScrollToPosition(int position) {
ShowScrollbar();
views::ScrollBar::ScrollToPosition(position);
}
void RoundedScrollBar::ObserveScrollEvent(const ui::ScrollEvent& event) {
// Scroll fling events are generated by moving a single finger over the
// trackpad; do not show the scrollbar for these events.
if (event.type() == ui::ET_SCROLL_FLING_CANCEL)
return;
ShowScrollbar();
}
views::BaseScrollBarThumb* RoundedScrollBar::GetThumbForTest() const {
return GetThumb();
}
void RoundedScrollBar::ShowScrollbar() {
if (!IsMouseHovered())
hide_scrollbar_timer_.Reset();
auto* thumb = GetThumb();
const float target_opacity =
thumb->IsMouseHovered() ? kActiveOpacity : kDefaultOpacity;
if (base::IsApproximatelyEqual(thumb->layer()->GetTargetOpacity(),
target_opacity,
std::numeric_limits<float>::epsilon())) {
return;
}
ui::ScopedLayerAnimationSettings animation(thumb->layer()->GetAnimator());
animation.SetTransitionDuration(kScrollThumbFadeDuration);
thumb->layer()->SetOpacity(target_opacity);
}
void RoundedScrollBar::HideScrollBar() {
// Never hide the scrollbar if the mouse is over it. The auto-hide timer
// will be reset when the mouse leaves the scrollable area.
if (IsMouseHovered())
return;
hide_scrollbar_timer_.Stop();
ui::ScopedLayerAnimationSettings animation(
GetThumb()->layer()->GetAnimator());
animation.SetTransitionDuration(kScrollThumbFadeDuration);
GetThumb()->layer()->SetOpacity(0.f);
}
void RoundedScrollBar::OnThumbStateChanged() {
// If the mouse is still in the scroll bar, the thumb hover state may have
// changed, so recompute opacity.
if (IsMouseHovered())
ShowScrollbar();
}
BEGIN_METADATA(RoundedScrollBar, ScrollBar)
END_METADATA
} // namespace ash