#include "nppDarkMode.h" #include "DarkMode/DarkMode.h" #include "DarkMode/UAHMenuBar.h" #include #include #include "Parameters.h" #include "resource.h" #include #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(SendMessage(hwnd, BM_GETSTATE, 0, 0)); DWORD uiState = static_cast(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(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(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(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(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(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(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(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(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(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(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(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(&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(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(&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(SendMessage(hwnd, msg, NULL, NULL)); if (hTips != nullptr) { SetWindowTheme(hTips, L"DarkMode_Explorer", NULL); } } } } }