#include "ui/native_theme/native_theme_aura.h"
#include <limits>
#include <utility>
#include "base/check_op.h"
#include "base/containers/fixed_flat_map.h"
#include "base/no_destructor.h"
#include "base/notreached.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/SkPath.h"
#include "ui/base/layout.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rrect_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/native_theme/common_theme.h"
#include "ui/native_theme/native_theme_features.h"
#include "ui/native_theme/overlay_scrollbar_constants_aura.h"
#if BUILDFLAG(IS_WIN)
#include "ui/native_theme/native_theme_fluent.h"
#endif
#if defined(OHOS_SCROLLBAR)
#include "base/ohos/sys_info_utils.h"
#endif
namespace ui {
namespace {
#ifdef OHOS_SCROLLBAR
constexpr int kOverlayScrollbarMinimumLength = 48;
constexpr int kOverlayScrollbarBorderPatchWidth = 0;
constexpr int kOverlayScrollbarHotSize = 0;
constexpr int kOverlayScrollbarHotSizePc = 0;
constexpr float kOverlayScrollbarCornerRatio = 1.2f;
int scrollbar_hot_size_ = kOverlayScrollbarHotSize;
constexpr int kForceScrollbarActiveWidth = 12;
constexpr int kForceScrollbarInactiveWidth = 12;
constexpr int kForceScrollbarActiveOffset = 0;
constexpr int kForceScrollbarInactiveOffset = 0;
constexpr int kForceScrollbarActiveRadius = 6;
constexpr int kForceScrollbarInactiveRadius = 3;
constexpr int kForceScrollbarActiveHotSize = 4;
constexpr int kForceScrollbarInactiveHotSize = 8;
#else
constexpr int kOverlayScrollbarMinimumLength = 32;
constexpr int kOverlayScrollbarBorderPatchWidth = 2;
constexpr int kOverlayScrollbarCenterPatchSize = 1;
#endif
#ifdef OHOS_SCROLLBAR
constexpr int kOverlayScrollbarDoubleOrHalf = 2;
#endif
const SkScalar kScrollRadius =
1;
}
#if !BUILDFLAG(IS_APPLE)
NativeTheme* NativeTheme::GetInstanceForWeb() {
#if BUILDFLAG(IS_WIN)
if (IsFluentScrollbarEnabled())
return NativeThemeFluent::web_instance();
#endif
return NativeThemeAura::web_instance();
}
#if !BUILDFLAG(IS_WIN)
NativeTheme* NativeTheme::GetInstanceForNativeUi() {
static base::NoDestructor<NativeThemeAura> s_native_theme(false, false);
return s_native_theme.get();
}
NativeTheme* NativeTheme::GetInstanceForDarkUI() {
static base::NoDestructor<NativeThemeAura> s_native_theme(false, true);
return s_native_theme.get();
}
#endif
#endif
NativeThemeAura::NativeThemeAura(bool use_overlay_scrollbars,
bool should_only_use_dark_colors,
ui::SystemTheme system_theme)
: NativeThemeBase(should_only_use_dark_colors, system_theme),
use_overlay_scrollbars_(use_overlay_scrollbars) {
#if BUILDFLAG(IS_CHROMEOS)
set_scrollbar_button_length(0);
#endif
if (use_overlay_scrollbars_) {
#ifdef OHOS_SCROLLBAR
if (base::ohos::IsPcDevice()) {
scrollbar_width_ = kOverlayScrollbarThumbWidthPressedPc;
scrollbar_hot_size_ = kOverlayScrollbarHotSizePc;
} else {
scrollbar_width_ = kOverlayScrollbarThumbWidthPressed;
}
#else
scrollbar_width_ =
kOverlayScrollbarThumbWidthPressed + kOverlayScrollbarStrokeWidth;
#endif
}
static_assert(kDisabled == 0, "states unexpectedly changed");
static_assert(kHovered == 1, "states unexpectedly changed");
static_assert(kNormal == 2, "states unexpectedly changed");
static_assert(kPressed == 3, "states unexpectedly changed");
}
NativeThemeAura::~NativeThemeAura() {}
NativeThemeAura* NativeThemeAura::web_instance() {
static base::NoDestructor<NativeThemeAura> s_native_theme_for_web(
IsOverlayScrollbarEnabled(), false);
return s_native_theme_for_web.get();
}
SkColor4f NativeThemeAura::FocusRingColorForBaseColor(
SkColor4f base_color) const {
#if BUILDFLAG(IS_APPLE)
return SkColor4f(base_color.fR, base_color.fG, base_color.fB, 166 / 255.0f);
#else
return base_color;
#endif
}
void NativeThemeAura::PaintMenuPopupBackground(
cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
const gfx::Size& size,
const MenuBackgroundExtraParams& menu_background,
ColorScheme color_scheme) const {
DCHECK(color_provider);
SkColor4f color =
SkColor4f::FromColor(color_provider->GetColor(kColorMenuBackground));
if (menu_background.corner_radius > 0) {
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setAntiAlias(true);
flags.setColor(color);
SkPath path;
SkRect rect = SkRect::MakeWH(SkIntToScalar(size.width()),
SkIntToScalar(size.height()));
SkScalar radius = SkIntToScalar(menu_background.corner_radius);
SkScalar radii[8] = {radius, radius, radius, radius,
radius, radius, radius, radius};
path.addRoundRect(rect, radii);
canvas->drawPath(path, flags);
} else {
canvas->drawColor(color, SkBlendMode::kSrc);
}
}
void NativeThemeAura::PaintMenuItemBackground(
cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
State state,
const gfx::Rect& rect,
const MenuItemExtraParams& menu_item,
ColorScheme color_scheme) const {
CommonThemePaintMenuItemBackground(this, color_provider, canvas, state, rect,
menu_item);
}
void NativeThemeAura::PaintArrowButton(
cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
const gfx::Rect& rect,
Part direction,
State state,
ColorScheme color_scheme,
const ScrollbarArrowExtraParams& arrow) const {
SkColor bg_color =
GetControlColor(kScrollbarArrowBackground, color_scheme, color_provider);
SkColor arrow_color = gfx::kPlaceholderColor;
switch (state) {
case kDisabled:
arrow_color = GetArrowColor(state, color_scheme, color_provider);
break;
case kHovered:
bg_color = GetControlColor(kScrollbarArrowBackgroundHovered, color_scheme,
color_provider);
arrow_color =
GetControlColor(kScrollbarArrowHovered, color_scheme, color_provider);
break;
case kNormal:
arrow_color =
GetControlColor(kScrollbarArrow, color_scheme, color_provider);
break;
case kPressed:
bg_color = GetControlColor(kScrollbarArrowBackgroundPressed, color_scheme,
color_provider);
arrow_color =
GetControlColor(kScrollbarArrowPressed, color_scheme, color_provider);
break;
case kNumStates:
break;
}
DCHECK_NE(arrow_color, gfx::kPlaceholderColor);
cc::PaintFlags flags;
flags.setColor(bg_color);
SkScalar upper_left_radius = 0;
SkScalar lower_left_radius = 0;
SkScalar upper_right_radius = 0;
SkScalar lower_right_radius = 0;
float zoom = arrow.zoom ? arrow.zoom : 1.0;
if (direction == kScrollbarUpArrow) {
if (arrow.right_to_left) {
upper_left_radius = kScrollRadius * zoom;
} else {
upper_right_radius = kScrollRadius * zoom;
}
} else if (direction == kScrollbarDownArrow) {
if (arrow.right_to_left) {
lower_left_radius = kScrollRadius * zoom;
} else {
lower_right_radius = kScrollRadius * zoom;
}
}
DrawPartiallyRoundRect(canvas, rect, upper_left_radius, upper_right_radius,
lower_right_radius, lower_left_radius, flags);
PaintArrow(canvas, rect, direction, arrow_color);
}
void NativeThemeAura::PaintScrollbarTrack(
cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
Part part,
State state,
const ScrollbarTrackExtraParams& extra_params,
const gfx::Rect& rect,
ColorScheme color_scheme) const {
DCHECK(!use_overlay_scrollbars_);
cc::PaintFlags flags;
const SkColor track_color =
GetControlColor(kScrollbarTrack, color_scheme, color_provider);
flags.setColor(track_color);
canvas->drawIRect(gfx::RectToSkIRect(rect), flags);
}
void NativeThemeAura::PaintScrollbarThumb(cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
Part part,
State state,
const gfx::Rect& rect,
ScrollbarOverlayColorTheme theme,
ColorScheme color_scheme
#ifdef OHOS_SCROLLBAR
, SkColor scrollbar_color
#endif
) const {
if (state == kDisabled)
return;
TRACE_EVENT0("blink", "NativeThemeAura::PaintScrollbarThumb");
gfx::Rect thumb_rect(rect);
SkColor thumb_color;
#ifdef OHOS_SCROLLBAR
bool isPcDevice = base::ohos::IsPcDevice();
float ratio = base::ohos::GetPixelRatio();
#endif
if (use_overlay_scrollbars_) {
if (state == NativeTheme::kDisabled)
return;
#ifdef OHOS_SCROLLBAR
thumb_color = SK_ColorTRANSPARENT;
SkColor aroundColor = SkColorSetA(scrollbar_color, 102);
cc::PaintFlags flags;
flags.setColor(aroundColor);
flags.setAntiAlias(true);
gfx::Rect aroundRRect;
int drawThumbThickness = scrollbar_width_ * ratio - scrollbar_hot_size_* ratio;
SkScalar radius = SkIntToScalar(drawThumbThickness / kOverlayScrollbarDoubleOrHalf);
SkScalar radiusX = 0.0;
if (part == kScrollbarHorizontalThumb) {
int horizontalX = isPcDevice ? thumb_rect.x() : 0;
int horizontalY = isPcDevice ? thumb_rect.y() + scrollbar_hot_size_ : scrollbar_hot_size_;
aroundRRect = gfx::Rect(horizontalX * ratio, horizontalY * ratio,
thumb_rect.width(), drawThumbThickness);
radius = SkIntToScalar(radius * kOverlayScrollbarCornerRatio);
radiusX = SkIntToScalar(drawThumbThickness / kOverlayScrollbarDoubleOrHalf);
} else {
int verticalX = isPcDevice ? thumb_rect.x() + scrollbar_hot_size_ : scrollbar_hot_size_;
int verticalY = isPcDevice ? thumb_rect.y() : 0;
aroundRRect = gfx::Rect(verticalX * ratio, verticalY * ratio,
drawThumbThickness, thumb_rect.height());
radiusX = SkIntToScalar(radius * kOverlayScrollbarCornerRatio);
}
gfx::RRectF rounded_rect(gfx::RectF(aroundRRect), radiusX, radius, radiusX,
radius, radiusX, radius, radiusX, radius);
canvas->drawRRect(static_cast<SkRRect>(rounded_rect), flags);
#else
const bool hovered = state != kNormal;
static constexpr auto kFillIdMap =
base::MakeFixedFlatMap<ScrollbarOverlayColorTheme, std::array<int, 2>>({
{ScrollbarOverlayColorTheme::kDefault,
{kColorOverlayScrollbarFill, kColorOverlayScrollbarFillHovered}},
{ScrollbarOverlayColorTheme::kLight,
{kColorOverlayScrollbarFillLight,
kColorOverlayScrollbarFillHoveredLight}},
{ScrollbarOverlayColorTheme::kDark,
{kColorOverlayScrollbarFillDark,
kColorOverlayScrollbarFillHoveredDark}},
});
static constexpr auto kStrokeIdMap =
base::MakeFixedFlatMap<ScrollbarOverlayColorTheme, std::array<int, 2>>({
{ScrollbarOverlayColorTheme::kDefault,
{kColorOverlayScrollbarStroke,
kColorOverlayScrollbarStrokeHovered}},
{ScrollbarOverlayColorTheme::kLight,
{kColorOverlayScrollbarStrokeLight,
kColorOverlayScrollbarStrokeHoveredLight}},
{ScrollbarOverlayColorTheme::kDark,
{kColorOverlayScrollbarStrokeDark,
kColorOverlayScrollbarStrokeHoveredDark}},
});
DCHECK(color_provider);
thumb_color = color_provider->GetColor(kFillIdMap.at(theme)[hovered]);
const SkColor stroke_color =
color_provider->GetColor(kStrokeIdMap.at(theme)[hovered]);
constexpr int kStrokeWidth = kOverlayScrollbarStrokeWidth;
cc::PaintFlags flags;
flags.setColor(stroke_color);
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setStrokeWidth(kStrokeWidth);
gfx::RectF stroke_rect(thumb_rect);
gfx::InsetsF stroke_insets(kStrokeWidth / 2.f);
gfx::Insets edge_adjust_insets;
if (part == NativeTheme::kScrollbarHorizontalThumb)
edge_adjust_insets.set_bottom(-kStrokeWidth);
else
edge_adjust_insets.set_right(-kStrokeWidth);
stroke_rect.Inset(stroke_insets + gfx::InsetsF(edge_adjust_insets));
canvas->drawRect(gfx::RectFToSkRect(stroke_rect), flags);
gfx::Insets fill_insets(kStrokeWidth);
thumb_rect.Inset(fill_insets + edge_adjust_insets);
#endif
} else {
#if defined(OHOS_SCROLLBAR)
cc::PaintFlags overflags;
SkScalar radius;
gfx::Rect aroundRRect;
if (color_scheme == ColorScheme::kDark) {
thumb_color = SkColorSetA(SK_ColorWHITE, 102);
} else {
thumb_color = SkColorSetA(scrollbar_color, 102);
}
overflags.setColor(thumb_color);
if (state == kHovered || state == kPressed) {
radius = SkIntToScalar(kForceScrollbarActiveRadius * ratio);
if (part == kScrollbarVerticalThumb) {
thumb_rect.set_x(thumb_rect.x() + thumb_rect.width() -
kForceScrollbarActiveWidth * ratio -
kForceScrollbarActiveOffset * ratio);
thumb_rect.set_width(kForceScrollbarActiveWidth * ratio);
aroundRRect = gfx::Rect(kForceScrollbarActiveHotSize * ratio, 0,
kForceScrollbarActiveWidth * ratio -
kForceScrollbarActiveHotSize * ratio,
thumb_rect.height());
} else {
thumb_rect.set_y(thumb_rect.y() + thumb_rect.height() -
kForceScrollbarActiveWidth * ratio -
kForceScrollbarActiveOffset * ratio);
thumb_rect.set_height(kForceScrollbarActiveWidth * ratio);
aroundRRect = gfx::Rect(0, kForceScrollbarActiveHotSize * ratio,
thumb_rect.width(),
kForceScrollbarActiveWidth * ratio -
kForceScrollbarActiveHotSize * ratio);
}
} else {
radius = SkIntToScalar(kForceScrollbarInactiveRadius * ratio);
if (part == kScrollbarVerticalThumb) {
thumb_rect.set_x(thumb_rect.x() + thumb_rect.width() -
kForceScrollbarInactiveOffset * ratio);
thumb_rect.set_width(kForceScrollbarInactiveWidth * ratio);
aroundRRect = gfx::Rect(kForceScrollbarInactiveHotSize * ratio, 0,
kForceScrollbarInactiveWidth * ratio -
kForceScrollbarInactiveHotSize * ratio,
thumb_rect.height());
} else {
thumb_rect.set_y(thumb_rect.y() + thumb_rect.height() -
kForceScrollbarInactiveOffset * ratio);
thumb_rect.set_height(kForceScrollbarInactiveWidth * ratio);
aroundRRect = gfx::Rect(0, kForceScrollbarInactiveHotSize * ratio,
thumb_rect.width(),
kForceScrollbarInactiveWidth * ratio -
kForceScrollbarInactiveHotSize * ratio);
}
}
gfx::RRectF rounded_rect(gfx::RectF(aroundRRect), radius, radius, radius,
radius, radius, radius, radius, radius);
canvas->drawRRect(static_cast<SkRRect>(rounded_rect), overflags);
return;
#else
ControlColorId color_id = kScrollbarThumb;
switch (state) {
case NativeTheme::kDisabled:
case NativeTheme::kNormal:
break;
case NativeTheme::kHovered:
color_id = kScrollbarThumbHovered;
break;
case NativeTheme::kPressed:
color_id = kScrollbarThumbPressed;
break;
case NativeTheme::kNumStates:
NOTREACHED();
break;
}
const int kThumbPadding = 2;
const int extra_padding =
(scrollbar_button_length() == 0) ? kThumbPadding : 0;
if (part == NativeTheme::kScrollbarVerticalThumb)
thumb_rect.Inset(gfx::Insets::VH(extra_padding, kThumbPadding));
else
thumb_rect.Inset(gfx::Insets::VH(kThumbPadding, extra_padding));
thumb_color = GetControlColor(color_id, color_scheme, color_provider);
#endif
}
cc::PaintFlags flags;
flags.setColor(thumb_color);
canvas->drawIRect(gfx::RectToSkIRect(thumb_rect), flags);
}
void NativeThemeAura::PaintScrollbarCorner(cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
State state,
const gfx::Rect& rect,
ColorScheme color_scheme) const {
DCHECK(!use_overlay_scrollbars_);
const SkColor corner_color = GetControlColor(kScrollbarCornerControlColorId,
color_scheme, color_provider);
cc::PaintFlags flags;
flags.setColor(corner_color);
canvas->drawIRect(RectToSkIRect(rect), flags);
}
gfx::Size NativeThemeAura::GetPartSize(Part part,
State state,
const ExtraParams& extra) const {
if (use_overlay_scrollbars_) {
constexpr int minimum_length =
kOverlayScrollbarMinimumLength + 2 * kOverlayScrollbarStrokeWidth;
#ifdef OHOS_SCROLLBAR
float ratio = base::ohos::GetPixelRatio();
switch (part) {
case kScrollbarHorizontalThumb:
return gfx::Size(minimum_length * ratio, scrollbar_width_);
case kScrollbarVerticalThumb:
return gfx::Size(scrollbar_width_, minimum_length * ratio);
default:
break;
}
} else {
switch (part) {
case kScrollbarDownArrow:
case kScrollbarUpArrow:
return gfx::Size(scrollbar_width_, 0);
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
return gfx::Size(0, scrollbar_width_);
default:
break;
}
#else
switch (part) {
case kScrollbarHorizontalThumb:
return gfx::Size(minimum_length, scrollbar_width_);
case kScrollbarVerticalThumb:
return gfx::Size(scrollbar_width_, minimum_length);
default:
break;
}
#endif
}
return NativeThemeBase::GetPartSize(part, state, extra);
}
void NativeThemeAura::DrawPartiallyRoundRect(cc::PaintCanvas* canvas,
const gfx::Rect& rect,
const SkScalar upper_left_radius,
const SkScalar upper_right_radius,
const SkScalar lower_right_radius,
const SkScalar lower_left_radius,
const cc::PaintFlags& flags) {
gfx::RRectF rounded_rect(
gfx::RectF(rect), upper_left_radius, upper_left_radius,
upper_right_radius, upper_right_radius, lower_right_radius,
lower_right_radius, lower_left_radius, lower_left_radius);
canvas->drawRRect(static_cast<SkRRect>(rounded_rect), flags);
}
bool NativeThemeAura::SupportsNinePatch(Part part) const {
if (!IsOverlayScrollbarEnabled())
return false;
return part == kScrollbarHorizontalThumb || part == kScrollbarVerticalThumb;
}
gfx::Size NativeThemeAura::GetNinePatchCanvasSize(Part part) const {
DCHECK(SupportsNinePatch(part));
#ifdef OHOS_SCROLLBAR
float ratio = base::ohos::GetPixelRatio();
return gfx::Size(
(kOverlayScrollbarBorderPatchWidth * kOverlayScrollbarDoubleOrHalf + scrollbar_width_) * ratio,
(kOverlayScrollbarBorderPatchWidth * kOverlayScrollbarDoubleOrHalf + scrollbar_width_) * ratio);
#else
return gfx::Size(
kOverlayScrollbarBorderPatchWidth * 2 + kOverlayScrollbarCenterPatchSize,
kOverlayScrollbarBorderPatchWidth * 2 + kOverlayScrollbarCenterPatchSize);
#endif
}
gfx::Rect NativeThemeAura::GetNinePatchAperture(Part part) const {
DCHECK(SupportsNinePatch(part));
#ifdef OHOS_SCROLLBAR
float ratio = base::ohos::GetPixelRatio();
if (part == kScrollbarHorizontalThumb) {
return gfx::Rect(scrollbar_width_ * ratio / kOverlayScrollbarDoubleOrHalf,
kOverlayScrollbarBorderPatchWidth * ratio,
(int)ratio,
scrollbar_width_ * ratio);
} else {
return gfx::Rect(kOverlayScrollbarBorderPatchWidth* ratio,
scrollbar_width_ * ratio / kOverlayScrollbarDoubleOrHalf,
scrollbar_width_ * ratio,
(int)ratio);
}
#else
return gfx::Rect(
kOverlayScrollbarBorderPatchWidth, kOverlayScrollbarBorderPatchWidth,
kOverlayScrollbarCenterPatchSize, kOverlayScrollbarCenterPatchSize);
#endif
}
}