#include "ui/native_theme/os_settings_provider.h"
#include <array>
#include <forward_list>
#include <optional>
#include <tuple>
#include <utility>
#include "base/callback_list.h"
#include "base/dcheck_is_on.h"
#include "base/functional/callback.h"
#include "base/no_destructor.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "base/types/cxx23_to_underlying.h"
#include "build/build_config.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/color/color_provider_key.h"
#include "ui/gfx/color_conversions.h"
#include "ui/gfx/color_utils.h"
#include "ui/native_theme/native_theme.h"
#if BUILDFLAG(IS_ANDROID)
#include "ui/native_theme/os_settings_provider_android.h"
#elif BUILDFLAG(IS_CHROMEOS)
#include "ui/native_theme/os_settings_provider_ash.h"
#elif BUILDFLAG(IS_MAC)
#include "ui/native_theme/os_settings_provider_mac.h"
#elif BUILDFLAG(IS_WIN)
#include "base/win/win_util.h"
#include "ui/native_theme/os_settings_provider_win.h"
#endif
namespace ui {
namespace {
std::forward_list<OsSettingsProvider*>& GetOsSettingsProviders(
OsSettingsProvider::PriorityLevel priority_level) {
#if DCHECK_IS_ON()
static base::NoDestructor<base::SequenceChecker> s_sequence_checker;
DCHECK_CALLED_ON_VALID_SEQUENCE(*s_sequence_checker);
#endif
static base::NoDestructor<std::array<
std::forward_list<OsSettingsProvider*>,
base::to_underlying(OsSettingsProvider::PriorityLevel::kLast) + 1>>
s_providers;
return (*s_providers)[base::to_underlying(priority_level)];
}
using CallbackList =
base::RepeatingCallbackList<OsSettingsProvider::SettingsChangedCallbackT>;
CallbackList* GetOsSettingsChangedCallbacks() {
#if DCHECK_IS_ON()
static base::NoDestructor<base::SequenceChecker> s_sequence_checker;
DCHECK_CALLED_ON_VALID_SEQUENCE(*s_sequence_checker);
#endif
static base::NoDestructor<CallbackList> s_callbacks;
return s_callbacks.get();
}
}
OsSettingsProvider::OsSettingsProvider(PriorityLevel priority_level)
: priority_level_(priority_level) {
GetOsSettingsProviders(priority_level_).push_front(this);
NotifyOnSettingsChanged();
}
OsSettingsProvider::~OsSettingsProvider() {
auto& providers = GetOsSettingsProviders(priority_level_);
const bool was_active = providers.front() == this;
providers.remove(this);
if (was_active && !providers.empty()) {
NotifyOnSettingsChanged();
}
}
OsSettingsProvider& OsSettingsProvider::Get() {
for (auto i = PriorityLevel::kLast; i > PriorityLevel::kProduction;
i = static_cast<PriorityLevel>(base::to_underlying(i) - 1)) {
if (const auto& providers = GetOsSettingsProviders(i); !providers.empty()) {
return *providers.front();
}
}
const auto& providers = GetOsSettingsProviders(PriorityLevel::kProduction);
if (providers.empty()) {
#if BUILDFLAG(IS_WIN)
if (!base::win::IsUser32AndGdi32Available()) {
static base::NoDestructor<OsSettingsProvider>
s_fallback_settings_provider(PriorityLevel::kProduction);
return *s_fallback_settings_provider;
}
#endif
static base::NoDestructor<OsSettingsProviderImpl> s_settings_provider;
CHECK(!providers.empty());
}
return *providers.front();
}
base::CallbackListSubscription
OsSettingsProvider::RegisterOsSettingsChangedCallback(
base::RepeatingCallback<SettingsChangedCallbackT> cb) {
return GetOsSettingsChangedCallbacks()->Add(std::move(cb));
}
bool OsSettingsProvider::DarkColorSchemeAvailable() const {
return true;
}
NativeTheme::PreferredColorScheme OsSettingsProvider::PreferredColorScheme()
const {
if (ForcedColorsActive()) {
if (const auto bg_color = Color(ColorId::kWindow)) {
const SkColor srgb_legacy = bg_color.value();
const auto [r, g, b] = gfx::SRGBLegacyToSRGB(SkColorGetR(srgb_legacy),
SkColorGetG(srgb_legacy),
SkColorGetB(srgb_legacy));
const auto [x, y, z] = gfx::SRGBToXYZD50(r, g, b);
const float lab_lightness = std::get<0>(gfx::XYZD50ToLab(x, y, z));
if (lab_lightness < 33.0f) {
return NativeTheme::PreferredColorScheme::kDark;
}
if (lab_lightness > 67.0f) {
return NativeTheme::PreferredColorScheme::kLight;
}
}
}
return NativeTheme::PreferredColorScheme::kNoPreference;
}
ColorProviderKey::UserColorSource OsSettingsProvider::PreferredColorSource()
const {
return ColorProviderKey::UserColorSource::kAccent;
}
NativeTheme::PreferredContrast OsSettingsProvider::PreferredContrast() const {
if (ForcedColorsActive()) {
if (const auto bg_color = Color(ColorId::kWindow),
fg_color = Color(ColorId::kWindowText);
bg_color.has_value() && fg_color.has_value()) {
const float contrast_ratio =
color_utils::GetContrastRatio(bg_color.value(), fg_color.value());
if (contrast_ratio >= 7) {
return NativeTheme::PreferredContrast::kMore;
}
return contrast_ratio <= 2.5 ? NativeTheme::PreferredContrast::kLess
: NativeTheme::PreferredContrast::kCustom;
}
}
return NativeTheme::PreferredContrast::kNoPreference;
}
bool OsSettingsProvider::PrefersReducedTransparency() const {
return false;
}
bool OsSettingsProvider::PrefersInvertedColors() const {
return false;
}
bool OsSettingsProvider::ForcedColorsActive() const {
return false;
}
std::optional<SkColor> OsSettingsProvider::AccentColor() const {
return std::nullopt;
}
std::optional<SkColor> OsSettingsProvider::Color(ColorId color_id) const {
return std::nullopt;
}
std::optional<ColorProviderKey::SchemeVariant>
OsSettingsProvider::SchemeVariant() const {
return std::nullopt;
}
base::TimeDelta OsSettingsProvider::CaretBlinkInterval() const {
return kDefaultCaretBlinkInterval;
}
void OsSettingsProvider::NotifyOnSettingsChanged(bool force_notify) {
if (&Get() == this) {
GetOsSettingsChangedCallbacks()->Notify(force_notify);
}
}
}