// This file is part of Notepad++ project // Copyright (C)2021 adzm / Adam D. Walling // 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 . #include "NppDarkMode.h" #include "DarkMode/DarkMode.h" #include "DarkMode/UAHMenuBar.h" #include #include #include #include "Parameters.h" #include "resource.h" #include #include #ifdef __GNUC__ #include #define WINAPI_LAMBDA WINAPI #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif #else #define WINAPI_LAMBDA #endif // already added in project files // keep for plugin authors //#ifdef _MSC_VER //#pragma comment(lib, "dwmapi.lib") //#pragma comment(lib, "uxtheme.lib") //#endif constexpr COLORREF HEXRGB(DWORD rrggbb) { // from 0xRRGGBB like natural #RRGGBB // to the little-endian 0xBBGGRR return ((rrggbb & 0xFF0000) >> 16) | ((rrggbb & 0x00FF00)) | ((rrggbb & 0x0000FF) << 16); } namespace NppDarkMode { struct Brushes//结构体用于管理颜色和画笔 { HBRUSH background = nullptr; HBRUSH softerBackground = nullptr; HBRUSH hotBackground = nullptr; HBRUSH pureBackground = nullptr; HBRUSH errorBackground = nullptr; //HBRUSH 类型的成员变量,用于存储不同背景颜色的画刷对象 HBRUSH edgeBrush = nullptr; HBRUSH hotEdgeBrush = nullptr; HBRUSH disabledEdgeBrush = nullptr; Brushes(const Colors& colors) //构造函数:接受一个 Colors 对象作为参数,使用 CreateSolidBrush 函数创建相应颜色的画刷对象。 : background(::CreateSolidBrush(colors.background)) , softerBackground(::CreateSolidBrush(colors.softerBackground)) , hotBackground(::CreateSolidBrush(colors.hotBackground)) , pureBackground(::CreateSolidBrush(colors.pureBackground)) , errorBackground(::CreateSolidBrush(colors.errorBackground)) , edgeBrush(::CreateSolidBrush(colors.edge)) , hotEdgeBrush(::CreateSolidBrush(colors.hotEdge)) , disabledEdgeBrush(::CreateSolidBrush(colors.disabledEdge)) {} ~Brushes()//析构函数 释放所有画刷对象,使用 DeleteObject 函数 { ::DeleteObject(background); background = nullptr; ::DeleteObject(softerBackground); softerBackground = nullptr; ::DeleteObject(hotBackground); hotBackground = nullptr; ::DeleteObject(pureBackground); pureBackground = nullptr; ::DeleteObject(errorBackground); errorBackground = nullptr; ::DeleteObject(edgeBrush); edgeBrush = nullptr; ::DeleteObject(hotEdgeBrush); hotEdgeBrush = nullptr; ::DeleteObject(disabledEdgeBrush); disabledEdgeBrush = nullptr; } void change(const Colors& colors)//change 函数:用于更改颜色,首先释放当前的画刷对象,然后创建新的画刷对象 { ::DeleteObject(background); ::DeleteObject(softerBackground); ::DeleteObject(hotBackground); ::DeleteObject(pureBackground); ::DeleteObject(errorBackground); ::DeleteObject(edgeBrush); ::DeleteObject(hotEdgeBrush); ::DeleteObject(disabledEdgeBrush); background = ::CreateSolidBrush(colors.background); softerBackground = ::CreateSolidBrush(colors.softerBackground); hotBackground = ::CreateSolidBrush(colors.hotBackground); pureBackground = ::CreateSolidBrush(colors.pureBackground); errorBackground = ::CreateSolidBrush(colors.errorBackground); edgeBrush = ::CreateSolidBrush(colors.edge); hotEdgeBrush = ::CreateSolidBrush(colors.hotEdge); disabledEdgeBrush = ::CreateSolidBrush(colors.disabledEdge); } }; struct Pens { HPEN darkerTextPen = nullptr; HPEN edgePen = nullptr; HPEN hotEdgePen = nullptr; HPEN disabledEdgePen = nullptr; //HPEN 类型的成员变量,用于存储不同颜色的画笔对象 Pens(const Colors& colors) //构造函数 接受一个 Colors 对象作为参数,使用 CreatePen 函数创建相应颜色的画笔对象 : darkerTextPen(::CreatePen(PS_SOLID, 1, colors.darkerText)) , edgePen(::CreatePen(PS_SOLID, 1, colors.edge)) , hotEdgePen(::CreatePen(PS_SOLID, 1, colors.hotEdge)) , disabledEdgePen(::CreatePen(PS_SOLID, 1, colors.disabledEdge)) {} ~Pens() {//析构函数:释放所有画笔对象,使用 DeleteObject 函数 ::DeleteObject(darkerTextPen); darkerTextPen = nullptr; ::DeleteObject(edgePen); edgePen = nullptr; ::DeleteObject(hotEdgePen); hotEdgePen = nullptr; ::DeleteObject(disabledEdgePen); disabledEdgePen = nullptr; } void change(const Colors& colors) {//change 函数:用于更改颜色,首先释放当前的画笔对象,然后创建新的画笔对象 ::DeleteObject(darkerTextPen); ::DeleteObject(edgePen); ::DeleteObject(hotEdgePen); ::DeleteObject(disabledEdgePen); darkerTextPen = ::CreatePen(PS_SOLID, 1, colors.darkerText); edgePen = ::CreatePen(PS_SOLID, 1, colors.edge); hotEdgePen = ::CreatePen(PS_SOLID, 1, colors.hotEdge); disabledEdgePen = ::CreatePen(PS_SOLID, 1, colors.disabledEdge); } }; // black (default) 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(0xFFFF00), // linkTextColor HEXRGB(0x646464), // edgeColor HEXRGB(0x9B9B9B), // hotEdgeColor HEXRGB(0x484848) // disabledEdgeColor }; // red tone static const Colors darkRedColors{ HEXRGB(0x302020), // background HEXRGB(0x504040), // softerBackground HEXRGB(0x504040), // hotBackground HEXRGB(0x302020), // pureBackground HEXRGB(0xC00000), // errorBackground HEXRGB(0xE0E0E0), // textColor HEXRGB(0xC0C0C0), // darkerTextColor HEXRGB(0x808080), // disabledTextColor HEXRGB(0xFFFF00), // linkTextColor HEXRGB(0x908080), // edgeColor HEXRGB(0xBBABAB), // hotEdgeColor HEXRGB(0x584848) // disabledEdgeColor }; // green tone static const Colors darkGreenColors{ HEXRGB(0x203020), // background HEXRGB(0x405040), // softerBackground HEXRGB(0x405040), // hotBackground HEXRGB(0x203020), // pureBackground HEXRGB(0xB01000), // errorBackground HEXRGB(0xE0E0E0), // textColor HEXRGB(0xC0C0C0), // darkerTextColor HEXRGB(0x808080), // disabledTextColor HEXRGB(0xFFFF00), // linkTextColor HEXRGB(0x809080), // edgeColor HEXRGB(0xABBBAB), // hotEdgeColor HEXRGB(0x485848) // disabledEdgeColor }; // blue tone static const Colors darkBlueColors{ HEXRGB(0x202040), // background HEXRGB(0x404060), // softerBackground HEXRGB(0x404060), // hotBackground HEXRGB(0x202040), // pureBackground HEXRGB(0xB00020), // errorBackground HEXRGB(0xE0E0E0), // textColor HEXRGB(0xC0C0C0), // darkerTextColor HEXRGB(0x808080), // disabledTextColor HEXRGB(0xFFFF00), // linkTextColor HEXRGB(0x8080A0), // edgeColor HEXRGB(0xABABCB), // hotEdgeColor HEXRGB(0x484868) // disabledEdgeColor }; // purple tone static const Colors darkPurpleColors{ HEXRGB(0x302040), // background HEXRGB(0x504060), // softerBackground HEXRGB(0x504060), // hotBackground HEXRGB(0x302040), // pureBackground HEXRGB(0xC00020), // errorBackground HEXRGB(0xE0E0E0), // textColor HEXRGB(0xC0C0C0), // darkerTextColor HEXRGB(0x808080), // disabledTextColor HEXRGB(0xFFFF00), // linkTextColor HEXRGB(0x9080A0), // edgeColor HEXRGB(0xBBABCB), // hotEdgeColor HEXRGB(0x584868) // disabledEdgeColor }; // cyan tone static const Colors darkCyanColors{ HEXRGB(0x203040), // background HEXRGB(0x405060), // softerBackground HEXRGB(0x405060), // hotBackground HEXRGB(0x203040), // pureBackground HEXRGB(0xB01020), // errorBackground HEXRGB(0xE0E0E0), // textColor HEXRGB(0xC0C0C0), // darkerTextColor HEXRGB(0x808080), // disabledTextColor HEXRGB(0xFFFF00), // linkTextColor HEXRGB(0x8090A0), // edgeColor HEXRGB(0xBBBBCB), // hotEdgeColor HEXRGB(0x485868) // disabledEdgeColor }; // olive tone static const Colors darkOliveColors{ HEXRGB(0x303020), // background HEXRGB(0x505040), // softerBackground HEXRGB(0x505040), // hotBackground HEXRGB(0x303020), // pureBackground HEXRGB(0xC01000), // errorBackground HEXRGB(0xE0E0E0), // textColor HEXRGB(0xC0C0C0), // darkerTextColor HEXRGB(0x808080), // disabledTextColor HEXRGB(0xFFFF00), // linkTextColor HEXRGB(0x909080), // edgeColor HEXRGB(0xBBBBAB), // hotEdgeColor HEXRGB(0x585848) // disabledEdgeColor }; // customized Colors darkCustomizedColors{ 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(0xFFFF00), // linkTextColor HEXRGB(0x646464), // edgeColor HEXRGB(0x9B9B9B), // hotEdgeColor HEXRGB(0x484848) // disabledEdgeColor }; ColorTone g_colorToneChoice = blackTone; void setDarkTone(ColorTone colorToneChoice) { g_colorToneChoice = colorToneChoice; } // 定义主题结构体,包含颜色、画刷和画笔 struct Theme { Colors _colors; // 存储颜色 Brushes _brushes; // 管理画刷 Pens _pens; // 管理画笔 // 构造函数,初始化颜色、画刷和画笔 Theme(const Colors& colors) : _colors(colors), _brushes(colors), _pens(colors) {} // 更改主题颜色 void change(const Colors& colors) { _colors = colors; // 更新颜色 _brushes.change(colors); // 更改画刷颜色 _pens.change(colors); // 更改画笔颜色 } }; // 定义不同主题的实例 Theme tDefault(darkColors); // 默认主题 Theme tR(darkRedColors); // 红色主题 Theme tG(darkGreenColors); // 绿色主题 Theme tB(darkBlueColors); // 蓝色主题 Theme tP(darkPurpleColors); // 紫色主题 Theme tC(darkCyanColors); // 青色主题 Theme tO(darkOliveColors); // 橄榄色主题 Theme tCustom(darkCustomizedColors); // 自定义主题 // 获取当前主题 Theme& getTheme() { switch (g_colorToneChoice) { case redTone: return tR; // 返回红色主题 case greenTone: return tG; // 返回绿色主题 case blueTone: return tB; // 返回蓝色主题 case purpleTone: return tP; // 返回紫色主题 case cyanTone: return tC; // 返回青色主题 case oliveTone: return tO; // 返回橄榄色主题 case customizedTone: return tCustom; // 返回自定义主题 default: return tDefault; // 返回默认主题 } } // 获取已配置的选项 Options configuredOptions() { NppGUI nppGui = NppParameters::getInstance().getNppGUI(); Options opt; opt.enable = nppGui._darkmode._isEnabled; // 设置是否启用暗模式 opt.enableMenubar = opt.enable; // 设置是否启用菜单栏 opt.enablePlugin = nppGui._darkmode._isEnabledPlugin; // 设置是否启用插件 g_colorToneChoice = nppGui._darkmode._colorTone; // 设置颜色调色板选择 tCustom.change(nppGui._darkmode._customColors); // 更改自定义主题的颜色 return opt; // 返回选项 } // 初始化暗模式 void initDarkMode() { _options = configuredOptions(); // 设置选项 initExperimentalDarkMode(); // 初始化实验性暗模式 initAdvancedOptions(); // 初始化高级选项 g_isAtLeastWindows10 = NppDarkMode::isWindows10(); // 判断是否至少为 Windows 10 if (!g_isAtLeastWindows10) { g_advOptions._enableWindowsMode = false; // 如果不是 Windows 10,则禁用 Windows 模式 } else if (NppDarkMode::isWindowsModeEnabled()) { NppParameters& nppParam = NppParameters::getInstance(); NppGUI& nppGUI = nppParam.getNppGUI(); nppGUI._darkmode._isEnabled = NppDarkMode::isDarkModeReg() && !IsHighContrast(); // 检查并设置暗模式状态 _options.enable = nppGUI._darkmode._isEnabled; // 更新选项中的暗模式状态 _options.enableMenubar = _options.enable; // 更新选项中的菜单栏状态 } } setDarkMode(_options.enable, true); using PWINEGETVERSION = const CHAR* (__cdecl *)(void); PWINEGETVERSION pWGV = nullptr; auto hNtdllModule = GetModuleHandle(L"ntdll.dll"); if (hNtdllModule) { pWGV = reinterpret_cast(GetProcAddress(hNtdllModule, "wine_get_version")); } g_isWine = pWGV != nullptr; } // 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; setDarkMode(_options.enable, _options.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); // wParam == true, will reset style and toolbar icon ::SendMessage(hwndRoot, NPPM_INTERNAL_REFRESHDARKMODE, static_cast(!forceRefresh), 0); } void initAdvancedOptions() { NppGUI& nppGui = NppParameters::getInstance().getNppGUI(); g_advOptions = nppGui._darkmode._advOptions; } bool isEnabled() { return _options.enable; } bool isEnabledForPlugins() { return _options.enablePlugin; } bool isDarkMenuEnabled() { return _options.enableMenubar; } bool isExperimentalActive() { return g_darkModeEnabled; } bool isExperimentalSupported() { return g_darkModeSupported; } bool isWindowsModeEnabled() { return g_advOptions._enableWindowsMode; } void setWindowsMode(bool enable) { g_advOptions._enableWindowsMode = enable; } void setThemeName(const generic_string& newThemeName) { if (NppDarkMode::isEnabled()) g_advOptions._darkDefaults._xmlFileName = newThemeName; else g_advOptions._lightDefaults._xmlFileName = newThemeName; } generic_string getThemeName() { auto& theme = NppDarkMode::isEnabled() ? g_advOptions._darkDefaults._xmlFileName : g_advOptions._lightDefaults._xmlFileName; return (lstrcmp(theme.c_str(), L"stylers.xml") == 0) ? L"" : theme; } static bool g_isCustomToolIconUsed = NppParameters::getInstance().getCustomizedToolIcons() != nullptr; void setToolBarIconSet(int state2Set, bool useDark) { if (useDark) g_advOptions._darkDefaults._toolBarIconSet = state2Set; else g_advOptions._lightDefaults._toolBarIconSet = state2Set; } int getToolBarIconSet(bool useDark) { if (g_isCustomToolIconUsed) { return -1; } return useDark ? g_advOptions._darkDefaults._toolBarIconSet : g_advOptions._lightDefaults._toolBarIconSet; } void setTabIconSet(bool useAltIcons, bool useDark) { if (useDark) g_advOptions._darkDefaults._tabIconSet = useAltIcons ? 1 : 2; else g_advOptions._lightDefaults._tabIconSet = useAltIcons ? 1 : 0; } int getTabIconSet(bool useDark) { return useDark ? g_advOptions._darkDefaults._tabIconSet : g_advOptions._lightDefaults._tabIconSet; } bool useTabTheme() { return NppDarkMode::isEnabled() ? g_advOptions._darkDefaults._tabUseTheme : g_advOptions._lightDefaults._tabUseTheme; } void setAdvancedOptions() { NppGUI& nppGui = NppParameters::getInstance().getNppGUI(); auto& advOpt = nppGui._darkmode._advOptions; advOpt = g_advOptions; } bool isWindows10() { return IsWindows10(); } bool isWindows11() { return IsWindows11(); } DWORD getWindowsBuildNumber() { return GetWindowsBuildNumber(); } 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(240U - l, 211U); COLORREF invert_c = ColorHLSToRGB(h, l, s); return invert_c; } static TreeViewStyle g_treeViewStyle = TreeViewStyle::classic; static COLORREF g_treeViewBg = NppParameters::getInstance().getCurrentDefaultBgColor(); static double g_lighnessTreeView = 50.0; // adapted from https://stackoverflow.com/a/56678483 double calculatePerceivedLighness(COLORREF c) { auto linearValue = [](double colorChannel) -> double { colorChannel /= 255.0; if (colorChannel <= 0.04045) return colorChannel / 12.92; return std::pow(((colorChannel + 0.055) / 1.055), 2.4); }; double r = linearValue(static_cast(GetRValue(c))); double g = linearValue(static_cast(GetGValue(c))); double b = linearValue(static_cast(GetBValue(c))); double luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b; double lighness = (luminance <= 216.0 / 24389.0) ? (luminance * 24389.0 / 27.0) : (std::pow(luminance, (1.0 / 3.0)) * 116.0 - 16.0); return lighness; } 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 getLinkTextColor() { return getTheme()._colors.linkText; } COLORREF getEdgeColor() { return getTheme()._colors.edge; } COLORREF getHotEdgeColor() { return getTheme()._colors.hotEdge; } COLORREF getDisabledEdgeColor() { return getTheme()._colors.disabledEdge; } 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; } HBRUSH getEdgeBrush() { return getTheme()._brushes.edgeBrush; } HBRUSH getHotEdgeBrush() { return getTheme()._brushes.hotEdgeBrush; } HBRUSH getDisabledEdgeBrush() { return getTheme()._brushes.disabledEdgeBrush; } HPEN getDarkerTextPen() { return getTheme()._pens.darkerTextPen; } HPEN getEdgePen() { return getTheme()._pens.edgePen; } HPEN getHotEdgePen() { return getTheme()._pens.hotEdgePen; } HPEN getDisabledEdgePen() { return getTheme()._pens.disabledEdgePen; } void setBackgroundColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.background = c; getTheme().change(clrs); } void setSofterBackgroundColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.softerBackground = c; getTheme().change(clrs); } void setHotBackgroundColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.hotBackground = c; getTheme().change(clrs); } void setDarkerBackgroundColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.pureBackground = c; getTheme().change(clrs); } void setErrorBackgroundColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.errorBackground = c; getTheme().change(clrs); } void setTextColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.text = c; getTheme().change(clrs); } void setDarkerTextColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.darkerText = c; getTheme().change(clrs); } void setDisabledTextColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.disabledText = c; getTheme().change(clrs); } void setLinkTextColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.linkText = c; getTheme().change(clrs); } void setEdgeColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.edge = c; getTheme().change(clrs); } void setHotEdgeColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.hotEdge = c; getTheme().change(clrs); } void setDisabledEdgeColor(COLORREF c) { Colors clrs = getTheme()._colors; clrs.disabledEdge = c; getTheme().change(clrs); } Colors getDarkModeDefaultColors() { return darkColors; } void changeCustomTheme(const Colors& colors) { tCustom.change(colors); } // handle events void handleSettingChange(HWND hwnd, LPARAM lParam, bool isFromBtn) { UNREFERENCED_PARAMETER(hwnd); if (!isExperimentalSupported()) { return; } if (IsColorSchemeChangeMessage(lParam) || isFromBtn) { // ShouldAppsUseDarkMode() is not reliable from 1903+, use NppDarkMode::isDarkModeReg() instead g_darkModeEnabled = NppDarkMode::isDarkModeReg() && !IsHighContrast(); } } bool isDarkModeReg() { DWORD data{}; DWORD dwBufSize = sizeof(data); LPCTSTR lpSubKey = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; LPCTSTR lpValue = L"AppsUseLightTheme"; auto result = RegGetValue(HKEY_CURRENT_USER, lpSubKey, lpValue, RRF_RT_REG_DWORD, nullptr, &data, &dwBufSize); if (result != ERROR_SUCCESS) { return false; } // dark mode is 0, light mode is 1 return data == 0UL; } // 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{}; // get the menubar rect { MENUBARINFO mbi{}; mbi.cbSize = sizeof(MENUBARINFO); 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{}; { mii.cbSize = sizeof(MENUITEMINFO); mii.fMask = 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{}; dttopts.dwSize = 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{}; mbi.cbSize = sizeof(MENUBARINFO); if (!GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi)) { return; } RECT rcClient{}; GetClientRect(hWnd, &rcClient); MapWindowPoints(hWnd, nullptr, (POINT*)&rcClient, 2); RECT rcWindow{}; 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() { ::InitDarkMode(); } void setDarkMode(bool useDark, bool fixDarkScrollbar) { ::SetDarkMode(useDark, fixDarkScrollbar); } void allowDarkModeForApp(bool allow) { ::AllowDarkModeForApp(allow); } bool allowDarkModeForWindow(HWND hWnd, bool allow) { return ::AllowDarkModeForWindow(hWnd, allow); } void setTitleBarThemeColor(HWND hWnd) { ::RefreshTitleBarThemeColor(hWnd); } void enableDarkScrollBarForWindowAndChildren(HWND hwnd) { ::EnableDarkScrollBarForWindowAndChildren(hwnd); } void paintRoundFrameRect(HDC hdc, const RECT rect, const HPEN hpen, int width, int height) { auto holdBrush = ::SelectObject(hdc, ::GetStockObject(NULL_BRUSH)); auto holdPen = ::SelectObject(hdc, hpen); ::RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, width, height); ::SelectObject(hdc, holdBrush); ::SelectObject(hdc, holdPen); } struct ButtonData { HTHEME hTheme = nullptr; int iStateID = 0; ~ButtonData() { closeTheme(); } bool ensureTheme(HWND hwnd) { if (!hTheme) { hTheme = OpenThemeData(hwnd, WC_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{}; WCHAR szText[256] = { '\0' }; DWORD nState = static_cast(SendMessage(hwnd, BM_GETSTATE, 0, 0)); DWORD uiState = static_cast(SendMessage(hwnd, WM_QUERYUISTATE, 0, 0)); auto nStyle = ::GetWindowLongPtr(hwnd, GWL_STYLE); HFONT hFont = nullptr; HFONT hOldFont = nullptr; HFONT hCreatedFont = nullptr; LOGFONT lf{}; 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)); } hOldFont = static_cast(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{}; dtto.dwSize = sizeof(DTTOPTS); dtto.dwFlags = 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)); const auto nStyle = GetWindowLongPtr(hwnd, GWL_STYLE); const auto nButtonStyle = nStyle & BS_TYPEMASK; int iPartID = BP_CHECKBOX; // Plugin might use BS_3STATE and BS_AUTO3STATE button style if (nButtonStyle == BS_CHECKBOX || nButtonStyle == BS_AUTOCHECKBOX || nButtonStyle == BS_3STATE || nButtonStyle == BS_AUTO3STATE) { 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{}; animParams.cbSize = sizeof(BP_ANIMATIONPARAMS); animParams.style = BPAS_LINEAR; if (iStateID != buttonData.iStateID) { GetThemeTransitionDuration(buttonData.hTheme, iPartID, buttonData.iStateID, iStateID, TMT_TRANSITIONDURATIONS, &animParams.dwDuration); } RECT rcClient{}; 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{}; 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) { auto nStyle = ::GetWindowLongPtr(hwnd, GWL_STYLE); bool isDisabled = (nStyle & WS_DISABLED) == WS_DISABLED; int iPartID = BP_GROUPBOX; int iStateID = isDisabled ? GBS_DISABLED : GBS_NORMAL; RECT rcClient{}; GetClientRect(hwnd, &rcClient); RECT rcText = rcClient; RECT rcBackground = rcClient; HFONT hFont = nullptr; HFONT hOldFont = nullptr; HFONT hCreatedFont = nullptr; LOGFONT lf{}; 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)); } hOldFont = static_cast(::SelectObject(hdc, hFont)); WCHAR szText[256] = { '\0' }; GetWindowText(hwnd, szText, _countof(szText)); auto style = static_cast(::GetWindowLongPtr(hwnd, GWL_STYLE)); bool isCenter = (style & BS_CENTER) == BS_CENTER; if (szText[0]) { SIZE textSize{}; GetTextExtentPoint32(hdc, szText, static_cast(wcslen(szText)), &textSize); int centerPosX = isCenter ? ((rcClient.right - rcClient.left - textSize.cx) / 2) : 7; rcBackground.top += textSize.cy / 2; rcText.left += centerPosX; 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{}; 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); NppDarkMode::paintRoundFrameRect(hdc, rcBackground, NppDarkMode::getEdgePen()); SelectClipRgn(hdc, nullptr); if (szText[0]) { rcText.right -= 2; rcText.left += 2; DTTOPTS dtto{}; dtto.dwSize = sizeof(DTTOPTS); dtto.dwFlags = DTT_TEXTCOLOR; dtto.crText = isDisabled ? NppDarkMode::getDisabledTextColor() : NppDarkMode::getTextColor(); DWORD textFlags = isCenter ? DT_CENTER : DT_LEFT; if(::SendMessage(hwnd, WM_QUERYUISTATE, 0, 0) != static_cast(NULL)) { textFlags |= DT_HIDEPREFIX; } DrawThemeTextEx(buttonData.hTheme, hdc, BP_GROUPBOX, iStateID, szText, -1, textFlags | 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{}; 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_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_BUTTONS) || (dwStyle & TCS_VERTICAL)) { break; } PAINTSTRUCT ps{}; HDC hdc = ::BeginPaint(hWnd, &ps); ::FillRect(hdc, &ps.rcPaint, NppDarkMode::getDarkerBackgroundBrush()); auto holdPen = static_cast(::SelectObject(hdc, NppDarkMode::getEdgePen())); 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{}; ::GetCursorPos(&ptCursor); ScreenToClient(hWnd, &ptCursor); int nTabs = TabCtrl_GetItemCount(hWnd); int nSelTab = TabCtrl_GetCurSel(hWnd); for (int i = 0; i < nTabs; ++i) { RECT rcItem{}; TabCtrl_GetItemRect(hWnd, i, &rcItem); RECT rcFrame = rcItem; RECT rcIntersect{}; if (IntersectRect(&rcIntersect, &ps.rcPaint, &rcItem)) { bool bHot = PtInRect(&rcItem, ptCursor); bool isSelectedTab = (i == nSelTab); HRGN hClip = CreateRectRgnIndirect(&rcItem); SelectClipRgn(hdc, hClip); SetTextColor(hdc, (bHot || isSelectedTab ) ? NppDarkMode::getTextColor() : NppDarkMode::getDarkerTextColor()); ::InflateRect(&rcItem, -1, -1); rcItem.right += 1; // for consistency getBackgroundBrush() // would be better, than getSofterBackgroundBrush(), // however default getBackgroundBrush() has same color // as getDarkerBackgroundBrush() ::FillRect(hdc, &rcItem, isSelectedTab ? NppDarkMode::getDarkerBackgroundBrush() : bHot ? NppDarkMode::getHotBackgroundBrush() : NppDarkMode::getSofterBackgroundBrush()); SetBkMode(hdc, TRANSPARENT); TCHAR label[MAX_PATH]{}; TCITEM tci{}; tci.mask = TCIF_TEXT; tci.pszText = label; tci.cchTextMax = MAX_PATH - 1; ::SendMessage(hWnd, TCM_GETITEM, i, reinterpret_cast(&tci)); auto dpiManager = NppParameters::getInstance()._dpiManager; RECT rcText = rcItem; rcText.left += dpiManager.scaleX(5); rcText.right -= dpiManager.scaleX(3); if (isSelectedTab) { rcText.bottom -= dpiManager.scaleY(4); ::InflateRect(&rcFrame, 0, 1); } if (i != nTabs - 1) { rcFrame.right += 1; } ::FrameRect(hdc, &rcFrame, NppDarkMode::getEdgeBrush()); 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; } case WM_PARENTNOTIFY: { switch (LOWORD(wParam)) { case WM_CREATE: { auto hwndUpdown = reinterpret_cast(lParam); if (NppDarkMode::subclassTabUpDownControl(hwndUpdown)) { return 0; } break; } } return 0; } } return DefSubclassProc(hWnd, uMsg, wParam, lParam); } void subclassTabControl(HWND hwnd) { SetWindowSubclass(hwnd, TabSubclass, g_tabSubclassID, 0); } constexpr UINT_PTR g_customBorderSubclassID = 42; LRESULT CALLBACK CustomBorderSubclass( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData ) { UNREFERENCED_PARAMETER(dwRefData); static bool isHotStatic = false; switch (uMsg) { case WM_NCPAINT: { if (!NppDarkMode::isEnabled()) { break; } DefSubclassProc(hWnd, uMsg, wParam, lParam); HDC hdc = ::GetWindowDC(hWnd); RECT rcClient{}; ::GetClientRect(hWnd, &rcClient); rcClient.right += (2 * ::GetSystemMetrics(SM_CXEDGE)); auto style = ::GetWindowLongPtr(hWnd, GWL_STYLE); bool hasVerScrollbar = (style & WS_VSCROLL) == WS_VSCROLL; if (hasVerScrollbar) { rcClient.right += ::GetSystemMetrics(SM_CXVSCROLL); } rcClient.bottom += (2 * ::GetSystemMetrics(SM_CYEDGE)); bool hasHorScrollbar = (style & WS_HSCROLL) == WS_HSCROLL; if (hasHorScrollbar) { rcClient.bottom += ::GetSystemMetrics(SM_CYHSCROLL); } HPEN hPen = ::CreatePen(PS_SOLID, 1, NppDarkMode::getBackgroundColor()); RECT rcInner = rcClient; ::InflateRect(&rcInner, -1, -1); NppDarkMode::paintRoundFrameRect(hdc, rcInner, hPen); ::DeleteObject(hPen); bool hasFocus = ::GetFocus() == hWnd; POINT ptCursor{}; ::GetCursorPos(&ptCursor); ::ScreenToClient(hWnd, &ptCursor); bool isHot = ::PtInRect(&rcClient, ptCursor); bool isWindowEnabled = ::IsWindowEnabled(hWnd) == TRUE; HPEN hEnabledPen = ((isHotStatic && isHot) || hasFocus ? NppDarkMode::getHotEdgePen() : NppDarkMode::getEdgePen()); NppDarkMode::paintRoundFrameRect(hdc, rcClient, isWindowEnabled ? hEnabledPen : NppDarkMode::getDisabledEdgePen()); ::ReleaseDC(hWnd, hdc); return 0; } break; case WM_NCCALCSIZE: { if (!NppDarkMode::isEnabled()) { break; } auto lpRect = reinterpret_cast(lParam); ::InflateRect(lpRect, -(::GetSystemMetrics(SM_CXEDGE)), -(::GetSystemMetrics(SM_CYEDGE))); auto style = ::GetWindowLongPtr(hWnd, GWL_STYLE); bool hasVerScrollbar = (style & WS_VSCROLL) == WS_VSCROLL; if (hasVerScrollbar) { lpRect->right -= ::GetSystemMetrics(SM_CXVSCROLL); } bool hasHorScrollbar = (style & WS_HSCROLL) == WS_HSCROLL; if (hasHorScrollbar) { lpRect->bottom -= ::GetSystemMetrics(SM_CYHSCROLL); } return 0; } break; case WM_MOUSEMOVE: { if (!NppDarkMode::isEnabled()) { break; } if (::GetFocus() == hWnd) { break; } TRACKMOUSEEVENT tme{}; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE; tme.hwndTrack = hWnd; tme.dwHoverTime = HOVER_DEFAULT; TrackMouseEvent(&tme); if (!isHotStatic) { isHotStatic = true; ::SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } } break; case WM_MOUSELEAVE: { if (!NppDarkMode::isEnabled()) { break; } if (isHotStatic) { isHotStatic = false; ::SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } TRACKMOUSEEVENT tme{}; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE | TME_CANCEL; tme.hwndTrack = hWnd; tme.dwHoverTime = HOVER_DEFAULT; TrackMouseEvent(&tme); } break; case WM_NCDESTROY: { RemoveWindowSubclass(hWnd, CustomBorderSubclass, uIdSubclass); } break; } return DefSubclassProc(hWnd, uMsg, wParam, lParam); } void subclassCustomBorderForListBoxAndEditControls(HWND hwnd) { SetWindowSubclass(hwnd, CustomBorderSubclass, g_customBorderSubclassID, 0); } constexpr UINT_PTR g_comboBoxSubclassID = 42; LRESULT CALLBACK ComboBoxSubclass( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData ) { auto hwndEdit = reinterpret_cast(dwRefData); switch (uMsg) { case WM_PAINT: { if (!NppDarkMode::isEnabled()) { break; } RECT rc{}; ::GetClientRect(hWnd, &rc); PAINTSTRUCT ps{}; auto hdc = ::BeginPaint(hWnd, &ps); ::SelectObject(hdc, reinterpret_cast(::SendMessage(hWnd, WM_GETFONT, 0, 0))); ::SetBkColor(hdc, NppDarkMode::getBackgroundColor()); auto holdBrush = ::SelectObject(hdc, NppDarkMode::getDarkerBackgroundBrush()); auto& dpiManager = NppParameters::getInstance()._dpiManager; RECT rcArrow{}; COMBOBOXINFO cbi{}; cbi.cbSize = sizeof(COMBOBOXINFO); const bool resultCbi = ::GetComboBoxInfo(hWnd, &cbi) != FALSE; if (resultCbi) { rcArrow = cbi.rcButton; rcArrow.left -= 1; } else { rcArrow = { rc.right - dpiManager.scaleX(17), rc.top + 1, rc.right - 1, rc.bottom - 1 }; } bool hasFocus = false; const bool isWindowEnabled = ::IsWindowEnabled(hWnd) == TRUE; // CBS_DROPDOWN text is handled by parent by WM_CTLCOLOREDIT auto style = ::GetWindowLongPtr(hWnd, GWL_STYLE); if ((style & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST) { hasFocus = ::GetFocus() == hWnd; RECT rcTextBg{}; if (resultCbi) { rcTextBg = cbi.rcItem; } else { rcTextBg = rc; rcTextBg.left += 1; rcTextBg.top += 1; rcTextBg.right = rcArrow.left - 1; rcTextBg.bottom -= 1; } ::FillRect(hdc, &rcTextBg, NppDarkMode::getBackgroundBrush()); // erase background on item change auto index = static_cast(::SendMessage(hWnd, CB_GETCURSEL, 0, 0)); if (index != CB_ERR) { ::SetTextColor(hdc, isWindowEnabled ? NppDarkMode::getTextColor() : NppDarkMode::getDisabledTextColor()); ::SetBkColor(hdc, NppDarkMode::getBackgroundColor()); auto bufferLen = static_cast(::SendMessage(hWnd, CB_GETLBTEXTLEN, index, 0)); TCHAR* buffer = new TCHAR[(bufferLen + 1)]; ::SendMessage(hWnd, CB_GETLBTEXT, index, reinterpret_cast(buffer)); RECT rcText = rcTextBg; rcText.left += 4; rcText.right -= 4; ::DrawText(hdc, buffer, -1, &rcText, DT_NOPREFIX | DT_LEFT | DT_VCENTER | DT_SINGLELINE); delete[] buffer; } if (hasFocus && ::SendMessage(hWnd, CB_GETDROPPEDSTATE, 0, 0) == FALSE) { ::DrawFocusRect(hdc, &rcTextBg); } } else if ((style & CBS_DROPDOWN) == CBS_DROPDOWN && hwndEdit != nullptr) { hasFocus = ::GetFocus() == hwndEdit; } POINT ptCursor{}; ::GetCursorPos(&ptCursor); ::ScreenToClient(hWnd, &ptCursor); bool isHot = ::PtInRect(&rc, ptCursor); auto colorEnabledText = isHot ? NppDarkMode::getTextColor() : NppDarkMode::getDarkerTextColor(); ::SetTextColor(hdc, isWindowEnabled ? colorEnabledText : NppDarkMode::getDisabledTextColor()); ::SetBkColor(hdc, isHot ? NppDarkMode::getHotBackgroundColor() : NppDarkMode::getBackgroundColor()); ::FillRect(hdc, &rcArrow, isHot ? NppDarkMode::getHotBackgroundBrush() : NppDarkMode::getBackgroundBrush()); TCHAR arrow[] = L"˅"; ::DrawText(hdc, arrow, -1, &rcArrow, DT_NOPREFIX | DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP); ::SetBkColor(hdc, NppDarkMode::getBackgroundColor()); auto hEnabledPen = (isHot || hasFocus) ? NppDarkMode::getHotEdgePen() : NppDarkMode::getEdgePen(); auto hSelectedPen = isWindowEnabled ? hEnabledPen : NppDarkMode::getDisabledEdgePen(); auto holdPen = static_cast(::SelectObject(hdc, hSelectedPen)); POINT edge[] = { {rcArrow.left - 1, rcArrow.top}, {rcArrow.left - 1, rcArrow.bottom} }; ::Polyline(hdc, edge, _countof(edge)); int roundCornerValue = NppDarkMode::isWindows11() ? dpiManager.scaleX(4) : 0; NppDarkMode::paintRoundFrameRect(hdc, rc, hSelectedPen, roundCornerValue, roundCornerValue); ::SelectObject(hdc, holdPen); ::SelectObject(hdc, holdBrush); ::EndPaint(hWnd, &ps); return 0; } case WM_NCDESTROY: { ::RemoveWindowSubclass(hWnd, ComboBoxSubclass, uIdSubclass); break; } } return DefSubclassProc(hWnd, uMsg, wParam, lParam); } void subclassComboBoxControl(HWND hwnd) { DWORD_PTR hwndEditData = 0; auto style = ::GetWindowLongPtr(hwnd, GWL_STYLE); if ((style & CBS_DROPDOWN) == CBS_DROPDOWN) { POINT pt = { 5, 5 }; hwndEditData = reinterpret_cast(::ChildWindowFromPoint(hwnd, pt)); } SetWindowSubclass(hwnd, ComboBoxSubclass, g_comboBoxSubclassID, hwndEditData); } constexpr UINT_PTR g_listViewSubclassID = 42; LRESULT CALLBACK ListViewSubclass( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData ) { UNREFERENCED_PARAMETER(dwRefData); switch (uMsg) { case WM_NCDESTROY: { ::RemoveWindowSubclass(hWnd, ListViewSubclass, uIdSubclass); break; } case WM_NOTIFY: { switch (reinterpret_cast(lParam)->code) { case NM_CUSTOMDRAW: { auto lpnmcd = reinterpret_cast(lParam); switch (lpnmcd->dwDrawStage) { case CDDS_PREPAINT: { if (NppDarkMode::isExperimentalSupported() && NppDarkMode::isEnabled()) { return CDRF_NOTIFYITEMDRAW; } return CDRF_DODEFAULT; } case CDDS_ITEMPREPAINT: { SetTextColor(lpnmcd->hdc, NppDarkMode::getDarkerTextColor()); return CDRF_NEWFONT; } default: return CDRF_DODEFAULT; } } break; } break; } break;, } return DefSubclassProc(hWnd, uMsg, wParam, lParam); } void subclassListViewControl(HWND hwnd) { SetWindowSubclass(hwnd, ListViewSubclass, g_listViewSubclassID, 0); } constexpr UINT_PTR g_upDownSubclassID = 42; LRESULT CALLBACK UpDownSubclass( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData ) { auto pButtonData = reinterpret_cast(dwRefData); // 获取与子类化控件相关的数据结构 switch (uMsg) { // 处理客户区绘制和重绘消息 case WM_PRINTCLIENT: case WM_PAINT: { if (!NppDarkMode::isEnabled()) // 如果暗模式未启用,则直接退出 { break; } const auto style = ::GetWindowLongPtr(hWnd, GWL_STYLE); const bool isHorizontal = ((style & UDS_HORZ) == UDS_HORZ); // 检查上下控件的方向 bool hasTheme = pButtonData->ensureTheme(hWnd); // 确保已创建主题 RECT rcClient{}; ::GetClientRect(hWnd, &rcClient); PAINTSTRUCT ps{}; auto hdc = ::BeginPaint(hWnd, &ps); // 开始绘制 ::FillRect(hdc, &rcClient, NppDarkMode::getDarkerBackgroundBrush()); // 填充客户区背景色 // 计算前后箭头的位置 RECT rcArrowPrev{}; RECT rcArrowNext{}; if (isHorizontal) { // 水平方向 RECT rcArrowLeft{ rcClient.left, rcClient.top, rcClient.right - ((rcClient.right - rcClient.left) / 2), rcClient.bottom }; RECT rcArrowRight{ rcArrowLeft.right - 1, rcClient.top, rcClient.right, rcClient.bottom }; rcArrowPrev = rcArrowLeft; rcArrowNext = rcArrowRight; } else { // 垂直方向 RECT rcArrowTop{ rcClient.left, rcClient.top, rcClient.right, rcClient.bottom - ((rcClient.bottom - rcClient.top) / 2) }; RECT rcArrowBottom{ rcClient.left, rcArrowTop.bottom - 1, rcClient.right, rcClient.bottom }; rcArrowPrev = rcArrowTop; rcArrowNext = rcArrowBottom; } POINT ptCursor{}; ::GetCursorPos(&ptCursor); ::ScreenToClient(hWnd, &ptCursor); // 检查鼠标是否在箭头范围内 bool isHotPrev = ::PtInRect(&rcArrowPrev, ptCursor); bool isHotNext = ::PtInRect(&rcArrowNext, ptCursor); ::SetBkMode(hdc, TRANSPARENT); // 根据主题绘制箭头按钮 if (hasTheme) { ::DrawThemeBackground(pButtonData->hTheme, hdc, BP_PUSHBUTTON, isHotPrev ? PBS_HOT : PBS_NORMAL, &rcArrowPrev, nullptr); ::DrawThemeBackground(pButtonData->hTheme, hdc, BP_PUSHBUTTON, isHotNext ? PBS_HOT : PBS_NORMAL, &rcArrowNext, nullptr); } else { // 绘制自定义的箭头样式 ::FillRect(hdc, &rcArrowPrev, isHotPrev ? NppDarkMode::getHotBackgroundBrush() : NppDarkMode::getBackgroundBrush()); ::FillRect(hdc, &rcArrowNext, isHotNext ? NppDarkMode::getHotBackgroundBrush() : NppDarkMode::getBackgroundBrush()); } const auto arrowTextFlags = DT_NOPREFIX | DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP; ::SetTextColor(hdc, isHotPrev ? NppDarkMode::getTextColor() : NppDarkMode::getDarkerTextColor()); ::DrawText(hdc, isHorizontal ? L"<" : L"˄", -1, &rcArrowPrev, arrowTextFlags); ::SetTextColor(hdc, isHotNext ? NppDarkMode::getTextColor() : NppDarkMode::getDarkerTextColor()); ::DrawText(hdc, isHorizontal ? L">" : L"˅", -1, &rcArrowNext, arrowTextFlags); if (!hasTheme) { NppDarkMode::paintRoundFrameRect(hdc, rcArrowPrev, NppDarkMode::getEdgePen()); NppDarkMode::paintRoundFrameRect(hdc, rcArrowNext, NppDarkMode::getEdgePen()); } ::EndPaint(hWnd, &ps); return FALSE; } case WM_THEMECHANGED: { pButtonData->closeTheme(); break; } case WM_NCDESTROY: { ::RemoveWindowSubclass(hWnd, UpDownSubclass, uIdSubclass); delete pButtonData; break; } case WM_ERASEBKGND: { if (NppDarkMode::isEnabled()) { RECT rcClient{}; ::GetClientRect(hWnd, &rcClient); ::FillRect(reinterpret_cast(wParam), &rcClient, NppDarkMode::getDarkerBackgroundBrush()); return TRUE; } break; } } return DefSubclassProc(hWnd, uMsg, wParam, lParam); } void subclassAndThemeUpDownControl(HWND hwnd, NppDarkModeParams p) { if (p._subclass) { auto pButtonData = reinterpret_cast(new ButtonData()); SetWindowSubclass(hwnd, UpDownSubclass, g_upDownSubclassID, pButtonData); } if (p._theme) { SetWindowTheme(hwnd, p._themeClassName, nullptr); } } bool subclassTabUpDownControl(HWND hwnd) { constexpr size_t classNameLen = 16; TCHAR className[classNameLen]{}; GetClassName(hwnd, className, classNameLen); if (wcscmp(className, UPDOWN_CLASS) == 0) { auto pButtonData = reinterpret_cast(new ButtonData()); SetWindowSubclass(hwnd, UpDownSubclass, g_upDownSubclassID, pButtonData); NppDarkMode::setDarkExplorerTheme(hwnd); return true; } return false; } void autoSubclassAndThemeChildControls(HWND hwndParent, bool subclass, bool theme) { NppDarkModeParams p{ g_isAtLeastWindows10 && NppDarkMode::isEnabled() ? L"DarkMode_Explorer" : nullptr , subclass , theme }; ::EnableThemeDialogTexture(hwndParent, theme && !NppDarkMode::isEnabled() ? ETDT_ENABLETAB : ETDT_DISABLE); EnumChildWindows(hwndParent, [](HWND hwnd, LPARAM lParam) WINAPI_LAMBDA { auto& p = *reinterpret_cast(lParam); constexpr size_t classNameLen = 32; TCHAR className[classNameLen]{}; GetClassName(hwnd, className, classNameLen); if (wcscmp(className, WC_BUTTON) == 0) { NppDarkMode::subclassAndThemeButton(hwnd, p); return TRUE; } if (wcscmp(className, WC_COMBOBOX) == 0) { NppDarkMode::subclassAndThemeComboBox(hwnd, p); return TRUE; } if (wcscmp(className, WC_EDIT) == 0) { if (!g_isWine) { NppDarkMode::subclassAndThemeListBoxOrEditControl(hwnd, p, false); } return TRUE; } if (wcscmp(className, WC_LISTBOX) == 0) { if (!g_isWine) { NppDarkMode::subclassAndThemeListBoxOrEditControl(hwnd, p, true); } return TRUE; } if (wcscmp(className, WC_LISTVIEW) == 0) { NppDarkMode::subclassAndThemeListView(hwnd, p); return TRUE; } if (wcscmp(className, WC_TREEVIEW) == 0) { NppDarkMode::themeTreeView(hwnd, p); return TRUE; } if (wcscmp(className, TOOLBARCLASSNAME) == 0) { NppDarkMode::themeToolbar(hwnd, p); return TRUE; } // Plugin might use rich edit control version 2.0 and later if (wcscmp(className, L"RichEdit20W") == 0 || wcscmp(className, L"RICHEDIT50W") == 0) { NppDarkMode::themeRichEdit(hwnd, p); return TRUE; } // For plugins if (wcscmp(className, UPDOWN_CLASS) == 0) { NppDarkMode::subclassAndThemeUpDownControl(hwnd, p); return TRUE; } /* // for debugging if (wcscmp(className, L"#32770") == 0) { return TRUE; } if (wcscmp(className, L"Static") == 0) { return TRUE; } if (wcscmp(className, L"msctls_trackbar32") == 0) { return TRUE; } */ return TRUE; }, reinterpret_cast(&p)); } void autoThemeChildControls(HWND hwndParent) { autoSubclassAndThemeChildControls(hwndParent, false, g_isAtLeastWindows10); } void subclassAndThemeButton(HWND hwnd, NppDarkModeParams p) { auto nButtonStyle = ::GetWindowLongPtr(hwnd, GWL_STYLE); switch (nButtonStyle & BS_TYPEMASK) { // Plugin might use BS_3STATE and BS_AUTO3STATE button style case BS_CHECKBOX: case BS_AUTOCHECKBOX: case BS_3STATE: case BS_AUTO3STATE: case BS_RADIOBUTTON: case BS_AUTORADIOBUTTON: { if ((nButtonStyle & BS_PUSHLIKE) == BS_PUSHLIKE) { if (p._theme) { SetWindowTheme(hwnd, p._themeClassName, nullptr); } break; } if (p._subclass) { NppDarkMode::subclassButtonControl(hwnd); } break; } case BS_GROUPBOX: { if (p._subclass) { NppDarkMode::subclassGroupboxControl(hwnd); } break; } case BS_PUSHBUTTON: case BS_DEFPUSHBUTTON: case BS_SPLITBUTTON: case BS_DEFSPLITBUTTON: { if (p._theme) { SetWindowTheme(hwnd, p._themeClassName, nullptr); } break; } default: { break; } } } void subclassAndThemeComboBox(HWND hwnd, NppDarkModeParams p) { auto style = ::GetWindowLongPtr(hwnd, GWL_STYLE); if ((style & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST || (style & CBS_DROPDOWN) == CBS_DROPDOWN) { COMBOBOXINFO cbi{}; cbi.cbSize = sizeof(COMBOBOXINFO); BOOL result = ::GetComboBoxInfo(hwnd, &cbi); if (result == TRUE) { if (p._theme && cbi.hwndList) { //dark scrollbar for listbox of combobox ::SetWindowTheme(cbi.hwndList, p._themeClassName, nullptr); } } if (p._subclass) { NppDarkMode::subclassComboBoxControl(hwnd); } } } void subclassAndThemeListBoxOrEditControl(HWND hwnd, NppDarkModeParams p, bool isListBox) { const auto style = ::GetWindowLongPtr(hwnd, GWL_STYLE); bool hasScrollBar = ((style & WS_HSCROLL) == WS_HSCROLL) || ((style & WS_VSCROLL) == WS_VSCROLL); if (p._theme && (isListBox || hasScrollBar)) { //dark scrollbar for listbox or edit control SetWindowTheme(hwnd, p._themeClassName, nullptr); } const auto exStyle = ::GetWindowLongPtr(hwnd, GWL_EXSTYLE); bool hasClientEdge = (exStyle & WS_EX_CLIENTEDGE) == WS_EX_CLIENTEDGE; bool isCBoxListBox = isListBox && (style & LBS_COMBOBOX) == LBS_COMBOBOX; if (p._subclass && hasClientEdge && !isCBoxListBox) { NppDarkMode::subclassCustomBorderForListBoxAndEditControls(hwnd); } #ifndef __MINGW64__ // mingw build for 64 bit has issue with GetWindowSubclass, it is undefined bool changed = false; if (::GetWindowSubclass(hwnd, CustomBorderSubclass, g_customBorderSubclassID, nullptr) == TRUE) { if (NppDarkMode::isEnabled()) { if (hasClientEdge) { ::SetWindowLongPtr(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE); changed = true; } } else if (!hasClientEdge) { ::SetWindowLongPtr(hwnd, GWL_EXSTYLE, exStyle | WS_EX_CLIENTEDGE); changed = true; } } if (changed) { ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } #endif // !__MINGW64__ } void subclassAndThemeListView(HWND hwnd, NppDarkModeParams p) { if (p._theme) { NppDarkMode::setDarkListView(hwnd); NppDarkMode::setDarkTooltips(hwnd, NppDarkMode::ToolTipsType::listview); } ListView_SetTextColor(hwnd, NppParameters::getInstance().getCurrentDefaultFgColor()); ListView_SetTextBkColor(hwnd, NppParameters::getInstance().getCurrentDefaultBgColor()); ListView_SetBkColor(hwnd, NppParameters::getInstance().getCurrentDefaultBgColor()); if (p._subclass) { auto exStyle = ListView_GetExtendedListViewStyle(hwnd); ListView_SetExtendedListViewStyle(hwnd, exStyle | LVS_EX_DOUBLEBUFFER); NppDarkMode::subclassListViewControl(hwnd); } } void themeTreeView(HWND hwnd, NppDarkModeParams p) { TreeView_SetTextColor(hwnd, NppParameters::getInstance().getCurrentDefaultFgColor()); TreeView_SetBkColor(hwnd, NppParameters::getInstance().getCurrentDefaultBgColor()); NppDarkMode::calculateTreeViewStyle(); NppDarkMode::setTreeViewStyle(hwnd); if (p._theme) { NppDarkMode::setDarkTooltips(hwnd, NppDarkMode::ToolTipsType::treeview); } } void themeToolbar(HWND hwnd, NppDarkModeParams p) { NppDarkMode::setDarkLineAbovePanelToolbar(hwnd); if (p._theme) { NppDarkMode::setDarkTooltips(hwnd, NppDarkMode::ToolTipsType::toolbar); } } void themeRichEdit(HWND hwnd, NppDarkModeParams p) { if (p._theme) { //dark scrollbar for rich edit control SetWindowTheme(hwnd, p._themeClassName, nullptr); } } LRESULT darkToolBarNotifyCustomDraw(LPARAM lParam) { auto nmtbcd = reinterpret_cast(lParam); static int roundCornerValue = 0; switch (nmtbcd->nmcd.dwDrawStage) { case CDDS_PREPAINT: { if (NppDarkMode::isEnabled()) { auto dpiManager = NppParameters::getInstance()._dpiManager; roundCornerValue = NppDarkMode::isWindows11() ? dpiManager.scaleX(5) : 0; ::FillRect(nmtbcd->nmcd.hdc, &nmtbcd->nmcd.rc, NppDarkMode::getDarkerBackgroundBrush()); return CDRF_NOTIFYITEMDRAW; } return CDRF_DODEFAULT; } case CDDS_ITEMPREPAINT: { nmtbcd->hbrLines = NppDarkMode::getEdgeBrush(); nmtbcd->clrText = NppDarkMode::getTextColor(); nmtbcd->clrTextHighlight = NppDarkMode::getTextColor(); nmtbcd->clrBtnFace = NppDarkMode::getBackgroundColor(); nmtbcd->clrBtnHighlight = NppDarkMode::getSofterBackgroundColor(); nmtbcd->clrHighlightHotTrack = NppDarkMode::getHotBackgroundColor(); nmtbcd->nStringBkMode = TRANSPARENT; nmtbcd->nHLStringBkMode = TRANSPARENT; if ((nmtbcd->nmcd.uItemState & CDIS_CHECKED) == CDIS_CHECKED) { auto holdBrush = ::SelectObject(nmtbcd->nmcd.hdc, NppDarkMode::getSofterBackgroundBrush()); auto holdPen = ::SelectObject(nmtbcd->nmcd.hdc, NppDarkMode::getEdgePen()); ::RoundRect(nmtbcd->nmcd.hdc, nmtbcd->nmcd.rc.left, nmtbcd->nmcd.rc.top, nmtbcd->nmcd.rc.right, nmtbcd->nmcd.rc.bottom, roundCornerValue, roundCornerValue); ::SelectObject(nmtbcd->nmcd.hdc, holdBrush); ::SelectObject(nmtbcd->nmcd.hdc, holdPen); nmtbcd->nmcd.uItemState &= ~CDIS_CHECKED; } return TBCDRF_HILITEHOTTRACK | TBCDRF_USECDCOLORS | CDRF_NOTIFYPOSTPAINT; } case CDDS_ITEMPOSTPAINT: { bool isDropDown = false; auto exStyle = ::SendMessage(nmtbcd->nmcd.hdr.hwndFrom, TB_GETEXTENDEDSTYLE, 0, 0); if ((exStyle & TBSTYLE_EX_DRAWDDARROWS) == TBSTYLE_EX_DRAWDDARROWS) { TBBUTTONINFO tbButtonInfo{}; tbButtonInfo.cbSize = sizeof(TBBUTTONINFO); tbButtonInfo.dwMask = TBIF_STYLE; ::SendMessage(nmtbcd->nmcd.hdr.hwndFrom, TB_GETBUTTONINFO, nmtbcd->nmcd.dwItemSpec, reinterpret_cast(&tbButtonInfo)); isDropDown = (tbButtonInfo.fsStyle & BTNS_DROPDOWN) == BTNS_DROPDOWN; } if ( !isDropDown && (nmtbcd->nmcd.uItemState & CDIS_HOT) == CDIS_HOT) { NppDarkMode::paintRoundFrameRect(nmtbcd->nmcd.hdc, nmtbcd->nmcd.rc, NppDarkMode::getHotEdgePen(), roundCornerValue, roundCornerValue); } return CDRF_DODEFAULT; } default: return CDRF_DODEFAULT; } } LRESULT darkListViewNotifyCustomDraw(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool isPlugin) { auto lplvcd = reinterpret_cast(lParam); switch (lplvcd->nmcd.dwDrawStage) { case CDDS_PREPAINT: { return CDRF_NOTIFYITEMDRAW; } case CDDS_ITEMPREPAINT: { auto isSelected = ListView_GetItemState(lplvcd->nmcd.hdr.hwndFrom, lplvcd->nmcd.dwItemSpec, LVIS_SELECTED) == LVIS_SELECTED; if (NppDarkMode::isEnabled()) { if (isSelected) { lplvcd->clrText = NppDarkMode::getTextColor(); lplvcd->clrTextBk = NppDarkMode::getSofterBackgroundColor(); ::FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, NppDarkMode::getSofterBackgroundBrush()); } else if ((lplvcd->nmcd.uItemState & CDIS_HOT) == CDIS_HOT) { lplvcd->clrText = NppDarkMode::getTextColor(); lplvcd->clrTextBk = NppDarkMode::getHotBackgroundColor(); ::FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, NppDarkMode::getHotBackgroundBrush()); } } if (isSelected) { ::DrawFocusRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc); } LRESULT lr = CDRF_DODEFAULT; if (isPlugin) { lr = ::DefSubclassProc(hWnd, uMsg, wParam, lParam); } return lr | CDRF_NEWFONT; } default: break; } return ::DefSubclassProc(hWnd, uMsg, wParam, lParam); } LRESULT darkTreeViewNotifyCustomDraw(LPARAM lParam) { auto lptvcd = reinterpret_cast(lParam); switch (lptvcd->nmcd.dwDrawStage) { case CDDS_PREPAINT: { if (NppDarkMode::isEnabled()) { return CDRF_NOTIFYITEMDRAW; } return CDRF_DODEFAULT; } case CDDS_ITEMPREPAINT: { if ((lptvcd->nmcd.uItemState & CDIS_SELECTED) == CDIS_SELECTED) { lptvcd->clrText = NppDarkMode::getTextColor(); lptvcd->clrTextBk = NppDarkMode::getSofterBackgroundColor(); ::FillRect(lptvcd->nmcd.hdc, &lptvcd->nmcd.rc, NppDarkMode::getSofterBackgroundBrush()); return CDRF_NEWFONT | CDRF_NOTIFYPOSTPAINT; } if ((lptvcd->nmcd.uItemState & CDIS_HOT) == CDIS_HOT) { lptvcd->clrText = NppDarkMode::getTextColor(); lptvcd->clrTextBk = NppDarkMode::getHotBackgroundColor(); auto notifyResult = CDRF_DODEFAULT; if (g_isAtLeastWindows10 || g_treeViewStyle == TreeViewStyle::light) { ::FillRect(lptvcd->nmcd.hdc, &lptvcd->nmcd.rc, NppDarkMode::getHotBackgroundBrush()); notifyResult = CDRF_NOTIFYPOSTPAINT; } return CDRF_NEWFONT | notifyResult; } return CDRF_DODEFAULT; } case CDDS_ITEMPOSTPAINT: { RECT rcFrame = lptvcd->nmcd.rc; rcFrame.left -= 1; rcFrame.right += 1; if ((lptvcd->nmcd.uItemState & CDIS_HOT) == CDIS_HOT) { NppDarkMode::paintRoundFrameRect(lptvcd->nmcd.hdc, rcFrame, NppDarkMode::getHotEdgePen(), 0, 0); } else if ((lptvcd->nmcd.uItemState & CDIS_SELECTED) == CDIS_SELECTED) { NppDarkMode::paintRoundFrameRect(lptvcd->nmcd.hdc, rcFrame, NppDarkMode::getEdgePen(), 0, 0); } return CDRF_DODEFAULT; } default: return CDRF_DODEFAULT; } } constexpr UINT_PTR g_pluginDockWindowSubclassID = 42; LRESULT CALLBACK PluginDockWindowSubclass( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData ) { UNREFERENCED_PARAMETER(dwRefData); switch (uMsg) { case WM_ERASEBKGND: { if (NppDarkMode::isEnabled()) { RECT rect{}; GetClientRect(hWnd, &rect); ::FillRect(reinterpret_cast(wParam), &rect, NppDarkMode::getDarkerBackgroundBrush()); return TRUE; } break; } case WM_NCDESTROY: { ::RemoveWindowSubclass(hWnd, PluginDockWindowSubclass, uIdSubclass); break; } case NPPM_INTERNAL_REFRESHDARKMODE: { NppDarkMode::autoThemeChildControls(hWnd); return TRUE; } case WM_CTLCOLOREDIT: { if (NppDarkMode::isEnabled()) { return NppDarkMode::onCtlColorSofter(reinterpret_cast(wParam)); } break; } case WM_CTLCOLORLISTBOX: { if (NppDarkMode::isEnabled()) { return NppDarkMode::onCtlColorListbox(wParam, lParam); } break; } case WM_CTLCOLORDLG: { if (NppDarkMode::isEnabled()) { return NppDarkMode::onCtlColorDarker(reinterpret_cast(wParam)); } break; } case WM_CTLCOLORSTATIC: { if (NppDarkMode::isEnabled()) { constexpr size_t classNameLen = 16; TCHAR className[classNameLen]{}; auto hwndEdit = reinterpret_cast(lParam); GetClassName(hwndEdit, className, classNameLen); if (wcscmp(className, WC_EDIT) == 0) { return NppDarkMode::onCtlColor(reinterpret_cast(wParam)); } return NppDarkMode::onCtlColorDarker(reinterpret_cast(wParam)); } break; } case WM_PRINTCLIENT: { if (NppDarkMode::isEnabled()) { return TRUE; } break; } case WM_NOTIFY: { const auto nmhdr = reinterpret_cast(lParam); switch (nmhdr->code) { case NM_CUSTOMDRAW: { constexpr size_t classNameLen = 16; TCHAR className[classNameLen]{}; GetClassName(nmhdr->hwndFrom, className, classNameLen); if (wcscmp(className, TOOLBARCLASSNAME) == 0) { return NppDarkMode::darkToolBarNotifyCustomDraw(lParam); } if (wcscmp(className, WC_LISTVIEW) == 0) { return NppDarkMode::darkListViewNotifyCustomDraw(hWnd, uMsg, wParam, lParam, true); } if (wcscmp(className, WC_TREEVIEW) == 0) { return NppDarkMode::darkTreeViewNotifyCustomDraw(lParam); } } break; } break; } } return DefSubclassProc(hWnd, uMsg, wParam, lParam); } void autoSubclassAndThemePluginDockWindow(HWND hwnd) { SetWindowSubclass(hwnd, PluginDockWindowSubclass, g_pluginDockWindowSubclassID, 0); NppDarkMode::autoSubclassAndThemeChildControls(hwnd, true, g_isAtLeastWindows10); } ULONG autoSubclassAndThemePlugin(HWND hwnd, ULONG dmFlags) { // Used on parent of edit, listbox, static text, treeview, listview and toolbar controls. // Should be used only one time on parent control after its creation // even when starting in light mode. // e.g. in WM_INITDIALOG, in WM_CREATE or after CreateWindow. constexpr ULONG dmfSubclassParent = 0x00000001UL; // Should be used only one time on main control/window after initializations of all its children controls // even when starting in light mode. // Will also use dmfSetThemeChildren flag. // e.g. in WM_INITDIALOG, in WM_CREATE or after CreateWindow. constexpr ULONG dmfSubclassChildren = 0x00000002UL; // Will apply theme on buttons with style: // BS_PUSHLIKE, BS_PUSHBUTTON, BS_DEFPUSHBUTTON, BS_SPLITBUTTON or BS_DEFSPLITBUTTON. // Will apply theme for scrollbars on edit, listbox and rich edit controls. // Will apply theme for tooltips on listview, treeview and toolbar buttons. // Should be handled after controls initializations and in NPPN_DARKMODECHANGED. // Requires at least Windows 10 to work properly. constexpr ULONG dmfSetThemeChildren = 0x00000004UL; // Set dark title bar. // Should be handled after controls initializations and in NPPN_DARKMODECHANGED. // Requires at least Windows 10 and WS_CAPTION style to work properly. constexpr ULONG dmfSetTitleBar = 0x00000008UL; // Will apply dark explorer theme. // Used mainly for scrollbars and tooltips not handled with dmfSetThemeChildren. // Might also change style for other elements. // Should be handled after controls initializations and in NPPN_DARKMODECHANGED. // Requires at least Windows 10 to work properly. constexpr ULONG dmfSetThemeDirectly = 0x00000010UL; // defined in Notepad_plus_msgs.h //constexpr ULONG dmfInit = dmfSubclassParent | dmfSubclassChildren | dmfSetTitleBar; // 0x000000BUL //constexpr ULONG dmfHandleChange = dmfSetThemeChildren | dmfSetTitleBar; // 0x000000CUL constexpr ULONG dmfRequiredMask = dmfSubclassParent | dmfSubclassChildren | dmfSetThemeChildren | dmfSetTitleBar | dmfSetThemeDirectly; //constexpr ULONG dmfAllMask = dmfSubclassParent | dmfSubclassChildren | dmfSetThemeChildren | dmfSetTitleBar | dmfSetThemeDirectly; if (hwnd == nullptr || (dmFlags & dmfRequiredMask) == 0) { return 0; } auto dmfBitwiseCheck = [dmFlags](ULONG flag) -> bool { return (dmFlags & flag) == flag; }; ULONG result = 0UL; if (dmfBitwiseCheck(dmfSubclassParent)) { const bool success = ::SetWindowSubclass(hwnd, PluginDockWindowSubclass, g_pluginDockWindowSubclassID, 0) == TRUE; if (success) { result |= dmfSubclassParent; } } const bool subclassChildren = dmfBitwiseCheck(dmfSubclassChildren); if (dmfBitwiseCheck(dmfSetThemeChildren) || subclassChildren) { NppDarkMode::autoSubclassAndThemeChildControls(hwnd, subclassChildren, g_isAtLeastWindows10); result |= dmfSetThemeChildren; if (subclassChildren) { result |= dmfSubclassChildren; } } if (dmfBitwiseCheck(dmfSetTitleBar)) { const auto style = ::GetWindowLongPtr(hwnd, GWL_STYLE); if (NppDarkMode::isExperimentalSupported() && ((style & WS_CAPTION) == WS_CAPTION)) { NppDarkMode::setDarkTitleBar(hwnd); result |= dmfSetTitleBar; } } if (dmfBitwiseCheck(dmfSetThemeDirectly)) { if (NppDarkMode::isWindows10()) { NppDarkMode::setDarkExplorerTheme(hwnd); result |= dmfSetThemeDirectly; } } return result; } constexpr UINT_PTR g_windowNotifySubclassID = 42; LRESULT CALLBACK WindowNotifySubclass( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData ) { UNREFERENCED_PARAMETER(dwRefData); switch (uMsg) { case WM_NCDESTROY: { ::RemoveWindowSubclass(hWnd, WindowNotifySubclass, uIdSubclass); break; } case WM_NOTIFY: { auto nmhdr = reinterpret_cast(lParam); constexpr size_t classNameLen = 16; TCHAR className[classNameLen]{}; GetClassName(nmhdr->hwndFrom, className, classNameLen); switch (nmhdr->code) { case NM_CUSTOMDRAW: { if (wcscmp(className, TOOLBARCLASSNAME) == 0) { return NppDarkMode::darkToolBarNotifyCustomDraw(lParam); } if (wcscmp(className, WC_LISTVIEW) == 0) { return NppDarkMode::darkListViewNotifyCustomDraw(hWnd, uMsg, wParam, lParam, false); } if (wcscmp(className, WC_TREEVIEW) == 0) { return NppDarkMode::darkTreeViewNotifyCustomDraw(lParam); } } break; } break; } } return DefSubclassProc(hWnd, uMsg, wParam, lParam); } void autoSubclassAndThemeWindowNotify(HWND hwnd) { SetWindowSubclass(hwnd, WindowNotifySubclass, g_windowNotifySubclassID, 0); } void setDarkTitleBar(HWND hwnd) { constexpr DWORD win10Build2004 = 19041; if (NppDarkMode::getWindowsBuildNumber() >= win10Build2004) { BOOL value = NppDarkMode::isEnabled() ? TRUE : FALSE; ::DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } else { NppDarkMode::allowDarkModeForWindow(hwnd, NppDarkMode::isEnabled()); NppDarkMode::setTitleBarThemeColor(hwnd); } } void setDarkExplorerTheme(HWND hwnd) { SetWindowTheme(hwnd, g_isAtLeastWindows10 && NppDarkMode::isEnabled() ? L"DarkMode_Explorer" : nullptr, nullptr); } void setDarkScrollBar(HWND hwnd) { NppDarkMode::setDarkExplorerTheme(hwnd); } void setDarkTooltips(HWND hwnd, ToolTipsType type) { UINT msg = 0; 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; case NppDarkMode::ToolTipsType::tabbar: msg = TCM_GETTOOLTIPS; break; default: msg = 0; break; } if (msg == 0) { NppDarkMode::setDarkExplorerTheme(hwnd); } else { auto hTips = reinterpret_cast(::SendMessage(hwnd, msg, 0, 0)); if (hTips != nullptr) { NppDarkMode::setDarkExplorerTheme(hTips); } } } void setDarkLineAbovePanelToolbar(HWND hwnd) { COLORSCHEME scheme{}; scheme.dwSize = sizeof(COLORSCHEME); if (NppDarkMode::isEnabled()) { scheme.clrBtnHighlight = NppDarkMode::getDarkerBackgroundColor(); scheme.clrBtnShadow = NppDarkMode::getDarkerBackgroundColor(); } else { scheme.clrBtnHighlight = CLR_DEFAULT; scheme.clrBtnShadow = CLR_DEFAULT; } ::SendMessage(hwnd, TB_SETCOLORSCHEME, 0, reinterpret_cast(&scheme)); } void setDarkListView(HWND hwnd) { if (NppDarkMode::isExperimentalSupported()) { bool useDark = NppDarkMode::isEnabled(); HWND hHeader = ListView_GetHeader(hwnd); NppDarkMode::allowDarkModeForWindow(hHeader, useDark); SetWindowTheme(hHeader, useDark ? L"ItemsView" : nullptr, nullptr); NppDarkMode::allowDarkModeForWindow(hwnd, useDark); SetWindowTheme(hwnd, L"Explorer", nullptr); } } void disableVisualStyle(HWND hwnd, bool doDisable) { if (doDisable) { SetWindowTheme(hwnd, L"", L""); } else { SetWindowTheme(hwnd, nullptr, nullptr); } } // range to determine when it should be better to use classic style constexpr double g_middleGrayRange = 2.0; void calculateTreeViewStyle() { COLORREF bgColor = NppParameters::getInstance().getCurrentDefaultBgColor(); if (g_treeViewBg != bgColor || g_lighnessTreeView == 50.0) { g_lighnessTreeView = calculatePerceivedLighness(bgColor); g_treeViewBg = bgColor; } if (g_lighnessTreeView < (50.0 - g_middleGrayRange)) { g_treeViewStyle = TreeViewStyle::dark; } else if (g_lighnessTreeView > (50.0 + g_middleGrayRange)) { g_treeViewStyle = TreeViewStyle::light; } else { g_treeViewStyle = TreeViewStyle::classic; } } void setTreeViewStyle(HWND hwnd) { auto style = static_cast(::GetWindowLongPtr(hwnd, GWL_STYLE)); bool hasHotStyle = (style & TVS_TRACKSELECT) == TVS_TRACKSELECT; bool change = false; switch (g_treeViewStyle) { case TreeViewStyle::light: { if (!hasHotStyle) { style |= TVS_TRACKSELECT; change = true; } SetWindowTheme(hwnd, L"Explorer", nullptr); break; } case TreeViewStyle::dark: { if (!hasHotStyle) { style |= TVS_TRACKSELECT; change = true; } SetWindowTheme(hwnd, g_isAtLeastWindows10 ? L"DarkMode_Explorer" : nullptr, nullptr); break; } default: { if (hasHotStyle) { style &= ~TVS_TRACKSELECT; change = true; } SetWindowTheme(hwnd, nullptr, nullptr); break; } } if (change) { ::SetWindowLongPtr(hwnd, GWL_STYLE, style); } } bool isThemeDark() { return g_treeViewStyle == TreeViewStyle::dark; } void setBorder(HWND hwnd, bool border) { auto style = static_cast(::GetWindowLongPtr(hwnd, GWL_STYLE)); bool hasBorder = (style & WS_BORDER) == WS_BORDER; bool change = false; if (!hasBorder && border) { style |= WS_BORDER; change = true; } else if (hasBorder && !border) { style &= ~WS_BORDER; change = true; } if (change) { ::SetWindowLongPtr(hwnd, GWL_STYLE, style); ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } } BOOL CALLBACK enumAutocompleteProc(HWND hwnd, LPARAM /*lParam*/) { constexpr size_t classNameLen = 16; TCHAR className[classNameLen]{}; GetClassName(hwnd, className, classNameLen); if ((wcscmp(className, L"ListBoxX") == 0)) { NppDarkMode::setDarkTitleBar(hwnd); NppDarkMode::autoThemeChildControls(hwnd); return FALSE; } return TRUE; } // set dark scrollbar for autocomplete list void setDarkAutoCompletion() { ::EnumThreadWindows(::GetCurrentThreadId(), (WNDENUMPROC)enumAutocompleteProc, 0); } LRESULT onCtlColor(HDC hdc) { if (!NppDarkMode::isEnabled()) { return FALSE; } ::SetTextColor(hdc, NppDarkMode::getTextColor()); ::SetBkColor(hdc, NppDarkMode::getBackgroundColor()); return reinterpret_cast(NppDarkMode::getBackgroundBrush()); } LRESULT onCtlColorSofter(HDC hdc) { if (!NppDarkMode::isEnabled()) { return FALSE; } ::SetTextColor(hdc, NppDarkMode::getTextColor()); ::SetBkColor(hdc, NppDarkMode::getSofterBackgroundColor()); return reinterpret_cast(NppDarkMode::getSofterBackgroundBrush()); } LRESULT onCtlColorDarker(HDC hdc) { if (!NppDarkMode::isEnabled()) { return FALSE; } ::SetTextColor(hdc, NppDarkMode::getTextColor()); ::SetBkColor(hdc, NppDarkMode::getDarkerBackgroundColor()); return reinterpret_cast(NppDarkMode::getDarkerBackgroundBrush()); } LRESULT onCtlColorError(HDC hdc) { if (!NppDarkMode::isEnabled()) { return FALSE; } ::SetTextColor(hdc, NppDarkMode::getTextColor()); ::SetBkColor(hdc, NppDarkMode::getErrorBackgroundColor()); return reinterpret_cast(NppDarkMode::getErrorBackgroundBrush()); } LRESULT onCtlColorDarkerBGStaticText(HDC hdc, bool isTextEnabled) { if (!NppDarkMode::isEnabled()) { ::SetTextColor(hdc, ::GetSysColor(isTextEnabled ? COLOR_WINDOWTEXT : COLOR_GRAYTEXT)); return FALSE; } ::SetTextColor(hdc, isTextEnabled ? NppDarkMode::getTextColor() : NppDarkMode::getDisabledTextColor()); ::SetBkColor(hdc, NppDarkMode::getDarkerBackgroundColor()); return reinterpret_cast(NppDarkMode::getDarkerBackgroundBrush()); } INT_PTR onCtlColorListbox(WPARAM wParam, LPARAM lParam) { auto hdc = reinterpret_cast(wParam); auto hwnd = reinterpret_cast(lParam); auto style = ::GetWindowLongPtr(hwnd, GWL_STYLE); bool isComboBox = (style & LBS_COMBOBOX) == LBS_COMBOBOX; if (!isComboBox && ::IsWindowEnabled(hwnd)) { return static_cast(NppDarkMode::onCtlColorSofter(hdc)); } return static_cast(NppDarkMode::onCtlColor(hdc)); } struct HLSColour { WORD _hue; WORD _lightness; WORD _saturation; COLORREF toRGB() const { return ColorHLSToRGB(_hue, _lightness, _saturation); } }; using IndividualTabColours = std::array; static constexpr IndividualTabColours individualTabHuesFor_Dark { { HLSColour{37, 60, 60}, HLSColour{70, 60, 60}, HLSColour{144, 70, 60}, HLSColour{255, 60, 60}, HLSColour{195, 60, 60} } }; static constexpr IndividualTabColours individualTabHues { { HLSColour{37, 210, 150}, HLSColour{70, 210, 150}, HLSColour{144, 210, 150}, HLSColour{255, 210, 150}, HLSColour{195, 210, 150}}}; COLORREF getIndividualTabColour(int colourIndex, bool themeDependant, bool saturated) { if (colourIndex < 0 || colourIndex > 4) return {}; HLSColour result; if (themeDependant) { result = individualTabHuesFor_Dark[colourIndex]; if (saturated) { result._lightness = 146U; result._saturation = std::min(240U, result._saturation + 100U); } } else { result = individualTabHues[colourIndex]; if (saturated) { result._lightness = 140U; result._saturation = std::min(240U, result._saturation + 30U); } } return result.toRGB(); } }