mirror of
https://github.com/notepad-plus-plus/notepad-plus-plus.git
synced 2025-07-23 22:04:55 +02:00
Dark mode enhancement for tab control
- enable to use in generic dark mode subclass for plugins - handle more styles (button style, usage of icons) - double buffer to avoid flicker - focus rect when keyboard is used for navigation Fix #16668, close #16673
This commit is contained in:
parent
7cc002680a
commit
a8ff8bb78e
@ -1761,102 +1761,98 @@ namespace NppDarkMode
|
|||||||
SetWindowSubclass(hwnd, GroupboxSubclass, g_groupboxSubclassID, pButtonData);
|
SetWindowSubclass(hwnd, GroupboxSubclass, g_groupboxSubclassID, pButtonData);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr UINT_PTR g_tabSubclassID = 42;
|
static void paintTab(HWND hWnd, HDC hdc, const RECT& rect)
|
||||||
|
|
||||||
static LRESULT CALLBACK TabSubclass(
|
|
||||||
HWND hWnd,
|
|
||||||
UINT uMsg,
|
|
||||||
WPARAM wParam,
|
|
||||||
LPARAM lParam,
|
|
||||||
UINT_PTR uIdSubclass,
|
|
||||||
DWORD_PTR /*dwRefData*/
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
switch (uMsg)
|
::FillRect(hdc, &rect, NppDarkMode::getDlgBackgroundBrush());
|
||||||
|
|
||||||
|
auto holdPen = static_cast<HPEN>(::SelectObject(hdc, NppDarkMode::getEdgePen()));
|
||||||
|
|
||||||
|
auto holdClip = ::CreateRectRgn(0, 0, 0, 0);
|
||||||
|
if (::GetClipRgn(hdc, holdClip) != 1)
|
||||||
{
|
{
|
||||||
case WM_ERASEBKGND:
|
::DeleteObject(holdClip);
|
||||||
|
holdClip = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hFont = reinterpret_cast<HFONT>(::SendMessage(hWnd, WM_GETFONT, 0, 0));
|
||||||
|
auto holdFont = ::SelectObject(hdc, hFont);
|
||||||
|
|
||||||
|
POINT ptCursor{};
|
||||||
|
::GetCursorPos(&ptCursor);
|
||||||
|
::ScreenToClient(hWnd, &ptCursor);
|
||||||
|
|
||||||
|
bool hasFocusRect = false;
|
||||||
|
if (::GetFocus() == hWnd)
|
||||||
|
{
|
||||||
|
const auto uiState = static_cast<DWORD>(::SendMessage(hWnd, WM_QUERYUISTATE, 0, 0));
|
||||||
|
hasFocusRect = ((uiState & UISF_HIDEFOCUS) != UISF_HIDEFOCUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int iSelTab = TabCtrl_GetCurSel(hWnd);
|
||||||
|
const int nTabs = TabCtrl_GetItemCount(hWnd);
|
||||||
|
for (int i = 0; i < nTabs; ++i)
|
||||||
|
{
|
||||||
|
RECT rcItem{};
|
||||||
|
TabCtrl_GetItemRect(hWnd, i, &rcItem);
|
||||||
|
RECT rcFrame{ rcItem };
|
||||||
|
|
||||||
|
RECT rcIntersect{};
|
||||||
|
if (::IntersectRect(&rcIntersect, &rect, &rcItem) == TRUE)
|
||||||
{
|
{
|
||||||
if (NppDarkMode::isEnabled())
|
const bool isHot = ::PtInRect(&rcItem, ptCursor) == TRUE;
|
||||||
|
const bool isSelectedTab = (i == iSelTab);
|
||||||
|
|
||||||
|
::SetBkMode(hdc, TRANSPARENT);
|
||||||
|
|
||||||
|
HRGN hClip = ::CreateRectRgnIndirect(&rcItem);
|
||||||
|
::SelectClipRgn(hdc, hClip);
|
||||||
|
|
||||||
|
::InflateRect(&rcItem, -1, -1);
|
||||||
|
rcItem.right += 1;
|
||||||
|
|
||||||
|
std::wstring label(MAX_PATH, L'\0');
|
||||||
|
TCITEM tci{};
|
||||||
|
tci.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_STATE;
|
||||||
|
tci.dwStateMask = TCIS_HIGHLIGHTED;
|
||||||
|
tci.pszText = label.data();
|
||||||
|
tci.cchTextMax = MAX_PATH - 1;
|
||||||
|
|
||||||
|
TabCtrl_GetItem(hWnd, i, &tci);
|
||||||
|
|
||||||
|
const auto nStyle = ::GetWindowLongPtr(hWnd, GWL_STYLE);
|
||||||
|
const bool isBtn = (nStyle & TCS_BUTTONS) == TCS_BUTTONS;
|
||||||
|
if (isBtn)
|
||||||
{
|
{
|
||||||
return TRUE;
|
const bool isHighlighted = (tci.dwState & TCIS_HIGHLIGHTED) == TCIS_HIGHLIGHTED;
|
||||||
|
::FillRect(hdc, &rcItem, isHighlighted ? NppDarkMode::getHotBackgroundBrush() : NppDarkMode::getDlgBackgroundBrush());
|
||||||
|
::SetTextColor(hdc, isHighlighted ? NppDarkMode::getLinkTextColor() : NppDarkMode::getDarkerTextColor());
|
||||||
}
|
}
|
||||||
break;
|
else
|
||||||
}
|
|
||||||
|
|
||||||
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::getDlgBackgroundBrush());
|
|
||||||
|
|
||||||
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()
|
// for consistency getBackgroundBrush()
|
||||||
// would be better, than getCtrlBackgroundBrush(),
|
// would be better, than getCtrlBackgroundBrush(),
|
||||||
// however default getBackgroundBrush() has same color
|
// however default getBackgroundBrush() has same color
|
||||||
// as getDlgBackgroundBrush()
|
// as getDlgBackgroundBrush()
|
||||||
::FillRect(hdc, &rcItem, isSelectedTab ? NppDarkMode::getDlgBackgroundBrush() : bHot ? NppDarkMode::getHotBackgroundBrush() : NppDarkMode::getCtrlBackgroundBrush());
|
auto getBrush = [&]() -> HBRUSH {
|
||||||
|
if (isSelectedTab)
|
||||||
|
{
|
||||||
|
return NppDarkMode::getDlgBackgroundBrush();
|
||||||
|
}
|
||||||
|
|
||||||
SetBkMode(hdc, TRANSPARENT);
|
if (isHot)
|
||||||
|
{
|
||||||
|
return NppDarkMode::getHotBackgroundBrush();
|
||||||
|
}
|
||||||
|
return NppDarkMode::getCtrlBackgroundBrush();
|
||||||
|
};
|
||||||
|
|
||||||
wchar_t label[MAX_PATH]{};
|
::FillRect(hdc, &rcItem, getBrush());
|
||||||
TCITEM tci{};
|
::SetTextColor(hdc, (isHot || isSelectedTab) ? NppDarkMode::getTextColor() : NppDarkMode::getDarkerTextColor());
|
||||||
tci.mask = TCIF_TEXT;
|
}
|
||||||
tci.pszText = label;
|
|
||||||
tci.cchTextMax = MAX_PATH - 1;
|
|
||||||
|
|
||||||
::SendMessage(hWnd, TCM_GETITEM, i, reinterpret_cast<LPARAM>(&tci));
|
RECT rcText{ rcItem };
|
||||||
|
if (!isBtn)
|
||||||
RECT rcText = rcItem;
|
{
|
||||||
if (isSelectedTab)
|
if (isSelectedTab)
|
||||||
{
|
{
|
||||||
::OffsetRect(&rcText, 0, -1);
|
::OffsetRect(&rcText, 0, -1);
|
||||||
@ -1867,62 +1863,184 @@ namespace NppDarkMode
|
|||||||
{
|
{
|
||||||
rcFrame.right += 1;
|
rcFrame.right += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
::FrameRect(hdc, &rcFrame, NppDarkMode::getEdgeBrush());
|
|
||||||
|
|
||||||
DrawText(hdc, label, -1, &rcText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
||||||
|
|
||||||
DeleteObject(hClip);
|
|
||||||
|
|
||||||
SelectClipRgn(hdc, holdClip);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
SelectObject(hdc, hOldFont);
|
if (tci.iImage != -1)
|
||||||
|
|
||||||
SelectClipRgn(hdc, holdClip);
|
|
||||||
if (holdClip)
|
|
||||||
{
|
|
||||||
DeleteObject(holdClip);
|
|
||||||
holdClip = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectObject(hdc, holdPen);
|
|
||||||
|
|
||||||
EndPaint(hWnd, &ps);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
case WM_NCDESTROY:
|
|
||||||
{
|
|
||||||
::RemoveWindowSubclass(hWnd, TabSubclass, uIdSubclass);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case WM_PARENTNOTIFY:
|
|
||||||
{
|
|
||||||
switch (LOWORD(wParam))
|
|
||||||
{
|
|
||||||
case WM_CREATE:
|
|
||||||
{
|
{
|
||||||
auto hwndUpdown = reinterpret_cast<HWND>(lParam);
|
int cx = 0;
|
||||||
if (NppDarkMode::subclassTabUpDownControl(hwndUpdown))
|
int cy = 0;
|
||||||
|
auto hImagelist = TabCtrl_GetImageList(hWnd);
|
||||||
|
static constexpr int offset = 2;
|
||||||
|
::ImageList_GetIconSize(hImagelist, &cx, &cy);
|
||||||
|
::ImageList_Draw(hImagelist, tci.iImage, hdc, rcText.left + offset, rcText.top + (((rcText.bottom - rcText.top) - cy) / 2), ILD_NORMAL);
|
||||||
|
rcText.left += cx;
|
||||||
|
}
|
||||||
|
|
||||||
|
::DrawText(hdc, label.c_str(), -1, &rcText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||||
|
|
||||||
|
::FrameRect(hdc, &rcFrame, NppDarkMode::getEdgeBrush());
|
||||||
|
|
||||||
|
if (isSelectedTab && hasFocusRect)
|
||||||
|
{
|
||||||
|
::InflateRect(&rcFrame, -2, -1);
|
||||||
|
::DrawFocusRect(hdc, &rcFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
::SelectClipRgn(hdc, holdClip);
|
||||||
|
::DeleteObject(hClip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::SelectObject(hdc, holdFont);
|
||||||
|
::SelectClipRgn(hdc, holdClip);
|
||||||
|
if (holdClip != nullptr)
|
||||||
|
{
|
||||||
|
::DeleteObject(holdClip);
|
||||||
|
holdClip = nullptr;
|
||||||
|
}
|
||||||
|
::SelectObject(hdc, holdPen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr UINT_PTR g_tabSubclassID = 42;
|
||||||
|
|
||||||
|
static LRESULT CALLBACK TabSubclass(
|
||||||
|
HWND hWnd,
|
||||||
|
UINT uMsg,
|
||||||
|
WPARAM wParam,
|
||||||
|
LPARAM lParam,
|
||||||
|
UINT_PTR uIdSubclass,
|
||||||
|
DWORD_PTR dwRefData
|
||||||
|
)
|
||||||
|
{
|
||||||
|
auto* pTabBufferData = reinterpret_cast<BufferData*>(dwRefData);
|
||||||
|
const auto& hMemDC = pTabBufferData->_hMemDC;
|
||||||
|
|
||||||
|
switch (uMsg)
|
||||||
|
{
|
||||||
|
case WM_NCDESTROY:
|
||||||
|
{
|
||||||
|
::RemoveWindowSubclass(hWnd, TabSubclass, uIdSubclass);
|
||||||
|
delete pTabBufferData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_ERASEBKGND:
|
||||||
|
{
|
||||||
|
if (!NppDarkMode::isEnabled())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* hdc = reinterpret_cast<HDC>(wParam);
|
||||||
|
if (hdc != hMemDC)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_PAINT:
|
||||||
|
{
|
||||||
|
if (!NppDarkMode::isEnabled())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto nStyle = ::GetWindowLongPtr(hWnd, GWL_STYLE);
|
||||||
|
if ((nStyle & TCS_VERTICAL) == TCS_VERTICAL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
PAINTSTRUCT ps{};
|
||||||
|
HDC hdc = ::BeginPaint(hWnd, &ps);
|
||||||
|
|
||||||
|
if (ps.rcPaint.right <= ps.rcPaint.left || ps.rcPaint.bottom <= ps.rcPaint.top)
|
||||||
|
{
|
||||||
|
::EndPaint(hWnd, &ps);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT rcClient{};
|
||||||
|
::GetClientRect(hWnd, &rcClient);
|
||||||
|
|
||||||
|
if (pTabBufferData->ensureBuffer(hdc, rcClient))
|
||||||
|
{
|
||||||
|
const int savedState = ::SaveDC(hMemDC);
|
||||||
|
::IntersectClipRect(
|
||||||
|
hMemDC,
|
||||||
|
ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom
|
||||||
|
);
|
||||||
|
|
||||||
|
NppDarkMode::paintTab(hWnd, hMemDC, rcClient);
|
||||||
|
|
||||||
|
::RestoreDC(hMemDC, savedState);
|
||||||
|
|
||||||
|
::BitBlt(
|
||||||
|
hdc,
|
||||||
|
ps.rcPaint.left, ps.rcPaint.top,
|
||||||
|
ps.rcPaint.right - ps.rcPaint.left,
|
||||||
|
ps.rcPaint.bottom - ps.rcPaint.top,
|
||||||
|
hMemDC,
|
||||||
|
ps.rcPaint.left, ps.rcPaint.top,
|
||||||
|
SRCCOPY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
::EndPaint(hWnd, &ps);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_UPDATEUISTATE:
|
||||||
|
{
|
||||||
|
if ((HIWORD(wParam) & (UISF_HIDEACCEL | UISF_HIDEFOCUS)) != 0)
|
||||||
|
{
|
||||||
|
::InvalidateRect(hWnd, nullptr, FALSE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_PARENTNOTIFY:
|
||||||
|
{
|
||||||
|
if (LOWORD(wParam) == WM_CREATE)
|
||||||
|
{
|
||||||
|
auto hUpDown = reinterpret_cast<HWND>(lParam);
|
||||||
|
if (NppDarkMode::subclassTabUpDownControl(hUpDown))
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
return ::DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
void subclassTabControl(HWND hwnd)
|
void subclassTabControl(HWND hWnd)
|
||||||
{
|
{
|
||||||
SetWindowSubclass(hwnd, TabSubclass, g_tabSubclassID, 0);
|
if (::GetWindowSubclass(hWnd, TabSubclass, g_tabSubclassID, nullptr) == FALSE)
|
||||||
|
{
|
||||||
|
auto pTabBufferData = reinterpret_cast<DWORD_PTR>(new BufferData());
|
||||||
|
::SetWindowSubclass(hWnd, TabSubclass, g_tabSubclassID, pTabBufferData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setTabCtrlSubclassAndTheme(HWND hWnd, NppDarkModeParams p)
|
||||||
|
{
|
||||||
|
if (p._theme)
|
||||||
|
{
|
||||||
|
NppDarkMode::setDarkTooltips(hWnd, ToolTipsType::tabbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p._subclass)
|
||||||
|
{
|
||||||
|
NppDarkMode::subclassTabControl(hWnd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BorderMetricsData
|
struct BorderMetricsData
|
||||||
@ -2829,6 +2947,12 @@ namespace NppDarkMode
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wcscmp(className, WC_TABCONTROL) == 0)
|
||||||
|
{
|
||||||
|
NppDarkMode::setTabCtrlSubclassAndTheme(hwnd, p);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
// Plugin might use rich edit control version 2.0 and later
|
// Plugin might use rich edit control version 2.0 and later
|
||||||
if (wcscmp(className, L"RichEdit20W") == 0 || wcscmp(className, L"RICHEDIT50W") == 0)
|
if (wcscmp(className, L"RichEdit20W") == 0 || wcscmp(className, L"RICHEDIT50W") == 0)
|
||||||
{
|
{
|
||||||
|
@ -1013,7 +1013,6 @@ intptr_t CALLBACK UserDefineDialog::run_dlgProc(UINT message, WPARAM wParam, LPA
|
|||||||
_pUserLang = _pCurrentUserLang;
|
_pUserLang = _pCurrentUserLang;
|
||||||
|
|
||||||
_ctrlTab.init(_hInst, _hSelf);
|
_ctrlTab.init(_hInst, _hSelf);
|
||||||
NppDarkMode::subclassTabControl(_ctrlTab.getHSelf());
|
|
||||||
|
|
||||||
_folderStyleDlg.init(_hInst, _hSelf);
|
_folderStyleDlg.init(_hInst, _hSelf);
|
||||||
_folderStyleDlg.create(IDD_FOLDER_STYLE_DLG);
|
_folderStyleDlg.create(IDD_FOLDER_STYLE_DLG);
|
||||||
|
@ -24,7 +24,6 @@ using namespace std;
|
|||||||
void ShortcutMapper::initTabs()
|
void ShortcutMapper::initTabs()
|
||||||
{
|
{
|
||||||
_hTabCtrl = ::GetDlgItem(_hSelf, IDC_BABYGRID_TABBAR);
|
_hTabCtrl = ::GetDlgItem(_hSelf, IDC_BABYGRID_TABBAR);
|
||||||
NppDarkMode::subclassTabControl(_hTabCtrl);
|
|
||||||
TCITEM tie{};
|
TCITEM tie{};
|
||||||
tie.mask = TCIF_TEXT;
|
tie.mask = TCIF_TEXT;
|
||||||
|
|
||||||
|
@ -130,7 +130,6 @@ void PluginsAdminDlg::create(int dialogID, bool isRTL, bool msgDestParent)
|
|||||||
RECT rect{};
|
RECT rect{};
|
||||||
getClientRect(rect);
|
getClientRect(rect);
|
||||||
_tab.init(_hInst, _hSelf, false, true);
|
_tab.init(_hInst, _hSelf, false, true);
|
||||||
NppDarkMode::subclassTabControl(_tab.getHSelf());
|
|
||||||
|
|
||||||
const wchar_t *available = L"Available";
|
const wchar_t *available = L"Available";
|
||||||
const wchar_t *updates = L"Updates";
|
const wchar_t *updates = L"Updates";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user