blob: 34218498007ff814d4f96a3a44030e08a8726544 [file] [log] [blame]
// Copyright 2019 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/gfx/mac/cocoa_scrollbar_painter.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/skia_util.h"
namespace gfx {
using Params = CocoaScrollbarPainter::Params;
using Orientation = CocoaScrollbarPainter::Orientation;
namespace {
// The width of the scroller track border.
constexpr int kTrackBorderWidth = 1;
// The amount the thumb is inset from the ends and the inside edge of track
// border.
constexpr int kThumbInset = 3;
constexpr int kThumbInsetOverlay = 2;
// The minimum sizes for the thumb. We will not inset the thumb if it will
// be smaller than this size.
constexpr int kThumbMinGirth = 6;
constexpr int kThumbMinLength = 18;
// Scrollbar thumb colors.
constexpr SkColor kThumbColorDefault = SkColorSetARGB(0x3A, 0, 0, 0);
constexpr SkColor kThumbColorHover = SkColorSetARGB(0x80, 0, 0, 0);
constexpr SkColor kThumbColorDarkMode = SkColorSetRGB(0x6B, 0x6B, 0x6B);
constexpr SkColor kThumbColorDarkModeHover = SkColorSetRGB(0x93, 0x93, 0x93);
constexpr SkColor kThumbColorOverlay = SkColorSetARGB(0x80, 0, 0, 0);
constexpr SkColor kThumbColorOverlayDarkMode =
SkColorSetARGB(0x80, 0xFF, 0xFF, 0xFF);
// Non-overlay scroller track colors are not transparent. On Safari, they are,
// but on all other macOS applications they are not.
constexpr SkColor kTrackGradientColors[] = {
SkColorSetRGB(0xFA, 0xFA, 0xFA),
SkColorSetRGB(0xFA, 0xFA, 0xFA),
};
constexpr SkColor kTrackInnerBorderColor = SkColorSetRGB(0xE8, 0xE8, 0xE8);
constexpr SkColor kTrackOuterBorderColor = SkColorSetRGB(0xED, 0xED, 0xED);
// Non-overlay dark mode scroller track colors.
constexpr SkColor kTrackGradientColorsDarkMode[] = {
SkColorSetRGB(0x2D, 0x2D, 0x2D),
SkColorSetRGB(0x2B, 0x2B, 0x2B),
};
constexpr SkColor kTrackInnerBorderColorDarkMode =
SkColorSetRGB(0x3D, 0x3D, 0x3D);
constexpr SkColor kTrackOuterBorderColorDarkMode =
SkColorSetRGB(0x51, 0x51, 0x51);
// Overlay scroller track colors are transparent.
constexpr SkColor kTrackGradientColorsOverlay[] = {
SkColorSetARGB(0xC6, 0xF8, 0xF8, 0xF8),
SkColorSetARGB(0xC2, 0xF8, 0xF8, 0xF8),
SkColorSetARGB(0xC2, 0xF8, 0xF8, 0xF8),
SkColorSetARGB(0xC2, 0xF8, 0xF8, 0xF8),
};
constexpr SkColor kTrackInnerBorderColorOverlay =
SkColorSetARGB(0xF9, 0xDF, 0xDF, 0xDF);
constexpr SkColor kTrackOuterBorderColorOverlay =
SkColorSetARGB(0xC6, 0xE8, 0xE8, 0xE8);
// Dark mode overlay scroller track colors.
constexpr SkColor kTrackGradientColorsOverlayDarkMode[] = {
SkColorSetARGB(0x28, 0xD8, 0xD8, 0xD8),
SkColorSetARGB(0x26, 0xCC, 0xCC, 0xCC),
SkColorSetARGB(0x26, 0xCC, 0xCC, 0xCC),
SkColorSetARGB(0x26, 0xCC, 0xCC, 0xCC),
};
constexpr SkColor kTrackInnerBorderColorOverlayDarkMode =
SkColorSetARGB(0x33, 0xE5, 0xE5, 0xE5);
constexpr SkColor kTrackOuterBorderColorOverlayDarkMode =
SkColorSetARGB(0x28, 0xD8, 0xD8, 0xD8);
void ConstrainInsets(int old_width, int min_width, int* left, int* right) {
int requested_total_inset = *left + *right;
if (requested_total_inset == 0)
return;
int max_total_inset = old_width - min_width;
if (requested_total_inset < max_total_inset)
return;
if (max_total_inset < 0) {
*left = *right = 0;
return;
}
// Multiply the right/bottom inset by the ratio by which we need to shrink the
// total inset. This has the effect of rounding down the right/bottom inset,
// if the two sides are to be affected unevenly.
*right *= max_total_inset * 1.f / requested_total_inset;
*left = max_total_inset - *right;
}
void ConstrainedInset(gfx::Rect* rect,
int min_width,
int min_height,
int inset_left,
int inset_top,
int inset_right,
int inset_bottom) {
ConstrainInsets(rect->width(), min_width, &inset_left, &inset_right);
ConstrainInsets(rect->height(), min_height, &inset_top, &inset_bottom);
rect->Inset(inset_left, inset_top, inset_right, inset_bottom);
}
void PaintTrackGradient(gfx::Canvas* canvas,
const gfx::Rect& rect,
const Params& params,
bool is_corner) {
// Select colors.
const SkColor* gradient_colors = nullptr;
size_t gradient_stops = 0;
if (params.overlay) {
if (params.dark_mode) {
gradient_colors = kTrackGradientColorsOverlayDarkMode;
gradient_stops = base::size(kTrackGradientColorsOverlayDarkMode);
} else {
gradient_colors = kTrackGradientColorsOverlay;
gradient_stops = base::size(kTrackGradientColorsOverlay);
}
} else {
if (params.dark_mode) {
gradient_colors = kTrackGradientColorsDarkMode;
gradient_stops = base::size(kTrackGradientColorsDarkMode);
} else {
gradient_colors = kTrackGradientColors;
gradient_stops = base::size(kTrackGradientColors);
}
}
// Set the gradient direction.
const SkPoint gradient_bounds_vertical[] = {
gfx::PointToSkPoint(rect.origin()),
gfx::PointToSkPoint(rect.bottom_left()),
};
const SkPoint gradient_bounds_horizontal[] = {
gfx::PointToSkPoint(rect.origin()),
gfx::PointToSkPoint(rect.top_right()),
};
const SkPoint gradient_bounds_corner_right[] = {
gfx::PointToSkPoint(rect.origin()),
gfx::PointToSkPoint(rect.bottom_right()),
};
const SkPoint gradient_bounds_corner_left[] = {
gfx::PointToSkPoint(rect.top_right()),
gfx::PointToSkPoint(rect.bottom_left()),
};
const SkPoint* gradient_bounds = nullptr;
if (is_corner) {
if (params.orientation == Orientation::kVerticalOnRight)
gradient_bounds = gradient_bounds_corner_right;
else
gradient_bounds = gradient_bounds_corner_left;
} else {
if (params.orientation == Orientation::kHorizontal)
gradient_bounds = gradient_bounds_horizontal;
else
gradient_bounds = gradient_bounds_vertical;
}
// And draw.
cc::PaintFlags gradient;
gradient.setShader(cc::PaintShader::MakeLinearGradient(
gradient_bounds, gradient_colors, nullptr, gradient_stops,
SkTileMode::kClamp));
canvas->DrawRect(rect, gradient);
}
void PaintTrackInnerBorder(gfx::Canvas* canvas,
const gfx::Rect& rect,
const Params& params,
bool is_corner) {
// Select the color.
SkColor inner_border_color = 0;
if (params.overlay) {
if (params.dark_mode)
inner_border_color = kTrackInnerBorderColorOverlayDarkMode;
else
inner_border_color = kTrackInnerBorderColorOverlay;
} else {
if (params.dark_mode)
inner_border_color = kTrackInnerBorderColorDarkMode;
else
inner_border_color = kTrackInnerBorderColor;
}
// Compute the rect for the border.
gfx::Rect inner_border(rect);
if (params.orientation == Orientation::kVerticalOnLeft)
inner_border.set_x(rect.right() - kTrackBorderWidth);
if (is_corner || params.orientation == Orientation::kHorizontal)
inner_border.set_height(kTrackBorderWidth);
if (is_corner || params.orientation != Orientation::kHorizontal)
inner_border.set_width(kTrackBorderWidth);
// And draw.
cc::PaintFlags flags;
flags.setColor(inner_border_color);
canvas->DrawRect(inner_border, flags);
}
void PaintTrackOuterBorder(gfx::Canvas* canvas,
const gfx::Rect& rect,
const Params& params,
bool is_corner) {
// Select the color.
SkColor outer_border_color = 0;
if (params.overlay) {
if (params.dark_mode)
outer_border_color = kTrackOuterBorderColorOverlayDarkMode;
else
outer_border_color = kTrackOuterBorderColorOverlay;
} else {
if (params.dark_mode)
outer_border_color = kTrackOuterBorderColorDarkMode;
else
outer_border_color = kTrackOuterBorderColor;
}
cc::PaintFlags flags;
flags.setColor(outer_border_color);
// Draw the horizontal outer border.
if (is_corner || params.orientation == Orientation::kHorizontal) {
gfx::Rect outer_border(rect);
outer_border.set_height(kTrackBorderWidth);
outer_border.set_y(rect.bottom() - kTrackBorderWidth);
canvas->DrawRect(outer_border, flags);
}
// Draw the vertial outer border.
if (is_corner || params.orientation != Orientation::kHorizontal) {
gfx::Rect outer_border(rect);
outer_border.set_width(kTrackBorderWidth);
if (params.orientation == Orientation::kVerticalOnRight)
outer_border.set_x(rect.right() - kTrackBorderWidth);
canvas->DrawRect(outer_border, flags);
}
}
} // namespace
// static
void CocoaScrollbarPainter::PaintTrack(cc::PaintCanvas* cc_canvas,
const SkIRect& sk_track_rect,
const Params& params) {
gfx::Canvas canvas(cc_canvas, 1.f);
const gfx::Rect track_rect(SkIRectToRect(sk_track_rect));
constexpr bool is_corner = false;
PaintTrackGradient(&canvas, track_rect, params, is_corner);
PaintTrackInnerBorder(&canvas, track_rect, params, is_corner);
PaintTrackOuterBorder(&canvas, track_rect, params, is_corner);
}
// static
void CocoaScrollbarPainter::PaintCorner(cc::PaintCanvas* cc_canvas,
const SkIRect& sk_corner_rect,
const Params& params) {
// Overlay scrollbars don't have a corner.
if (params.overlay)
return;
gfx::Canvas canvas(cc_canvas, 1.f);
const gfx::Rect corner_rect(SkIRectToRect(sk_corner_rect));
constexpr bool is_corner = true;
PaintTrackGradient(&canvas, corner_rect, params, is_corner);
PaintTrackInnerBorder(&canvas, corner_rect, params, is_corner);
PaintTrackOuterBorder(&canvas, corner_rect, params, is_corner);
}
// static
void CocoaScrollbarPainter::PaintThumb(cc::PaintCanvas* cc_canvas,
const SkIRect& sk_bounds,
const Params& params) {
gfx::Canvas canvas(cc_canvas, 1.f);
// Select the color.
SkColor thumb_color = 0;
if (params.overlay) {
if (params.dark_mode)
thumb_color = kThumbColorOverlayDarkMode;
else
thumb_color = kThumbColorOverlay;
} else {
if (params.dark_mode) {
if (params.hovered)
thumb_color = kThumbColorDarkModeHover;
else
thumb_color = kThumbColorDarkMode;
} else {
if (params.hovered)
thumb_color = kThumbColorHover;
else
thumb_color = kThumbColorDefault;
}
}
// Compute the bounds for the rounded rect for the thumb from the bounds of
// the thumb.
gfx::Rect bounds(SkIRectToRect(sk_bounds));
{
// Shrink the thumb evenly in length and girth to fit within the track.
const int thumb_inset = params.overlay ? kThumbInsetOverlay : kThumbInset;
int inset_left = thumb_inset;
int inset_top = thumb_inset;
int inset_right = thumb_inset;
int inset_bottom = thumb_inset;
// Also shrink the thumb in girth to not touch the border.
if (params.orientation == Orientation::kHorizontal) {
inset_top += kTrackBorderWidth;
ConstrainedInset(&bounds, kThumbMinLength, kThumbMinGirth, inset_left,
inset_top, inset_right, inset_bottom);
} else {
inset_left += kTrackBorderWidth;
ConstrainedInset(&bounds, kThumbMinGirth, kThumbMinLength, inset_left,
inset_top, inset_right, inset_bottom);
}
}
// Draw.
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setColor(thumb_color);
const SkScalar radius = std::min(bounds.width(), bounds.height());
canvas.DrawRoundRect(bounds, radius, flags);
}
} // namespace gfx