blob: f62df6eb7d12b9decea1b3c0f29f557aa0ccddac [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.h"
#include <cstring>
#include <optional>
#include "base/command_line.h"
#include "base/containers/fixed_flat_map.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#include "ui/base/ui_base_switches.h"
#include "ui/color/color_id.h"
#include "ui/color/color_metrics.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_key.h"
#include "ui/color/color_provider_utils.h"
#include "ui/native_theme/common_theme.h"
#include "ui/native_theme/native_theme_utils.h"
namespace ui {
namespace {
static constexpr base::TimeDelta kDefaultCaretBlinkInterval =
base::Milliseconds(500);
}
NativeTheme::MenuListExtraParams::MenuListExtraParams() = default;
NativeTheme::TextFieldExtraParams::TextFieldExtraParams() = default;
NativeTheme::MenuListExtraParams::MenuListExtraParams(
const NativeTheme::MenuListExtraParams&) = default;
NativeTheme::TextFieldExtraParams::TextFieldExtraParams(
const NativeTheme::TextFieldExtraParams&) = default;
NativeTheme::MenuListExtraParams& NativeTheme::MenuListExtraParams::operator=(
const NativeTheme::MenuListExtraParams&) = default;
NativeTheme::TextFieldExtraParams& NativeTheme::TextFieldExtraParams::operator=(
const NativeTheme::TextFieldExtraParams&) = default;
#if !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_APPLE)
// static
bool NativeTheme::SystemDarkModeSupported() {
return false;
}
#endif
ColorProviderKey NativeTheme::GetColorProviderKey(
scoped_refptr<ColorProviderKey::ThemeInitializerSupplier> custom_theme,
bool use_custom_frame) const {
const auto get_forced_colors_key = [](bool forced_colors,
PageColors page_colors) {
if (!forced_colors) {
return ColorProviderKey::ForcedColors::kNone;
}
static constexpr auto kForcedColorsMap =
base::MakeFixedFlatMap<PageColors, ColorProviderKey::ForcedColors>(
{{PageColors::kOff, ColorProviderKey::ForcedColors::kNone},
{PageColors::kDusk, ColorProviderKey::ForcedColors::kDusk},
{PageColors::kDesert, ColorProviderKey::ForcedColors::kDesert},
{PageColors::kBlack, ColorProviderKey::ForcedColors::kBlack},
{PageColors::kWhite, ColorProviderKey::ForcedColors::kWhite},
{PageColors::kHighContrast,
ColorProviderKey::ForcedColors::kActive}});
return kForcedColorsMap.at(page_colors);
};
ui::ColorProviderKey key;
switch (GetDefaultSystemColorScheme()) {
case ColorScheme::kDark:
key.color_mode = ColorProviderKey::ColorMode::kDark;
break;
case ColorScheme::kLight:
key.color_mode = ColorProviderKey::ColorMode::kLight;
break;
case ColorScheme::kPlatformHighContrast:
key.color_mode = GetPreferredColorScheme() == PreferredColorScheme::kDark
? ColorProviderKey::ColorMode::kDark
: ColorProviderKey::ColorMode::kLight;
break;
default:
NOTREACHED_NORETURN();
}
key.contrast_mode = UserHasContrastPreference()
? ColorProviderKey::ContrastMode::kHigh
: ColorProviderKey::ContrastMode::kNormal;
key.forced_colors = get_forced_colors_key(InForcedColorsMode(), page_colors_);
key.system_theme = system_theme_;
key.frame_type = use_custom_frame ? ColorProviderKey::FrameType::kChromium
: ColorProviderKey::FrameType::kNative;
key.user_color_source = should_use_system_accent_color_
? ColorProviderKey::UserColorSource::kAccent
: ColorProviderKey::UserColorSource::kBaseline;
key.user_color = user_color_;
key.scheme_variant = scheme_variant_;
key.custom_theme = std::move(custom_theme);
return key;
}
SkColor NativeTheme::GetSystemButtonPressedColor(SkColor base_color) const {
return base_color;
}
SkColor4f NativeTheme::FocusRingColorForBaseColor(SkColor4f base_color) const {
return base_color;
}
float NativeTheme::GetBorderRadiusForPart(Part part,
float width,
float height) const {
return 0;
}
void NativeTheme::AddObserver(NativeThemeObserver* observer) {
native_theme_observers_.AddObserver(observer);
}
void NativeTheme::RemoveObserver(NativeThemeObserver* observer) {
native_theme_observers_.RemoveObserver(observer);
}
void NativeTheme::NotifyOnNativeThemeUpdated() {
base::ElapsedTimer timer;
auto& color_provider_manager = ui::ColorProviderManager::Get();
const size_t initial_providers_initialized =
color_provider_manager.num_providers_initialized();
// This specific method is prone to being mistakenly called on the wrong
// sequence, because it is often invoked from a platform-specific event
// listener, and those events may be delivered on unexpected sequences.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Reset the ColorProviderManager's cache so that ColorProviders requested
// from this point onwards incorporate the changes to the system theme.
color_provider_manager.ResetColorProviderCache();
for (NativeThemeObserver& observer : native_theme_observers_)
observer.OnNativeThemeUpdated(this);
RecordNumColorProvidersInitializedDuringOnNativeThemeUpdated(
color_provider_manager.num_providers_initialized() -
initial_providers_initialized);
RecordTimeSpentProcessingOnNativeThemeUpdatedEvent(timer.Elapsed());
}
void NativeTheme::NotifyOnCaptionStyleUpdated() {
// This specific method is prone to being mistakenly called on the wrong
// sequence, because it is often invoked from a platform-specific event
// listener, and those events may be delivered on unexpected sequences.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (NativeThemeObserver& observer : native_theme_observers_)
observer.OnCaptionStyleUpdated();
}
void NativeTheme::NotifyOnPreferredContrastUpdated() {
// This specific method is prone to being mistakenly called on the wrong
// sequence, because it is often invoked from a platform-specific event
// listener, and those events may be delivered on unexpected sequences.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (NativeThemeObserver& observer : native_theme_observers_)
observer.OnPreferredContrastChanged();
}
float NativeTheme::AdjustBorderWidthByZoom(float border_width,
float zoom_level) const {
float zoomed = floorf(border_width * zoom_level);
return std::max(1.0f, zoomed);
}
float NativeTheme::AdjustBorderRadiusByZoom(Part part,
float border_radius,
float zoom) const {
if (part == kCheckbox || part == kTextField || part == kPushButton) {
float zoomed = floorf(border_radius * zoom);
return std::max(1.0f, zoomed);
}
return border_radius;
}
base::TimeDelta NativeTheme::GetCaretBlinkInterval() const {
if (caret_blink_interval_.has_value()) {
return caret_blink_interval_.value();
}
std::optional<base::TimeDelta> platform_interval =
GetPlatformCaretBlinkInterval();
if (platform_interval.has_value()) {
return platform_interval.value();
}
return kDefaultCaretBlinkInterval;
}
NativeTheme::NativeTheme(bool should_use_dark_colors,
ui::SystemTheme system_theme)
: should_use_dark_colors_(should_use_dark_colors || IsForcedDarkMode()),
system_theme_(system_theme),
forced_colors_(IsForcedHighContrast()),
prefers_reduced_transparency_(false),
inverted_colors_(false),
preferred_color_scheme_(CalculatePreferredColorScheme()),
preferred_contrast_(CalculatePreferredContrast()) {}
NativeTheme::~NativeTheme() = default;
bool NativeTheme::ShouldUseDarkColors() const {
return should_use_dark_colors_;
}
bool NativeTheme::UserHasContrastPreference() const {
return GetPreferredContrast() !=
NativeTheme::PreferredContrast::kNoPreference;
}
bool NativeTheme::InForcedColorsMode() const {
return forced_colors_;
}
NativeTheme::PlatformHighContrastColorScheme
NativeTheme::GetPlatformHighContrastColorScheme() const {
if (GetDefaultSystemColorScheme() != ColorScheme::kPlatformHighContrast)
return PlatformHighContrastColorScheme::kNone;
return (GetPreferredColorScheme() == PreferredColorScheme::kDark)
? PlatformHighContrastColorScheme::kDark
: PlatformHighContrastColorScheme::kLight;
}
NativeTheme::PageColors NativeTheme::GetPageColors() const {
return page_colors_;
}
NativeTheme::PreferredColorScheme NativeTheme::CalculatePreferredColorScheme()
const {
return ShouldUseDarkColors() ? NativeTheme::PreferredColorScheme::kDark
: NativeTheme::PreferredColorScheme::kLight;
}
std::optional<base::TimeDelta> NativeTheme::GetPlatformCaretBlinkInterval()
const {
return std::nullopt;
}
NativeTheme::PreferredColorScheme NativeTheme::GetPreferredColorScheme() const {
return preferred_color_scheme_;
}
bool NativeTheme::GetPrefersReducedTransparency() const {
return prefers_reduced_transparency_;
}
bool NativeTheme::GetInvertedColors() const {
return inverted_colors_;
}
NativeTheme::PreferredContrast NativeTheme::GetPreferredContrast() const {
return preferred_contrast_;
}
void NativeTheme::SetPreferredContrast(
NativeTheme::PreferredContrast preferred_contrast) {
if (preferred_contrast_ == preferred_contrast)
return;
preferred_contrast_ = preferred_contrast;
NotifyOnPreferredContrastUpdated();
}
bool NativeTheme::IsForcedDarkMode() {
static bool kIsForcedDarkMode =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceDarkMode);
return kIsForcedDarkMode;
}
bool NativeTheme::IsForcedHighContrast() {
static bool kIsForcedHighContrast =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceHighContrast);
return kIsForcedHighContrast;
}
NativeTheme::PreferredContrast NativeTheme::CalculatePreferredContrast() const {
return IsForcedHighContrast() ? PreferredContrast::kMore
: PreferredContrast::kNoPreference;
}
std::optional<CaptionStyle> NativeTheme::GetSystemCaptionStyle() const {
return CaptionStyle::FromSystemSettings();
}
const std::map<NativeTheme::SystemThemeColor, SkColor>&
NativeTheme::GetSystemColors() const {
return system_colors_;
}
std::optional<SkColor> NativeTheme::GetSystemThemeColor(
SystemThemeColor theme_color) const {
auto color = system_colors_.find(theme_color);
if (color != system_colors_.end())
return color->second;
return std::nullopt;
}
bool NativeTheme::HasDifferentSystemColors(
const std::map<NativeTheme::SystemThemeColor, SkColor>& colors) const {
return system_colors_ != colors;
}
void NativeTheme::set_system_colors(
const std::map<NativeTheme::SystemThemeColor, SkColor>& colors) {
system_colors_ = colors;
}
NativeTheme::ColorSchemeNativeThemeObserver::ColorSchemeNativeThemeObserver(
NativeTheme* theme_to_update)
: theme_to_update_(theme_to_update) {}
NativeTheme::ColorSchemeNativeThemeObserver::~ColorSchemeNativeThemeObserver() =
default;
void NativeTheme::ColorSchemeNativeThemeObserver::OnNativeThemeUpdated(
ui::NativeTheme* observed_theme) {
bool should_use_dark_colors = observed_theme->ShouldUseDarkColors();
bool forced_colors = observed_theme->InForcedColorsMode();
PageColors page_colors = observed_theme->GetPageColors();
bool prefers_reduced_transparency =
observed_theme->GetPrefersReducedTransparency();
PreferredColorScheme preferred_color_scheme =
observed_theme->GetPreferredColorScheme();
PreferredContrast preferred_contrast = observed_theme->GetPreferredContrast();
bool inverted_colors = observed_theme->GetInvertedColors();
base::TimeDelta caret_blink_interval =
observed_theme->GetCaretBlinkInterval();
bool notify_observers = false;
const auto default_page_colors =
forced_colors ? PageColors::kHighContrast : PageColors::kOff;
if (page_colors != default_page_colors) {
if (page_colors == PageColors::kOff) {
forced_colors = false;
preferred_contrast = PreferredContrast::kNoPreference;
} else if (page_colors != PageColors::kHighContrast) {
// Set other states based on the selected theme (i.e. `kDusk`, `kDesert`,
// `kBlack`, or `kWhite`). This block is only executed when one of these
// themes is chosen. `kHighContrast` is not a valid theme here, as it is
// only available in forced colors mode.
CHECK_GE(page_colors, ui::NativeTheme::PageColors::kDusk);
CHECK_LE(page_colors, ui::NativeTheme::PageColors::kWhite);
bool is_dark_color =
page_colors == PageColors::kBlack || page_colors == PageColors::kDusk;
PreferredColorScheme page_colors_theme_scheme =
is_dark_color ? PreferredColorScheme::kDark
: PreferredColorScheme::kLight;
forced_colors = true;
should_use_dark_colors = is_dark_color;
preferred_color_scheme = page_colors_theme_scheme;
preferred_contrast = PreferredContrast::kMore;
}
}
if (theme_to_update_->ShouldUseDarkColors() != should_use_dark_colors) {
theme_to_update_->set_use_dark_colors(should_use_dark_colors);
notify_observers = true;
}
if (theme_to_update_->InForcedColorsMode() != forced_colors) {
theme_to_update_->set_forced_colors(forced_colors);
notify_observers = true;
}
if (theme_to_update_->GetPageColors() != page_colors) {
theme_to_update_->set_page_colors(page_colors);
notify_observers = true;
}
if (theme_to_update_->GetPreferredColorScheme() != preferred_color_scheme) {
theme_to_update_->set_preferred_color_scheme(preferred_color_scheme);
notify_observers = true;
}
if (theme_to_update_->GetPreferredContrast() != preferred_contrast) {
theme_to_update_->SetPreferredContrast(preferred_contrast);
notify_observers = true;
}
if (theme_to_update_->GetPrefersReducedTransparency() !=
prefers_reduced_transparency) {
theme_to_update_->set_prefers_reduced_transparency(
prefers_reduced_transparency);
notify_observers = true;
}
if (theme_to_update_->GetInvertedColors() != inverted_colors) {
theme_to_update_->set_inverted_colors(inverted_colors);
notify_observers = true;
}
if (theme_to_update_->GetCaretBlinkInterval() != caret_blink_interval) {
theme_to_update_->set_caret_blink_interval(caret_blink_interval);
notify_observers = true;
}
if (notify_observers) {
DCHECK(theme_to_update_->UserHasContrastPreference() ||
!theme_to_update_->InForcedColorsMode());
theme_to_update_->NotifyOnNativeThemeUpdated();
}
}
NativeTheme::ColorScheme NativeTheme::GetDefaultSystemColorScheme() const {
return ShouldUseDarkColors() ? ColorScheme::kDark : ColorScheme::kLight;
}
int NativeTheme::GetPaintedScrollbarTrackInset() const {
return 0;
}
} // namespace ui