#include "ui/native_theme/native_theme_win.h"
#include <windows.h>
#include <stddef.h>
#include <uxtheme.h>
#include <vsstyle.h>
#include <vssym32.h>
#include "base/check.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "base/win/dark_mode_support.h"
#include "base/win/scoped_gdi_object.h"
#include "base/win/scoped_hdc.h"
#include "base/win/scoped_select_object.h"
#include "base/win/win_util.h"
#include "cc/paint/paint_canvas.h"
#include "cc/paint/paint_flags.h"
#include "skia/ext/platform_canvas.h"
#include "skia/ext/skia_utils_win.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkColorPriv.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkShader.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"
#include "ui/color/color_provider.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/gdi_util.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/native_theme/common_theme.h"
#if !defined(COLOR_MENUHIGHLIGHT)
#define COLOR_MENUHIGHLIGHT 29
#endif
namespace {
const int kSysColors[] = {
COLOR_BTNFACE, COLOR_BTNTEXT, COLOR_GRAYTEXT, COLOR_HIGHLIGHT,
COLOR_HIGHLIGHTTEXT, COLOR_HOTLIGHT, COLOR_MENUHIGHLIGHT, COLOR_SCROLLBAR,
COLOR_WINDOW, COLOR_WINDOWTEXT,
};
void SetCheckerboardShader(SkPaint* paint, const RECT& align_rect) {
const SkColor face = color_utils::GetSysSkColor(COLOR_3DFACE);
const SkColor highlight = color_utils::GetSysSkColor(COLOR_3DHILIGHT);
SkColor buffer[] = { face, highlight, highlight, face };
SkImageInfo info = SkImageInfo::MakeN32Premul(2, 2);
SkBitmap temp_bitmap;
temp_bitmap.installPixels(info, buffer, info.minRowBytes());
SkBitmap bitmap;
if (bitmap.tryAllocPixels(info))
temp_bitmap.readPixels(info, bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
SkMatrix local_matrix;
local_matrix.setTranslate(SkIntToScalar(align_rect.left),
SkIntToScalar(align_rect.top));
paint->setShader(bitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
SkSamplingOptions(), &local_matrix));
}
int ComputeAnimationProgress(int frame_width,
int object_width,
int pixels_per_second,
double animated_seconds) {
int animation_width = frame_width + object_width;
double interval = static_cast<double>(animation_width) / pixels_per_second;
double ratio = fmod(animated_seconds, interval) / interval;
return static_cast<int>(animation_width * ratio) - object_width;
}
class ScopedCreateDCWithBitmap {
public:
explicit ScopedCreateDCWithBitmap(base::win::ScopedCreateDC::Handle hdc)
: dc_(hdc) {}
ScopedCreateDCWithBitmap(const ScopedCreateDCWithBitmap&) = delete;
ScopedCreateDCWithBitmap& operator=(const ScopedCreateDCWithBitmap&) = delete;
~ScopedCreateDCWithBitmap() {
dc_.Close();
}
bool IsValid() const { return dc_.IsValid(); }
base::win::ScopedCreateDC::Handle Get() const { return dc_.Get(); }
bool SelectBitmap(base::win::ScopedBitmap::element_type handle) {
bitmap_.reset(handle);
if (!bitmap_.is_valid())
return false;
SelectObject(dc_.Get(), bitmap_.get());
return true;
}
private:
base::win::ScopedCreateDC dc_;
base::win::ScopedBitmap bitmap_;
};
base::win::RegKey OpenThemeRegKey(REGSAM access) {
base::win::RegKey hkcu_themes_regkey;
hkcu_themes_regkey.Open(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\"
L"Themes\\Personalize",
access);
return hkcu_themes_regkey;
}
}
namespace ui {
NativeTheme::SystemThemeColor SysColorToSystemThemeColor(int system_color) {
switch (system_color) {
case COLOR_BTNFACE:
return NativeTheme::SystemThemeColor::kButtonFace;
case COLOR_BTNTEXT:
return NativeTheme::SystemThemeColor::kButtonText;
case COLOR_GRAYTEXT:
return NativeTheme::SystemThemeColor::kGrayText;
case COLOR_HIGHLIGHT:
return NativeTheme::SystemThemeColor::kHighlight;
case COLOR_HIGHLIGHTTEXT:
return NativeTheme::SystemThemeColor::kHighlightText;
case COLOR_HOTLIGHT:
return NativeTheme::SystemThemeColor::kHotlight;
case COLOR_MENUHIGHLIGHT:
return NativeTheme::SystemThemeColor::kMenuHighlight;
case COLOR_SCROLLBAR:
return NativeTheme::SystemThemeColor::kScrollbar;
case COLOR_WINDOW:
return NativeTheme::SystemThemeColor::kWindow;
case COLOR_WINDOWTEXT:
return NativeTheme::SystemThemeColor::kWindowText;
default:
return NativeTheme::SystemThemeColor::kNotSupported;
}
}
NativeTheme* NativeTheme::GetInstanceForNativeUi() {
static base::NoDestructor<NativeThemeWin> s_native_theme(true, false);
return s_native_theme.get();
}
NativeTheme* NativeTheme::GetInstanceForDarkUI() {
static base::NoDestructor<NativeThemeWin> s_dark_native_theme(false, true);
return s_dark_native_theme.get();
}
bool NativeTheme::SystemDarkModeSupported() {
static bool system_supports_dark_mode =
([]() { return OpenThemeRegKey(KEY_READ).Valid(); })();
return system_supports_dark_mode;
}
void NativeThemeWin::CloseHandles() {
static_cast<NativeThemeWin*>(NativeTheme::GetInstanceForNativeUi())
->CloseHandlesInternal();
}
gfx::Size NativeThemeWin::GetPartSize(Part part,
State state,
const ExtraParams& extra) const {
switch (part) {
case kScrollbarDownArrow:
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
case kScrollbarUpArrow:
case kScrollbarHorizontalThumb:
case kScrollbarVerticalThumb:
case kScrollbarHorizontalTrack:
case kScrollbarVerticalTrack: {
int size = display::win::ScreenWin::GetSystemMetricsInDIP(SM_CXVSCROLL);
if (size == 0)
size = 17;
return gfx::Size(size, size);
}
default:
break;
}
int part_id = GetWindowsPart(part, state, extra);
int state_id = GetWindowsState(part, state, extra);
base::win::ScopedGetDC screen_dc(nullptr);
SIZE size;
HANDLE handle = GetThemeHandle(GetThemeName(part));
if (handle && SUCCEEDED(GetThemePartSize(handle, screen_dc, part_id, state_id,
nullptr, TS_TRUE, &size)))
return gfx::Size(size.cx, size.cy);
return (part == kCheckbox || part == kRadio) ?
gfx::Size(13, 13) : gfx::Size();
}
void NativeThemeWin::Paint(cc::PaintCanvas* canvas,
const ui::ColorProvider* color_provider,
Part part,
State state,
const gfx::Rect& rect,
const ExtraParams& extra,
ColorScheme color_scheme,
const absl::optional<SkColor>& accent_color) const {
if (rect.IsEmpty())
return;
switch (part) {
case kMenuPopupGutter:
PaintMenuGutter(canvas, color_provider, rect);
return;
case kMenuPopupSeparator:
PaintMenuSeparator(canvas, color_provider, extra.menu_separator);
return;
case kMenuPopupBackground:
PaintMenuBackground(canvas, color_provider, rect);
return;
case kMenuItemBackground:
CommonThemePaintMenuItemBackground(this, color_provider, canvas, state,
rect, extra.menu_item);
return;
default:
PaintIndirect(canvas, part, state, rect, extra);
return;
}
}
NativeThemeWin::NativeThemeWin(bool configure_web_instance,
bool should_only_use_dark_colors)
: NativeTheme(should_only_use_dark_colors),
supports_windows_dark_mode_(base::win::IsDarkModeAvailable()),
color_change_listener_(this) {
if (!should_only_use_dark_colors && !IsForcedDarkMode() &&
!IsForcedHighContrast() &&
base::SequencedTaskRunner::HasCurrentDefault()) {
hkcu_themes_regkey_ = OpenThemeRegKey(KEY_READ | KEY_NOTIFY);
if (hkcu_themes_regkey_.Valid()) {
UpdateDarkModeStatus();
RegisterThemeRegkeyObserver();
}
}
if (!IsForcedHighContrast()) {
set_forced_colors(IsUsingHighContrastThemeInternal());
}
UpdateSystemColors();
set_preferred_color_scheme(CalculatePreferredColorScheme());
SetPreferredContrast(CalculatePreferredContrast());
memset(theme_handles_, 0, sizeof(theme_handles_));
if (configure_web_instance)
ConfigureWebInstance();
}
void NativeThemeWin::ConfigureWebInstance() {
if (!IsForcedDarkMode() && !IsForcedHighContrast() &&
base::SequencedTaskRunner::HasCurrentDefault()) {
color_scheme_observer_ =
std::make_unique<NativeTheme::ColorSchemeNativeThemeObserver>(
NativeTheme::GetInstanceForWeb());
AddObserver(color_scheme_observer_.get());
}
NativeTheme* web_instance = NativeTheme::GetInstanceForWeb();
web_instance->set_use_dark_colors(ShouldUseDarkColors());
web_instance->set_forced_colors(InForcedColorsMode());
web_instance->set_preferred_color_scheme(GetPreferredColorScheme());
web_instance->SetPreferredContrast(GetPreferredContrast());
web_instance->set_system_colors(GetSystemColors());
}
NativeThemeWin::~NativeThemeWin() {
}
bool NativeThemeWin::IsUsingHighContrastThemeInternal() const {
HIGHCONTRAST result;
result.cbSize = sizeof(HIGHCONTRAST);
return SystemParametersInfo(SPI_GETHIGHCONTRAST, result.cbSize, &result, 0) &&
(result.dwFlags & HCF_HIGHCONTRASTON) == HCF_HIGHCONTRASTON;
}
void NativeThemeWin::CloseHandlesInternal() {
for (int i = 0; i < LAST; ++i) {
if (theme_handles_[i]) {
CloseThemeData(theme_handles_[i]);
theme_handles_[i] = nullptr;
}
}
}
void NativeThemeWin::OnSysColorChange() {
UpdateSystemColors();
if (!IsForcedHighContrast())
set_forced_colors(IsUsingHighContrastThemeInternal());
set_preferred_color_scheme(CalculatePreferredColorScheme());
SetPreferredContrast(CalculatePreferredContrast());
NotifyOnNativeThemeUpdated();
}
void NativeThemeWin::UpdateSystemColors() {
for (int sys_color : kSysColors)
system_colors_[SysColorToSystemThemeColor(sys_color)] =
color_utils::GetSysSkColor(sys_color);
}
void NativeThemeWin::PaintMenuSeparator(
cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
const MenuSeparatorExtraParams& params) const {
DCHECK(color_provider);
const gfx::RectF rect(*params.paint_rect);
gfx::PointF start = rect.CenterPoint();
gfx::PointF end = start;
if (params.type == ui::VERTICAL_SEPARATOR) {
start.set_y(rect.y());
end.set_y(rect.bottom());
} else {
start.set_x(rect.x());
end.set_x(rect.right());
}
cc::PaintFlags flags;
flags.setColor(color_provider->GetColor(kColorMenuSeparator));
canvas->drawLine(start.x(), start.y(), end.x(), end.y(), flags);
}
void NativeThemeWin::PaintMenuGutter(cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
const gfx::Rect& rect) const {
DCHECK(color_provider);
cc::PaintFlags flags;
flags.setColor(color_provider->GetColor(kColorMenuSeparator));
int position_x = rect.x() + rect.width() / 2;
canvas->drawLine(position_x, rect.y(), position_x, rect.bottom(), flags);
}
void NativeThemeWin::PaintMenuBackground(cc::PaintCanvas* canvas,
const ColorProvider* color_provider,
const gfx::Rect& rect) const {
DCHECK(color_provider);
cc::PaintFlags flags;
flags.setColor(color_provider->GetColor(kColorMenuBackground));
canvas->drawRect(gfx::RectToSkRect(rect), flags);
}
void NativeThemeWin::PaintDirect(SkCanvas* destination_canvas,
HDC hdc,
Part part,
State state,
const gfx::Rect& rect,
const ExtraParams& extra) const {
if (part == kScrollbarCorner) {
destination_canvas->drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
return;
}
RECT rect_win = rect.ToRECT();
if (part == kTrackbarTrack) {
constexpr int kChannelThickness = 4;
if (extra.trackbar.vertical) {
rect_win.top += (rect_win.bottom - rect_win.top - kChannelThickness) / 2;
rect_win.bottom = rect_win.top + kChannelThickness;
} else {
rect_win.left += (rect_win.right - rect_win.left - kChannelThickness) / 2;
rect_win.right = rect_win.left + kChannelThickness;
}
}
const HANDLE handle = GetThemeHandle(GetThemeName(part));
const int part_id = GetWindowsPart(part, state, extra);
const int state_id = GetWindowsState(part, state, extra);
if (handle) {
switch (part) {
case kMenuPopupArrow:
if (!extra.menu_arrow.pointing_right) {
PaintLeftMenuArrowThemed(hdc, handle, part_id, state_id, rect);
return;
}
[[fallthrough]];
case kCheckbox:
case kInnerSpinButton:
case kMenuCheck:
case kMenuCheckBackground:
case kMenuList:
case kProgressBar:
case kPushButton:
case kRadio:
case kScrollbarHorizontalTrack:
case kScrollbarVerticalTrack:
case kTabPanelBackground:
case kTrackbarThumb:
case kTrackbarTrack:
case kWindowResizeGripper:
DrawThemeBackground(handle, hdc, part_id, state_id, &rect_win, nullptr);
if (part == kProgressBar)
break;
return;
case kScrollbarDownArrow:
case kScrollbarHorizontalGripper:
case kScrollbarHorizontalThumb:
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
case kScrollbarUpArrow:
case kScrollbarVerticalGripper:
case kScrollbarVerticalThumb:
PaintScaledTheme(handle, hdc, part_id, state_id, rect);
return;
case kTextField:
break;
case kMenuItemBackground:
case kMenuPopupBackground:
case kMenuPopupGutter:
case kMenuPopupSeparator:
case kScrollbarCorner:
case kSliderTrack:
case kSliderThumb:
case kMaxPart:
NOTREACHED();
}
}
switch (part) {
case kCheckbox:
case kPushButton:
case kRadio:
PaintButtonClassic(hdc, part, state, &rect_win, extra.button);
return;
case kInnerSpinButton:
DrawFrameControl(hdc, &rect_win, DFC_SCROLL,
extra.inner_spin.classic_state);
return;
case kMenuCheck:
PaintFrameControl(
hdc, rect, DFC_MENU,
extra.menu_check.is_radio ? DFCS_MENUBULLET : DFCS_MENUCHECK,
extra.menu_check.is_selected, state);
return;
case kMenuList:
DrawFrameControl(hdc, &rect_win, DFC_SCROLL,
DFCS_SCROLLCOMBOBOX | extra.menu_list.classic_state);
return;
case kMenuPopupArrow:
PaintFrameControl(hdc, rect, DFC_MENU,
extra.menu_arrow.pointing_right ? DFCS_MENUARROW
: DFCS_MENUARROWRIGHT,
extra.menu_arrow.is_selected, state);
return;
case kProgressBar: {
RECT value_rect = gfx::Rect(extra.progress_bar.value_rect_x,
extra.progress_bar.value_rect_y,
extra.progress_bar.value_rect_width,
extra.progress_bar.value_rect_height)
.ToRECT();
if (handle) {
PaintProgressBarOverlayThemed(hdc, handle, &rect_win, &value_rect,
extra.progress_bar);
} else {
FillRect(hdc, &rect_win, GetSysColorBrush(COLOR_BTNFACE));
FillRect(hdc, &value_rect, GetSysColorBrush(COLOR_BTNSHADOW));
DrawEdge(hdc, &rect_win, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
}
return;
}
case kScrollbarDownArrow:
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
case kScrollbarUpArrow:
PaintScrollbarArrowClassic(hdc, part, state, &rect_win);
return;
case kScrollbarHorizontalThumb:
case kScrollbarVerticalThumb:
DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_MIDDLE);
return;
case kScrollbarHorizontalTrack:
case kScrollbarVerticalTrack:
PaintScrollbarTrackClassic(destination_canvas, hdc, &rect_win,
extra.scrollbar_track);
return;
case kTabPanelBackground:
FillRect(hdc, &rect_win, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1));
return;
case kTextField: {
base::win::ScopedGDIObject<HBRUSH> bg_brush(CreateSolidBrush(
skia::SkColorToCOLORREF(extra.text_field.background_color)));
if (handle) {
PaintTextFieldThemed(hdc, handle, bg_brush.get(), part_id, state_id,
&rect_win, extra.text_field);
} else {
PaintTextFieldClassic(hdc, bg_brush.get(), &rect_win, extra.text_field);
}
return;
}
case kTrackbarThumb:
if (extra.trackbar.vertical) {
DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE);
} else {
PaintHorizontalTrackbarThumbClassic(destination_canvas, hdc, rect_win,
extra.trackbar);
}
return;
case kTrackbarTrack:
DrawEdge(hdc, &rect_win, EDGE_SUNKEN, BF_RECT);
return;
case kWindowResizeGripper:
DrawFrameControl(hdc, &rect_win, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
return;
case kMenuCheckBackground:
case kScrollbarHorizontalGripper:
case kScrollbarVerticalGripper:
return;
case kMenuItemBackground:
case kMenuPopupBackground:
case kMenuPopupGutter:
case kMenuPopupSeparator:
case kScrollbarCorner:
case kSliderTrack:
case kSliderThumb:
case kMaxPart:
NOTREACHED();
}
}
bool NativeThemeWin::SupportsNinePatch(Part part) const {
return false;
}
gfx::Size NativeThemeWin::GetNinePatchCanvasSize(Part part) const {
NOTREACHED() << "NativeThemeWin doesn't support nine-patch resources.";
return gfx::Size();
}
gfx::Rect NativeThemeWin::GetNinePatchAperture(Part part) const {
NOTREACHED() << "NativeThemeWin doesn't support nine-patch resources.";
return gfx::Rect();
}
bool NativeThemeWin::ShouldUseDarkColors() const {
if (InForcedColorsMode() && !IsForcedDarkMode())
return false;
return NativeTheme::ShouldUseDarkColors();
}
NativeTheme::PreferredColorScheme
NativeThemeWin::CalculatePreferredColorScheme() const {
if (!InForcedColorsMode())
return NativeTheme::CalculatePreferredColorScheme();
SkColor bg_color = system_colors_[SystemThemeColor::kWindow];
float luminance = color_utils::GetRelativeLuminance(bg_color);
if (luminance < 0.33)
return NativeTheme::PreferredColorScheme::kDark;
return NativeTheme::PreferredColorScheme::kLight;
}
NativeTheme::PreferredContrast NativeThemeWin::CalculatePreferredContrast()
const {
if (!InForcedColorsMode())
return NativeTheme::CalculatePreferredContrast();
SkColor bg_color = system_colors_[SystemThemeColor::kWindow];
SkColor fg_color = system_colors_[SystemThemeColor::kWindowText];
float contrast_ratio = color_utils::GetContrastRatio(bg_color, fg_color);
if (contrast_ratio >= 7)
return NativeTheme::PreferredContrast::kMore;
return contrast_ratio <= 2.5 ? NativeTheme::PreferredContrast::kLess
: NativeTheme::PreferredContrast::kCustom;
}
NativeTheme::ColorScheme NativeThemeWin::GetDefaultSystemColorScheme() const {
return InForcedColorsMode() ? ColorScheme::kPlatformHighContrast
: NativeTheme::GetDefaultSystemColorScheme();
}
void NativeThemeWin::PaintIndirect(cc::PaintCanvas* destination_canvas,
Part part,
State state,
const gfx::Rect& rect,
const ExtraParams& extra) const {
if (!base::win::IsUser32AndGdi32Available())
return;
ScopedCreateDCWithBitmap offscreen_hdc(CreateCompatibleDC(nullptr));
if (!offscreen_hdc.IsValid())
return;
skia::InitializeDC(offscreen_hdc.Get());
HRGN clip = CreateRectRgn(0, 0, rect.width(), rect.height());
if ((SelectClipRgn(offscreen_hdc.Get(), clip) == ERROR) ||
!DeleteObject(clip)) {
return;
}
base::win::ScopedBitmap hbitmap = skia::CreateHBitmapXRGB8888(
rect.width(), rect.height(), nullptr, nullptr);
if (!offscreen_hdc.SelectBitmap(hbitmap.release()))
return;
sk_sp<SkSurface> offscreen_surface =
skia::MapPlatformSurface(offscreen_hdc.Get());
if (!offscreen_surface)
return;
SkCanvas* offscreen_canvas = offscreen_surface->getCanvas();
DCHECK(offscreen_canvas);
constexpr SkColor placeholder = SkColorSetARGB(1, 0, 0, 0);
offscreen_canvas->clear(placeholder);
gfx::Rect adjusted_rect(rect.size());
ExtraParams adjusted_extra(extra);
switch (part) {
case kProgressBar:
adjusted_extra.progress_bar.value_rect_x = 0;
adjusted_extra.progress_bar.value_rect_y = 0;
break;
case kScrollbarHorizontalTrack:
case kScrollbarVerticalTrack:
adjusted_extra.scrollbar_track.track_x = 0;
adjusted_extra.scrollbar_track.track_y = 0;
break;
default:
break;
}
PaintDirect(offscreen_canvas, offscreen_hdc.Get(), part, state,
adjusted_rect, adjusted_extra);
SkBitmap offscreen_bitmap = skia::MapPlatformBitmap(offscreen_hdc.Get());
const SkPMColor placeholder_value = SkPreMultiplyColor(placeholder);
const int pixel_count = rect.width() * rect.height();
SkPMColor* pixels = offscreen_bitmap.getAddr32(0, 0);
for (int i = 0; i < pixel_count; i++) {
if (pixels[i] == placeholder_value) {
pixels[i] = SkPackARGB32(0, 0, 0, 0);
} else if (SkGetPackedA32(pixels[i]) == 0) {
pixels[i] = SkPackARGB32(0xFF,
SkGetPackedR32(pixels[i]),
SkGetPackedG32(pixels[i]),
SkGetPackedB32(pixels[i]));
}
}
destination_canvas->drawImage(
cc::PaintImage::CreateFromBitmap(std::move(offscreen_bitmap)), rect.x(),
rect.y());
}
void NativeThemeWin::PaintButtonClassic(HDC hdc,
Part part,
State state,
RECT* rect,
const ButtonExtraParams& extra) const {
int classic_state = extra.classic_state;
switch (part) {
case kCheckbox:
classic_state |= DFCS_BUTTONCHECK;
break;
case kPushButton:
classic_state |= DFCS_BUTTONRADIO;
break;
case kRadio:
classic_state |= DFCS_BUTTONPUSH;
break;
default:
NOTREACHED();
break;
}
if (state == kDisabled)
classic_state |= DFCS_INACTIVE;
else if (state == kPressed)
classic_state |= DFCS_PUSHED;
if (extra.checked)
classic_state |= DFCS_CHECKED;
if ((part == kPushButton) && ((state == kPressed) || extra.is_default)) {
HBRUSH brush = GetSysColorBrush(COLOR_3DDKSHADOW);
if (brush) {
FrameRect(hdc, rect, brush);
InflateRect(rect, -1, -1);
}
}
DrawFrameControl(hdc, rect, DFC_BUTTON, classic_state);
if ((part == kPushButton) && extra.is_default) {
InflateRect(rect, -GetSystemMetrics(SM_CXEDGE),
-GetSystemMetrics(SM_CYEDGE));
DrawFocusRect(hdc, rect);
}
if ((part == kCheckbox) && extra.indeterminate) {
RECT inner_rect = *rect;
int padding = (inner_rect.right - inner_rect.left) * 4 / 13;
InflateRect(&inner_rect, -padding, -padding);
int color_index = (state == kDisabled) ? COLOR_GRAYTEXT : COLOR_WINDOWTEXT;
FillRect(hdc, &inner_rect, GetSysColorBrush(color_index));
}
}
void NativeThemeWin::PaintLeftMenuArrowThemed(HDC hdc,
HANDLE handle,
int part_id,
int state_id,
const gfx::Rect& rect) const {
base::win::ScopedCreateDC mem_dc(CreateCompatibleDC(hdc));
base::win::ScopedBitmap mem_bitmap(
CreateCompatibleBitmap(hdc, rect.width(), rect.height()));
base::win::ScopedSelectObject select_bitmap(mem_dc.Get(), mem_bitmap.get());
StretchBlt(mem_dc.Get(), 0, 0, rect.width(), rect.height(), hdc,
rect.right() - 1, rect.y(), -rect.width(), rect.height(), SRCCOPY);
RECT theme_rect = {0, 0, rect.width(), rect.height()};
DrawThemeBackground(handle, mem_dc.Get(), part_id, state_id, &theme_rect,
nullptr);
StretchBlt(hdc, rect.x(), rect.y(), rect.width(), rect.height(), mem_dc.Get(),
rect.width() - 1, 0, -rect.width(), rect.height(), SRCCOPY);
}
void NativeThemeWin::PaintScrollbarArrowClassic(HDC hdc,
Part part,
State state,
RECT* rect) const {
int classic_state = DFCS_SCROLLDOWN;
switch (part) {
case kScrollbarDownArrow:
break;
case kScrollbarLeftArrow:
classic_state = DFCS_SCROLLLEFT;
break;
case kScrollbarRightArrow:
classic_state = DFCS_SCROLLRIGHT;
break;
case kScrollbarUpArrow:
classic_state = DFCS_SCROLLUP;
break;
default:
NOTREACHED();
break;
}
switch (state) {
case kDisabled:
classic_state |= DFCS_INACTIVE;
break;
case kHovered:
classic_state |= DFCS_HOT;
break;
case kNormal:
break;
case kPressed:
classic_state |= DFCS_PUSHED;
break;
case kNumStates:
NOTREACHED();
break;
}
DrawFrameControl(hdc, rect, DFC_SCROLL, classic_state);
}
void NativeThemeWin::PaintScrollbarTrackClassic(
SkCanvas* canvas,
HDC hdc,
RECT* rect,
const ScrollbarTrackExtraParams& extra) const {
if ((system_colors_[SystemThemeColor::kScrollbar] !=
system_colors_[SystemThemeColor::kButtonFace]) &&
(system_colors_[SystemThemeColor::kScrollbar] !=
system_colors_[SystemThemeColor::kWindow])) {
FillRect(hdc, rect, reinterpret_cast<HBRUSH>(COLOR_SCROLLBAR + 1));
} else {
SkPaint paint;
RECT align_rect = gfx::Rect(extra.track_x, extra.track_y, extra.track_width,
extra.track_height)
.ToRECT();
SetCheckerboardShader(&paint, align_rect);
canvas->drawIRect(skia::RECTToSkIRect(*rect), paint);
}
if (extra.classic_state & DFCS_PUSHED)
InvertRect(hdc, rect);
}
void NativeThemeWin::PaintHorizontalTrackbarThumbClassic(
SkCanvas* canvas,
HDC hdc,
const RECT& rect,
const TrackbarExtraParams& extra) const {
RECT top_section = rect;
RECT bottom_section = rect;
top_section.bottom -= ((bottom_section.right - bottom_section.left) / 2);
bottom_section.top = top_section.bottom;
DrawEdge(hdc, &top_section, EDGE_RAISED,
BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
RECT& left_half = bottom_section;
RECT right_half = bottom_section;
right_half.left += ((bottom_section.right - bottom_section.left) / 2);
left_half.right = right_half.left;
DrawEdge(hdc, &left_half, EDGE_RAISED,
BF_DIAGONAL_ENDTOPLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
DrawEdge(hdc, &right_half, EDGE_RAISED,
BF_DIAGONAL_ENDBOTTOMLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
if (extra.classic_state & DFCS_PUSHED) {
SkPaint paint;
SetCheckerboardShader(&paint, rect);
canvas->drawIRect(skia::RECTToSkIRect(top_section), paint);
SkScalar left_triangle_top = SkIntToScalar(left_half.top);
SkScalar left_triangle_right = SkIntToScalar(left_half.right);
SkPath left_triangle;
left_triangle.moveTo(SkIntToScalar(left_half.left), left_triangle_top);
left_triangle.lineTo(left_triangle_right, left_triangle_top);
left_triangle.lineTo(left_triangle_right, SkIntToScalar(left_half.bottom));
left_triangle.close();
canvas->drawPath(left_triangle, paint);
SkScalar right_triangle_left = SkIntToScalar(right_half.left);
SkScalar right_triangle_top = SkIntToScalar(right_half.top);
SkPath right_triangle;
right_triangle.moveTo(right_triangle_left, right_triangle_top);
right_triangle.lineTo(SkIntToScalar(right_half.right), right_triangle_top);
right_triangle.lineTo(right_triangle_left,
SkIntToScalar(right_half.bottom));
right_triangle.close();
canvas->drawPath(right_triangle, paint);
}
}
void NativeThemeWin::PaintProgressBarOverlayThemed(
HDC hdc,
HANDLE handle,
RECT* bar_rect,
RECT* value_rect,
const ProgressBarExtraParams& extra) const {
constexpr int kDeterminateOverlayWidth = 120;
constexpr int kDeterminateOverlayPixelsPerSecond = 300;
constexpr int kIndeterminateOverlayWidth = 120;
constexpr int kIndeterminateOverlayPixelsPerSecond = 175;
int bar_width = bar_rect->right - bar_rect->left;
if (!extra.determinate) {
int width_with_margin = bar_width + kIndeterminateOverlayPixelsPerSecond;
int overlay_width = kIndeterminateOverlayWidth;
RECT overlay_rect = *bar_rect;
overlay_rect.left += ComputeAnimationProgress(
width_with_margin, overlay_width, kIndeterminateOverlayPixelsPerSecond,
extra.animated_seconds);
overlay_rect.right = overlay_rect.left + overlay_width;
DrawThemeBackground(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect,
bar_rect);
return;
}
const bool mirror = bar_rect->right == value_rect->right &&
bar_rect->left != value_rect->left;
const DTBGOPTS value_draw_options = {sizeof(DTBGOPTS),
mirror ? DTBG_MIRRORDC : 0u, *bar_rect};
DrawThemeBackgroundEx(handle, hdc, PP_FILL, 0, value_rect,
&value_draw_options);
RECT overlay_rect = *value_rect;
overlay_rect.left += ComputeAnimationProgress(
bar_width, kDeterminateOverlayWidth, kDeterminateOverlayPixelsPerSecond,
extra.animated_seconds);
overlay_rect.right = overlay_rect.left + kDeterminateOverlayWidth;
DrawThemeBackground(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect,
value_rect);
}
void NativeThemeWin::PaintTextFieldThemed(
HDC hdc,
HANDLE handle,
HBRUSH bg_brush,
int part_id,
int state_id,
RECT* rect,
const TextFieldExtraParams& extra) const {
static constexpr DTBGOPTS kOmitBorderOptions = {
sizeof(DTBGOPTS), DTBG_OMITBORDER, {0, 0, 0, 0}};
DrawThemeBackgroundEx(handle, hdc, part_id, state_id, rect,
extra.draw_edges ? nullptr : &kOmitBorderOptions);
if (extra.fill_content_area) {
RECT content_rect;
GetThemeBackgroundContentRect(handle, hdc, part_id, state_id, rect,
&content_rect);
FillRect(hdc, &content_rect, bg_brush);
}
}
void NativeThemeWin::PaintTextFieldClassic(
HDC hdc,
HBRUSH bg_brush,
RECT* rect,
const TextFieldExtraParams& extra) const {
if (extra.draw_edges)
DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
if (extra.fill_content_area) {
if (extra.classic_state & DFCS_INACTIVE)
bg_brush = reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1);
FillRect(hdc, rect, bg_brush);
}
}
void NativeThemeWin::PaintScaledTheme(HANDLE theme,
HDC hdc,
int part_id,
int state_id,
const gfx::Rect& rect) const {
XFORM save_transform;
if (GetWorldTransform(hdc, &save_transform)) {
float scale = save_transform.eM11;
if (scale != 1 && save_transform.eM12 == 0) {
ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);
gfx::Rect scaled_rect = gfx::ScaleToEnclosedRect(rect, scale);
scaled_rect.Offset(save_transform.eDx, save_transform.eDy);
RECT bounds = scaled_rect.ToRECT();
DrawThemeBackground(theme, hdc, part_id, state_id, &bounds, nullptr);
SetWorldTransform(hdc, &save_transform);
return;
}
}
RECT bounds = rect.ToRECT();
DrawThemeBackground(theme, hdc, part_id, state_id, &bounds, nullptr);
}
NativeThemeWin::ThemeName NativeThemeWin::GetThemeName(Part part) {
switch (part) {
case kCheckbox:
case kPushButton:
case kRadio:
return BUTTON;
case kMenuList:
case kMenuCheck:
case kMenuCheckBackground:
case kMenuPopupArrow:
case kMenuPopupGutter:
case kMenuPopupSeparator:
return MENU;
case kProgressBar:
return PROGRESS;
case kScrollbarDownArrow:
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
case kScrollbarUpArrow:
case kScrollbarHorizontalGripper:
case kScrollbarVerticalGripper:
case kScrollbarHorizontalThumb:
case kScrollbarVerticalThumb:
case kScrollbarHorizontalTrack:
case kScrollbarVerticalTrack:
return SCROLLBAR;
case kInnerSpinButton:
return SPIN;
case kWindowResizeGripper:
return STATUS;
case kTabPanelBackground:
return TAB;
case kTextField:
return TEXTFIELD;
case kTrackbarThumb:
case kTrackbarTrack:
return TRACKBAR;
case kMenuPopupBackground:
case kMenuItemBackground:
case kScrollbarCorner:
case kSliderTrack:
case kSliderThumb:
case kMaxPart:
NOTREACHED();
}
return LAST;
}
int NativeThemeWin::GetWindowsPart(Part part,
State state,
const ExtraParams& extra) {
switch (part) {
case kCheckbox:
return BP_CHECKBOX;
case kPushButton:
return BP_PUSHBUTTON;
case kRadio:
return BP_RADIOBUTTON;
case kMenuList:
return CP_DROPDOWNBUTTON;
case kTextField:
return EP_EDITTEXT;
case kMenuCheck:
return MENU_POPUPCHECK;
case kMenuCheckBackground:
return MENU_POPUPCHECKBACKGROUND;
case kMenuPopupGutter:
return MENU_POPUPGUTTER;
case kMenuPopupSeparator:
return MENU_POPUPSEPARATOR;
case kMenuPopupArrow:
return MENU_POPUPSUBMENU;
case kProgressBar:
return PP_BAR;
case kScrollbarDownArrow:
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
case kScrollbarUpArrow:
return SBP_ARROWBTN;
case kScrollbarHorizontalGripper:
return SBP_GRIPPERHORZ;
case kScrollbarVerticalGripper:
return SBP_GRIPPERVERT;
case kScrollbarHorizontalThumb:
return SBP_THUMBBTNHORZ;
case kScrollbarVerticalThumb:
return SBP_THUMBBTNVERT;
case kScrollbarHorizontalTrack:
return extra.scrollbar_track.is_upper ? SBP_UPPERTRACKHORZ
: SBP_LOWERTRACKHORZ;
case kScrollbarVerticalTrack:
return extra.scrollbar_track.is_upper ? SBP_UPPERTRACKVERT
: SBP_LOWERTRACKVERT;
case kWindowResizeGripper:
return SP_GRIPPER;
case kInnerSpinButton:
return extra.inner_spin.spin_up ? SPNP_UP : SPNP_DOWN;
case kTabPanelBackground:
return TABP_BODY;
case kTrackbarThumb:
return extra.trackbar.vertical ? TKP_THUMBVERT : TKP_THUMBBOTTOM;
case kTrackbarTrack:
return extra.trackbar.vertical ? TKP_TRACKVERT : TKP_TRACK;
case kMenuPopupBackground:
case kMenuItemBackground:
case kScrollbarCorner:
case kSliderTrack:
case kSliderThumb:
case kMaxPart:
NOTREACHED();
}
return 0;
}
int NativeThemeWin::GetWindowsState(Part part,
State state,
const ExtraParams& extra) {
switch (part) {
case kScrollbarDownArrow:
switch (state) {
case kDisabled:
return ABS_DOWNDISABLED;
case kHovered:
return extra.scrollbar_arrow.is_hovering ? ABS_DOWNHOVER
: ABS_DOWNHOT;
case kNormal:
return ABS_DOWNNORMAL;
case kPressed:
return ABS_DOWNPRESSED;
case kNumStates:
NOTREACHED();
return 0;
}
case kScrollbarLeftArrow:
switch (state) {
case kDisabled:
return ABS_LEFTDISABLED;
case kHovered:
return extra.scrollbar_arrow.is_hovering ? ABS_LEFTHOVER
: ABS_LEFTHOT;
case kNormal:
return ABS_LEFTNORMAL;
case kPressed:
return ABS_LEFTPRESSED;
case kNumStates:
NOTREACHED();
return 0;
}
case kScrollbarRightArrow:
switch (state) {
case kDisabled:
return ABS_RIGHTDISABLED;
case kHovered:
return extra.scrollbar_arrow.is_hovering ? ABS_RIGHTHOVER
: ABS_RIGHTHOT;
case kNormal:
return ABS_RIGHTNORMAL;
case kPressed:
return ABS_RIGHTPRESSED;
case kNumStates:
NOTREACHED();
return 0;
}
case kScrollbarUpArrow:
switch (state) {
case kDisabled:
return ABS_UPDISABLED;
case kHovered:
return extra.scrollbar_arrow.is_hovering ? ABS_UPHOVER : ABS_UPHOT;
case kNormal:
return ABS_UPNORMAL;
case kPressed:
return ABS_UPPRESSED;
case kNumStates:
NOTREACHED();
return 0;
}
case kCheckbox: {
const ButtonExtraParams& button = extra.button;
switch (state) {
case kDisabled:
return button.checked
? CBS_CHECKEDDISABLED
: (button.indeterminate ? CBS_MIXEDDISABLED
: CBS_UNCHECKEDDISABLED);
case kHovered:
return button.checked
? CBS_CHECKEDHOT
: (button.indeterminate ? CBS_MIXEDHOT : CBS_UNCHECKEDHOT);
case kNormal:
return button.checked ? CBS_CHECKEDNORMAL
: (button.indeterminate ? CBS_MIXEDNORMAL
: CBS_UNCHECKEDNORMAL);
case kPressed:
return button.checked ? CBS_CHECKEDPRESSED
: (button.indeterminate ? CBS_MIXEDPRESSED
: CBS_UNCHECKEDPRESSED);
case kNumStates:
NOTREACHED();
return 0;
}
}
case kMenuList:
switch (state) {
case kDisabled:
return CBXS_DISABLED;
case kHovered:
return CBXS_HOT;
case kNormal:
return CBXS_NORMAL;
case kPressed:
return CBXS_PRESSED;
case kNumStates:
NOTREACHED();
return 0;
}
case kTextField:
switch (state) {
case kDisabled:
return ETS_DISABLED;
case kHovered:
return ETS_HOT;
case kNormal:
if (extra.text_field.is_read_only)
return ETS_READONLY;
return extra.text_field.is_focused ? ETS_FOCUSED : ETS_NORMAL;
case kPressed:
return ETS_SELECTED;
case kNumStates:
NOTREACHED();
return 0;
}
case kMenuPopupArrow:
return (state == kDisabled) ? MSM_DISABLED : MSM_NORMAL;
case kMenuCheck:
if (state == kDisabled) {
return extra.menu_check.is_radio ? MC_BULLETDISABLED
: MC_CHECKMARKDISABLED;
}
return extra.menu_check.is_radio ? MC_BULLETNORMAL : MC_CHECKMARKNORMAL;
case kMenuCheckBackground:
return (state == kDisabled) ? MCB_DISABLED : MCB_NORMAL;
case kPushButton:
switch (state) {
case kDisabled:
return PBS_DISABLED;
case kHovered:
return PBS_HOT;
case kNormal:
return extra.button.is_default ? PBS_DEFAULTED : PBS_NORMAL;
case kPressed:
return PBS_PRESSED;
case kNumStates:
NOTREACHED();
return 0;
}
case kRadio: {
const ButtonExtraParams& button = extra.button;
switch (state) {
case kDisabled:
return button.checked ? RBS_CHECKEDDISABLED : RBS_UNCHECKEDDISABLED;
case kHovered:
return button.checked ? RBS_CHECKEDHOT : RBS_UNCHECKEDHOT;
case kNormal:
return button.checked ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL;
case kPressed:
return button.checked ? RBS_CHECKEDPRESSED : RBS_UNCHECKEDPRESSED;
case kNumStates:
NOTREACHED();
return 0;
}
}
case kScrollbarHorizontalGripper:
case kScrollbarVerticalGripper:
case kScrollbarHorizontalThumb:
case kScrollbarVerticalThumb:
if ((state == kHovered) && !extra.scrollbar_thumb.is_hovering)
return SCRBS_HOT;
[[fallthrough]];
case kScrollbarHorizontalTrack:
case kScrollbarVerticalTrack:
switch (state) {
case kDisabled:
return SCRBS_DISABLED;
case kHovered:
return SCRBS_HOVER;
case kNormal:
return SCRBS_NORMAL;
case kPressed:
return SCRBS_PRESSED;
case kNumStates:
NOTREACHED();
return 0;
}
case kTrackbarThumb:
case kTrackbarTrack:
switch (state) {
case kDisabled:
return TUS_DISABLED;
case kHovered:
return TUS_HOT;
case kNormal:
return TUS_NORMAL;
case kPressed:
return TUS_PRESSED;
case kNumStates:
NOTREACHED();
return 0;
}
case kInnerSpinButton:
switch (state) {
case kDisabled:
return extra.inner_spin.spin_up ? static_cast<int>(UPS_DISABLED)
: static_cast<int>(DNS_DISABLED);
case kHovered:
return extra.inner_spin.spin_up ? static_cast<int>(UPS_HOT)
: static_cast<int>(DNS_HOT);
case kNormal:
return extra.inner_spin.spin_up ? static_cast<int>(UPS_NORMAL)
: static_cast<int>(DNS_NORMAL);
case kPressed:
return extra.inner_spin.spin_up ? static_cast<int>(UPS_PRESSED)
: static_cast<int>(DNS_PRESSED);
case kNumStates:
NOTREACHED();
return 0;
}
case kMenuPopupGutter:
case kMenuPopupSeparator:
case kProgressBar:
case kTabPanelBackground:
case kWindowResizeGripper:
switch (state) {
case kDisabled:
case kHovered:
case kNormal:
case kPressed:
return 0;
case kNumStates:
NOTREACHED();
return 0;
}
case kMenuPopupBackground:
case kMenuItemBackground:
case kScrollbarCorner:
case kSliderTrack:
case kSliderThumb:
case kMaxPart:
NOTREACHED();
}
return 0;
}
HRESULT NativeThemeWin::PaintFrameControl(HDC hdc,
const gfx::Rect& rect,
UINT type,
UINT state,
bool is_selected,
State control_state) const {
const int width = rect.width();
const int height = rect.height();
base::win::ScopedBitmap mask_bitmap(CreateBitmap(width, height, 1, 1, NULL));
if (mask_bitmap == NULL)
return E_OUTOFMEMORY;
base::win::ScopedCreateDC bitmap_dc(CreateCompatibleDC(NULL));
base::win::ScopedSelectObject select_bitmap(bitmap_dc.Get(),
mask_bitmap.get());
RECT local_rect = { 0, 0, width, height };
DrawFrameControl(bitmap_dc.Get(), &local_rect, type, state);
int bg_color_key = COLOR_MENU;
int text_color_key = COLOR_MENUTEXT;
switch (control_state) {
case kDisabled:
bg_color_key = is_selected ? COLOR_HIGHLIGHT : COLOR_MENU;
text_color_key = COLOR_GRAYTEXT;
break;
case kHovered:
bg_color_key = COLOR_HIGHLIGHT;
text_color_key = COLOR_HIGHLIGHTTEXT;
break;
case kNormal:
break;
case kPressed:
case kNumStates:
NOTREACHED();
break;
}
COLORREF old_bg_color = SetBkColor(hdc, GetSysColor(bg_color_key));
COLORREF old_text_color = SetTextColor(hdc, GetSysColor(text_color_key));
BitBlt(hdc, rect.x(), rect.y(), width, height, bitmap_dc.Get(), 0, 0,
SRCCOPY);
SetBkColor(hdc, old_bg_color);
SetTextColor(hdc, old_text_color);
return S_OK;
}
HANDLE NativeThemeWin::GetThemeHandle(ThemeName theme_name) const {
if (theme_name < 0 || theme_name >= LAST)
return nullptr;
if (theme_handles_[theme_name])
return theme_handles_[theme_name];
HANDLE handle = nullptr;
switch (theme_name) {
case BUTTON:
handle = OpenThemeData(nullptr, L"Button");
break;
case LIST:
handle = OpenThemeData(nullptr, L"Listview");
break;
case MENU:
handle = OpenThemeData(nullptr, L"Menu");
break;
case MENULIST:
handle = OpenThemeData(nullptr, L"Combobox");
break;
case SCROLLBAR:
handle = OpenThemeData(nullptr, supports_windows_dark_mode_
? L"Explorer::Scrollbar"
: L"Scrollbar");
break;
case STATUS:
handle = OpenThemeData(nullptr, L"Status");
break;
case TAB:
handle = OpenThemeData(nullptr, L"Tab");
break;
case TEXTFIELD:
handle = OpenThemeData(nullptr, L"Edit");
break;
case TRACKBAR:
handle = OpenThemeData(nullptr, L"Trackbar");
break;
case WINDOW:
handle = OpenThemeData(nullptr, L"Window");
break;
case PROGRESS:
handle = OpenThemeData(nullptr, L"Progress");
break;
case SPIN:
handle = OpenThemeData(nullptr, L"Spin");
break;
case LAST:
NOTREACHED();
break;
}
theme_handles_[theme_name] = handle;
return handle;
}
void NativeThemeWin::RegisterThemeRegkeyObserver() {
DCHECK(hkcu_themes_regkey_.Valid());
hkcu_themes_regkey_.StartWatching(base::BindOnce(
[](NativeThemeWin* native_theme) {
native_theme->UpdateDarkModeStatus();
native_theme->RegisterThemeRegkeyObserver();
},
base::Unretained(this)));
}
void NativeThemeWin::UpdateDarkModeStatus() {
bool dark_mode_enabled = false;
if (hkcu_themes_regkey_.Valid()) {
DWORD apps_use_light_theme = 1;
hkcu_themes_regkey_.ReadValueDW(L"AppsUseLightTheme",
&apps_use_light_theme);
dark_mode_enabled = (apps_use_light_theme == 0);
}
set_use_dark_colors(dark_mode_enabled);
set_preferred_color_scheme(CalculatePreferredColorScheme());
CloseHandlesInternal();
NotifyOnNativeThemeUpdated();
}
}