ozone10 ffbf3af029 Make DarkMode title bar to apply on all dialog on dark mode
Also remove border from clipboard history panel.

Close  #10023
2021-06-20 19:09:35 +02:00

1095 lines
26 KiB
C++

#include "nppDarkMode.h"
#include "DarkMode/DarkMode.h"
#include "DarkMode/UAHMenuBar.h"
#include <Uxtheme.h>
#include <Vssym32.h>
#include "Parameters.h"
#include "resource.h"
#include <Shlwapi.h>
#pragma comment(lib, "uxtheme.lib")
namespace NppDarkMode
{
struct Colors
{
COLORREF background = 0;
COLORREF softerBackground = 0;
COLORREF hotBackground = 0;
COLORREF pureBackground = 0;
COLORREF errorBackground = 0;
COLORREF text = 0;
COLORREF darkerText = 0;
COLORREF disabledText = 0;
COLORREF edge = 0;
};
struct Brushes
{
HBRUSH background = nullptr;
HBRUSH softerBackground = nullptr;
HBRUSH hotBackground = nullptr;
HBRUSH pureBackground = nullptr;
HBRUSH errorBackground = nullptr;
Brushes(const Colors& colors)
: background(::CreateSolidBrush(colors.background))
, softerBackground(::CreateSolidBrush(colors.softerBackground))
, hotBackground(::CreateSolidBrush(colors.hotBackground))
, pureBackground(::CreateSolidBrush(colors.pureBackground))
, errorBackground(::CreateSolidBrush(colors.errorBackground))
{}
~Brushes()
{
::DeleteObject(background); background = nullptr;
::DeleteObject(softerBackground); softerBackground = nullptr;
::DeleteObject(hotBackground); hotBackground = nullptr;
::DeleteObject(pureBackground); pureBackground = nullptr;
::DeleteObject(errorBackground); errorBackground = nullptr;
}
};
static const Colors darkColors{
HEXRGB(0x202020), // background
HEXRGB(0x404040), // softerBackground
HEXRGB(0x404040), // hotBackground
HEXRGB(0x202020), // pureBackground
HEXRGB(0xB00000), // errorBackground
HEXRGB(0xE0E0E0), // textColor
HEXRGB(0xC0C0C0), // darkerTextColor
HEXRGB(0x808080), // disabledTextColor
HEXRGB(0x808080) // edgeColor
};
struct Theme
{
Colors colors;
Brushes brushes;
Theme(const Colors& colors)
: colors(colors)
, brushes(colors)
{}
};
Theme& getTheme()
{
static Theme g_theme(darkColors);
return g_theme;
}
static Options _options; // actual runtime options
const Options& configuredOptions()
{
return NppParameters::getInstance().getNppGUI()._darkmode;
}
void initDarkMode()
{
_options = configuredOptions();
if (_options.enableExperimental)
{
initExperimentalDarkMode(_options.enableScrollbarHack, _options.enable);
}
}
// attempts to apply new options from NppParameters, sends NPPM_INTERNAL_REFRESHDARKMODE to hwnd's top level parent
void refreshDarkMode(HWND hwnd, bool forceRefresh)
{
bool supportedChanged = false;
auto& config = configuredOptions();
if (_options.enable != config.enable)
{
supportedChanged = true;
_options.enable = config.enable;
}
if (_options.enableMenubar != config.enableMenubar)
{
supportedChanged = true;
_options.enableMenubar = config.enableMenubar;
}
// other options not supported to change at runtime currently
if (!supportedChanged && !forceRefresh)
{
// nothing to refresh, changes were not supported.
return;
}
HWND hwndRoot = GetAncestor(hwnd, GA_ROOTOWNER);
::SendMessage(hwndRoot, NPPM_INTERNAL_REFRESHDARKMODE, 0, 0);
}
bool isEnabled()
{
return _options.enable;
}
bool isDarkMenuEnabled()
{
return _options.enableMenubar;
}
bool isExperimentalEnabled()
{
return _options.enableExperimental;
}
bool isScrollbarHackEnabled()
{
return _options.enableScrollbarHack;
}
bool isExperimentalActive()
{
return g_darkModeEnabled;
}
bool isExperimentalSupported()
{
return g_darkModeSupported;
}
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 = min(240 - l, 211);
COLORREF invert_c = ColorHLSToRGB(h, l, s);
return invert_c;
}
COLORREF getBackgroundColor() { return getTheme().colors.background; }
COLORREF getSofterBackgroundColor() { return getTheme().colors.softerBackground; }
COLORREF getHotBackgroundColor() { return getTheme().colors.hotBackground; }
COLORREF getDarkerBackgroundColor() { return getTheme().colors.pureBackground; }
COLORREF getErrorBackgroundColor() { return getTheme().colors.errorBackground; }
COLORREF getTextColor() { return getTheme().colors.text; }
COLORREF getDarkerTextColor() { return getTheme().colors.darkerText; }
COLORREF getDisabledTextColor() { return getTheme().colors.disabledText; }
COLORREF getEdgeColor() { return getTheme().colors.edge; }
HBRUSH getBackgroundBrush() { return getTheme().brushes.background; }
HBRUSH getSofterBackgroundBrush() { return getTheme().brushes.softerBackground; }
HBRUSH getHotBackgroundBrush() { return getTheme().brushes.hotBackground; }
HBRUSH getDarkerBackgroundBrush() { return getTheme().brushes.pureBackground; }
HBRUSH getErrorBackgroundBrush() { return getTheme().brushes.errorBackground; }
// handle events
void handleSettingChange(HWND hwnd, LPARAM lParam)
{
UNREFERENCED_PARAMETER(hwnd);
if (!isExperimentalEnabled())
{
return;
}
if (IsColorSchemeChangeMessage(lParam))
{
g_darkModeEnabled = ShouldAppsUseDarkMode() && !IsHighContrast();
}
}
// processes messages related to UAH / custom menubar drawing.
// return true if handled, false to continue with normal processing in your wndproc
bool runUAHWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT* lr)
{
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, NppDarkMode::getDarkerBackgroundBrush());
*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::getDarkerBackgroundBrush());
}
else if (iBackgroundStateID == MPI_HOT || iBackgroundStateID == MPI_DISABLEDHOT)
{
FillRect(pUDMI->um.hdc, &pUDMI->dis.rcItem, NppDarkMode::getHotBackgroundBrush());
}
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, NppDarkMode::getDarkerBackgroundBrush());
ReleaseDC(hWnd, hdc);
}
// from DarkMode.h
void initExperimentalDarkMode(bool fixDarkScrollbar, bool dark)
{
::InitDarkMode(fixDarkScrollbar, dark);
}
void allowDarkModeForApp(bool allow)
{
::AllowDarkModeForApp(allow);
}
bool allowDarkModeForWindow(HWND hWnd, bool allow)
{
return ::AllowDarkModeForWindow(hWnd, allow);
}
void setTitleBarThemeColor(HWND hWnd, bool dark)
{
::SetTitleBarThemeColor(hWnd, dark);
}
void enableDarkScrollBarForWindowAndChildren(HWND hwnd)
{
::EnableDarkScrollBarForWindowAndChildren(hwnd);
}
struct ButtonData
{
HTHEME hTheme = nullptr;
int iStateID = 0;
~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 };
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));
DWORD nStyle = GetWindowLong(hwnd, GWL_STYLE);
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));
}
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 = NppDarkMode::getTextColor();
if (nStyle & WS_DISABLED)
{
dtto.crText = NppDarkMode::getDisabledTextColor();
}
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
{
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)
{
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);
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 (NppDarkMode::isEnabled() && pButtonData->ensureTheme(hWnd))
{
return TRUE;
}
else
{
break;
}
case WM_THEMECHANGED:
pButtonData->closeTheme();
break;
case WM_PRINTCLIENT:
case WM_PAINT:
if (NppDarkMode::isEnabled() && pButtonData->ensureTheme(hWnd))
{
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);
}
return 0;
}
else
{
break;
}
case WM_SIZE:
case WM_DESTROY:
BufferedPaintStopAllAnimations(hWnd);
break;
case WM_ENABLE:
if (NppDarkMode::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 paintGroupbox(HWND hwnd, HDC hdc, ButtonData& buttonData)
{
DWORD nStyle = GetWindowLong(hwnd, GWL_STYLE);
int iPartID = BP_GROUPBOX;
int iStateID = GBS_NORMAL;
if (nStyle & WS_DISABLED)
{
iStateID = GBS_DISABLED;
}
RECT rcClient = { 0 };
GetClientRect(hwnd, &rcClient);
RECT rcText = rcClient;
RECT rcBackground = rcClient;
HFONT hFont = nullptr;
HFONT hOldFont = nullptr;
HFONT hCreatedFont = nullptr;
LOGFONT lf = { 0 };
if (SUCCEEDED(GetThemeFont(buttonData.hTheme, hdc, iPartID, iStateID, TMT_FONT, &lf)))
{
hCreatedFont = CreateFontIndirect(&lf);
hFont = hCreatedFont;
}
if (!hFont) {
hFont = reinterpret_cast<HFONT>(SendMessage(hwnd, WM_GETFONT, 0, 0));
}
SelectObject(hdc, hFont);
WCHAR szText[256] = { 0 };
GetWindowText(hwnd, szText, _countof(szText));
if (szText[0])
{
SIZE textSize = { 0 };
GetTextExtentPoint32(hdc, szText, static_cast<int>(wcslen(szText)), &textSize);
rcBackground.top += textSize.cy / 2;
rcText.left += 7;
rcText.bottom = rcText.top + textSize.cy;
rcText.right = rcText.left + textSize.cx + 4;
ExcludeClipRect(hdc, rcText.left, rcText.top, rcText.right, rcText.bottom);
}
else
{
SIZE textSize = { 0 };
GetTextExtentPoint32(hdc, L"M", 1, &textSize);
rcBackground.top += textSize.cy / 2;
}
RECT rcContent = rcBackground;
GetThemeBackgroundContentRect(buttonData.hTheme, hdc, BP_GROUPBOX, iStateID, &rcBackground, &rcContent);
ExcludeClipRect(hdc, rcContent.left, rcContent.top, rcContent.right, rcContent.bottom);
DrawThemeParentBackground(hwnd, hdc, &rcClient);
DrawThemeBackground(buttonData.hTheme, hdc, BP_GROUPBOX, iStateID, &rcBackground, nullptr);
SelectClipRgn(hdc, nullptr);
if (szText[0])
{
rcText.right -= 2;
rcText.left += 2;
DTTOPTS dtto = { sizeof(DTTOPTS), DTT_TEXTCOLOR };
dtto.crText = NppDarkMode::getTextColor();
DrawThemeTextEx(buttonData.hTheme, hdc, BP_GROUPBOX, iStateID, szText, -1, DT_LEFT | DT_SINGLELINE, &rcText, &dtto);
}
if (hCreatedFont) DeleteObject(hCreatedFont);
SelectObject(hdc, hOldFont);
}
constexpr UINT_PTR g_groupboxSubclassID = 42;
LRESULT CALLBACK GroupboxSubclass(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData
)
{
UNREFERENCED_PARAMETER(uIdSubclass);
auto pButtonData = reinterpret_cast<ButtonData*>(dwRefData);
switch (uMsg)
{
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, GroupboxSubclass, g_groupboxSubclassID);
delete pButtonData;
break;
case WM_ERASEBKGND:
if (NppDarkMode::isEnabled() && pButtonData->ensureTheme(hWnd))
{
return TRUE;
}
else
{
break;
}
case WM_THEMECHANGED:
pButtonData->closeTheme();
break;
case WM_PRINTCLIENT:
case WM_PAINT:
if (NppDarkMode::isEnabled() && pButtonData->ensureTheme(hWnd))
{
PAINTSTRUCT ps = { 0 };
HDC hdc = reinterpret_cast<HDC>(wParam);
if (!hdc)
{
hdc = BeginPaint(hWnd, &ps);
}
paintGroupbox(hWnd, hdc, *pButtonData);
if (ps.hdc)
{
EndPaint(hWnd, &ps);
}
return 0;
}
else
{
break;
}
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
void subclassGroupboxControl(HWND hwnd)
{
DWORD_PTR pButtonData = reinterpret_cast<DWORD_PTR>(new ButtonData());
SetWindowSubclass(hwnd, GroupboxSubclass, g_groupboxSubclassID, pButtonData);
}
constexpr UINT_PTR g_toolbarSubclassID = 42;
LRESULT CALLBACK ToolbarSubclass(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData
)
{
UNREFERENCED_PARAMETER(uIdSubclass);
UNREFERENCED_PARAMETER(dwRefData);
switch (uMsg)
{
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, ToolbarSubclass, g_toolbarSubclassID);
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
void subclassToolbarControl(HWND hwnd)
{
SetWindowSubclass(hwnd, ToolbarSubclass, g_toolbarSubclassID, 0);
}
constexpr UINT_PTR g_tabSubclassID = 42;
LRESULT CALLBACK TabSubclass(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData
)
{
UNREFERENCED_PARAMETER(uIdSubclass);
UNREFERENCED_PARAMETER(dwRefData);
switch (uMsg)
{
case WM_PAINT:
{
if (!NppDarkMode::isEnabled())
{
break;
}
LONG_PTR dwStyle = GetWindowLongPtr(hWnd, GWL_STYLE);
if ((dwStyle & TCS_BOTTOM) || (dwStyle & TCS_BUTTONS) || (dwStyle & TCS_VERTICAL))
{
break;
}
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
FillRect(hdc, &ps.rcPaint, NppDarkMode::getBackgroundBrush());
static HPEN g_hpen = CreatePen(PS_SOLID, 1, NppDarkMode::getEdgeColor());
HPEN holdPen = (HPEN)SelectObject(hdc, g_hpen);
HRGN holdClip = CreateRectRgn(0, 0, 0, 0);
if (1 != GetClipRgn(hdc, holdClip))
{
DeleteObject(holdClip);
holdClip = nullptr;
}
HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
auto hOldFont = SelectObject(hdc, hFont);
POINT ptCursor = { 0 };
::GetCursorPos(&ptCursor);
ScreenToClient(hWnd, &ptCursor);
int nTabs = TabCtrl_GetItemCount(hWnd);
int nSelTab = TabCtrl_GetCurSel(hWnd);
for (int i = 0; i < nTabs; ++i)
{
RECT rcItem = { 0 };
TabCtrl_GetItemRect(hWnd, i, &rcItem);
RECT rcIntersect = { 0 };
if (IntersectRect(&rcIntersect, &ps.rcPaint, &rcItem))
{
bool bHot = PtInRect(&rcItem, ptCursor);
POINT edges[] = {
{rcItem.right - 1, rcItem.top},
{rcItem.right - 1, rcItem.bottom}
};
Polyline(hdc, edges, _countof(edges));
rcItem.right -= 1;
HRGN hClip = CreateRectRgnIndirect(&rcItem);
SelectClipRgn(hdc, hClip);
SetTextColor(hdc, (bHot || (i == nSelTab) ) ? NppDarkMode::getTextColor() : NppDarkMode::getDarkerTextColor());
FillRect(hdc, &rcItem, (i == nSelTab) ? NppDarkMode::getBackgroundBrush() : NppDarkMode::getSofterBackgroundBrush());
SetBkMode(hdc, TRANSPARENT);
TCHAR label[MAX_PATH];
TCITEM tci = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = label;
tci.cchTextMax = MAX_PATH - 1;
::SendMessage(hWnd, TCM_GETITEM, i, reinterpret_cast<LPARAM>(&tci));
RECT rcText = rcItem;
rcText.left += NppParameters::getInstance()._dpiManager.scaleX(6);
rcText.right -= NppParameters::getInstance()._dpiManager.scaleX(3);
if (i == nSelTab) {
rcText.bottom -= NppParameters::getInstance()._dpiManager.scaleX(4);
}
DrawText(hdc, label, -1, &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
DeleteObject(hClip);
SelectClipRgn(hdc, holdClip);
}
}
SelectObject(hdc, hOldFont);
SelectClipRgn(hdc, holdClip);
if (holdClip)
{
DeleteObject(holdClip);
holdClip = nullptr;
}
SelectObject(hdc, holdPen);
EndPaint(hWnd, &ps);
return 0;
}
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, TabSubclass, g_tabSubclassID);
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
void subclassTabControl(HWND hwnd)
{
SetWindowSubclass(hwnd, TabSubclass, g_tabSubclassID, 0);
}
void autoSubclassAndThemeChildControls(HWND hwndParent, bool subclass, bool theme)
{
struct Params
{
const wchar_t* themeClassName = nullptr;
bool subclass = false;
bool theme = false;
};
Params p{
NppDarkMode::isEnabled() ? L"DarkMode_Explorer" : L"Button"
, subclass
, theme
};
EnumChildWindows(hwndParent, [](HWND hwnd, LPARAM lParam) {
auto& p = *reinterpret_cast<Params*>(lParam);
wchar_t className[16] = { 0 };
GetClassName(hwnd, className, 9);
if (wcscmp(className, L"Button"))
{
return TRUE;
}
DWORD nButtonStyle = (DWORD)GetWindowLong(hwnd, GWL_STYLE) & 0xF;
switch (nButtonStyle)
{
case BS_CHECKBOX:
case BS_AUTOCHECKBOX:
case BS_RADIOBUTTON:
case BS_AUTORADIOBUTTON:
if (p.subclass)
{
NppDarkMode::subclassButtonControl(hwnd);
}
break;
case BS_GROUPBOX:
if (p.subclass)
{
NppDarkMode::subclassGroupboxControl(hwnd);
}
break;
case BS_DEFPUSHBUTTON:
case BS_PUSHBUTTON:
if (p.theme)
{
SetWindowTheme(hwnd, p.themeClassName, nullptr);
}
break;
}
return TRUE;
}, reinterpret_cast<LPARAM>(&p));
}
void autoThemeChildControls(HWND hwndParent)
{
autoSubclassAndThemeChildControls(hwndParent, false, true);
}
void setDarkTitleBar(HWND hwnd)
{
bool useDark = NppDarkMode::isExperimentalEnabled() && NppDarkMode::isEnabled();
NppDarkMode::allowDarkModeForWindow(hwnd, useDark);
NppDarkMode::setTitleBarThemeColor(hwnd, useDark);
if (useDark)
{
SetWindowTheme(hwnd, L"Explorer", nullptr);
}
else
{
SetWindowTheme(hwnd, nullptr, nullptr);
}
}
void setDarkTooltips(HWND hwnd, ToolTipsType type)
{
if (NppDarkMode::isEnabled())
{
int msg = NULL;
switch (type)
{
case NppDarkMode::ToolTipsType::toolbar:
msg = TB_GETTOOLTIPS;
break;
case NppDarkMode::ToolTipsType::listview:
msg = LVM_GETTOOLTIPS;
break;
case NppDarkMode::ToolTipsType::treeview:
msg = TVM_GETTOOLTIPS;
break;
default:
msg = NULL;
break;
}
if (!msg)
{
SetWindowTheme(hwnd, L"DarkMode_Explorer", NULL);
}
else
{
auto hTips = reinterpret_cast<HWND>(SendMessage(hwnd, msg, NULL, NULL));
if (hTips != nullptr)
{
SetWindowTheme(hTips, L"DarkMode_Explorer", NULL);
}
}
}
}
}