Dark Mode independent of OS setting

Close #9802
pull/9807/head
Adam D. Walling 2021-04-26 02:15:16 -04:00 committed by Don HO
parent bd6dbd4bb6
commit 85aaef2fba
8 changed files with 266 additions and 87 deletions

View File

@ -123,6 +123,17 @@ bool IsHighContrast()
return false;
}
void SetTitleBarThemeColor(HWND hWnd, BOOL dark)
{
if (g_buildNumber < 18362)
SetPropW(hWnd, L"UseImmersiveDarkModeColors", reinterpret_cast<HANDLE>(static_cast<INT_PTR>(dark)));
else if (_SetWindowCompositionAttribute)
{
WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &dark, sizeof(dark) };
_SetWindowCompositionAttribute(hWnd, &data);
}
}
void RefreshTitleBarThemeColor(HWND hWnd)
{
BOOL dark = FALSE;
@ -132,13 +143,8 @@ void RefreshTitleBarThemeColor(HWND hWnd)
{
dark = TRUE;
}
if (g_buildNumber < 18362)
SetPropW(hWnd, L"UseImmersiveDarkModeColors", reinterpret_cast<HANDLE>(static_cast<INT_PTR>(dark)));
else if (_SetWindowCompositionAttribute)
{
WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &dark, sizeof(dark) };
_SetWindowCompositionAttribute(hWnd, &data);
}
SetTitleBarThemeColor(hWnd, dark);
}
bool IsColorSchemeChangeMessage(LPARAM lParam)
@ -165,7 +171,7 @@ void AllowDarkModeForApp(bool allow)
if (_AllowDarkModeForApp)
_AllowDarkModeForApp(allow);
else if (_SetPreferredAppMode)
_SetPreferredAppMode(allow ? AllowDark : Default);
_SetPreferredAppMode(allow ? ForceDark : Default);
}
// limit dark scroll bar to specific windows and their children
@ -233,7 +239,7 @@ constexpr bool CheckBuildNumber(DWORD buildNumber)
buildNumber == 19043); // 21H1
}
void InitDarkMode(bool fixDarkScrollbar)
void InitDarkMode(bool fixDarkScrollbar, bool dark)
{
auto RtlGetNtVersionNumbers = reinterpret_cast<fnRtlGetNtVersionNumbers>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetNtVersionNumbers"));
if (RtlGetNtVersionNumbers)
@ -273,7 +279,7 @@ void InitDarkMode(bool fixDarkScrollbar)
{
g_darkModeSupported = true;
AllowDarkModeForApp(true);
AllowDarkModeForApp(dark);
_RefreshImmersiveColorPolicyState();
g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast();

View File

@ -8,9 +8,10 @@ bool ShouldAppsUseDarkMode();
bool AllowDarkModeForWindow(HWND hWnd, bool allow);
bool IsHighContrast();
void RefreshTitleBarThemeColor(HWND hWnd);
void SetTitleBarThemeColor(HWND hWnd, BOOL dark);
bool IsColorSchemeChangeMessage(LPARAM lParam);
bool IsColorSchemeChangeMessage(UINT message, LPARAM lParam);
void AllowDarkModeForApp(bool allow);
void EnableDarkScrollBarForWindowAndChildren(HWND hwnd);
void InitDarkMode(bool fixDarkScrollbar);
void InitDarkMode(bool fixDarkScrollbar, bool dark);

View File

@ -7503,8 +7503,41 @@ void Notepad_plus::restoreMinimizeDialogs()
void Notepad_plus::refreshDarkMode()
{
SendMessage(_pPublicInterface->getHSelf(), NPPM_SETEDITORBORDEREDGE, 0, NppParameters::getInstance().getSVP()._showBorderEdge);
if (NppDarkMode::isExperimentalEnabled())
{
NppDarkMode::allowDarkModeForApp(NppDarkMode::isEnabled());
NppDarkMode::allowDarkModeForWindow(_pPublicInterface->getHSelf(), NppDarkMode::isEnabled());
NppDarkMode::allowDarkModeForWindow(_findReplaceDlg.getHSelf(), NppDarkMode::isEnabled());
NppDarkMode::setTitleBarThemeColor(_pPublicInterface->getHSelf(), NppDarkMode::isEnabled());
NppDarkMode::setTitleBarThemeColor(_findReplaceDlg.getHSelf(), NppDarkMode::isEnabled());
}
SendMessage(_findReplaceDlg.getHSelf(), NPPM_INTERNAL_REFRESHDARKMODE, 0, 0);
SendMessage(_incrementFindDlg.getHSelf(), NPPM_INTERNAL_REFRESHDARKMODE, 0, 0);
RedrawWindow(_pPublicInterface->getHSelf(), nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN);
RedrawWindow(_findReplaceDlg.getHSelf(), nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN);
if (NppDarkMode::isExperimentalEnabled())
{
RECT rcClient;
GetWindowRect(_pPublicInterface->getHSelf(), &rcClient);
// Inform application of the frame change.
SetWindowPos(_pPublicInterface->getHSelf(),
NULL,
rcClient.left, rcClient.top,
rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
SWP_FRAMECHANGED);
GetWindowRect(_findReplaceDlg.getHSelf(), &rcClient);
// Inform application of the frame change.
SetWindowPos(_findReplaceDlg.getHSelf(),
NULL,
rcClient.left, rcClient.top,
rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
SWP_FRAMECHANGED);
}
}
void Notepad_plus::launchDocumentBackupTask()

View File

@ -70,7 +70,7 @@ LRESULT CALLBACK Notepad_plus_Window::Notepad_plus_Proc(HWND hwnd, UINT message,
pM30ide->_hSelf = hwnd;
::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pM30ide));
if (NppDarkMode::isExperimentalEnabled() && NppDarkMode::isScrollbarHackEnabled())
if (NppDarkMode::isExperimentalEnabled() && NppDarkMode::isEnabled() && NppDarkMode::isScrollbarHackEnabled())
{
NppDarkMode::enableDarkScrollBarForWindowAndChildren(hwnd);
}
@ -96,14 +96,14 @@ LRESULT Notepad_plus_Window::runProc(HWND hwnd, UINT message, WPARAM wParam, LPA
{
if (NppDarkMode::isExperimentalEnabled())
{
NppDarkMode::allowDarkModeForWindow(hwnd, true);
NppDarkMode::refreshTitleBarThemeColor(hwnd);
NppDarkMode::allowDarkModeForWindow(hwnd, NppDarkMode::isEnabled());
NppDarkMode::setTitleBarThemeColor(hwnd, NppDarkMode::isEnabled());
}
_notepad_plus_plus_core._pPublicInterface = this;
LRESULT lRet = _notepad_plus_plus_core.init(hwnd);
if (NppDarkMode::isExperimentalEnabled())
if (NppDarkMode::isExperimentalEnabled() && NppDarkMode::isEnabled())
{
RECT rcClient;
GetWindowRect(hwnd, &rcClient);
@ -150,7 +150,7 @@ LRESULT Notepad_plus::process(HWND hwnd, UINT message, WPARAM wParam, LPARAM lPa
LRESULT result = FALSE;
NppParameters& nppParam = NppParameters::getInstance();
if (NppDarkMode::isDarkMenuEnabled() && NppDarkMode::runUAHWndProc(hwnd, message, wParam, lParam, &result))
if (NppDarkMode::isDarkMenuEnabled() && NppDarkMode::isEnabled() && NppDarkMode::runUAHWndProc(hwnd, message, wParam, lParam, &result))
{
return result;
}
@ -181,11 +181,7 @@ LRESULT Notepad_plus::process(HWND hwnd, UINT message, WPARAM wParam, LPARAM lPa
case WM_SETTINGCHANGE:
{
if (NppDarkMode::handleSettingChange(hwnd, lParam))
{
// dark mode may have been toggled by the OS
NppDarkMode::refreshDarkMode(hwnd, true);
}
NppDarkMode::handleSettingChange(hwnd, lParam);
return ::DefWindowProc(hwnd, message, wParam, lParam);
}

View File

@ -28,7 +28,7 @@ namespace NppDarkMode
if (_options.enableExperimental)
{
initExperimentalDarkMode(_options.enableScrollbarHack);
initExperimentalDarkMode(_options.enableScrollbarHack, _options.enable);
}
}
@ -193,28 +193,19 @@ namespace NppDarkMode
// handle events
bool handleSettingChange(HWND hwnd, LPARAM lParam) // true if dark mode toggled
void handleSettingChange(HWND hwnd, LPARAM lParam)
{
UNREFERENCED_PARAMETER(hwnd);
if (!isExperimentalEnabled())
{
return false;
return;
}
bool toggled = false;
if (IsColorSchemeChangeMessage(lParam))
{
bool darkModeWasEnabled = g_darkModeEnabled;
g_darkModeEnabled = ShouldAppsUseDarkMode() && !IsHighContrast();
NppDarkMode::refreshTitleBarThemeColor(hwnd);
if (!!darkModeWasEnabled != !!g_darkModeEnabled)
{
toggled = true;
}
}
return toggled;
}
// processes messages related to UAH / custom menubar drawing.
@ -349,9 +340,9 @@ namespace NppDarkMode
// from DarkMode.h
void initExperimentalDarkMode(bool fixDarkScrollbar)
void initExperimentalDarkMode(bool fixDarkScrollbar, bool dark)
{
::InitDarkMode(fixDarkScrollbar);
::InitDarkMode(fixDarkScrollbar, dark);
}
void allowDarkModeForApp(bool allow)
@ -364,9 +355,9 @@ namespace NppDarkMode
return ::AllowDarkModeForWindow(hWnd, allow);
}
void refreshTitleBarThemeColor(HWND hWnd)
void setTitleBarThemeColor(HWND hWnd, bool dark)
{
::RefreshTitleBarThemeColor(hWnd);
::SetTitleBarThemeColor(hWnd, dark);
}
void enableDarkScrollBarForWindowAndChildren(HWND hwnd)
@ -932,5 +923,63 @@ namespace NppDarkMode
{
SetWindowSubclass(hwnd, TabSubclass, g_tabSubclassID, 0);
}
void autoSubclassAndThemeChildControls(HWND hwndParent, bool subclass, bool theme)
{
struct Params
{
const wchar_t* themeClassName = nullptr;
bool subclass = false;
bool theme = false;
};
Params p{
NppDarkMode::isEnabled() ? L"DarkMode_Explorer" : L"Button"
, subclass
, theme
};
EnumChildWindows(hwndParent, [](HWND hwnd, LPARAM lParam) {
auto& p = *reinterpret_cast<Params*>(lParam);
wchar_t className[16] = { 0 };
GetClassName(hwnd, className, 9);
if (wcscmp(className, L"Button"))
{
return TRUE;
}
DWORD nButtonStyle = (DWORD)GetWindowLong(hwnd, GWL_STYLE) & 0xF;
switch (nButtonStyle)
{
case BS_CHECKBOX:
case BS_AUTOCHECKBOX:
case BS_RADIOBUTTON:
case BS_AUTORADIOBUTTON:
if (p.subclass)
{
NppDarkMode::subclassButtonControl(hwnd);
}
break;
case BS_GROUPBOX:
if (p.subclass)
{
NppDarkMode::subclassGroupboxControl(hwnd);
}
break;
case BS_DEFPUSHBUTTON:
case BS_PUSHBUTTON:
if (p.theme)
{
SetWindowTheme(hwnd, p.themeClassName, nullptr);
}
break;
}
return TRUE;
}, reinterpret_cast<LPARAM>(&p));
}
void autoThemeChildControls(HWND hwndParent)
{
autoSubclassAndThemeChildControls(hwndParent, false, true);
}
}

View File

@ -20,9 +20,6 @@ namespace NppDarkMode
bool isExperimentalEnabled();
bool isScrollbarHackEnabled();
bool isExperimentalActive();
bool isExperimentalSupported();
COLORREF invertLightness(COLORREF c);
COLORREF invertLightnessSofter(COLORREF c);
@ -42,17 +39,17 @@ namespace NppDarkMode
HBRUSH getErrorBackgroundBrush();
// handle events
bool handleSettingChange(HWND hwnd, LPARAM lParam); // true if dark mode toggled
void handleSettingChange(HWND hwnd, LPARAM lParam);
// 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);
// from DarkMode.h
void initExperimentalDarkMode(bool fixDarkScrollbar);
void initExperimentalDarkMode(bool fixDarkScrollbar, bool dark);
void allowDarkModeForApp(bool allow);
bool allowDarkModeForWindow(HWND hWnd, bool allow);
void refreshTitleBarThemeColor(HWND hWnd);
void setTitleBarThemeColor(HWND hWnd, bool dark);
// enhancements to DarkMode.h
void enableDarkScrollBarForWindowAndChildren(HWND hwnd);
@ -61,5 +58,8 @@ namespace NppDarkMode
void subclassGroupboxControl(HWND hwnd);
void subclassToolbarControl(HWND hwnd);
void subclassTabControl(HWND hwnd);
void autoSubclassAndThemeChildControls(HWND hwndParent, bool subclass = true, bool theme = true);
void autoThemeChildControls(HWND hwndParent);
}

View File

@ -876,44 +876,21 @@ INT_PTR CALLBACK FindReplaceDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM
return TRUE;
}
case WM_SETTINGCHANGE:
case NPPM_INTERNAL_REFRESHDARKMODE:
{
if (NppDarkMode::handleSettingChange(_hSelf, lParam))
{
// dark mode may have been toggled by the OS
NppDarkMode::refreshDarkMode(_hSelf, true);
}
break;
NppDarkMode::autoThemeChildControls(_hSelf);
return TRUE;
}
case WM_INITDIALOG :
{
if (NppDarkMode::isExperimentalEnabled())
{
NppDarkMode::allowDarkModeForWindow(_hSelf, true);
NppDarkMode::refreshTitleBarThemeColor(_hSelf);
NppDarkMode::allowDarkModeForWindow(_hSelf, NppDarkMode::isEnabled());
NppDarkMode::setTitleBarThemeColor(_hSelf, NppDarkMode::isEnabled());
}
EnumChildWindows(_hSelf, [](HWND hwnd, LPARAM) {
wchar_t className[16] = { 0 };
GetClassName(hwnd, className, 9);
if (wcscmp(className, L"Button")) {
return TRUE;
}
DWORD nButtonStyle = (DWORD)GetWindowLong(hwnd, GWL_STYLE) & 0xF;
switch (nButtonStyle) {
case BS_CHECKBOX:
case BS_AUTOCHECKBOX:
case BS_RADIOBUTTON:
case BS_AUTORADIOBUTTON:
NppDarkMode::subclassButtonControl(hwnd);
break;
case BS_GROUPBOX:
NppDarkMode::subclassGroupboxControl(hwnd);
break;
}
return TRUE;
}, NULL);
NppDarkMode::autoSubclassAndThemeChildControls(_hSelf);
HWND hFindCombo = ::GetDlgItem(_hSelf, IDFINDWHAT);
HWND hReplaceCombo = ::GetDlgItem(_hSelf, IDREPLACEWITH);
@ -3699,12 +3676,45 @@ void FindReplaceDlg::drawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
ptStr = TEXT("");
}
if (NppDarkMode::isEnabled())
{
fgColor = NppDarkMode::getTextColor();
if (_statusbarFindStatus == FSNotFound)
{
fgColor = RGB(0xFF, 0x50, 0x50); // red
}
else if (_statusbarFindStatus == FSMessage)
{
fgColor = RGB(0x50, 0x50, 0xFF); // blue
}
else if (_statusbarFindStatus == FSTopReached || _statusbarFindStatus == FSEndReached)
{
fgColor = RGB(0x50, 0xFF, 0x50); // green
}
}
SetTextColor(lpDrawItemStruct->hDC, fgColor);
COLORREF bgColor = getCtrlBgColor(_statusBar.getHSelf());
COLORREF bgColor;
if (NppDarkMode::isEnabled())
{
bgColor = NppDarkMode::getBackgroundColor();
}
else
{
bgColor = getCtrlBgColor(_statusBar.getHSelf());
}
::SetBkColor(lpDrawItemStruct->hDC, bgColor);
RECT rect;
_statusBar.getClientRect(rect);
if (NppDarkMode::isEnabled())
{
rect.left += 2;
}
::DrawText(lpDrawItemStruct->hDC, ptStr, lstrlen(ptStr), &rect, DT_SINGLELINE | DT_VCENTER | DT_LEFT);
if (_statusbarTooltipMsg.length() == 0) return;
@ -4437,11 +4447,17 @@ INT_PTR CALLBACK FindIncrementDlg::run_dlgProc(UINT message, WPARAM wParam, LPAR
return (LRESULT)GetStockObject(BLACK_BRUSH);
}
case NPPM_INTERNAL_REFRESHDARKMODE:
{
NppDarkMode::autoThemeChildControls(getHSelf());
return TRUE;
}
case WM_INITDIALOG:
{
LRESULT lr = DefWindowProc(getHSelf(), message, wParam, lParam);
NppDarkMode::subclassButtonControl(GetDlgItem(getHSelf(), IDC_INCFINDHILITEALL));
NppDarkMode::subclassButtonControl(GetDlgItem(getHSelf(), IDC_INCFINDMATCHCASE));
NppDarkMode::autoSubclassAndThemeChildControls(getHSelf());
return lr;
}

View File

@ -23,6 +23,8 @@
#include <cassert>
#include "Parameters.h"
#include "NppDarkMode.h"
#include <Uxtheme.h>
#include <Vssym32.h>
//#define IDC_STATUSBAR 789
@ -47,6 +49,34 @@ void StatusBar::init(HINSTANCE /*hInst*/, HWND /*hPere*/)
assert(false and "should never be called");
}
struct StatusBarSubclassInfo
{
HTHEME hTheme = nullptr;
~StatusBarSubclassInfo()
{
closeTheme();
}
bool ensureTheme(HWND hwnd)
{
if (!hTheme)
{
hTheme = OpenThemeData(hwnd, L"Status");
}
return hTheme != nullptr;
}
void closeTheme()
{
if (hTheme)
{
CloseThemeData(hTheme);
hTheme = nullptr;
}
}
};
constexpr UINT_PTR g_statusBarSubclassID = 42;
LRESULT CALLBACK StatusBarSubclass(
@ -58,9 +88,10 @@ LRESULT CALLBACK StatusBarSubclass(
DWORD_PTR dwRefData
)
{
UNREFERENCED_PARAMETER(dwRefData);
UNREFERENCED_PARAMETER(uIdSubclass);
StatusBarSubclassInfo* pStatusBarInfo = reinterpret_cast<StatusBarSubclassInfo*>(dwRefData);
switch (uMsg) {
case WM_ERASEBKGND:
{
@ -89,6 +120,9 @@ LRESULT CALLBACK StatusBarSubclass(
SendMessage(hWnd, SB_GETBORDERS, 0, (LPARAM)&borders);
DWORD style = GetWindowLong(hWnd, GWL_STYLE);
bool isSizeGrip = style & SBARS_SIZEGRIP;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
@ -100,7 +134,7 @@ LRESULT CALLBACK StatusBarSubclass(
FillRect(hdc, &ps.rcPaint, NppDarkMode::getBackgroundBrush());
int nParts = static_cast<int>(SendMessage(hWnd, SB_GETPARTS, 0, 0));
std::vector<wchar_t> str;
std::wstring str;
for (int i = 0; i < nParts; ++i)
{
RECT rcPart = { 0 };
@ -113,18 +147,59 @@ LRESULT CALLBACK StatusBarSubclass(
RECT rcDivider = { rcPart.right - borders.vertical, rcPart.top, rcPart.right, rcPart.bottom };
DWORD cchText = LOWORD(SendMessage(hWnd, SB_GETTEXTLENGTH, i, 0));
DWORD cchText = 0;
cchText = LOWORD(SendMessage(hWnd, SB_GETTEXTLENGTH, i, 0));
str.resize(cchText + 1);
SendMessage(hWnd, SB_GETTEXT, i, (LPARAM)str.data());
LRESULT lr = SendMessage(hWnd, SB_GETTEXT, i, (LPARAM)str.data());
bool ownerDraw = false;
if (cchText == 0 && (lr & ~(SBT_NOBORDERS | SBT_POPOUT | SBT_RTLREADING)) != 0)
{
// this is a pointer to the text
ownerDraw = true;
}
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, NppDarkMode::getTextColor());
rcPart.left += borders.between;
rcPart.right -= borders.vertical;
DrawText(hdc, str.data(), cchText, &rcPart, DT_SINGLELINE | DT_VCENTER | DT_LEFT);
if (ownerDraw)
{
UINT id = GetDlgCtrlID(hWnd);
DRAWITEMSTRUCT dis = {
0
, 0
, static_cast<UINT>(i)
, ODA_DRAWENTIRE
, id
, hWnd
, hdc
, rcPart
, static_cast<ULONG_PTR>(lr)
};
FillRect(hdc, &rcDivider, NppDarkMode::getSofterBackgroundBrush());
SendMessage(GetParent(hWnd), WM_DRAWITEM, id, (LPARAM)&dis);
}
else
{
DrawText(hdc, str.data(), static_cast<int>(str.size()), &rcPart, DT_SINGLELINE | DT_VCENTER | DT_LEFT);
}
if (!isSizeGrip && i < (nParts - 1))
{
FillRect(hdc, &rcDivider, NppDarkMode::getSofterBackgroundBrush());
}
}
if (isSizeGrip)
{
pStatusBarInfo->ensureTheme(hWnd);
SIZE gripSize = { 0 };
GetThemePartSize(pStatusBarInfo->hTheme, hdc, SP_GRIPPER, 0, &rcClient, TS_DRAW, &gripSize);
RECT rc = rcClient;
rc.left = rc.right - gripSize.cx;
rc.top = rc.bottom - gripSize.cy;
DrawThemeBackground(pStatusBarInfo->hTheme, hdc, SP_GRIPPER, 0, &rc, nullptr);
}
::SelectObject(hdc, holdFont);
@ -134,6 +209,10 @@ LRESULT CALLBACK StatusBarSubclass(
}
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, StatusBarSubclass, g_statusBarSubclassID);
delete pStatusBarInfo;
break;
case WM_THEMECHANGED:
pStatusBarInfo->closeTheme();
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
@ -156,10 +235,9 @@ void StatusBar::init(HINSTANCE hInst, HWND hPere, int nbParts)
if (!_hSelf)
throw std::runtime_error("StatusBar::init : CreateWindowEx() function return null");
if (nbParts > 0)
{
SetWindowSubclass(_hSelf, StatusBarSubclass, g_statusBarSubclassID, 0);
}
auto* pStatusBarInfo = new StatusBarSubclassInfo();
SetWindowSubclass(_hSelf, StatusBarSubclass, g_statusBarSubclassID, reinterpret_cast<DWORD_PTR>(pStatusBarInfo));
_partWidthArray.clear();
if (nbParts > 0)