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(); + } +}