You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
git_read/NppDarkMode.cpp

3242 lines
83 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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 <https://www.gnu.org/licenses/>.
#include "NppDarkMode.h"
#include "DarkMode/DarkMode.h"
#include "DarkMode/UAHMenuBar.h"
#include <dwmapi.h>
#include <uxtheme.h>
#include <vssym32.h>
#include "Parameters.h"
#include "resource.h"
#include <shlwapi.h>
#include <array>
#ifdef __GNUC__
#include <cmath>
#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<PWINEGETVERSION>(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<WPARAM>(!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<WORD>(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<double>(GetRValue(c)));
double g = linearValue(static_cast<double>(GetGValue(c)));
double b = linearValue(static_cast<double>(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<DWORD>(SendMessage(hwnd, BM_GETSTATE, 0, 0));
DWORD uiState = static_cast<DWORD>(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<HFONT>(SendMessage(hwnd, WM_GETFONT, 0, 0));
}
hOldFont = static_cast<HFONT>(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<DWORD>(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<ButtonData*>(dwRefData);
switch (uMsg)
{
case WM_UPDATEUISTATE:
if (HIWORD(wParam) & (UISF_HIDEACCEL | UISF_HIDEFOCUS))
{
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, ButtonSubclass, g_buttonSubclassID);
delete pButtonData;
break;
case WM_ERASEBKGND:
if (NppDarkMode::isEnabled() && pButtonData->ensureTheme(hWnd))
{
return TRUE;
}
else
{
break;
}
case WM_THEMECHANGED:
pButtonData->closeTheme();
break;
case WM_PRINTCLIENT:
case WM_PAINT:
if (NppDarkMode::isEnabled() && pButtonData->ensureTheme(hWnd))
{
PAINTSTRUCT ps{};
HDC hdc = reinterpret_cast<HDC>(wParam);
if (!hdc)
{
hdc = BeginPaint(hWnd, &ps);
}
paintButton(hWnd, hdc, *pButtonData);
if (ps.hdc)
{
EndPaint(hWnd, &ps);
}
return 0;
}
else
{
break;
}
case WM_SIZE:
case WM_DESTROY:
BufferedPaintStopAllAnimations(hWnd);
break;
case WM_ENABLE:
if (NppDarkMode::isEnabled())
{
// skip the button's normal wndproc so it won't redraw out of wm_paint
LRESULT lr = DefWindowProc(hWnd, uMsg, wParam, lParam);
InvalidateRect(hWnd, nullptr, FALSE);
return lr;
}
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
void subclassButtonControl(HWND hwnd)
{
DWORD_PTR pButtonData = reinterpret_cast<DWORD_PTR>(new ButtonData());
SetWindowSubclass(hwnd, ButtonSubclass, g_buttonSubclassID, pButtonData);
}
void paintGroupbox(HWND hwnd, HDC hdc, ButtonData& buttonData)
{
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<HFONT>(SendMessage(hwnd, WM_GETFONT, 0, 0));
}
hOldFont = static_cast<HFONT>(::SelectObject(hdc, hFont));
WCHAR szText[256] = { '\0' };
GetWindowText(hwnd, szText, _countof(szText));
auto style = static_cast<long>(::GetWindowLongPtr(hwnd, GWL_STYLE));
bool isCenter = (style & BS_CENTER) == BS_CENTER;
if (szText[0])
{
SIZE textSize{};
GetTextExtentPoint32(hdc, szText, static_cast<int>(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<LRESULT>(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<ButtonData*>(dwRefData);
switch (uMsg)
{
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, GroupboxSubclass, g_groupboxSubclassID);
delete pButtonData;
break;
case WM_ERASEBKGND:
if (NppDarkMode::isEnabled() && pButtonData->ensureTheme(hWnd))
{
return TRUE;
}
else
{
break;
}
case WM_THEMECHANGED:
pButtonData->closeTheme();
break;
case WM_PRINTCLIENT:
case WM_PAINT:
if (NppDarkMode::isEnabled() && pButtonData->ensureTheme(hWnd))
{
PAINTSTRUCT ps{};
HDC hdc = reinterpret_cast<HDC>(wParam);
if (!hdc)
{
hdc = BeginPaint(hWnd, &ps);
}
paintGroupbox(hWnd, hdc, *pButtonData);
if (ps.hdc)
{
EndPaint(hWnd, &ps);
}
return 0;
}
else
{
break;
}
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
void subclassGroupboxControl(HWND hwnd)
{
DWORD_PTR pButtonData = reinterpret_cast<DWORD_PTR>(new ButtonData());
SetWindowSubclass(hwnd, GroupboxSubclass, g_groupboxSubclassID, pButtonData);
}
constexpr UINT_PTR g_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<HPEN>(::SelectObject(hdc, NppDarkMode::getEdgePen()));
HRGN holdClip = CreateRectRgn(0, 0, 0, 0);
if (1 != GetClipRgn(hdc, holdClip))
{
DeleteObject(holdClip);
holdClip = nullptr;
}
HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
auto hOldFont = SelectObject(hdc, hFont);
POINT ptCursor{};
::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<LPARAM>(&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<HWND>(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<LPRECT>(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<HWND>(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<HFONT>(::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<int>(::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<size_t>(::SendMessage(hWnd, CB_GETLBTEXTLEN, index, 0));
TCHAR* buffer = new TCHAR[(bufferLen + 1)];
::SendMessage(hWnd, CB_GETLBTEXT, index, reinterpret_cast<LPARAM>(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<HPEN>(::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<DWORD_PTR>(::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<LPNMHDR>(lParam)->code)
{
case NM_CUSTOMDRAW:
{
auto lpnmcd = reinterpret_cast<LPNMCUSTOMDRAW>(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<ButtonData*>(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<HDC>(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<DWORD_PTR>(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<DWORD_PTR>(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<NppDarkModeParams*>(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<LPARAM>(&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<LPNMTBCUSTOMDRAW>(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<LPARAM>(&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<LPNMLVCUSTOMDRAW>(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<LPNMTVCUSTOMDRAW>(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<HDC>(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<HDC>(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<HDC>(wParam));
}
break;
}
case WM_CTLCOLORSTATIC:
{
if (NppDarkMode::isEnabled())
{
constexpr size_t classNameLen = 16;
TCHAR className[classNameLen]{};
auto hwndEdit = reinterpret_cast<HWND>(lParam);
GetClassName(hwndEdit, className, classNameLen);
if (wcscmp(className, WC_EDIT) == 0)
{
return NppDarkMode::onCtlColor(reinterpret_cast<HDC>(wParam));
}
return NppDarkMode::onCtlColorDarker(reinterpret_cast<HDC>(wParam));
}
break;
}
case WM_PRINTCLIENT:
{
if (NppDarkMode::isEnabled())
{
return TRUE;
}
break;
}
case WM_NOTIFY:
{
const auto nmhdr = reinterpret_cast<LPNMHDR>(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<LPNMHDR>(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<HWND>(::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<LPARAM>(&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<long>(::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<long>(::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<LRESULT>(NppDarkMode::getBackgroundBrush());
}
LRESULT onCtlColorSofter(HDC hdc)
{
if (!NppDarkMode::isEnabled())
{
return FALSE;
}
::SetTextColor(hdc, NppDarkMode::getTextColor());
::SetBkColor(hdc, NppDarkMode::getSofterBackgroundColor());
return reinterpret_cast<LRESULT>(NppDarkMode::getSofterBackgroundBrush());
}
LRESULT onCtlColorDarker(HDC hdc)
{
if (!NppDarkMode::isEnabled())
{
return FALSE;
}
::SetTextColor(hdc, NppDarkMode::getTextColor());
::SetBkColor(hdc, NppDarkMode::getDarkerBackgroundColor());
return reinterpret_cast<LRESULT>(NppDarkMode::getDarkerBackgroundBrush());
}
LRESULT onCtlColorError(HDC hdc)
{
if (!NppDarkMode::isEnabled())
{
return FALSE;
}
::SetTextColor(hdc, NppDarkMode::getTextColor());
::SetBkColor(hdc, NppDarkMode::getErrorBackgroundColor());
return reinterpret_cast<LRESULT>(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<LRESULT>(NppDarkMode::getDarkerBackgroundBrush());
}
INT_PTR onCtlColorListbox(WPARAM wParam, LPARAM lParam)
{
auto hdc = reinterpret_cast<HDC>(wParam);
auto hwnd = reinterpret_cast<HWND>(lParam);
auto style = ::GetWindowLongPtr(hwnd, GWL_STYLE);
bool isComboBox = (style & LBS_COMBOBOX) == LBS_COMBOBOX;
if (!isComboBox && ::IsWindowEnabled(hwnd))
{
return static_cast<INT_PTR>(NppDarkMode::onCtlColorSofter(hdc));
}
return static_cast<INT_PTR>(NppDarkMode::onCtlColor(hdc));
}
struct HLSColour
{
WORD _hue;
WORD _lightness;
WORD _saturation;
COLORREF toRGB() const { return ColorHLSToRGB(_hue, _lightness, _saturation); }
};
using IndividualTabColours = std::array<HLSColour, 5>;
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<WORD>(240U, result._saturation + 100U);
}
}
else
{
result = individualTabHues[colourIndex];
if (saturated)
{
result._lightness = 140U;
result._saturation = std::min<WORD>(240U, result._saturation + 30U);
}
}
return result.toRGB();
}
}