blob: 7fcf736c925e3a4ffc9d4c324b773ab4c3189e86 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/native_theme/native_theme_aura.h"
#include <optional>
#include "base/check.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "cc/paint/paint_canvas.h"
#include "cc/paint/paint_flags.h"
#include "third_party/skia/include/core/SkBlendMode.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/insets_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/rrect_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_base.h"
#include "ui/native_theme/overlay_scrollbar_constants.h"
namespace ui {
namespace {
// The border is 2 pixels despite the stroke width being 1 so that the inner
// pixel can match the center tile color. This prevents color interpolation
// between the patches.
constexpr gfx::Insets kOverlayScrollbarBorderInsets(2);
// Killswitch for the changed behavior (only drawing rounded corner for form
// controls). Should remove after M120 ships.
BASE_FEATURE(kNewScrollbarArrowRadius, base::FEATURE_ENABLED_BY_DEFAULT);
} // namespace
gfx::Insets NativeThemeAura::GetScrollbarSolidColorThumbInsets(
Part part) const {
if (use_overlay_scrollbar()) {
return {};
}
static constexpr int kThumbPadding = 2;
auto insets = gfx::Insets::VH(
// If there are no buttons then provide some padding so that the thumb
// doesn't touch the end of the track.
GetVerticalScrollbarButtonSize().IsEmpty() ? kThumbPadding : 0,
kThumbPadding);
if (part == kScrollbarHorizontalTrack) {
insets.Transpose();
}
return insets;
}
bool NativeThemeAura::SupportsNinePatch(Part part) const {
return use_overlay_scrollbar() &&
(part == kScrollbarHorizontalThumb || part == kScrollbarVerticalThumb);
}
gfx::Size NativeThemeAura::GetNinePatchCanvasSize(Part part) const {
CHECK(SupportsNinePatch(part));
static constexpr int kCenterPatchSize = 1;
return gfx::Size(kOverlayScrollbarBorderInsets.width() + kCenterPatchSize,
kOverlayScrollbarBorderInsets.height() + kCenterPatchSize);
}
gfx::Rect NativeThemeAura::GetNinePatchAperture(Part part) const {
CHECK(SupportsNinePatch(part));
gfx::Rect aperture(GetNinePatchCanvasSize(part));
aperture.Inset(kOverlayScrollbarBorderInsets);
return aperture;
}
NativeThemeAura::NativeThemeAura(bool use_overlay_scrollbar) {
set_use_overlay_scrollbar(use_overlay_scrollbar);
}
NativeThemeAura::NativeThemeAura(SystemTheme system_theme)
: NativeThemeBase(system_theme) {}
NativeThemeAura::~NativeThemeAura() = default;
gfx::Size NativeThemeAura::GetVerticalScrollbarButtonSize() const {
gfx::Size size = NativeThemeBase::GetVerticalScrollbarButtonSize();
if (use_overlay_scrollbar()) {
// NOTE: The overlay scrollbar thumb omits the stroke on the trailing
// "thickness" edge, so only including its width a single time here is
// intentional.
size.set_width(kOverlayScrollbarThumbWidthPressed +
kOverlayScrollbarStrokeWidth);
}
#if BUILDFLAG(IS_CHROMEOS)
// CrOS does not draw scrollbar buttons. Be careful to leave the width valid,
// however, as that value is also used for the track width.
size.set_height(0);
#endif
return size;
}
gfx::Size NativeThemeAura::GetVerticalScrollbarThumbSize() const {
if (use_overlay_scrollbar()) {
return gfx::Size(GetVerticalScrollbarButtonSize().width(),
32 + 2 * kOverlayScrollbarStrokeWidth);
}
return NativeThemeBase::GetVerticalScrollbarThumbSize();
}
std::optional<ColorId> NativeThemeAura::GetScrollbarThumbColorId(
State state,
const ScrollbarThumbExtraParams& extra_params) const {
if (!use_overlay_scrollbar()) {
return std::nullopt;
}
return (state == kHovered || state == kPressed)
? kColorOverlayScrollbarFillHovered
: kColorOverlayScrollbarFill;
}
float NativeThemeAura::GetScrollbarPartContrastRatioForState(
State state) const {
return state == kPressed ? 1.3f : 1.8f;
}
void NativeThemeAura::PaintMenuPopupBackground(
cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
const gfx::Size& size,
const MenuBackgroundExtraParams& extra_params) const {
CHECK(color_provider);
// TODO(crbug.com/40219248): Use `SkColor4f` everywhere.
const auto color =
SkColor4f::FromColor(color_provider->GetColor(kColorMenuBackground));
if (extra_params.corner_radius > 0) {
const SkScalar r = SkIntToScalar(extra_params.corner_radius);
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setColor(color);
canvas->drawRoundRect(gfx::RectToSkRect(gfx::Rect(size)), r, r, flags);
} else {
canvas->drawColor(color, SkBlendMode::kSrc);
}
}
void NativeThemeAura::PaintArrowButton(
cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
const gfx::Rect& rect,
Part part,
State state,
bool forced_colors,
bool dark_mode,
PreferredContrast contrast,
const ScrollbarArrowExtraParams& extra_params) const {
const SkColor bg_color = GetScrollbarArrowBackgroundColor(
extra_params, state, dark_mode, contrast, color_provider);
cc::PaintFlags bg_flags;
bg_flags.setColor(bg_color);
if (base::FeatureList::IsEnabled(kNewScrollbarArrowRadius) &&
!extra_params.needs_rounded_corner) {
canvas->drawRect(gfx::RectToSkRect(rect), bg_flags);
} else {
// This radius lets scrollbar arrows fit in the default rounded border of
// some form controls.
// TODO(crbug.com/40285711): We should probably let blink pass the actual
// border radii.
static constexpr SkScalar kUnscaledRadius = 1;
const SkScalar radius =
kUnscaledRadius * (extra_params.zoom ? extra_params.zoom : 1.0);
SkScalar ul = 0, ll = 0, ur = 0, lr = 0;
if (part == kScrollbarDownArrow) {
(extra_params.right_to_left ? ll : lr) = radius;
} else if (part == kScrollbarLeftArrow) {
ll = radius;
} else if (part == kScrollbarRightArrow) {
lr = radius;
} else if (part == kScrollbarUpArrow) {
(extra_params.right_to_left ? ul : ur) = radius;
}
const gfx::RRectF rrect(gfx::RectF(rect), ul, ul, ur, ur, lr, lr, ll, ll);
bg_flags.setAntiAlias(true);
canvas->drawRRect(static_cast<SkRRect>(rrect), bg_flags);
}
PaintArrow(
canvas, rect, part, state,
GetScrollbarArrowForegroundColor(bg_color, extra_params, state, dark_mode,
contrast, color_provider));
}
void NativeThemeAura::PaintScrollbarThumb(
cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
Part part,
State state,
const gfx::Rect& rect,
const ScrollbarThumbExtraParams& extra_params) const {
if (state == kDisabled) {
return;
}
TRACE_EVENT0("blink", "NativeThemeAura::PaintScrollbarThumb");
gfx::Rect fill_rect(rect);
fill_rect.Inset(GetScrollbarSolidColorThumbInsets(part));
if (use_overlay_scrollbar()) {
// Paint a stroke.
gfx::RectF stroke_rect(fill_rect);
// The edge to which the scrollbar is attached shouldn't have a border.
gfx::Insets edge_adjust_insets;
if (part == NativeTheme::kScrollbarHorizontalThumb) {
edge_adjust_insets.set_bottom(-kOverlayScrollbarStrokeWidth);
} else {
edge_adjust_insets.set_right(-kOverlayScrollbarStrokeWidth);
}
stroke_rect.Inset(gfx::InsetsF(kOverlayScrollbarStrokeWidth / 2.0f) +
static_cast<gfx::InsetsF>(edge_adjust_insets));
cc::PaintFlags stroke_flags;
CHECK(color_provider);
stroke_flags.setColor(color_provider->GetColor(
state == kNormal ? kColorOverlayScrollbarStroke
: kColorOverlayScrollbarStrokeHovered));
stroke_flags.setStyle(cc::PaintFlags::kStroke_Style);
stroke_flags.setStrokeWidth(kOverlayScrollbarStrokeWidth);
canvas->drawRect(gfx::RectFToSkRect(stroke_rect), stroke_flags);
// Inset all the edges so we don't fill in the stroke below. For a left
// vertical scrollbar, we will horizontally flip the canvas in
// `ScrollbarThemeOverlay::PaintThumb()`.
fill_rect.Inset(gfx::Insets(kOverlayScrollbarStrokeWidth) +
edge_adjust_insets);
}
cc::PaintFlags fill_flags;
fill_flags.setColor(
GetScrollbarThumbColor(color_provider, state, extra_params));
canvas->drawIRect(gfx::RectToSkIRect(fill_rect), fill_flags);
}
void NativeThemeAura::PaintScrollbarTrack(
cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
Part part,
State state,
const ScrollbarTrackExtraParams& extra_params,
const gfx::Rect& rect,
bool forced_colors,
PreferredContrast contrast) const {
CHECK(!use_overlay_scrollbar());
cc::PaintFlags flags;
flags.setColor(extra_params.track_color.value_or(
GetControlColor(kScrollbarTrack, {}, {}, color_provider)));
canvas->drawIRect(gfx::RectToSkIRect(rect), flags);
}
void NativeThemeAura::PaintScrollbarCorner(
cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
State state,
const gfx::Rect& rect,
const ScrollbarTrackExtraParams& extra_params) const {
CHECK(!use_overlay_scrollbar());
cc::PaintFlags flags;
flags.setColor(extra_params.track_color.value_or(
GetControlColor(kScrollbarCornerControlColorId, {}, {}, color_provider)));
canvas->drawIRect(RectToSkIRect(rect), flags);
}
} // namespace ui