123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- // Copyright (c) 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/native_theme/native_theme_mac.h"
- #import <Cocoa/Cocoa.h>
- #include <MediaAccessibility/MediaAccessibility.h>
- #include <stddef.h>
- #include <vector>
- #include "base/command_line.h"
- #include "base/mac/mac_util.h"
- #include "base/mac/scoped_block.h"
- #include "base/no_destructor.h"
- #include "cc/paint/paint_shader.h"
- #include "ui/base/ui_base_features.h"
- #include "ui/base/ui_base_switches.h"
- #include "ui/color/color_provider.h"
- #include "ui/color/mac/scoped_current_nsappearance.h"
- #include "ui/gfx/canvas.h"
- #include "ui/gfx/color_palette.h"
- #include "ui/gfx/color_utils.h"
- #include "ui/gfx/geometry/insets.h"
- #include "ui/gfx/geometry/rect.h"
- #include "ui/gfx/geometry/skia_conversions.h"
- #include "ui/native_theme/common_theme.h"
- #include "ui/native_theme/native_theme_aura.h"
- #include "ui/native_theme/native_theme_features.h"
- namespace {
- bool IsDarkMode() {
- if (@available(macOS 10.14, *)) {
- NSAppearanceName appearance =
- [[NSApp effectiveAppearance] bestMatchFromAppearancesWithNames:@[
- NSAppearanceNameAqua, NSAppearanceNameDarkAqua
- ]];
- return [appearance isEqual:NSAppearanceNameDarkAqua];
- }
- return false;
- }
- bool IsHighContrast() {
- return NSWorkspace.sharedWorkspace.accessibilityDisplayShouldIncreaseContrast;
- }
- } // namespace
- // Helper object to respond to light mode/dark mode changeovers.
- @interface NativeThemeEffectiveAppearanceObserver : NSObject
- @end
- @implementation NativeThemeEffectiveAppearanceObserver {
- base::mac::ScopedBlock<void (^)()> _handler;
- }
- - (instancetype)initWithHandler:(void (^)())handler {
- self = [super init];
- if (self) {
- _handler.reset([handler copy]);
- if (@available(macOS 10.14, *)) {
- [NSApp addObserver:self
- forKeyPath:@"effectiveAppearance"
- options:0
- context:nullptr];
- }
- }
- return self;
- }
- - (void)dealloc {
- if (@available(macOS 10.14, *)) {
- [NSApp removeObserver:self forKeyPath:@"effectiveAppearance"];
- }
- [super dealloc];
- }
- - (void)observeValueForKeyPath:(NSString*)forKeyPath
- ofObject:(id)object
- change:(NSDictionary*)change
- context:(void*)context {
- _handler.get()();
- }
- @end
- namespace {
- // Helper to make indexing an array by an enum class easier.
- template <class KEY, class VALUE>
- struct EnumArray {
- VALUE& operator[](const KEY& key) { return array[static_cast<size_t>(key)]; }
- VALUE array[static_cast<size_t>(KEY::COUNT)];
- };
- } // namespace
- namespace ui {
- // static
- NativeTheme* NativeTheme::GetInstanceForWeb() {
- return NativeThemeMacWeb::instance();
- }
- // static
- NativeTheme* NativeTheme::GetInstanceForNativeUi() {
- return NativeThemeMac::instance();
- }
- NativeTheme* NativeTheme::GetInstanceForDarkUI() {
- static base::NoDestructor<NativeThemeMac> s_native_theme(false, true);
- return s_native_theme.get();
- }
- // static
- bool NativeTheme::SystemDarkModeSupported() {
- if (@available(macOS 10.14, *)) {
- return true;
- }
- return false;
- }
- // static
- NativeThemeMac* NativeThemeMac::instance() {
- static base::NoDestructor<NativeThemeMac> s_native_theme(true, false);
- return s_native_theme.get();
- }
- NativeThemeAura::PreferredContrast NativeThemeMac::CalculatePreferredContrast()
- const {
- return IsHighContrast() ? NativeThemeAura::PreferredContrast::kMore
- : NativeThemeAura::PreferredContrast::kNoPreference;
- }
- void NativeThemeMac::Paint(cc::PaintCanvas* canvas,
- const ColorProvider* color_provider,
- Part part,
- State state,
- const gfx::Rect& rect,
- const ExtraParams& extra,
- ColorScheme color_scheme,
- const absl::optional<SkColor>& accent_color) const {
- ColorScheme color_scheme_updated = color_scheme;
- if (color_scheme_updated == ColorScheme::kDefault)
- color_scheme_updated = GetDefaultSystemColorScheme();
- if (rect.IsEmpty())
- return;
- switch (part) {
- case kScrollbarHorizontalThumb:
- case kScrollbarVerticalThumb:
- PaintMacScrollbarThumb(canvas, part, state, rect, extra.scrollbar_extra,
- color_scheme_updated);
- break;
- case kScrollbarHorizontalTrack:
- case kScrollbarVerticalTrack:
- PaintMacScrollBarTrackOrCorner(canvas, part, state, extra.scrollbar_extra,
- rect, color_scheme_updated, false);
- break;
- case kScrollbarCorner:
- PaintMacScrollBarTrackOrCorner(canvas, part, state, extra.scrollbar_extra,
- rect, color_scheme_updated, true);
- break;
- default:
- NativeThemeBase::Paint(canvas, color_provider, part, state, rect, extra,
- color_scheme, accent_color);
- break;
- }
- }
- 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.
- // This is done instead of using inset scale functions to maintain expected
- // behavior and to map to how it looks like other scrollbars work on MacOS.
- *right *= max_total_inset * 1.0f / requested_total_inset;
- *left = max_total_inset - *right;
- }
- void ConstrainedInset(gfx::Rect* rect,
- gfx::Size min_size,
- gfx::Insets initial_insets) {
- int inset_left = initial_insets.left();
- int inset_right = initial_insets.right();
- int inset_top = initial_insets.top();
- int inset_bottom = initial_insets.bottom();
- ConstrainInsets(rect->width(), min_size.width(), &inset_left, &inset_right);
- ConstrainInsets(rect->height(), min_size.height(), &inset_top, &inset_bottom);
- rect->Inset(
- gfx::Insets::TLBR(inset_top, inset_left, inset_bottom, inset_right));
- }
- void NativeThemeMac::PaintMacScrollBarTrackOrCorner(
- cc::PaintCanvas* canvas,
- Part part,
- State state,
- const ScrollbarExtraParams& extra_params,
- const gfx::Rect& rect,
- ColorScheme color_scheme,
- bool is_corner) const {
- if (is_corner && extra_params.is_overlay)
- return;
- PaintScrollBarTrackGradient(canvas, rect, extra_params, is_corner,
- color_scheme);
- PaintScrollbarTrackInnerBorder(canvas, rect, extra_params, is_corner,
- color_scheme);
- PaintScrollbarTrackOuterBorder(canvas, rect, extra_params, is_corner,
- color_scheme);
- }
- void NativeThemeMac::PaintScrollBarTrackGradient(
- cc::PaintCanvas* canvas,
- const gfx::Rect& rect,
- const ScrollbarExtraParams& extra_params,
- bool is_corner,
- ColorScheme color_scheme) const {
- gfx::Canvas paint_canvas(canvas, 1.0f);
- // Select colors.
- std::vector<SkColor4f> gradient_colors;
- bool dark_mode = color_scheme == ColorScheme::kDark;
- if (extra_params.is_overlay) {
- if (dark_mode) {
- gradient_colors = {SkColor4f{0.847f, 0.847f, 0.847f, 0.157f},
- SkColor4f{0.8f, 0.8f, 0.8f, 0.149f},
- SkColor4f{0.8f, 0.8f, 0.8f, 0.149f},
- SkColor4f{0.8f, 0.8f, 0.8f, 0.149f}};
- } else {
- gradient_colors = {SkColor4f{0.973f, 0.973f, 0.973f, 0.776f},
- SkColor4f{0.973f, 0.973f, 0.973f, 0.761f},
- SkColor4f{0.973f, 0.973f, 0.973f, 0.761f},
- SkColor4f{0.973f, 0.973f, 0.973f, 0.761f}};
- }
- } else {
- // Non-overlay scroller track colors are not transparent. On Safari, they
- // are, but on all other macOS applications they are not.
- if (dark_mode) {
- gradient_colors = {SkColor4f{0.176f, 0.176f, 0.176f, 1.0f},
- SkColor4f{0.169f, 0.169f, 0.169f, 1.0f}};
- } else {
- gradient_colors = {SkColor4f{0.98f, 0.98f, 0.98f, 1.0f},
- SkColor4f{0.98f, 0.98f, 0.98f, 1.0f}};
- }
- }
- // Set the gradient direction.
- std::vector<SkPoint> gradient_bounds;
- if (is_corner) {
- if (extra_params.orientation == ScrollbarOrientation::kVerticalOnRight) {
- gradient_bounds = {gfx::PointToSkPoint(rect.origin()),
- gfx::PointToSkPoint(rect.bottom_right())};
- } else {
- gradient_bounds = {gfx::PointToSkPoint(rect.top_right()),
- gfx::PointToSkPoint(rect.bottom_left())};
- }
- } else {
- if (extra_params.orientation == ScrollbarOrientation::kHorizontal) {
- gradient_bounds = {gfx::PointToSkPoint(rect.origin()),
- gfx::PointToSkPoint(rect.top_right())};
- } else {
- gradient_bounds = {gfx::PointToSkPoint(rect.origin()),
- gfx::PointToSkPoint(rect.bottom_left())};
- }
- }
- // And draw.
- cc::PaintFlags gradient;
- gradient.setShader(cc::PaintShader::MakeLinearGradient(
- gradient_bounds.data(), gradient_colors.data(), nullptr,
- gradient_colors.size(), SkTileMode::kClamp));
- paint_canvas.DrawRect(rect, gradient);
- }
- void NativeThemeMac::PaintScrollbarTrackInnerBorder(
- cc::PaintCanvas* canvas,
- const gfx::Rect& rect,
- const ScrollbarExtraParams& extra_params,
- bool is_corner,
- ColorScheme color_scheme) const {
- gfx::Canvas paint_canvas(canvas, 1.0f);
- // Compute the rect for the border.
- gfx::Rect inner_border(rect);
- if (extra_params.orientation == ScrollbarOrientation::kVerticalOnLeft)
- inner_border.set_x(rect.right() -
- ScrollbarTrackBorderWidth(extra_params.scale_from_dip));
- if (is_corner ||
- extra_params.orientation == ScrollbarOrientation::kHorizontal)
- inner_border.set_height(
- ScrollbarTrackBorderWidth(extra_params.scale_from_dip));
- if (is_corner ||
- extra_params.orientation != ScrollbarOrientation::kHorizontal)
- inner_border.set_width(
- ScrollbarTrackBorderWidth(extra_params.scale_from_dip));
- // And draw.
- cc::PaintFlags flags;
- SkColor inner_border_color =
- GetScrollbarColor(ScrollbarPart::kTrackInnerBorder, color_scheme,
- extra_params)
- .value();
- flags.setColor(inner_border_color);
- paint_canvas.DrawRect(inner_border, flags);
- }
- void NativeThemeMac::PaintScrollbarTrackOuterBorder(
- cc::PaintCanvas* canvas,
- const gfx::Rect& rect,
- const ScrollbarExtraParams& extra_params,
- bool is_corner,
- ColorScheme color_scheme) const {
- gfx::Canvas paint_canvas(canvas, 1.0f);
- cc::PaintFlags flags;
- SkColor outer_border_color =
- GetScrollbarColor(ScrollbarPart::kTrackOuterBorder, color_scheme,
- extra_params)
- .value();
- flags.setColor(outer_border_color);
- // Draw the horizontal outer border.
- if (is_corner ||
- extra_params.orientation == ScrollbarOrientation::kHorizontal) {
- gfx::Rect outer_border(rect);
- outer_border.set_height(
- ScrollbarTrackBorderWidth(extra_params.scale_from_dip));
- outer_border.set_y(rect.bottom() -
- ScrollbarTrackBorderWidth(extra_params.scale_from_dip));
- paint_canvas.DrawRect(outer_border, flags);
- }
- // Draw the vertial outer border.
- if (is_corner ||
- extra_params.orientation != ScrollbarOrientation::kHorizontal) {
- gfx::Rect outer_border(rect);
- outer_border.set_width(
- ScrollbarTrackBorderWidth(extra_params.scale_from_dip));
- if (extra_params.orientation == ScrollbarOrientation::kVerticalOnRight)
- outer_border.set_x(rect.right() - ScrollbarTrackBorderWidth(
- extra_params.scale_from_dip));
- paint_canvas.DrawRect(outer_border, flags);
- }
- }
- gfx::Size NativeThemeMac::GetThumbMinSize(bool vertical, float scale) const {
- const int kLength = 18 * scale;
- const int kGirth = 6 * scale;
- return vertical ? gfx::Size(kGirth, kLength) : gfx::Size(kLength, kGirth);
- }
- void NativeThemeMac::PaintMacScrollbarThumb(
- cc::PaintCanvas* canvas,
- Part part,
- State state,
- const gfx::Rect& rect,
- const ScrollbarExtraParams& scroll_thumb,
- ColorScheme color_scheme) const {
- gfx::Canvas paint_canvas(canvas, 1.0f);
- // Compute the bounds for the rounded rect for the thumb from the bounds of
- // the thumb.
- gfx::Rect bounds(rect);
- {
- // Shrink the thumb evenly in length and girth to fit within the track.
- gfx::Insets thumb_insets(GetScrollbarThumbInset(
- scroll_thumb.is_overlay, scroll_thumb.scale_from_dip));
- // Also shrink the thumb in girth to not touch the border.
- if (scroll_thumb.orientation == ScrollbarOrientation::kHorizontal) {
- thumb_insets.set_top(
- thumb_insets.top() +
- ScrollbarTrackBorderWidth(scroll_thumb.scale_from_dip));
- ConstrainedInset(&bounds,
- GetThumbMinSize(false, scroll_thumb.scale_from_dip),
- thumb_insets);
- } else {
- thumb_insets.set_left(
- thumb_insets.left() +
- ScrollbarTrackBorderWidth(scroll_thumb.scale_from_dip));
- ConstrainedInset(&bounds,
- GetThumbMinSize(true, scroll_thumb.scale_from_dip),
- thumb_insets);
- }
- }
- // Draw.
- cc::PaintFlags flags;
- flags.setAntiAlias(true);
- flags.setStyle(cc::PaintFlags::kFill_Style);
- SkColor thumb_color =
- GetScrollbarColor(ScrollbarPart::kThumb, color_scheme, scroll_thumb)
- .value();
- flags.setColor(thumb_color);
- const SkScalar radius = std::min(bounds.width(), bounds.height());
- paint_canvas.DrawRoundRect(bounds, radius, flags);
- }
- absl::optional<SkColor> NativeThemeMac::GetScrollbarColor(
- ScrollbarPart part,
- ColorScheme color_scheme,
- const ScrollbarExtraParams& extra_params) const {
- // This function is called from the renderer process through the scrollbar
- // drawing functions. Due to this, it cannot use any of the dynamic NS system
- // colors.
- bool dark_mode = color_scheme == ColorScheme::kDark;
- if (part == ScrollbarPart::kThumb) {
- if (extra_params.is_overlay)
- return dark_mode ? SkColorSetARGB(0x80, 0xFF, 0xFF, 0xFF)
- : SkColorSetARGB(0x80, 0, 0, 0);
- if (dark_mode)
- return extra_params.is_hovering ? SkColorSetRGB(0x93, 0x93, 0x93)
- : SkColorSetRGB(0x6B, 0x6B, 0x6B);
- return extra_params.is_hovering ? SkColorSetARGB(0x80, 0, 0, 0)
- : SkColorSetARGB(0x3A, 0, 0, 0);
- } else if (part == ScrollbarPart::kTrackInnerBorder) {
- if (extra_params.is_overlay)
- return dark_mode ? SkColorSetARGB(0x33, 0xE5, 0xE5, 0xE5)
- : SkColorSetARGB(0xF9, 0xDF, 0xDF, 0xDF);
- return dark_mode ? SkColorSetRGB(0x3D, 0x3D, 0x3D)
- : SkColorSetRGB(0xE8, 0xE8, 0xE8);
- } else if (part == ScrollbarPart::kTrackOuterBorder) {
- if (extra_params.is_overlay)
- return dark_mode ? SkColorSetARGB(0x28, 0xD8, 0xD8, 0xD8)
- : SkColorSetARGB(0xC6, 0xE8, 0xE8, 0xE8);
- return dark_mode ? SkColorSetRGB(0x51, 0x51, 0x51)
- : SkColorSetRGB(0xED, 0xED, 0xED);
- }
- return absl::nullopt;
- }
- SkColor NativeThemeMac::GetSystemButtonPressedColor(SkColor base_color) const {
- // TODO crbug.com/1003612: This should probably be replaced with a color
- // transform.
- // Mac has a different "pressed button" styling because it doesn't use
- // ripples.
- return color_utils::GetResultingPaintColor(SkColorSetA(SK_ColorBLACK, 0x10),
- base_color);
- }
- void NativeThemeMac::PaintMenuPopupBackground(
- cc::PaintCanvas* canvas,
- const ColorProvider* color_provider,
- const gfx::Size& size,
- const MenuBackgroundExtraParams& menu_background,
- ColorScheme color_scheme) const {
- DCHECK(color_provider);
- cc::PaintFlags flags;
- flags.setAntiAlias(true);
- flags.setColor(color_provider->GetColor(kColorMenuBackground));
- const SkScalar radius = SkIntToScalar(menu_background.corner_radius);
- SkRect rect = gfx::RectToSkRect(gfx::Rect(size));
- canvas->drawRoundRect(rect, radius, radius, flags);
- }
- void NativeThemeMac::PaintMenuItemBackground(
- cc::PaintCanvas* canvas,
- const ColorProvider* color_provider,
- State state,
- const gfx::Rect& rect,
- const MenuItemExtraParams& menu_item,
- ColorScheme color_scheme) const {
- switch (state) {
- case NativeTheme::kNormal:
- case NativeTheme::kDisabled:
- // Draw nothing over the regular background.
- break;
- case NativeTheme::kHovered:
- PaintSelectedMenuItem(canvas, color_provider, rect);
- break;
- default:
- NOTREACHED();
- break;
- }
- }
- // static
- static void CaptionSettingsChangedNotificationCallback(CFNotificationCenterRef,
- void*,
- CFStringRef,
- const void*,
- CFDictionaryRef) {
- NativeTheme::GetInstanceForWeb()->NotifyOnCaptionStyleUpdated();
- }
- NativeThemeMac::NativeThemeMac(bool configure_web_instance,
- bool should_only_use_dark_colors)
- : NativeThemeBase(should_only_use_dark_colors) {
- if (!should_only_use_dark_colors)
- InitializeDarkModeStateAndObserver();
- if (!IsForcedHighContrast()) {
- set_preferred_contrast(CalculatePreferredContrast());
- __block auto theme = this;
- high_contrast_notification_token_ =
- [[[NSWorkspace sharedWorkspace] notificationCenter]
- addObserverForName:
- NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification
- object:nil
- queue:nil
- usingBlock:^(NSNotification* notification) {
- theme->set_preferred_contrast(
- CalculatePreferredContrast());
- theme->NotifyOnNativeThemeUpdated();
- }];
- }
- if (configure_web_instance)
- ConfigureWebInstance();
- }
- NativeThemeMac::~NativeThemeMac() {
- [[NSNotificationCenter defaultCenter]
- removeObserver:high_contrast_notification_token_];
- }
- void NativeThemeMac::PaintSelectedMenuItem(cc::PaintCanvas* canvas,
- const ColorProvider* color_provider,
- const gfx::Rect& rect) const {
- DCHECK(color_provider);
- // Draw the background.
- cc::PaintFlags flags;
- flags.setColor(color_provider->GetColor(kColorMenuItemBackgroundSelected));
- canvas->drawRect(gfx::RectToSkRect(rect), flags);
- }
- void NativeThemeMac::InitializeDarkModeStateAndObserver() {
- __block auto theme = this;
- set_use_dark_colors(IsDarkMode());
- set_preferred_color_scheme(CalculatePreferredColorScheme());
- appearance_observer_.reset(
- [[NativeThemeEffectiveAppearanceObserver alloc] initWithHandler:^{
- theme->set_use_dark_colors(IsDarkMode());
- theme->set_preferred_color_scheme(CalculatePreferredColorScheme());
- theme->NotifyOnNativeThemeUpdated();
- }]);
- }
- void NativeThemeMac::ConfigureWebInstance() {
- // NativeThemeAura is used as web instance so we need to initialize its state.
- NativeTheme* web_instance = NativeTheme::GetInstanceForWeb();
- web_instance->set_use_dark_colors(IsDarkMode());
- web_instance->set_preferred_color_scheme(CalculatePreferredColorScheme());
- web_instance->set_preferred_contrast(CalculatePreferredContrast());
- // Add the web native theme as an observer to stay in sync with color scheme
- // changes.
- color_scheme_observer_ =
- std::make_unique<NativeTheme::ColorSchemeNativeThemeObserver>(
- NativeTheme::GetInstanceForWeb());
- AddObserver(color_scheme_observer_.get());
- // Observe caption style changes.
- CFNotificationCenterAddObserver(
- CFNotificationCenterGetLocalCenter(), this,
- CaptionSettingsChangedNotificationCallback,
- kMACaptionAppearanceSettingsChangedNotification, nullptr,
- CFNotificationSuspensionBehaviorDeliverImmediately);
- }
- NativeThemeMacWeb::NativeThemeMacWeb()
- : NativeThemeAura(IsOverlayScrollbarEnabled(), false) {}
- // static
- NativeThemeMacWeb* NativeThemeMacWeb::instance() {
- static base::NoDestructor<NativeThemeMacWeb> s_native_theme;
- return s_native_theme.get();
- }
- } // namespace ui
|