diff --git a/NppDarkMode.cpp b/NppDarkMode.cpp
new file mode 100644
index 0000000..07e0490
--- /dev/null
+++ b/NppDarkMode.cpp
@@ -0,0 +1,3241 @@
+// 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();
+ }
+}