822 lines
23 KiB
C++
822 lines
23 KiB
C++
// For compilers that support precompilation, includes "wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
#include "wx/settings.h"
|
|
#include "wx/font.h"
|
|
|
|
#include "wx/msw/colour.h"
|
|
#include "dark_mode.hpp"
|
|
#include "dark_mode/dark_mode.hpp"
|
|
#include "dark_mode/UAHMenuBar.hpp"
|
|
|
|
#include <Shlwapi.h>
|
|
#include "windowsx.h"
|
|
#include "stdlib.h"
|
|
|
|
#ifdef __GNUC__
|
|
#include <cmath>
|
|
#define WINAPI_LAMBDA WINAPI
|
|
#else
|
|
#define WINAPI_LAMBDA
|
|
#endif
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma comment(lib, "uxtheme.lib")
|
|
#endif
|
|
|
|
namespace NppDarkMode
|
|
{
|
|
bool IsEnabled()
|
|
{
|
|
return g_darkModeEnabled;
|
|
}
|
|
|
|
bool IsSupported()
|
|
{
|
|
return g_darkModeSupported;
|
|
}
|
|
|
|
bool IsSystemMenuEnabled()
|
|
{
|
|
return g_SystemMenuEnabled;
|
|
}
|
|
|
|
COLORREF InvertLightness(COLORREF c)
|
|
{
|
|
WORD h = 0;
|
|
WORD s = 0;
|
|
WORD l = 0;
|
|
ColorRGBToHLS(c, &h, &l, &s);
|
|
|
|
l = 240 - l;
|
|
|
|
COLORREF invert_c = ColorHLSToRGB(h, l, s);
|
|
|
|
return invert_c;
|
|
}
|
|
|
|
COLORREF InvertLightnessSofter(COLORREF c)
|
|
{
|
|
WORD h = 0;
|
|
WORD s = 0;
|
|
WORD l = 0;
|
|
ColorRGBToHLS(c, &h, &l, &s);
|
|
|
|
l = std::min(240 - l, 211);
|
|
|
|
COLORREF invert_c = ColorHLSToRGB(h, l, s);
|
|
|
|
return invert_c;
|
|
}
|
|
|
|
COLORREF GetBackgroundColor()
|
|
{
|
|
return IsEnabled() ? RGB(0x2B, 0x2B, 0x2B) : wxSystemSettings::GetColour(wxSYS_COLOUR_MENUBAR).GetRGB();
|
|
}
|
|
|
|
COLORREF GetSofterBackgroundColor()
|
|
{
|
|
return IsEnabled() ? RGB(0x40, 0x40, 0x40) : RGB(0xD9, 0xD9, 0xD9); //RGB(0x78, 0x78, 0x78);
|
|
}
|
|
|
|
COLORREF GetTextColor()
|
|
{
|
|
return IsEnabled() ? RGB(0xF0, 0xF0, 0xF0) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT).GetRGB();
|
|
}
|
|
|
|
COLORREF GetHotTextColor()
|
|
{
|
|
return IsEnabled() ? RGB(0xFF, 0xFF, 0xFF) : wxSystemSettings::GetColour(wxSYS_COLOUR_ACTIVEBORDER).GetRGB();
|
|
}
|
|
|
|
COLORREF GetSofterTextColor()
|
|
{
|
|
return IsEnabled() ? RGB(0xF0, 0xF0, 0xF0) : RGB(0x64, 0x64, 0x64);
|
|
}
|
|
|
|
COLORREF GetDarkerTextColor()
|
|
{
|
|
return RGB(0xC0, 0xC0, 0xC0);
|
|
}
|
|
|
|
COLORREF GetEdgeColor()
|
|
{
|
|
return RGB(0x80, 0x80, 0x80);
|
|
}
|
|
|
|
HBRUSH GetBackgroundBrush()
|
|
{
|
|
static HBRUSH g_hbrBackground = ::CreateSolidBrush(GetBackgroundColor());
|
|
return g_hbrBackground;
|
|
}
|
|
|
|
HPEN GetDarkerTextPen()
|
|
{
|
|
static HPEN g_hpDarkerText = ::CreatePen(PS_SOLID, 1, GetDarkerTextColor());
|
|
return g_hpDarkerText;
|
|
}
|
|
|
|
HPEN GetEdgePen()
|
|
{
|
|
static HPEN g_hpEdgePen = ::CreatePen(PS_SOLID, 1, GetEdgeColor());
|
|
return g_hpEdgePen;
|
|
}
|
|
|
|
HBRUSH GetSofterBackgroundBrush()
|
|
{
|
|
static HBRUSH g_hbrSofterBackground = ::CreateSolidBrush(GetSofterBackgroundColor());
|
|
return g_hbrSofterBackground;
|
|
}
|
|
|
|
// handle events
|
|
|
|
bool OnSettingChange(HWND hwnd, LPARAM lParam) // true if dark mode toggled
|
|
{
|
|
bool toggled = false;
|
|
if (IsColorSchemeChangeMessage(lParam))
|
|
{
|
|
bool darkModeWasEnabled = g_darkModeEnabled;
|
|
g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast();
|
|
|
|
NppDarkMode::RefreshTitleBarThemeColor(hwnd);
|
|
|
|
if (!!darkModeWasEnabled != !!g_darkModeEnabled) {
|
|
toggled = true;
|
|
RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN);
|
|
}
|
|
}
|
|
|
|
return toggled;
|
|
}
|
|
|
|
// processes messages related to UAH / custom menubar drawing.
|
|
// return true if handled, false to continue with normal processing in your wndproc
|
|
bool UAHWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT* lr)
|
|
{
|
|
if (!IsEnabled())
|
|
return false;
|
|
|
|
static HTHEME g_menuTheme = nullptr;
|
|
|
|
UNREFERENCED_PARAMETER(wParam);
|
|
switch (message)
|
|
{
|
|
case WM_UAHDRAWMENU:
|
|
{
|
|
UAHMENU* pUDM = (UAHMENU*)lParam;
|
|
RECT rc = { 0 };
|
|
|
|
// get the menubar rect
|
|
{
|
|
MENUBARINFO mbi = { sizeof(mbi) };
|
|
GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi);
|
|
|
|
RECT rcWindow;
|
|
GetWindowRect(hWnd, &rcWindow);
|
|
|
|
// the rcBar is offset by the window rect
|
|
rc = mbi.rcBar;
|
|
OffsetRect(&rc, -rcWindow.left, -rcWindow.top);
|
|
|
|
rc.top -= 1;
|
|
}
|
|
|
|
FillRect(pUDM->hdc, &rc, GetBackgroundBrush());
|
|
|
|
*lr = 0;
|
|
|
|
return true;
|
|
}
|
|
case WM_UAHDRAWMENUITEM:
|
|
{
|
|
UAHDRAWMENUITEM* pUDMI = (UAHDRAWMENUITEM*)lParam;
|
|
|
|
// get the menu item string
|
|
wchar_t menuString[256] = { 0 };
|
|
MENUITEMINFO mii = { sizeof(mii), MIIM_STRING };
|
|
{
|
|
mii.dwTypeData = menuString;
|
|
mii.cch = (sizeof(menuString) / 2) - 1;
|
|
|
|
GetMenuItemInfo(pUDMI->um.hmenu, pUDMI->umi.iPosition, TRUE, &mii);
|
|
}
|
|
|
|
// get the item state for drawing
|
|
|
|
DWORD dwFlags = DT_CENTER | DT_SINGLELINE | DT_VCENTER;
|
|
|
|
int iTextStateID = MPI_NORMAL;
|
|
int iBackgroundStateID = MPI_NORMAL;
|
|
{
|
|
if ((pUDMI->dis.itemState & ODS_INACTIVE) | (pUDMI->dis.itemState & ODS_DEFAULT)) {
|
|
// normal display
|
|
iTextStateID = MPI_NORMAL;
|
|
iBackgroundStateID = MPI_NORMAL;
|
|
}
|
|
if (pUDMI->dis.itemState & ODS_HOTLIGHT) {
|
|
// hot tracking
|
|
iTextStateID = MPI_HOT;
|
|
iBackgroundStateID = MPI_HOT;
|
|
}
|
|
if (pUDMI->dis.itemState & ODS_SELECTED) {
|
|
// clicked -- MENU_POPUPITEM has no state for this, though MENU_BARITEM does
|
|
iTextStateID = MPI_HOT;
|
|
iBackgroundStateID = MPI_HOT;
|
|
}
|
|
if ((pUDMI->dis.itemState & ODS_GRAYED) || (pUDMI->dis.itemState & ODS_DISABLED)) {
|
|
// disabled / grey text
|
|
iTextStateID = MPI_DISABLED;
|
|
iBackgroundStateID = MPI_DISABLED;
|
|
}
|
|
if (pUDMI->dis.itemState & ODS_NOACCEL) {
|
|
dwFlags |= DT_HIDEPREFIX;
|
|
}
|
|
}
|
|
|
|
if (!g_menuTheme) {
|
|
g_menuTheme = OpenThemeData(hWnd, L"Menu");
|
|
}
|
|
|
|
if (iBackgroundStateID == MPI_NORMAL || iBackgroundStateID == MPI_DISABLED) {
|
|
FillRect(pUDMI->um.hdc, &pUDMI->dis.rcItem, NppDarkMode::GetBackgroundBrush());
|
|
}
|
|
else if (iBackgroundStateID == MPI_HOT) {
|
|
FillRect(pUDMI->um.hdc, &pUDMI->dis.rcItem, NppDarkMode::GetSofterBackgroundBrush());
|
|
}
|
|
else {
|
|
DrawThemeBackground(g_menuTheme, pUDMI->um.hdc, MENU_POPUPITEM, iBackgroundStateID, &pUDMI->dis.rcItem, nullptr);
|
|
}
|
|
DTTOPTS dttopts = { sizeof(dttopts) };
|
|
if (iTextStateID == MPI_NORMAL || iTextStateID == MPI_HOT) {
|
|
dttopts.dwFlags |= DTT_TEXTCOLOR;
|
|
dttopts.crText = NppDarkMode::GetTextColor();
|
|
}
|
|
DrawThemeTextEx(g_menuTheme, pUDMI->um.hdc, MENU_POPUPITEM, iTextStateID, menuString, mii.cch, dwFlags, &pUDMI->dis.rcItem, &dttopts);
|
|
|
|
*lr = 0;
|
|
|
|
return true;
|
|
}
|
|
case WM_THEMECHANGED:
|
|
{
|
|
if (g_menuTheme) {
|
|
CloseThemeData(g_menuTheme);
|
|
g_menuTheme = nullptr;
|
|
}
|
|
// continue processing in main wndproc
|
|
return false;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void DrawUAHMenuNCBottomLine(HWND hWnd)
|
|
{
|
|
MENUBARINFO mbi = { sizeof(mbi) };
|
|
if (!GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
RECT rcClient = { 0 };
|
|
GetClientRect(hWnd, &rcClient);
|
|
MapWindowPoints(hWnd, nullptr, (POINT*)&rcClient, 2);
|
|
|
|
RECT rcWindow = { 0 };
|
|
GetWindowRect(hWnd, &rcWindow);
|
|
|
|
OffsetRect(&rcClient, -rcWindow.left, -rcWindow.top);
|
|
|
|
// the rcBar is offset by the window rect
|
|
RECT rcAnnoyingLine = rcClient;
|
|
rcAnnoyingLine.bottom = rcAnnoyingLine.top;
|
|
rcAnnoyingLine.top--;
|
|
|
|
HDC hdc = GetWindowDC(hWnd);
|
|
FillRect(hdc, &rcAnnoyingLine, GetBackgroundBrush());
|
|
ReleaseDC(hWnd, hdc);
|
|
}
|
|
|
|
// from DarkMode.h
|
|
|
|
void InitDarkMode(bool set_dark_mode, bool set_sys_menu)
|
|
{
|
|
::InitDarkMode();
|
|
g_SystemMenuEnabled = set_sys_menu;
|
|
SetDarkMode(set_dark_mode);
|
|
}
|
|
|
|
void SetDarkMode(bool dark_mode)
|
|
{
|
|
g_darkModeEnabled = dark_mode;
|
|
|
|
if (!IsSupported())
|
|
return;
|
|
|
|
AllowDarkModeForApp(dark_mode);
|
|
if (g_SystemMenuEnabled && _FlushMenuThemes)
|
|
_FlushMenuThemes();
|
|
FixDarkScrollBar();
|
|
}
|
|
|
|
void SetSystemMenuForApp(bool set_sys_menu)
|
|
{
|
|
if (IsSupported())
|
|
g_SystemMenuEnabled = set_sys_menu;
|
|
}
|
|
|
|
void AllowDarkModeForApp(bool allow)
|
|
{
|
|
if (IsSupported())
|
|
::AllowDarkModeForApp(allow);
|
|
}
|
|
|
|
bool AllowDarkModeForWindow(HWND hWnd, bool allow)
|
|
{
|
|
if (IsSupported())
|
|
return ::AllowDarkModeForWindow(hWnd, allow);
|
|
return false;
|
|
}
|
|
|
|
void RefreshTitleBarThemeColor(HWND hWnd)
|
|
{
|
|
if (IsSupported())
|
|
::RefreshTitleBarThemeColor(hWnd);
|
|
}
|
|
|
|
void EnableDarkScrollBarForWindowAndChildren(HWND hwnd)
|
|
{
|
|
if (IsSupported())
|
|
::EnableDarkScrollBarForWindowAndChildren(hwnd);
|
|
}
|
|
|
|
void SetDarkTitleBar(HWND hwnd)
|
|
{
|
|
if (!IsSupported())
|
|
return;
|
|
|
|
::AllowDarkModeForWindow(hwnd, IsEnabled());
|
|
::RefreshTitleBarThemeColor(hwnd);
|
|
SetDarkExplorerTheme(hwnd);
|
|
}
|
|
|
|
void SetDarkExplorerTheme(HWND hwnd)
|
|
{
|
|
if (IsSupported())
|
|
SetWindowTheme(hwnd, IsEnabled() ? L"DarkMode_Explorer" : nullptr, nullptr);
|
|
}
|
|
|
|
void SetDarkListView(HWND hwnd)
|
|
{
|
|
if (!IsSupported())
|
|
return;
|
|
bool useDark = IsEnabled();
|
|
|
|
if (HWND hHeader = ListView_GetHeader(hwnd)) {
|
|
_AllowDarkModeForWindow(hHeader, useDark);
|
|
SetWindowTheme(hHeader, useDark ? L"ItemsView" : nullptr, nullptr);
|
|
}
|
|
|
|
_AllowDarkModeForWindow(hwnd, useDark);
|
|
SetWindowTheme(hwnd, L"Explorer", nullptr);
|
|
}
|
|
|
|
void SetDarkListViewHeader(HWND hHeader)
|
|
{
|
|
if (!IsSupported())
|
|
return;
|
|
bool useDark = IsEnabled();
|
|
|
|
_AllowDarkModeForWindow(hHeader, useDark);
|
|
SetWindowTheme(hHeader, useDark ? L"ItemsView" : nullptr, nullptr);
|
|
}
|
|
|
|
int scaled(HWND hwnd, int val)
|
|
{
|
|
float scale = 1.0;
|
|
// Both GetDpiForWindow and GetDpiForSystem shall be supported since Windows 10, version 1607
|
|
if (_GetDpiForWindow && _GetDpiForSystem)
|
|
scale = float(_GetDpiForWindow(hwnd)) / _GetDpiForSystem();
|
|
return std::round(scale * val);
|
|
}
|
|
|
|
struct ButtonData
|
|
{
|
|
HTHEME hTheme = nullptr;
|
|
int iStateID = 0;
|
|
POINT mouse_pos{ -1,-1 };
|
|
|
|
~ButtonData()
|
|
{
|
|
closeTheme();
|
|
}
|
|
|
|
bool ensureTheme(HWND hwnd)
|
|
{
|
|
if (!hTheme)
|
|
{
|
|
hTheme = OpenThemeData(hwnd, L"Button");
|
|
}
|
|
return hTheme != nullptr;
|
|
}
|
|
|
|
void closeTheme()
|
|
{
|
|
if (hTheme)
|
|
{
|
|
CloseThemeData(hTheme);
|
|
hTheme = nullptr;
|
|
}
|
|
}
|
|
};
|
|
|
|
void renderButton(HWND hwnd, HDC hdc, HTHEME hTheme, int iPartID, int iStateID)
|
|
{
|
|
RECT rcClient = { 0 };
|
|
DWORD nStyle = GetWindowLong(hwnd, GWL_STYLE);
|
|
/* WCHAR szText[256] = { 0 };
|
|
DWORD nState = static_cast<DWORD>(SendMessage(hwnd, BM_GETSTATE, 0, 0));
|
|
DWORD uiState = static_cast<DWORD>(SendMessage(hwnd, WM_QUERYUISTATE, 0, 0));
|
|
|
|
HFONT hFont = nullptr;
|
|
HFONT hOldFont = nullptr;
|
|
HFONT hCreatedFont = nullptr;
|
|
LOGFONT lf = { 0 };
|
|
if (SUCCEEDED(GetThemeFont(hTheme, hdc, iPartID, iStateID, TMT_FONT, &lf)))
|
|
{
|
|
hCreatedFont = CreateFontIndirect(&lf);
|
|
hFont = hCreatedFont;
|
|
}
|
|
|
|
if (!hFont) {
|
|
hFont = reinterpret_cast<HFONT>(SendMessage(hwnd, WM_GETFONT, 0, 0));
|
|
}
|
|
|
|
hOldFont = static_cast<HFONT>(SelectObject(hdc, hFont));
|
|
|
|
DWORD dtFlags = DT_LEFT; // DT_LEFT is 0
|
|
dtFlags |= (nStyle & BS_MULTILINE) ? DT_WORDBREAK : DT_SINGLELINE;
|
|
dtFlags |= ((nStyle & BS_CENTER) == BS_CENTER) ? DT_CENTER : (nStyle & BS_RIGHT) ? DT_RIGHT : 0;
|
|
dtFlags |= ((nStyle & BS_VCENTER) == BS_VCENTER) ? DT_VCENTER : (nStyle & BS_BOTTOM) ? DT_BOTTOM : 0;
|
|
dtFlags |= (uiState & UISF_HIDEACCEL) ? DT_HIDEPREFIX : 0;
|
|
|
|
if (!(nStyle & BS_MULTILINE) && !(nStyle & BS_BOTTOM) && !(nStyle & BS_TOP))
|
|
{
|
|
dtFlags |= DT_VCENTER;
|
|
}
|
|
|
|
GetClientRect(hwnd, &rcClient);
|
|
GetWindowText(hwnd, szText, _countof(szText));
|
|
|
|
SIZE szBox = { 13, 13 };
|
|
GetThemePartSize(hTheme, hdc, iPartID, iStateID, NULL, TS_DRAW, &szBox);
|
|
|
|
RECT rcText = rcClient;
|
|
GetThemeBackgroundContentRect(hTheme, hdc, iPartID, iStateID, &rcClient, &rcText);
|
|
|
|
RECT rcBackground = rcClient;
|
|
if (dtFlags & DT_SINGLELINE)
|
|
{
|
|
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, nullptr);
|
|
|
|
DTTOPTS dtto = { sizeof(DTTOPTS), DTT_TEXTCOLOR };
|
|
dtto.crText = iPartID == SBP_ARROWBTN ? GetSofterTextColor() : GetTextColor();
|
|
|
|
if (nStyle & WS_DISABLED)
|
|
{
|
|
dtto.crText = GetSofterBackgroundColor();
|
|
}
|
|
*/
|
|
GetClientRect(hwnd, &rcClient);
|
|
DrawThemeParentBackground(hwnd, hdc, &rcClient);
|
|
|
|
if (iPartID == SBP_ARROWBTN)
|
|
{
|
|
{
|
|
HBRUSH hbrush = ::CreateSolidBrush(RGB(0x64, 0x64, 0x64));
|
|
::FrameRect(hdc, &rcClient, hbrush);
|
|
::DeleteObject(hbrush);
|
|
}
|
|
|
|
COLORREF color = nStyle & WS_DISABLED ? GetSofterBackgroundColor() : GetSofterTextColor();
|
|
|
|
HPEN hPen = CreatePen(PS_SOLID, 1, color);
|
|
HPEN hOldPen = SelectPen(hdc, hPen);
|
|
|
|
HBRUSH hBrush = CreateSolidBrush(color);
|
|
HBRUSH hOldBrush = SelectBrush(hdc, hBrush);
|
|
|
|
// Up arrow
|
|
RECT rcFocus = rcClient;
|
|
rcFocus.bottom *= 0.5;
|
|
rcFocus.left += 1;
|
|
InflateRect(&rcFocus, -1, -1);
|
|
|
|
int triangle_edge = int(0.25 * (rcClient.right - rcClient.left));
|
|
|
|
int left_pos = triangle_edge + 1;
|
|
int shift_from_center = 0.5 * triangle_edge;
|
|
int bottom_pos = rcFocus.bottom - shift_from_center;
|
|
rcFocus.bottom += 1;
|
|
POINT vertices_up[] = { {left_pos, bottom_pos }, {left_pos + triangle_edge, bottom_pos - triangle_edge}, {left_pos + 2*triangle_edge, bottom_pos} };
|
|
Polygon(hdc, vertices_up, 3);
|
|
|
|
if (iStateID == ARROWBTNSTATES::ABS_UPHOT)
|
|
DrawFocusRect(hdc, &rcFocus);
|
|
|
|
// Down arrow
|
|
rcFocus = rcClient;
|
|
rcFocus.top = 0.5 * rcFocus.bottom;
|
|
rcFocus.left += 1;
|
|
InflateRect(&rcFocus, -1, -1);
|
|
|
|
int top_pos = rcFocus.top + shift_from_center;
|
|
POINT vertices_down[] = { {left_pos, top_pos }, {left_pos + triangle_edge, top_pos + triangle_edge}, {left_pos + 2 * triangle_edge, top_pos} };
|
|
Polygon(hdc, vertices_down, 3);
|
|
|
|
if (iStateID == ARROWBTNSTATES::ABS_DOWNHOT)
|
|
DrawFocusRect(hdc, &rcFocus);
|
|
|
|
SelectBrush(hdc, hOldBrush);
|
|
DeleteObject(hBrush);
|
|
|
|
SelectPen(hdc, hOldPen);
|
|
DeleteObject(hPen);
|
|
}
|
|
/* else
|
|
DrawThemeTextEx(hTheme, hdc, iPartID, iStateID, szText, -1, dtFlags, &rcText, &dtto);
|
|
|
|
if ((nState & BST_FOCUS) && !(uiState & UISF_HIDEFOCUS))
|
|
{
|
|
RECT rcTextOut = rcText;
|
|
dtto.dwFlags |= DTT_CALCRECT;
|
|
DrawThemeTextEx(hTheme, hdc, iPartID, iStateID, szText, -1, dtFlags | DT_CALCRECT, &rcTextOut, &dtto);
|
|
RECT rcFocus = rcTextOut;
|
|
rcFocus.bottom++;
|
|
rcFocus.left--;
|
|
rcFocus.right++;
|
|
DrawFocusRect(hdc, &rcFocus);
|
|
}
|
|
|
|
if (hCreatedFont) DeleteObject(hCreatedFont);
|
|
SelectObject(hdc, hOldFont);
|
|
*/ }
|
|
|
|
void paintButton(HWND hwnd, HDC hdc, ButtonData& buttonData)
|
|
{
|
|
DWORD nState = static_cast<DWORD>(SendMessage(hwnd, BM_GETSTATE, 0, 0));
|
|
DWORD nStyle = GetWindowLong(hwnd, GWL_STYLE);
|
|
DWORD nButtonStyle = nStyle & 0xF;
|
|
|
|
int iPartID = BP_CHECKBOX;
|
|
if (nButtonStyle == BS_CHECKBOX || nButtonStyle == BS_AUTOCHECKBOX)
|
|
{
|
|
iPartID = BP_CHECKBOX;
|
|
}
|
|
else if (nButtonStyle == BS_RADIOBUTTON || nButtonStyle == BS_AUTORADIOBUTTON)
|
|
{
|
|
iPartID = BP_RADIOBUTTON;
|
|
}
|
|
else if (nButtonStyle == BS_AUTO3STATE)
|
|
{
|
|
iPartID = SBP_ARROWBTN;
|
|
}
|
|
else
|
|
{
|
|
assert(false);
|
|
}
|
|
|
|
// states of BP_CHECKBOX and BP_RADIOBUTTON are the same
|
|
int iStateID = RBS_UNCHECKEDNORMAL;
|
|
|
|
if (nStyle & WS_DISABLED) iStateID = RBS_UNCHECKEDDISABLED;
|
|
else if (nState & BST_PUSHED) iStateID = RBS_UNCHECKEDPRESSED;
|
|
else if (nState & BST_HOT) iStateID = RBS_UNCHECKEDHOT;
|
|
|
|
if (nState & BST_CHECKED) iStateID += 4;
|
|
|
|
if (BufferedPaintRenderAnimation(hwnd, hdc))
|
|
{
|
|
return;
|
|
}
|
|
|
|
BP_ANIMATIONPARAMS animParams = { sizeof(animParams) };
|
|
animParams.style = BPAS_LINEAR;
|
|
if (iStateID != buttonData.iStateID)
|
|
{
|
|
GetThemeTransitionDuration(buttonData.hTheme, iPartID, buttonData.iStateID, iStateID, TMT_TRANSITIONDURATIONS, &animParams.dwDuration);
|
|
}
|
|
|
|
RECT rcClient = { 0 };
|
|
GetClientRect(hwnd, &rcClient);
|
|
|
|
HDC hdcFrom = nullptr;
|
|
HDC hdcTo = nullptr;
|
|
HANIMATIONBUFFER hbpAnimation = BeginBufferedAnimation(hwnd, hdc, &rcClient, BPBF_COMPATIBLEBITMAP, nullptr, &animParams, &hdcFrom, &hdcTo);
|
|
if (hbpAnimation)
|
|
{
|
|
if (hdcFrom)
|
|
{
|
|
renderButton(hwnd, hdcFrom, buttonData.hTheme, iPartID, buttonData.iStateID);
|
|
}
|
|
if (hdcTo)
|
|
{
|
|
if (iPartID == SBP_ARROWBTN && (buttonData.iStateID == ARROWBTNSTATES::ABS_DOWNHOT || buttonData.iStateID == ARROWBTNSTATES::ABS_UPHOT) )
|
|
iStateID = buttonData.iStateID;
|
|
renderButton(hwnd, hdcTo, buttonData.hTheme, iPartID, iStateID);
|
|
}
|
|
|
|
buttonData.iStateID = iStateID;
|
|
|
|
EndBufferedAnimation(hbpAnimation, TRUE);
|
|
}
|
|
else
|
|
{
|
|
renderButton(hwnd, hdc, buttonData.hTheme, iPartID, iStateID);
|
|
|
|
buttonData.iStateID = iStateID;
|
|
}
|
|
}
|
|
|
|
constexpr UINT_PTR g_buttonSubclassID = 42;
|
|
|
|
LRESULT CALLBACK ButtonSubclass(
|
|
HWND hWnd,
|
|
UINT uMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
UINT_PTR uIdSubclass,
|
|
DWORD_PTR dwRefData
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(uIdSubclass);
|
|
|
|
auto pButtonData = reinterpret_cast<ButtonData*>(dwRefData);
|
|
|
|
auto paint = [pButtonData](HWND hWnd, WPARAM wParam)
|
|
{
|
|
PAINTSTRUCT ps = { 0 };
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
if (!hdc)
|
|
{
|
|
hdc = BeginPaint(hWnd, &ps);
|
|
}
|
|
|
|
paintButton(hWnd, hdc, *pButtonData);
|
|
|
|
if (ps.hdc)
|
|
{
|
|
EndPaint(hWnd, &ps);
|
|
}
|
|
};
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_UPDATEUISTATE:
|
|
if (HIWORD(wParam) & (UISF_HIDEACCEL | UISF_HIDEFOCUS))
|
|
{
|
|
InvalidateRect(hWnd, nullptr, FALSE);
|
|
}
|
|
break;
|
|
case WM_NCDESTROY:
|
|
RemoveWindowSubclass(hWnd, ButtonSubclass, g_buttonSubclassID);
|
|
delete pButtonData;
|
|
break;
|
|
case WM_ERASEBKGND:
|
|
if (IsEnabled() && pButtonData->ensureTheme(hWnd))
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
case WM_THEMECHANGED:
|
|
pButtonData->closeTheme();
|
|
break;
|
|
case WM_PRINTCLIENT:
|
|
case WM_PAINT:
|
|
if (pButtonData->ensureTheme(hWnd))
|
|
{
|
|
paint(hWnd, wParam);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
DWORD nStyle = GetWindowLong(hWnd, GWL_STYLE);
|
|
DWORD nButtonStyle = nStyle & 0xF;
|
|
if (nButtonStyle == BS_AUTO3STATE)
|
|
{
|
|
int xPos = GET_X_LPARAM(lParam);
|
|
int yPos = GET_Y_LPARAM(lParam);
|
|
|
|
RECT rcClient = { 0 };
|
|
GetClientRect(hWnd, &rcClient);
|
|
|
|
int iStateID = 0;
|
|
if (xPos <= rcClient.top || xPos >= rcClient.bottom ||
|
|
yPos <= rcClient.left || yPos >= rcClient.right)
|
|
iStateID = ARROWBTNSTATES::ABS_UPNORMAL;
|
|
else
|
|
iStateID = yPos > 0.5 * rcClient.bottom ? ARROWBTNSTATES::ABS_DOWNHOT : ARROWBTNSTATES::ABS_UPHOT;
|
|
|
|
if (pButtonData->iStateID != iStateID)
|
|
{
|
|
pButtonData->iStateID = iStateID;
|
|
paint(hWnd, wParam);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case WM_KEYUP:
|
|
case WM_CHAR:
|
|
case WM_KEYDOWN:
|
|
case WM_VSCROLL:
|
|
{
|
|
DWORD nState = GET_KEYSTATE_WPARAM(wParam);
|
|
if (nState == VK_UP || nState == VK_DOWN) {
|
|
int iStateID = nState == VK_DOWN ? ARROWBTNSTATES::ABS_DOWNHOT : ARROWBTNSTATES::ABS_UPHOT;
|
|
if (pButtonData->iStateID != iStateID)
|
|
{
|
|
pButtonData->iStateID = iStateID;
|
|
paint(hWnd, wParam);
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case WM_SIZE:
|
|
case WM_DESTROY:
|
|
BufferedPaintStopAllAnimations(hWnd);
|
|
break;
|
|
case WM_ENABLE:
|
|
if (IsEnabled())
|
|
{
|
|
// skip the button's normal wndproc so it won't redraw out of wm_paint
|
|
LRESULT lr = DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
InvalidateRect(hWnd, nullptr, FALSE);
|
|
return lr;
|
|
}
|
|
break;
|
|
}
|
|
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
void subclassButtonControl(HWND hwnd)
|
|
{
|
|
DWORD_PTR pButtonData = reinterpret_cast<DWORD_PTR>(new ButtonData());
|
|
SetWindowSubclass(hwnd, ButtonSubclass, g_buttonSubclassID, pButtonData);
|
|
}
|
|
|
|
void AutoSubclassAndThemeChildControls(HWND hwndParent, bool subclass, bool theme)
|
|
{
|
|
if (!IsSupported())
|
|
return;
|
|
|
|
struct Params
|
|
{
|
|
const wchar_t* themeClassName = nullptr;
|
|
bool subclass = false;
|
|
bool theme = false;
|
|
};
|
|
|
|
Params p{
|
|
IsEnabled() ? L"DarkMode_Explorer" : nullptr
|
|
, subclass
|
|
, theme
|
|
};
|
|
|
|
EnumChildWindows(hwndParent, [](HWND hwnd, LPARAM lParam) WINAPI_LAMBDA{
|
|
auto & p = *reinterpret_cast<Params*>(lParam);
|
|
const size_t classNameLen = 16;
|
|
TCHAR className[classNameLen] = { 0 };
|
|
GetClassName(hwnd, className, classNameLen);
|
|
|
|
if (wcscmp(className, UPDOWN_CLASS) == 0)
|
|
{
|
|
auto nButtonStyle = ::GetWindowLongPtr(hwnd, GWL_STYLE) & 0xF;
|
|
if (nButtonStyle == BS_AUTO3STATE && p.subclass)
|
|
{
|
|
subclassButtonControl(hwnd);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
return TRUE;
|
|
}, reinterpret_cast<LPARAM>(&p));
|
|
}
|
|
|
|
}
|
|
|