| // Copyright 2013 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 "ui/views/controls/scrollbar/overlay_scroll_bar.h" |
| |
| #include "base/macros.h" |
| #include "cc/paint/paint_flags.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/native_theme/overlay_scrollbar_constants_aura.h" |
| #include "ui/views/background.h" |
| #include "ui/views/border.h" |
| |
| namespace views { |
| namespace { |
| |
| // Total thickness of the thumb (matches visuals when hovered). |
| constexpr int kThumbThickness = |
| ui::kOverlayScrollbarThumbWidthPressed + ui::kOverlayScrollbarStrokeWidth; |
| // When hovered, the thumb takes up the full width. Otherwise, it's a bit |
| // slimmer. |
| constexpr int kThumbHoverOffset = 4; |
| // The layout size of the thumb stroke, in DIP. |
| constexpr int kThumbStroke = ui::kOverlayScrollbarStrokeWidth; |
| // The visual size of the thumb stroke, in px. |
| constexpr int kThumbStrokeVisualSize = ui::kOverlayScrollbarStrokeWidth; |
| |
| } // namespace |
| |
| OverlayScrollBar::Thumb::Thumb(OverlayScrollBar* scroll_bar) |
| : BaseScrollBarThumb(scroll_bar), scroll_bar_(scroll_bar) { |
| // |scroll_bar| isn't done being constructed; it's not safe to do anything |
| // that might reference it yet. |
| } |
| |
| OverlayScrollBar::Thumb::~Thumb() {} |
| |
| void OverlayScrollBar::Thumb::Init() { |
| EnableCanvasFlippingForRTLUI(true); |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| // Animate all changes to the layer except the first one. |
| OnStateChanged(); |
| layer()->SetAnimator(ui::LayerAnimator::CreateImplicitAnimator()); |
| } |
| |
| gfx::Size OverlayScrollBar::Thumb::CalculatePreferredSize() const { |
| // The visual size of the thumb is kThumbThickness, but it slides back and |
| // forth by kThumbHoverOffset. To make event targetting work well, expand the |
| // width of the thumb such that it's always taking up the full width of the |
| // track regardless of the offset. |
| return gfx::Size(kThumbThickness + kThumbHoverOffset, |
| kThumbThickness + kThumbHoverOffset); |
| } |
| |
| void OverlayScrollBar::Thumb::OnPaint(gfx::Canvas* canvas) { |
| cc::PaintFlags fill_flags; |
| fill_flags.setStyle(cc::PaintFlags::kFill_Style); |
| fill_flags.setColor(SK_ColorBLACK); |
| gfx::RectF fill_bounds(GetLocalBounds()); |
| fill_bounds.Inset(gfx::InsetsF(IsHorizontal() ? kThumbHoverOffset : 0, |
| IsHorizontal() ? 0 : kThumbHoverOffset, 0, 0)); |
| fill_bounds.Inset(gfx::InsetsF(kThumbStroke, kThumbStroke, |
| IsHorizontal() ? 0 : kThumbStroke, |
| IsHorizontal() ? kThumbStroke : 0)); |
| canvas->DrawRect(fill_bounds, fill_flags); |
| |
| cc::PaintFlags stroke_flags; |
| stroke_flags.setStyle(cc::PaintFlags::kStroke_Style); |
| stroke_flags.setColor( |
| SkColorSetA(SK_ColorWHITE, (ui::kOverlayScrollbarStrokeNormalAlpha / |
| ui::kOverlayScrollbarThumbNormalAlpha) * |
| SK_AlphaOPAQUE)); |
| stroke_flags.setStrokeWidth(kThumbStrokeVisualSize); |
| stroke_flags.setStrokeCap(cc::PaintFlags::kSquare_Cap); |
| |
| // The stroke is a single pixel, so we must deal with the unscaled canvas. |
| const float dsf = canvas->UndoDeviceScaleFactor(); |
| gfx::RectF stroke_bounds(fill_bounds); |
| stroke_bounds.Scale(dsf); |
| // The stroke should be aligned to the pixel center that is nearest the fill, |
| // so outset by a half pixel. |
| stroke_bounds.Inset(gfx::InsetsF(-kThumbStrokeVisualSize / 2.0f)); |
| // The stroke doesn't apply to the far edge of the thumb. |
| SkPath path; |
| path.moveTo(gfx::PointFToSkPoint(stroke_bounds.top_right())); |
| path.lineTo(gfx::PointFToSkPoint(stroke_bounds.origin())); |
| path.lineTo(gfx::PointFToSkPoint(stroke_bounds.bottom_left())); |
| if (IsHorizontal()) { |
| path.moveTo(gfx::PointFToSkPoint(stroke_bounds.bottom_right())); |
| path.close(); |
| } else { |
| path.lineTo(gfx::PointFToSkPoint(stroke_bounds.bottom_right())); |
| } |
| canvas->DrawPath(path, stroke_flags); |
| } |
| |
| void OverlayScrollBar::Thumb::OnBoundsChanged( |
| const gfx::Rect& previous_bounds) { |
| scroll_bar_->Show(); |
| // Don't start the hide countdown if the thumb is still hovered or pressed. |
| if (GetState() == Button::STATE_NORMAL) |
| scroll_bar_->StartHideCountdown(); |
| } |
| |
| void OverlayScrollBar::Thumb::OnStateChanged() { |
| if (GetState() == Button::STATE_NORMAL) { |
| gfx::Transform translation; |
| const int direction = base::i18n::IsRTL() ? -1 : 1; |
| translation.Translate( |
| gfx::Vector2d(IsHorizontal() ? 0 : direction * kThumbHoverOffset, |
| IsHorizontal() ? kThumbHoverOffset : 0)); |
| layer()->SetTransform(translation); |
| layer()->SetOpacity(ui::kOverlayScrollbarThumbNormalAlpha); |
| |
| if (GetWidget()) |
| scroll_bar_->StartHideCountdown(); |
| } else { |
| layer()->SetTransform(gfx::Transform()); |
| layer()->SetOpacity(ui::kOverlayScrollbarThumbHoverAlpha); |
| } |
| } |
| |
| OverlayScrollBar::OverlayScrollBar(bool horizontal) |
| : BaseScrollBar(horizontal), hide_timer_(false, false) { |
| auto* thumb = new Thumb(this); |
| SetThumb(thumb); |
| thumb->Init(); |
| set_notify_enter_exit_on_child(true); |
| SetPaintToLayer(); |
| layer()->SetMasksToBounds(true); |
| layer()->SetFillsBoundsOpaquely(false); |
| } |
| |
| OverlayScrollBar::~OverlayScrollBar() {} |
| |
| gfx::Rect OverlayScrollBar::GetTrackBounds() const { |
| gfx::Rect local = GetLocalBounds(); |
| // The track has to be wide enough for the thumb. |
| local.Inset(gfx::Insets(IsHorizontal() ? -kThumbHoverOffset : 0, |
| IsHorizontal() ? 0 : -kThumbHoverOffset, 0, 0)); |
| return local; |
| } |
| |
| int OverlayScrollBar::GetThickness() const { |
| return kThumbThickness; |
| } |
| |
| bool OverlayScrollBar::OverlapsContent() const { |
| return true; |
| } |
| |
| void OverlayScrollBar::Layout() { |
| gfx::Rect thumb_bounds = GetTrackBounds(); |
| BaseScrollBarThumb* thumb = GetThumb(); |
| if (IsHorizontal()) { |
| thumb_bounds.set_x(thumb->x()); |
| thumb_bounds.set_width(thumb->width()); |
| } else { |
| thumb_bounds.set_y(thumb->y()); |
| thumb_bounds.set_height(thumb->height()); |
| } |
| thumb->SetBoundsRect(thumb_bounds); |
| } |
| |
| void OverlayScrollBar::OnMouseEntered(const ui::MouseEvent& event) { |
| Show(); |
| } |
| |
| void OverlayScrollBar::OnMouseExited(const ui::MouseEvent& event) { |
| StartHideCountdown(); |
| } |
| |
| void OverlayScrollBar::Show() { |
| layer()->SetOpacity(1.0f); |
| hide_timer_.Stop(); |
| } |
| |
| void OverlayScrollBar::Hide() { |
| ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator()); |
| settings.SetTransitionDuration(ui::kOverlayScrollbarFadeDuration); |
| layer()->SetOpacity(0.0f); |
| } |
| |
| void OverlayScrollBar::StartHideCountdown() { |
| if (IsMouseHovered()) |
| return; |
| hide_timer_.Start( |
| FROM_HERE, ui::kOverlayScrollbarFadeDelay, |
| base::Bind(&OverlayScrollBar::Hide, base::Unretained(this))); |
| } |
| |
| } // namespace views |