/*
* Rufus: The Reliable USB Formatting Utility
* Dark mode UI implementation
* Copyright © 2025 ozone10
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/* Memory leaks detection - define _CRTDBG_MAP_ALLOC as preprocessor macro */
#ifdef _CRTDBG_MAP_ALLOC
#include
#include
#endif
#include
#include
#include
#include
#include
#include
#include "rufus.h"
#include "ui.h"
#include "settings.h"
#include "darkmode.h"
PF_TYPE_DECL(WINAPI, BOOL, AllowDarkModeForWindow, (HWND, BOOL));
PF_TYPE_DECL(WINAPI, PreferredAppMode, SetPreferredAppMode, (PreferredAppMode));
PF_TYPE_DECL(WINAPI, VOID, FlushMenuThemes, (VOID));
PF_TYPE_DECL(WINAPI, BOOL, SetWindowCompositionAttribute, (HWND, WINDOWCOMPOSITIONATTRIBDATA*));
BOOL is_darkmode_enabled = FALSE;
static COLORREF color_accent = TOOLBAR_ICON_COLOR;
static inline BOOL IsAtLeastWin10Build(DWORD buildNumber)
{
OSVERSIONINFOEXW osvi = { 0 };
if (!IsWindows10OrGreater())
return FALSE;
osvi.dwOSVersionInfoSize = sizeof(osvi);
osvi.dwBuildNumber = buildNumber;
return VerifyVersionInfoW(&osvi, VER_BUILDNUMBER, VerSetConditionMask(0, VER_BUILDNUMBER, VER_GREATER_EQUAL));
}
static inline BOOL IsAtLeastWin10(void)
{
return IsAtLeastWin10Build(WIN10_1809);
}
static inline BOOL IsAtLeastWin11(void)
{
return IsAtLeastWin10Build(WIN11_21H2);
}
static inline BOOL IsHighContrast(void)
{
HIGHCONTRASTW highContrast = { 0 };
highContrast.cbSize = sizeof(HIGHCONTRASTW);
if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRASTW), &highContrast, FALSE))
return (highContrast.dwFlags & HCF_HIGHCONTRASTON) == HCF_HIGHCONTRASTON;
return FALSE;
}
BOOL GetDarkModeFromRegistry(void)
{
DWORD data = 0, size = sizeof(data);
if (!IsAtLeastWin10() || IsHighContrast())
return FALSE;
// 0 = follow system, 1 = dark mode always, anything else = light mode always
switch (ReadSetting32(SETTING_DARK_MODE)) {
case 0:
if (RegGetValueA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
"AppsUseLightTheme", RRF_RT_REG_DWORD, NULL, &data, &size) == ERROR_SUCCESS)
// Dark mode is 0, light mode is 1
return (data == 0);
return FALSE;
case 1:
return TRUE;
default:
return FALSE;
}
}
void InitDarkMode(HWND hWnd)
{
if (!IsAtLeastWin10() || IsHighContrast())
goto out;
PF_INIT_ID_OR_OUT(AllowDarkModeForWindow, UxTheme, 133);
PF_INIT_ID_OR_OUT(SetPreferredAppMode, UxTheme, 135);
PF_INIT_ID_OR_OUT(FlushMenuThemes, UxTheme, 136);
pfAllowDarkModeForWindow(hWnd, is_darkmode_enabled);
pfSetPreferredAppMode(is_darkmode_enabled ? AppModeForceDark : AppModeForceLight);
pfFlushMenuThemes();
return;
out:
is_darkmode_enabled = FALSE;
}
void SetDarkTitleBar(HWND hWnd)
{
if (IsAtLeastWin11()) {
DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &is_darkmode_enabled, sizeof(is_darkmode_enabled));
return;
}
if (IsAtLeastWin10Build(WIN10_1903)) {
PF_INIT_OR_OUT(SetWindowCompositionAttribute, user32);
WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &is_darkmode_enabled, sizeof(is_darkmode_enabled) };
pfSetWindowCompositionAttribute(hWnd, &data);
return;
}
// only for Windows 10 1809 build 17763
if (IsAtLeastWin10()) {
SetPropW(hWnd, L"UseImmersiveDarkModeColors", (HANDLE)(intptr_t)is_darkmode_enabled);
return;
}
out:
is_darkmode_enabled = FALSE;
}
void SetDarkTheme(HWND hWnd)
{
SetWindowTheme(hWnd, is_darkmode_enabled ? L"DarkMode_Explorer" : NULL, NULL);
}
/*
* Accent color functions
*/
// Adapted from https://stackoverflow.com/a/56678483
static double LinearValue(double color_channel)
{
color_channel /= 255.0;
if (color_channel <= 0.04045)
return color_channel / 12.92;
return pow((color_channel + 0.055) / 1.055, 2.4);
}
static double CalculatePerceivedLightness(COLORREF clr)
{
double r, g, b, luminance, lightness;
r = LinearValue((double)GetRValue(clr));
g = LinearValue((double)GetGValue(clr));
b = LinearValue((double)GetBValue(clr));
luminance = (0.2126 * r) + (0.7152 * g) + (0.0722 * b);
lightness = (luminance <= 216.0 / 24389.0) ?
(luminance * 24389.0 / 27.0) :
((pow(luminance, 1.0 / 3.0) * 116.0) - 16.0);
return lightness;
}
BOOL InitAccentColor(void)
{
const double lightnessTreshold = 50.0 - 4.0;
BOOL opaque = TRUE;
if (SUCCEEDED(DwmGetColorizationColor(&color_accent, &opaque))) {
color_accent = RGB(GetBValue(color_accent), GetGValue(color_accent), GetRValue(color_accent));
// Check if accent color is too dark
if (CalculatePerceivedLightness(color_accent) < lightnessTreshold) {
color_accent = DARKMODE_TOOLBAR_ICON_COLOR;
return FALSE;
}
return TRUE;
}
color_accent = TOOLBAR_ICON_COLOR;
return FALSE;
}
// Rufus uses monocolour icons, so changing colour is easy
BOOL ChangeIconColor(HICON* hIcon, COLORREF new_color)
{
HDC hdcScreen = NULL, hdcBitmap = NULL;
HBITMAP hbm = NULL;
BITMAP bmp = { 0 };
ICONINFO ii;
HICON hIconNew = NULL;
RGBQUAD* pixels = NULL;
if (!*hIcon || !is_darkmode_enabled)
return FALSE;
if (new_color == 0)
new_color = color_accent;
hdcBitmap = CreateCompatibleDC(NULL);
hdcScreen = GetDC(NULL);
if (hdcScreen) {
if (hdcBitmap) {
if (GetIconInfo(*hIcon, &ii) && GetObject(ii.hbmColor, sizeof(BITMAP), &bmp)) {
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = bmp.bmWidth;
bmi.bmiHeader.biHeight = -bmp.bmHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
pixels = (RGBQUAD*)malloc((size_t)bmp.bmWidth * bmp.bmHeight * sizeof(RGBQUAD));
if (pixels && GetDIBits(hdcBitmap, ii.hbmColor, 0, bmp.bmHeight, pixels, &bmi, DIB_RGB_COLORS)) {
for (int i = 0; i < bmp.bmWidth * bmp.bmHeight; i++) {
if (pixels[i].rgbReserved != 0) {
pixels[i].rgbRed = GetRValue(new_color);
pixels[i].rgbGreen = GetGValue(new_color);
pixels[i].rgbBlue = GetBValue(new_color);
}
}
hbm = CreateCompatibleBitmap(hdcScreen, bmp.bmWidth, bmp.bmHeight);
if (hbm != NULL) {
SetDIBits(hdcBitmap, hbm, 0, bmp.bmHeight, pixels, &bmi, DIB_RGB_COLORS);
if (ii.hbmColor != NULL)
DeleteObject(ii.hbmColor);
ii.hbmColor = hbm;
hIconNew = CreateIconIndirect(&ii);
} else {
safe_free(pixels);
DeleteObject(ii.hbmColor);
DeleteObject(ii.hbmMask);
DeleteDC(hdcBitmap);
ReleaseDC(NULL, hdcScreen);
return FALSE;
}
}
safe_free(pixels);
safe_delete_object(hbm);
DeleteObject(ii.hbmColor);
DeleteObject(ii.hbmMask);
}
}
ReleaseDC(NULL, hdcScreen);
}
if (hdcBitmap != NULL)
DeleteDC(hdcBitmap);
if (hIconNew == NULL)
return FALSE;
DestroyIcon(*hIcon);
*hIcon = hIconNew;
return TRUE;
}
/*
* Dark mode custom colors
*/
static ThemeResources theme_resources = {
NULL, NULL, NULL, NULL, NULL, NULL
};
static HBRUSH GetDlgBackgroundBrush(void)
{
if (theme_resources.hbrBackground == NULL)
theme_resources.hbrBackground = CreateSolidBrush(DARKMODE_NORMAL_DIALOG_BACKGROUND_COLOR);
return theme_resources.hbrBackground;
}
static HBRUSH GetCtrlBackgroundBrush(void)
{
if (theme_resources.hbrBackgroundControl == NULL)
theme_resources.hbrBackgroundControl = CreateSolidBrush(DARKMODE_NORMAL_CONTROL_BACKGROUND_COLOR);
return theme_resources.hbrBackgroundControl;
}
static HBRUSH GetHotBackgroundBrush(void)
{
if (theme_resources.hbrBackgroundHot == NULL)
theme_resources.hbrBackgroundHot = CreateSolidBrush(DARKMODE_HOT_CONTROL_BACKGROUND_COLOR);
return theme_resources.hbrBackgroundHot;
}
static HBRUSH GetEdgeBrush(void)
{
if (theme_resources.hbrEdge == NULL)
theme_resources.hbrEdge = CreateSolidBrush(DARKMODE_NORMAL_CONTROL_EDGE_COLOR);
return theme_resources.hbrEdge;
}
static HPEN GetEdgePen(void)
{
if (theme_resources.hpnEdge == NULL)
theme_resources.hpnEdge = CreatePen(PS_SOLID, 1, DARKMODE_NORMAL_CONTROL_EDGE_COLOR);
return theme_resources.hpnEdge;
}
static HPEN GetHotEdgePen(void)
{
if (theme_resources.hpnEdgeHot == NULL)
theme_resources.hpnEdgeHot = CreatePen(PS_SOLID, 1, DARKMODE_HOT_CONTROL_EDGE_COLOR);
return theme_resources.hpnEdgeHot;
}
void DestroyDarkModeGDIObjects(void)
{
safe_delete_object(theme_resources.hbrBackground);
safe_delete_object(theme_resources.hbrBackgroundControl);
safe_delete_object(theme_resources.hbrBackgroundHot);
safe_delete_object(theme_resources.hbrEdge);
safe_delete_object(theme_resources.hpnEdge);
safe_delete_object(theme_resources.hpnEdgeHot);
}
/*
* Paint round rect helpers
*/
static void PaintRoundRect(HDC hdc, const RECT rect, HPEN hpen, HBRUSH hBrush, int width, int height)
{
HBRUSH holdBrush = (HBRUSH)SelectObject(hdc, hBrush);
HPEN holdPen = (HPEN)SelectObject(hdc, hpen);
RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, width, height);
SelectObject(hdc, holdBrush);
SelectObject(hdc, holdPen);
}
static void PaintRoundFrameRect(HDC hdc, const RECT rect, HPEN hpen, int width, int height)
{
PaintRoundRect(hdc, rect, hpen, GetStockObject(NULL_BRUSH), width, height);
}
/*
* Button section, checkbox, radio, and groupbox style buttons
*/
static void RenderButton(HWND hWnd, HDC hdc, HTHEME hTheme, int iPartID, int iStateID)
{
HFONT hFont = NULL, hOldFont;
BOOL created_font = FALSE;
LOGFONT lf;
RECT rcClient = { 0 }, rcText = { 0 }, rcBackground, rcFocus;
WCHAR buffer[MAX_PATH] = { '\0' };
SIZE szBox = { 0 };
LONG_PTR nStyle = GetWindowLongPtr(hWnd, GWL_STYLE);
DWORD flags, ui_state;
DTTOPTS dtto = { 0 };
const BOOL isMultiline = (nStyle & BS_MULTILINE) == BS_MULTILINE;
const BOOL isTop = (nStyle & BS_TOP) == BS_TOP;
const BOOL isBottom = (nStyle & BS_BOTTOM) == BS_BOTTOM;
const BOOL isCenter = (nStyle & BS_CENTER) == BS_CENTER;
const BOOL isRight = (nStyle & BS_RIGHT) == BS_RIGHT;
const BOOL isVCenter = (nStyle & BS_VCENTER) == BS_VCENTER;
if (SUCCEEDED(GetThemeFont(hTheme, hdc, iPartID, iStateID, TMT_FONT, &lf))) {
hFont = CreateFontIndirect(&lf);
created_font = TRUE;
}
if (!hFont)
hFont = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0);
hOldFont = (HFONT)SelectObject(hdc, hFont);
flags = DT_LEFT | (isMultiline ? DT_WORDBREAK : DT_SINGLELINE);
if (isCenter)
flags |= DT_CENTER;
else if (isRight)
flags |= DT_RIGHT;
if (isVCenter || (!isMultiline && !isBottom && !isTop))
flags |= DT_VCENTER;
else if (isBottom)
flags |= DT_BOTTOM;
ui_state = (DWORD)SendMessage(hWnd, WM_QUERYUISTATE, 0, 0);
if ((ui_state & UISF_HIDEACCEL) == UISF_HIDEACCEL)
flags |= DT_HIDEPREFIX;
GetClientRect(hWnd, &rcClient);
GetWindowText(hWnd, buffer, _countof(buffer));
GetThemePartSize(hTheme, hdc, iPartID, iStateID, NULL, TS_DRAW, &szBox);
GetThemeBackgroundContentRect(hTheme, hdc, iPartID, iStateID, &rcClient, &rcText);
rcBackground = rcClient;
if (!isMultiline)
rcBackground.top += (rcText.bottom - rcText.top - szBox.cy) / 2;
rcBackground.bottom = rcBackground.top + szBox.cy;
rcBackground.right = rcBackground.left + szBox.cx;
rcText.left = rcBackground.right + 3;
DrawThemeParentBackground(hWnd, hdc, &rcClient);
DrawThemeBackground(hTheme, hdc, iPartID, iStateID, &rcBackground, NULL);
dtto.dwSize = sizeof(DTTOPTS);
dtto.dwFlags = DTT_TEXTCOLOR;
dtto.crText = !IsWindowEnabled(hWnd) ? DARKMODE_DISABLED_TEXT_COLOR : DARKMODE_NORMAL_TEXT_COLOR;
// coverity[negative_returns]
DrawThemeTextEx(hTheme, hdc, iPartID, iStateID, buffer, -1, flags, &rcText, &dtto);
if (((SendMessage(hWnd, BM_GETSTATE, 0, 0) & BST_FOCUS) == BST_FOCUS) && ((ui_state & UISF_HIDEFOCUS) != UISF_HIDEFOCUS)) {
dtto.dwFlags |= DTT_CALCRECT;
// coverity[negative_returns]
DrawThemeTextEx(hTheme, hdc, iPartID, iStateID, buffer, -1, flags | DT_CALCRECT, &rcText, &dtto);
rcFocus = rcText;
rcFocus.bottom++;
rcFocus.left--;
rcFocus.right++;
DrawFocusRect(hdc, &rcFocus);
}
SelectObject(hdc, hOldFont);
if (created_font)
DeleteObject(hFont);
}
static void PaintButton(HWND hWnd, HDC hdc, ButtonData* pButtonData)
{
const DWORD state = (DWORD)SendMessage(hWnd, BM_GETSTATE, 0, 0);
int part_id = BP_CHECKBOX, state_id = RBS_UNCHECKEDNORMAL;
BP_ANIMATIONPARAMS anim_params = { 0 };
HANIMATIONBUFFER anim_buffer;
RECT rcClient = { 0 };
HDC hdcFrom = NULL, hdcTo = NULL;
switch (GetWindowLongPtr(hWnd, GWL_STYLE) & BS_TYPEMASK) {
case BS_CHECKBOX:
case BS_AUTOCHECKBOX:
case BS_3STATE:
case BS_AUTO3STATE:
part_id = BP_CHECKBOX;
break;
case BS_RADIOBUTTON:
case BS_AUTORADIOBUTTON:
part_id = BP_RADIOBUTTON;
break;
default:
break;
}
// States of BP_CHECKBOX and BP_RADIOBUTTON are the same
if (!IsWindowEnabled(hWnd))
state_id = RBS_UNCHECKEDDISABLED;
else if (state & BST_PUSHED)
state_id = RBS_UNCHECKEDPRESSED;
else if (state & BST_HOT)
state_id = RBS_UNCHECKEDHOT;
if (state & BST_CHECKED)
state_id += 4;
if (BufferedPaintRenderAnimation(hWnd, hdc))
return;
anim_params.cbSize = sizeof(BP_ANIMATIONPARAMS);
anim_params.style = BPAS_LINEAR;
if (state_id != pButtonData->iStateID)
GetThemeTransitionDuration(pButtonData->hTheme, part_id, pButtonData->iStateID, state_id, TMT_TRANSITIONDURATIONS, &anim_params.dwDuration);
GetClientRect(hWnd, &rcClient);
anim_buffer = BeginBufferedAnimation(hWnd, hdc, &rcClient, BPBF_COMPATIBLEBITMAP, NULL, &anim_params, &hdcFrom, &hdcTo);
if (anim_buffer != NULL) {
if (hdcFrom != NULL)
RenderButton(hWnd, hdcFrom, pButtonData->hTheme, part_id, pButtonData->iStateID);
if (hdcTo != NULL)
RenderButton(hWnd, hdcTo, pButtonData->hTheme, part_id, state_id);
pButtonData->iStateID = state_id;
EndBufferedAnimation(anim_buffer, TRUE);
} else {
RenderButton(hWnd, hdc, pButtonData->hTheme, part_id, state_id);
pButtonData->iStateID = state_id;
}
}
static LRESULT CALLBACK ButtonSubclass(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
ButtonData* data = (ButtonData*)dwRefData;
switch (uMsg) {
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, ButtonSubclass, uIdSubclass);
if (data->hTheme != NULL)
CloseThemeData(data->hTheme);
free(data);
break;
case WM_ERASEBKGND:
if (data->hTheme == NULL)
data->hTheme = OpenThemeData(hWnd, VSCLASS_BUTTON);
if (data->hTheme != NULL)
return TRUE;
break;
case WM_PRINTCLIENT:
case WM_PAINT:
PAINTSTRUCT ps = { 0 };
HDC hdc = (HDC)wParam;
if (data->hTheme == NULL) {
data->hTheme = OpenThemeData(hWnd, VSCLASS_BUTTON);
if (data->hTheme == NULL)
break;
}
if (hdc == NULL)
hdc = BeginPaint(hWnd, &ps);
PaintButton(hWnd, hdc, data);
if (ps.hdc != NULL)
EndPaint(hWnd, &ps);
return 0;
case WM_THEMECHANGED:
if (data->hTheme != NULL)
CloseThemeData(data->hTheme);
break;
case WM_SIZE:
case WM_DESTROY:
BufferedPaintStopAllAnimations(hWnd);
break;
case WM_ENABLE:
// Skip the button's normal WindowProc so it won't redraw out of WM_PAINT
LRESULT lr = DefWindowProc(hWnd, uMsg, wParam, lParam);
InvalidateRect(hWnd, NULL, FALSE);
return lr;
case WM_UPDATEUISTATE:
if (HIWORD(wParam) & (UISF_HIDEACCEL | UISF_HIDEFOCUS))
InvalidateRect(hWnd, NULL, FALSE);
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
static void SubclassButtonControl(HWND hWnd)
{
void* data = NULL;
if (GetWindowSubclass(hWnd, ButtonSubclass, (UINT_PTR)ButtonSubclassID, NULL))
return;
data = calloc(1, sizeof(ButtonData));
SetWindowSubclass(hWnd, ButtonSubclass, (UINT_PTR)ButtonSubclassID, (DWORD_PTR)data);
}
static void PaintGroupbox(HWND hWnd, HDC hdc, ButtonData buttonData)
{
const int iStateID = IsWindowEnabled(hWnd) ? GBS_NORMAL : GBS_DISABLED;
const BOOL centered = (GetWindowLongPtr(hWnd, GWL_STYLE) & BS_CENTER) == BS_CENTER;
BOOL font_created = FALSE;
HFONT hFont = NULL, hOldFont;
LOGFONT lf;
WCHAR buffer[MAX_PATH] = { '\0' };
SIZE szText = { 0 };
RECT rcClient = { 0 }, rcText, rcBackground, rcContent;
if (SUCCEEDED(GetThemeFont(buttonData.hTheme, hdc, BP_GROUPBOX, iStateID, TMT_FONT, &lf))) {
hFont = CreateFontIndirect(&lf);
font_created = TRUE;
}
if (hFont == NULL) {
hFont = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0);
font_created = FALSE;
}
hOldFont = (HFONT)SelectObject(hdc, hFont);
GetWindowText(hWnd, buffer, _countof(buffer));
GetClientRect(hWnd, &rcClient);
rcClient.bottom -= 1;
rcText = rcClient; rcBackground = rcClient;
if (buffer[0]) {
GetTextExtentPoint32(hdc, buffer, (int)wcslen(buffer), &szText);
rcBackground.top += szText.cy / 2;
rcText.left += centered ? ((rcClient.right - rcClient.left - szText.cx) / 2) : 7;
rcText.bottom = rcText.top + szText.cy;
rcText.right = rcText.left + szText.cx + 4;
ExcludeClipRect(hdc, rcText.left, rcText.top, rcText.right, rcText.bottom);
} else {
GetTextExtentPoint32(hdc, L"M", 1, &szText);
rcBackground.top += szText.cy / 2;
}
rcContent = rcBackground;
GetThemeBackgroundContentRect(buttonData.hTheme, hdc, BP_GROUPBOX, iStateID, &rcBackground, &rcContent);
ExcludeClipRect(hdc, rcContent.left, rcContent.top, rcContent.right, rcContent.bottom);
PaintRoundFrameRect(hdc, rcBackground, GetEdgePen(), 0, 0);
SelectClipRgn(hdc, NULL);
if (buffer[0]) {
DTTOPTS dtto = { 0 };
DWORD flags = centered ? DT_CENTER : DT_LEFT;
InflateRect(&rcText, -2, 0);
dtto.dwSize = sizeof(DTTOPTS);
dtto.dwFlags = DTT_TEXTCOLOR;
dtto.crText = IsWindowEnabled(hWnd) ? DARKMODE_NORMAL_TEXT_COLOR : DARKMODE_DISABLED_TEXT_COLOR;
if (SendMessage(hWnd, WM_QUERYUISTATE, 0, 0) != (LRESULT)NULL)
flags |= DT_HIDEPREFIX;
// coverity[negative_returns]
DrawThemeTextEx(buttonData.hTheme, hdc, BP_GROUPBOX, iStateID, buffer, -1, flags | DT_SINGLELINE, &rcText, &dtto);
}
SelectObject(hdc, hOldFont);
if (font_created)
DeleteObject(hFont);
}
static LRESULT CALLBACK GroupboxSubclass(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
ButtonData* data = (ButtonData*)dwRefData;
switch (uMsg) {
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, ButtonSubclass, uIdSubclass);
if (data->hTheme != NULL)
CloseThemeData(data->hTheme);
free(data);
break;
case WM_ERASEBKGND:
if (data->hTheme == NULL)
data->hTheme = OpenThemeData(hWnd, VSCLASS_BUTTON);
if (data->hTheme != NULL)
return TRUE;
break;
case WM_PRINTCLIENT:
case WM_PAINT:
PAINTSTRUCT ps = { 0 };
HDC hdc = (HDC)wParam;
if (data->hTheme == NULL) {
data->hTheme = OpenThemeData(hWnd, VSCLASS_BUTTON);
if (data->hTheme == NULL)
break;
}
if (hdc == NULL)
hdc = BeginPaint(hWnd, &ps);
PaintGroupbox(hWnd, hdc, *data);
if (ps.hdc != NULL)
EndPaint(hWnd, &ps);
return 0;
case WM_THEMECHANGED:
if (data->hTheme != NULL)
CloseThemeData(data->hTheme);
break;
case WM_ENABLE:
RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE);
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
static void SubclassGroupboxControl(HWND hWnd)
{
void* data = NULL;
if (GetWindowSubclass(hWnd, GroupboxSubclass, (UINT_PTR)GroupboxSubclassID, NULL))
return;
data = calloc(1, sizeof(ButtonData));
SetWindowSubclass(hWnd, GroupboxSubclass, (UINT_PTR)GroupboxSubclassID, (DWORD_PTR)data);
}
/*
* Toolbar section
*/
static LRESULT DarkToolBarNotifyCustomDraw(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LPNMTBCUSTOMDRAW lpnmtbcd = (LPNMTBCUSTOMDRAW)lParam;
static int roundness = 0;
switch (lpnmtbcd->nmcd.dwDrawStage) {
case CDDS_PREPAINT:
if (IsAtLeastWin11())
roundness = 5;
FillRect(lpnmtbcd->nmcd.hdc, &lpnmtbcd->nmcd.rc, GetDlgBackgroundBrush());
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
LRESULT lr = TBCDRF_USECDCOLORS;
lpnmtbcd->hbrMonoDither = GetDlgBackgroundBrush();
lpnmtbcd->hbrLines = GetEdgeBrush();
lpnmtbcd->hpenLines = GetEdgePen();
lpnmtbcd->clrText = DARKMODE_NORMAL_TEXT_COLOR;
lpnmtbcd->clrTextHighlight = DARKMODE_NORMAL_TEXT_COLOR;
lpnmtbcd->clrBtnFace = DARKMODE_NORMAL_DIALOG_BACKGROUND_COLOR;
lpnmtbcd->clrBtnHighlight = DARKMODE_NORMAL_CONTROL_BACKGROUND_COLOR;
lpnmtbcd->clrHighlightHotTrack = DARKMODE_HOT_CONTROL_BACKGROUND_COLOR;
lpnmtbcd->nStringBkMode = TRANSPARENT;
lpnmtbcd->nHLStringBkMode = TRANSPARENT;
if ((lpnmtbcd->nmcd.uItemState & CDIS_HOT) == CDIS_HOT) {
PaintRoundRect(lpnmtbcd->nmcd.hdc, lpnmtbcd->nmcd.rc, GetHotEdgePen(), GetHotBackgroundBrush(), roundness, roundness);
lpnmtbcd->nmcd.uItemState &= ~(CDIS_CHECKED | CDIS_HOT);
} else if ((lpnmtbcd->nmcd.uItemState & CDIS_CHECKED) == CDIS_CHECKED) {
PaintRoundRect(lpnmtbcd->nmcd.hdc, lpnmtbcd->nmcd.rc, GetEdgePen(), GetCtrlBackgroundBrush(), roundness, roundness);
lpnmtbcd->nmcd.uItemState &= ~CDIS_CHECKED;
}
if ((lpnmtbcd->nmcd.uItemState & CDIS_SELECTED) == CDIS_SELECTED)
lr |= TBCDRF_NOBACKGROUND;
return lr;
default:
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
static LRESULT DarkTrackBarNotifyCustomDraw(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LPNMCUSTOMDRAW lpnmcd = (LPNMCUSTOMDRAW)lParam;
switch (lpnmcd->dwDrawStage) {
case CDDS_PREPAINT:
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
switch (lpnmcd->dwItemSpec) {
case TBCD_THUMB:
if ((lpnmcd->uItemState & CDIS_SELECTED) == CDIS_SELECTED) {
FillRect(lpnmcd->hdc, &lpnmcd->rc, GetCtrlBackgroundBrush());
return CDRF_SKIPDEFAULT;
}
break;
case TBCD_CHANNEL:
if (!IsWindowEnabled(lpnmcd->hdr.hwndFrom)) {
FillRect(lpnmcd->hdc, &lpnmcd->rc, GetDlgBackgroundBrush());
PaintRoundFrameRect(lpnmcd->hdc, lpnmcd->rc, GetEdgePen(), 0, 0);
} else {
FillRect(lpnmcd->hdc, &lpnmcd->rc, GetCtrlBackgroundBrush());
}
return CDRF_SKIPDEFAULT;
default:
break;
}
break;
default:
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
static LRESULT CALLBACK WindowNotifySubclass(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
(void)dwRefData;
switch (uMsg) {
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, WindowNotifySubclass, uIdSubclass);
break;
case WM_NOTIFY:
LPNMHDR lpnmhdr = (LPNMHDR)lParam;
WCHAR str[32] = { 0 };
GetClassName(lpnmhdr->hwndFrom, str, ARRAYSIZE(str));
switch (lpnmhdr->code) {
case NM_CUSTOMDRAW:
if (_wcsicmp(str, TOOLBARCLASSNAME) == 0)
return DarkToolBarNotifyCustomDraw(hWnd, uMsg, wParam, lParam);
if (_wcsicmp(str, TRACKBAR_CLASS) == 0)
return DarkTrackBarNotifyCustomDraw(hWnd, uMsg, wParam, lParam);
break;
}
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
void SubclassNotifyCustomDraw(HWND hWnd)
{
if (!GetWindowSubclass(hWnd, WindowNotifySubclass, (UINT_PTR)WindowNotifySubclassID, NULL))
SetWindowSubclass(hWnd, WindowNotifySubclass, (UINT_PTR)WindowNotifySubclassID, 0);
}
/*
* Status bar section
*/
static void PaintStatusBar(HWND hWnd, HDC hdc, StatusBarData statusBarData)
{
const int last_div = (int)SendMessage(hWnd, SB_GETPARTS, 0, 0) - 1;
Borders borders = { 0 };
RECT rcClient = { 0 }, rcPart = { 0 }, rcIntersect = { 0 };
HPEN hOldPen = (HPEN)SelectObject(hdc, GetEdgePen());
HFONT hOldFont = (HFONT)SelectObject(hdc, statusBarData.hFont);
LRESULT r1, r2;
LPWSTR buffer;
DWORD text_len;
UINT id;
SendMessage(hWnd, SB_GETBORDERS, 0, (LPARAM)&borders);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, DARKMODE_NORMAL_TEXT_COLOR);
GetClientRect(hWnd, &rcClient);
FillRect(hdc, &rcClient, GetDlgBackgroundBrush());
for (int i = 0; i <= last_div; i++) {
SendMessage(hWnd, SB_GETRECT, i, (LPARAM)&rcPart);
if (!IntersectRect(&rcIntersect, &rcPart, &rcClient))
continue;
if (i < last_div) {
POINT edges[] = {
{ rcPart.right - borders.between, rcPart.top + 1 },
{ rcPart.right - borders.between, rcPart.bottom - 3 }
};
Polyline(hdc, edges, _countof(edges));
}
rcPart.left += borders.between;
rcPart.right -= borders.vertical;
r1 = SendMessage(hWnd, SB_GETTEXTLENGTH, i, 0);
text_len = LOWORD(r1);
buffer = (LPWSTR)calloc((size_t)text_len + 1, sizeof(WCHAR));
r2 = SendMessage(hWnd, SB_GETTEXT, i, (LPARAM)buffer);
if (text_len == 0 && (HIWORD(r1) & SBT_OWNERDRAW)) {
id = GetDlgCtrlID(hWnd);
DRAWITEMSTRUCT dis = { 0, 0, (UINT)i, ODA_DRAWENTIRE, id, hWnd, hdc, rcPart, (ULONG_PTR)r2 };
SendMessage(GetParent(hWnd), WM_DRAWITEM, id, (LPARAM)&dis);
} else if (buffer != NULL) {
DrawText(hdc, buffer, (int)text_len, &rcPart, DT_SINGLELINE | DT_VCENTER | DT_LEFT);
}
free(buffer);
}
POINT horizontal_edge[] = { { rcClient.left, rcClient.top }, { rcClient.right, rcClient.top } };
Polyline(hdc, horizontal_edge, _countof(horizontal_edge));
SelectObject(hdc, hOldFont);
SelectObject(hdc, hOldPen);
}
static LRESULT CALLBACK StatusBarSubclass(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
StatusBarData* data = (StatusBarData*)dwRefData;
switch (uMsg) {
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, StatusBarSubclass, uIdSubclass);
if (data->hFont != NULL) {
DeleteObject(data->hFont);
data->hFont = NULL;
}
free(data);
break;
case WM_ERASEBKGND:
return TRUE;
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
PaintStatusBar(hWnd, hdc, *data);
EndPaint(hWnd, &ps);
return 0;
case WM_THEMECHANGED:
NONCLIENTMETRICS ncm = { 0 };
ncm.cbSize = sizeof(NONCLIENTMETRICS);
if (data->hFont != NULL) {
DeleteObject(data->hFont);
data->hFont = NULL;
}
if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0))
data->hFont = CreateFontIndirect(&ncm.lfStatusFont);
return 0;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
void SubclassStatusBar(HWND hWnd)
{
StatusBarData* data = NULL;
NONCLIENTMETRICS ncm = { 0 };
ncm.cbSize = sizeof(NONCLIENTMETRICS);
if (!is_darkmode_enabled || GetWindowSubclass(hWnd, StatusBarSubclass, (UINT_PTR)StatusBarSubclassID, NULL))
return;
data = (StatusBarData*)malloc(sizeof(StatusBarData));
if (data == NULL)
return;
if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0))
data->hFont = CreateFontIndirect(&ncm.lfStatusFont);
SetWindowSubclass(hWnd, StatusBarSubclass, (UINT_PTR)StatusBarSubclassID, (DWORD_PTR)data);
}
/*
* Progress bar section
*/
static void GetProgressBarRects(HWND hWnd, RECT* rcEmpty, RECT* rcFilled)
{
const int pos = (int)SendMessage(hWnd, PBM_GETPOS, 0, 0);
PBRANGE range = { 0, 0 };
SendMessage(hWnd, PBM_GETRANGE, TRUE, (LPARAM)&range);
const int min = range.iLow;
const int cur_pos = pos - min;
if (cur_pos != 0) {
int totalWidth = rcEmpty->right - rcEmpty->left;
rcFilled->left = rcEmpty->left;
rcFilled->top = rcEmpty->top;
rcFilled->bottom = rcEmpty->bottom;
if (range.iHigh - min != 0)
rcFilled->right = rcEmpty->left + (int)((double)(cur_pos) / (range.iHigh - min) * totalWidth);
else
rcFilled->right = rcEmpty->right;
rcEmpty->left = rcFilled->right; // To avoid painting under filled part
}
}
static void PaintProgressBar(HWND hWnd, HDC hdc, ProgressBarData progressBarData)
{
RECT rcClient = { 0 }, rcFill = { 0 };
GetClientRect(hWnd, &rcClient);
PaintRoundFrameRect(hdc, rcClient, GetEdgePen(), 0, 0);
InflateRect(&rcClient, -1, -1);
rcClient.left = 1;
GetProgressBarRects(hWnd, &rcClient, &rcFill);
DrawThemeBackground(progressBarData.hTheme, hdc, PP_FILL, progressBarData.iStateID, &rcFill, NULL);
FillRect(hdc, &rcClient, GetCtrlBackgroundBrush());
}
static LRESULT CALLBACK ProgressBarSubclass(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
ProgressBarData* data = (ProgressBarData*)dwRefData;
switch (uMsg) {
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, ProgressBarSubclass, uIdSubclass);
if (data->hTheme != NULL)
CloseThemeData(data->hTheme);
free(data);
break;
case WM_ERASEBKGND:
if (data->hTheme == NULL)
data->hTheme = OpenThemeData(hWnd, VSCLASS_PROGRESS);
if (data->hTheme != NULL)
return TRUE;
break;
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc;
if (data->hTheme == NULL) {
data->hTheme = OpenThemeData(hWnd, VSCLASS_PROGRESS);
if (data->hTheme == NULL)
break;
}
hdc = BeginPaint(hWnd, &ps);
PaintProgressBar(hWnd, hdc, *data);
EndPaint(hWnd, &ps);
return 0;
case WM_THEMECHANGED:
if (data->hTheme != NULL)
CloseThemeData(data->hTheme);
break;
case PBM_SETSTATE:
switch (wParam) {
case PBST_NORMAL:
data->iStateID = PBFS_NORMAL; // Green
break;
case PBST_ERROR:
data->iStateID = PBFS_ERROR; // Red
break;
case PBST_PAUSED:
data->iStateID = PBFS_PAUSED; // Yellow
break;
}
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
void SubclassProgressBarControl(HWND hWnd)
{
void* data = NULL;
if (!is_darkmode_enabled || GetWindowSubclass(hWnd, ProgressBarSubclass, (UINT_PTR)ProgressBarSubclassID, NULL))
return;
data = calloc(1, sizeof(ProgressBarData));
SetWindowSubclass(hWnd, ProgressBarSubclass, (UINT_PTR)ProgressBarSubclassID, (DWORD_PTR)data);
}
/*
* Static text section
*/
static LRESULT CALLBACK StaticTextSubclass(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
StaticTextData* data = (StaticTextData*)dwRefData;
switch (uMsg) {
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, StaticTextSubclass, uIdSubclass);
free(data);
break;
case WM_ENABLE:
RECT rcClient = { 0 };
const LONG_PTR style = GetWindowLongPtr(hWnd, GWL_STYLE);
data->disabled = (wParam == FALSE);
if (data->disabled)
SetWindowLongPtr(hWnd, GWL_STYLE, style & ~WS_DISABLED);
GetClientRect(hWnd, &rcClient);
MapWindowPoints(hWnd, GetParent(hWnd), (LPPOINT)&rcClient, 2);
RedrawWindow(GetParent(hWnd), &rcClient, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
if (data->disabled)
SetWindowLongPtr(hWnd, GWL_STYLE, style | WS_DISABLED);
return 0;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
static void SubclassStaticText(HWND hWnd)
{
void* data = NULL;
if (GetWindowSubclass(hWnd, StaticTextSubclass, (UINT_PTR)StaticTextSubclassID, NULL))
return;
data = calloc(1, sizeof(StaticTextData));
SetWindowSubclass(hWnd, StaticTextSubclass, (UINT_PTR)StaticTextSubclassID, (DWORD_PTR)data);
}
/*
* Ctl color messages section
*/
static LRESULT OnCtlColorDlg(HDC hdc)
{
SetTextColor(hdc, DARKMODE_NORMAL_TEXT_COLOR);
SetBkColor(hdc, DARKMODE_NORMAL_DIALOG_BACKGROUND_COLOR);
return (LRESULT)GetDlgBackgroundBrush();
}
static LRESULT OnCtlColorStatic(WPARAM wParam, LPARAM lParam)
{
HDC hdc = (HDC)wParam;
HWND hWnd = (HWND)lParam;
WCHAR str[32] = { 0 };
GetClassName(hWnd, str, ARRAYSIZE(str));
if (_wcsicmp(str, WC_STATIC) == 0) {
if ((GetWindowLongPtr(hWnd, GWL_STYLE) & SS_NOTIFY) == SS_NOTIFY) {
DWORD_PTR dwRefData = 0;
COLORREF cText = color_accent;
if (GetWindowSubclass(hWnd, StaticTextSubclass, (UINT_PTR)StaticTextSubclassID, &dwRefData)) {
if (((StaticTextData*)dwRefData)->disabled)
cText = DARKMODE_DISABLED_TEXT_COLOR;
}
SetTextColor(hdc, cText);
SetBkColor(hdc, DARKMODE_NORMAL_DIALOG_BACKGROUND_COLOR);
return (LRESULT)GetDlgBackgroundBrush();
}
}
// Read-only WC_EDIT
return OnCtlColorDlg(hdc);
}
static LRESULT OnCtlColorCtrl(HDC hdc)
{
SetTextColor(hdc, DARKMODE_NORMAL_TEXT_COLOR);
SetBkColor(hdc, DARKMODE_NORMAL_CONTROL_BACKGROUND_COLOR);
return (LRESULT)GetCtrlBackgroundBrush();
}
static INT_PTR OnCtlColorListbox(WPARAM wParam, LPARAM lParam)
{
HDC hdc = (HDC)wParam;
HWND hWnd = (HWND)lParam;
const LONG_PTR nStyle = GetWindowLongPtr(hWnd, GWL_STYLE);
BOOL isComboBox = (nStyle & LBS_COMBOBOX) == LBS_COMBOBOX;
if ((!isComboBox || !is_darkmode_enabled) && IsWindowEnabled(hWnd))
return (INT_PTR)OnCtlColorCtrl(hdc);
return (INT_PTR)OnCtlColorDlg(hdc);
}
static LRESULT CALLBACK WindowCtlColorSubclass(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
(void)dwRefData;
switch (uMsg) {
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, WindowCtlColorSubclass, uIdSubclass);
break;
case WM_CTLCOLOREDIT:
return OnCtlColorCtrl((HDC)wParam);
case WM_CTLCOLORLISTBOX:
return OnCtlColorListbox(wParam, lParam);
case WM_CTLCOLORDLG:
return OnCtlColorDlg((HDC)wParam);
case WM_CTLCOLORSTATIC:
return OnCtlColorStatic(wParam, lParam);
case WM_PRINTCLIENT:
return TRUE;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
void SubclassCtlColor(HWND hWnd)
{
if (!GetWindowSubclass(hWnd, WindowCtlColorSubclass, (UINT_PTR)WindowCtlColorSubclassID, NULL))
SetWindowSubclass(hWnd, WindowCtlColorSubclass, (UINT_PTR)WindowCtlColorSubclassID, 0);
}
/*
* Dark mode for child controls
*/
static BOOL CALLBACK DarkModeForChildCallback(HWND hWnd, LPARAM lParam)
{
(void)lParam;
WCHAR str[32] = { 0 };
GetClassName(hWnd, str, ARRAYSIZE(str));
if (_wcsicmp(str, WC_STATIC) == 0) {
if ((GetWindowLongPtr(hWnd, GWL_STYLE) & SS_NOTIFY) == SS_NOTIFY)
SubclassStaticText(hWnd);
} else if (_wcsicmp(str, WC_BUTTON) == 0) {
switch (GetWindowLongPtr(hWnd, GWL_STYLE) & BS_TYPEMASK) {
case BS_CHECKBOX:
case BS_AUTOCHECKBOX:
case BS_3STATE:
case BS_AUTO3STATE:
case BS_RADIOBUTTON:
case BS_AUTORADIOBUTTON:
if (IsAtLeastWin11())
SetDarkTheme(hWnd);
SubclassButtonControl(hWnd);
break;
case BS_GROUPBOX:
SubclassGroupboxControl(hWnd);
break;
case BS_PUSHBUTTON:
case BS_DEFPUSHBUTTON:
case BS_SPLITBUTTON:
case BS_DEFSPLITBUTTON:
SetDarkTheme(hWnd);
break;
default:
break;
}
} else if (_wcsicmp(str, WC_COMBOBOX) == 0) {
SetWindowTheme(hWnd, L"DarkMode_CFD", NULL);
} else if (_wcsicmp(str, TOOLBARCLASSNAME) == 0) {
HWND hTips = (HWND)SendMessage(hWnd, TB_GETTOOLTIPS, 0, 0);
if (hTips != NULL) {
SetDarkTheme(hTips);
}
} else if (_wcsicmp(str, WC_EDIT) == 0) {
const LONG_PTR style = GetWindowLongPtr(hWnd, GWL_STYLE);
const LONG_PTR ex_style = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
if (((style & WS_VSCROLL) == WS_VSCROLL) || ((style & WS_HSCROLL) == WS_HSCROLL)) {
SetWindowLongPtr(hWnd, GWL_STYLE, style | WS_BORDER);
SetWindowLongPtr(hWnd, GWL_EXSTYLE, ex_style & ~WS_EX_CLIENTEDGE);
SetDarkTheme(hWnd);
} else {
SetWindowTheme(hWnd, L"DarkMode_CFD", NULL);
}
} else if (_wcsicmp(str, RICHEDIT_CLASS) == 0) {
const LONG_PTR style = GetWindowLongPtr(hWnd, GWL_STYLE);
const LONG_PTR ex_style = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
const BOOL has_static_edge = (ex_style & WS_EX_STATICEDGE) == WS_EX_STATICEDGE;
CHARFORMATW cf = { 0 };
cf.cbSize = sizeof(CHARFORMATW);
cf.dwMask = CFM_COLOR;
cf.crTextColor = DARKMODE_NORMAL_TEXT_COLOR;
SendMessage(hWnd, EM_SETBKGNDCOLOR, 0, (LPARAM)(has_static_edge ?
DARKMODE_NORMAL_CONTROL_BACKGROUND_COLOR : DARKMODE_NORMAL_DIALOG_BACKGROUND_COLOR));
SendMessage(hWnd, EM_SETCHARFORMAT, SCF_DEFAULT, (LPARAM)&cf);
SetWindowLongPtr(hWnd, GWL_STYLE, style | WS_BORDER);
SetWindowLongPtr(hWnd, GWL_EXSTYLE, ex_style & ~WS_EX_STATICEDGE);
SetWindowTheme(hWnd, NULL, L"DarkMode_Explorer::ScrollBar");
}
return TRUE;
}
void SetDarkModeForChild(HWND hParent)
{
if (is_darkmode_enabled)
EnumChildWindows(hParent, DarkModeForChildCallback, (LPARAM)NULL);
}