Skip to content

Commit 1bbd99b

Browse files
Implement themed rendering for ListView and Header in ThemeRenderer
- Migrated custom drawing logic from FileList to ThemeRenderer. - Introduced IListViewDataProvider interface to decouple ThemeRenderer from FileList. - Implemented themed rendering for ListView Header (WC_HEADER) in dark mode. - Handled NM_CUSTOMDRAW for ListView items in parent window subclass to support themes. - Improved TreeView support by applying DarkMode_Explorer theme. - Fixed header rendering using BeginPaint/EndPaint in WM_PAINT handler. - Cleaned up FileList by removing redundant custom draw logic. Co-authored-by: funap <31555185+funap@users.noreply.github.com>
1 parent 84ebb87 commit 1bbd99b

4 files changed

Lines changed: 196 additions & 34 deletions

File tree

src/Explorer/FileList.cpp

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,21 @@ FileList::~FileList()
8989
{
9090
}
9191

92+
bool FileList::IsFileOpen(size_t index) const
93+
{
94+
if (index >= _uMaxFolders) {
95+
std::wstring strFilePath = _pSettings->GetCurrentDir() + _vFileList[index].Name();
96+
return ::IsFileOpen(strFilePath) == TRUE;
97+
}
98+
return false;
99+
}
100+
92101
void FileList::init(HINSTANCE hInst, HWND hParent, HWND hParentList)
93102
{
94103
/* this is the list element */
95104
Window::init(hInst, hParent);
96105
_hSelf = hParentList;
106+
::SetWindowLongPtr(_hSelf, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(static_cast<IListViewDataProvider*>(this)));
97107

98108
/* create semaphore for thead */
99109
_hSemaphore = ::CreateSemaphore(nullptr, 1, 1, nullptr);
@@ -534,38 +544,8 @@ BOOL FileList::notify(WPARAM wParam, LPARAM lParam)
534544
UpdateSelItems();
535545
break;
536546
case NM_CUSTOMDRAW: {
537-
LPNMLVCUSTOMDRAW lpCD = (LPNMLVCUSTOMDRAW)lParam;
538-
switch (lpCD->nmcd.dwDrawStage) {
539-
case CDDS_PREPAINT:
540-
::SetWindowLongPtr(_hParent, DWLP_MSGRESULT, CDRF_NOTIFYITEMDRAW);
541-
return TRUE;
542-
case CDDS_ITEMPREPAINT: {
543-
auto index = static_cast<INT>(lpCD->nmcd.dwItemSpec);
544-
if (index >= static_cast<INT>(_uMaxFolders)) {
545-
std::wstring strFilePath = _pSettings->GetCurrentDir() + _vFileList[index].Name();
546-
if (IsFileOpen(strFilePath) == TRUE) {
547-
::SelectObject(lpCD->nmcd.hdc, _pSettings->GetUnderlineFont());
548-
::SetWindowLongPtr(_hParent, DWLP_MSGRESULT, CDRF_NEWFONT);
549-
}
550-
}
551-
if (_vFileList[index].IsParent()) {
552-
::SetWindowLongPtr(_hParent, DWLP_MSGRESULT, CDRF_NOTIFYPOSTPAINT);
553-
}
554-
return TRUE;
555-
}
556-
case CDDS_ITEMPOSTPAINT: {
557-
auto index = static_cast<INT>(lpCD->nmcd.dwItemSpec);
558-
RECT rc {};
559-
ListView_GetSubItemRect(_hSelf, index, lpCD->iSubItem, LVIR_ICON, &rc);
560-
UINT state = ListView_GetItemState(_hSelf, index, LVIS_SELECTED);
561-
bool isSelected = ((state & LVIS_SELECTED) ? (::GetFocus() == _hSelf) : ((state & LVIS_DROPHILITED) == LVIS_DROPHILITED));
562-
ImageList_Draw(_hImlParent, ICON_PARENT, lpCD->nmcd.hdc, rc.left, rc.top, ILD_NORMAL | (isSelected ? ILD_SELECTED : 0));
563-
return TRUE;
564-
}
565-
default:
566-
return FALSE;
567-
}
568-
break;
547+
// Handled by ThemeRenderer
548+
return FALSE;
569549
}
570550
default:
571551
break;

src/Explorer/FileList.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ static const WORD DotPattern[] =
4646
};
4747

4848
#include "FileSystemService.h"
49+
#include "ThemeRenderer.h"
4950

5051

51-
class FileList : public Window, public CIDropTarget
52+
class FileList : public Window, public CIDropTarget, public IListViewDataProvider
5253
{
5354
public:
5455
FileList() = delete;
@@ -58,6 +59,17 @@ class FileList : public Window, public CIDropTarget
5859
void init(HINSTANCE hInst, HWND hParent, HWND hParentList);
5960
void initProp(Settings* prop);
6061

62+
// IListViewDataProvider
63+
virtual size_t GetMaxFolders() const override { return _uMaxFolders; }
64+
virtual std::wstring GetItemName(size_t index) const override { return _vFileList[index].Name(); }
65+
virtual bool IsItemParent(size_t index) const override { return _vFileList[index].IsParent(); }
66+
virtual bool IsFileOpen(size_t index) const override;
67+
virtual HFONT GetUnderlineFont() const override { return _pSettings->GetUnderlineFont(); }
68+
virtual HIMAGELIST GetParentImageList() const override { return _hImlParent; }
69+
70+
bool IsItemHidden(size_t index) const { return _vFileList[index].IsHidden(); }
71+
Settings* GetSettings() const { return _pSettings; }
72+
6173
void viewPath(const std::wstring& currentDir, BOOL redraw = FALSE);
6274

6375
BOOL notify(WPARAM wParam, LPARAM lParam);

src/Explorer/ThemeRenderer.cpp

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626
#include <uxtheme.h>
2727
#include <Vsstyle.h>
2828
#include <Vssym32.h>
29+
#include "FileList.h"
2930

3031
namespace {
3132
constexpr UINT_PTR WINDOW_SUBCLASS_ID = 0;
3233
constexpr UINT_PTR REBAR_SUBCLASS_ID = 1;
3334
constexpr UINT_PTR BUTTON_SUBCLASS_ID = 2;
3435
constexpr UINT_PTR EDIT_SUBCLASS_ID = 3;
36+
constexpr UINT_PTR LISTVIEW_SUBCLASS_ID = 4;
37+
constexpr UINT_PTR HEADER_SUBCLASS_ID = 5;
3538

3639
auto GetClassName(HWND hwnd) -> std::wstring
3740
{
@@ -135,6 +138,16 @@ void ThemeRenderer::Register(HWND hwnd)
135138
else if (className == WC_EDIT) {
136139
::SetWindowSubclass(childWindow, DefaultSubclassProc, EDIT_SUBCLASS_ID, reinterpret_cast<DWORD_PTR>(self));
137140
}
141+
else if (className == WC_LISTVIEW) {
142+
::SetWindowSubclass(childWindow, DefaultSubclassProc, LISTVIEW_SUBCLASS_ID, reinterpret_cast<DWORD_PTR>(self));
143+
HWND hHeader = ListView_GetHeader(childWindow);
144+
if (hHeader) {
145+
::SetWindowSubclass(hHeader, DefaultSubclassProc, HEADER_SUBCLASS_ID, reinterpret_cast<DWORD_PTR>(self));
146+
}
147+
}
148+
else if (className == WC_TREEVIEW) {
149+
::SetWindowTheme(childWindow, self->m_isDarkMode ? L"DarkMode_Explorer" : L"Explorer", nullptr);
150+
}
138151
return TRUE;
139152
}, reinterpret_cast<LPARAM>(this));
140153

@@ -164,6 +177,16 @@ void ThemeRenderer::ApplyTheme(HWND hwnd)
164177
ListView_SetTextColor(childWindow, self->m_colors.secondary);
165178
ListView_SetTextBkColor(childWindow, CLR_NONE);
166179
::SetWindowTheme(childWindow, self->m_isDarkMode ? L"DarkMode_Explorer" : L"Explorer", nullptr);
180+
181+
HWND hHeader = ListView_GetHeader(childWindow);
182+
if (hHeader) {
183+
::SetWindowSubclass(hHeader, DefaultSubclassProc, HEADER_SUBCLASS_ID, reinterpret_cast<DWORD_PTR>(self));
184+
}
185+
}
186+
else if (className == WC_TREEVIEW) {
187+
TreeView_SetBkColor(childWindow, self->m_colors.secondary_bg);
188+
TreeView_SetTextColor(childWindow, self->m_colors.secondary);
189+
::SetWindowTheme(childWindow, self->m_isDarkMode ? L"DarkMode_Explorer" : L"Explorer", nullptr);
167190
}
168191
return TRUE;
169192
}, reinterpret_cast<LPARAM>(this));
@@ -181,6 +204,10 @@ LRESULT CALLBACK ThemeRenderer::DefaultSubclassProc(HWND hWnd, UINT uMsg, WPARAM
181204
return self->ButtonProc(hWnd, uMsg, wParam, lParam);
182205
case EDIT_SUBCLASS_ID:
183206
return self->EditProc(hWnd, uMsg, wParam, lParam);
207+
case LISTVIEW_SUBCLASS_ID:
208+
return self->ListViewProc(hWnd, uMsg, wParam, lParam);
209+
case HEADER_SUBCLASS_ID:
210+
return self->HeaderProc(hWnd, uMsg, wParam, lParam);
184211
default:
185212
break;
186213
}
@@ -203,6 +230,54 @@ LRESULT ThemeRenderer::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP
203230
::SetBkColor(hdc, m_colors.secondary_bg);
204231
return (LRESULT)(HBRUSH)m_brushes.secondary_bg;
205232
}
233+
case WM_NOTIFY: {
234+
LPNMHDR nmhdr = (LPNMHDR)lParam;
235+
if (nmhdr->code == NM_CUSTOMDRAW) {
236+
std::wstring className = GetClassName(nmhdr->hwndFrom);
237+
if (className == WC_LISTVIEW) {
238+
LPNMLVCUSTOMDRAW lpCD = (LPNMLVCUSTOMDRAW)lParam;
239+
switch (lpCD->nmcd.dwDrawStage) {
240+
case CDDS_PREPAINT:
241+
return CDRF_NOTIFYITEMDRAW;
242+
case CDDS_ITEMPREPAINT: {
243+
auto index = static_cast<INT>(lpCD->nmcd.dwItemSpec);
244+
IListViewDataProvider* pDataProvider = reinterpret_cast<IListViewDataProvider*>(::GetWindowLongPtr(nmhdr->hwndFrom, GWLP_USERDATA));
245+
246+
if (m_isDarkMode) {
247+
lpCD->clrText = m_colors.secondary;
248+
lpCD->clrTextBk = m_colors.secondary_bg;
249+
250+
if (lpCD->nmcd.uItemState & CDIS_SELECTED) {
251+
lpCD->clrText = m_colors.primary;
252+
lpCD->clrTextBk = m_colors.primary_bg;
253+
}
254+
}
255+
256+
if (pDataProvider) {
257+
if (pDataProvider->IsFileOpen(index)) {
258+
::SelectObject(lpCD->nmcd.hdc, pDataProvider->GetUnderlineFont());
259+
}
260+
}
261+
262+
return CDRF_NEWFONT | CDRF_NOTIFYPOSTPAINT;
263+
}
264+
case CDDS_ITEMPOSTPAINT: {
265+
auto index = static_cast<INT>(lpCD->nmcd.dwItemSpec);
266+
IListViewDataProvider* pDataProvider = reinterpret_cast<IListViewDataProvider*>(::GetWindowLongPtr(nmhdr->hwndFrom, GWLP_USERDATA));
267+
if (pDataProvider && pDataProvider->IsItemParent(index)) {
268+
RECT rc {};
269+
ListView_GetSubItemRect(nmhdr->hwndFrom, index, lpCD->iSubItem, LVIR_ICON, &rc);
270+
UINT state = ListView_GetItemState(nmhdr->hwndFrom, index, LVIS_SELECTED | LVIS_DROPHILITED);
271+
bool isSelected = ((state & LVIS_SELECTED) ? (::GetFocus() == nmhdr->hwndFrom) : ((state & LVIS_DROPHILITED) == LVIS_DROPHILITED));
272+
ImageList_Draw(pDataProvider->GetParentImageList(), ICON_PARENT, lpCD->nmcd.hdc, rc.left, rc.top, ILD_NORMAL | (isSelected ? ILD_SELECTED : 0));
273+
}
274+
return CDRF_DODEFAULT;
275+
}
276+
}
277+
}
278+
}
279+
break;
280+
}
206281
case WM_NCDESTROY:
207282
::RemoveWindowSubclass(hWnd, DefaultSubclassProc, REBAR_SUBCLASS_ID);
208283
m_windows.erase(hWnd);
@@ -266,7 +341,88 @@ LRESULT ThemeRenderer::EditProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
266341
}
267342
case WM_NCDESTROY:
268343
::RemoveWindowSubclass(hWnd, DefaultSubclassProc, EDIT_SUBCLASS_ID);
269-
m_windows.erase(hWnd);
344+
break;
345+
}
346+
return ::DefSubclassProc(hWnd, uMsg, wParam, lParam);
347+
}
348+
349+
LRESULT ThemeRenderer::ListViewProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
350+
{
351+
switch (uMsg) {
352+
case WM_NCDESTROY:
353+
::RemoveWindowSubclass(hWnd, DefaultSubclassProc, LISTVIEW_SUBCLASS_ID);
354+
break;
355+
}
356+
return ::DefSubclassProc(hWnd, uMsg, wParam, lParam);
357+
}
358+
359+
LRESULT ThemeRenderer::HeaderProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
360+
{
361+
switch (uMsg) {
362+
case WM_PAINT: {
363+
if (!m_isDarkMode) break;
364+
PAINTSTRUCT ps;
365+
HDC hdc = BeginPaint(hWnd, &ps);
366+
367+
RECT rcHeader;
368+
GetClientRect(hWnd, &rcHeader);
369+
370+
// Fill background
371+
FillRect(hdc, &rcHeader, m_brushes.secondary_bg);
372+
373+
int count = Header_GetItemCount(hWnd);
374+
for (int i = 0; i < count; i++) {
375+
RECT rcItem;
376+
Header_GetItemRect(hWnd, i, &rcItem);
377+
378+
// Draw text
379+
WCHAR text[MAX_PATH];
380+
HDITEM hdi = { .mask = HDI_TEXT | HDI_FORMAT, .pszText = text, .cchTextMax = MAX_PATH };
381+
Header_GetItem(hWnd, i, &hdi);
382+
383+
SetTextColor(hdc, m_colors.secondary);
384+
SetBkMode(hdc, TRANSPARENT);
385+
386+
RECT rcText = rcItem;
387+
rcText.left += 6;
388+
DrawText(hdc, text, -1, &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
389+
390+
// Draw sort arrow
391+
if (hdi.fmt & (HDF_SORTUP | HDF_SORTDOWN)) {
392+
HPEN hPen = CreatePen(PS_SOLID, 1, m_colors.secondary);
393+
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
394+
395+
int x = rcItem.right - 15;
396+
int y = rcItem.top + (rcItem.bottom - rcItem.top) / 2;
397+
398+
if (hdi.fmt & HDF_SORTUP) {
399+
MoveToEx(hdc, x, y + 2, nullptr);
400+
LineTo(hdc, x + 4, y - 2);
401+
LineTo(hdc, x + 8, y + 2);
402+
LineTo(hdc, x, y + 2);
403+
} else {
404+
MoveToEx(hdc, x, y - 2, nullptr);
405+
LineTo(hdc, x + 4, y + 2);
406+
LineTo(hdc, x + 8, y - 2);
407+
LineTo(hdc, x, y - 2);
408+
}
409+
410+
SelectObject(hdc, hOldPen);
411+
DeleteObject(hPen);
412+
}
413+
414+
// Draw separator
415+
if (i < count - 1) {
416+
RECT rcSep = { rcItem.right - 1, rcItem.top + 4, rcItem.right, rcItem.bottom - 4 };
417+
FillRect(hdc, &rcSep, m_brushes.border);
418+
}
419+
}
420+
421+
EndPaint(hWnd, &ps);
422+
return 0;
423+
}
424+
case WM_NCDESTROY:
425+
::RemoveWindowSubclass(hWnd, DefaultSubclassProc, HEADER_SUBCLASS_ID);
270426
break;
271427
}
272428
return ::DefSubclassProc(hWnd, uMsg, wParam, lParam);

src/Explorer/ThemeRenderer.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <memory>
2929
#include <set>
3030
#include <stdexcept>
31+
#include <string>
3132

3233
#include "Graphics.h"
3334

@@ -56,6 +57,17 @@ struct Brushes
5657
Brush primary_border; // for primary element borders
5758
};
5859

60+
class IListViewDataProvider {
61+
public:
62+
virtual ~IListViewDataProvider() = default;
63+
virtual size_t GetMaxFolders() const = 0;
64+
virtual std::wstring GetItemName(size_t index) const = 0;
65+
virtual bool IsItemParent(size_t index) const = 0;
66+
virtual bool IsFileOpen(size_t index) const = 0;
67+
virtual HFONT GetUnderlineFont() const = 0;
68+
virtual HIMAGELIST GetParentImageList() const = 0;
69+
};
70+
5971
class ThemeRenderer
6072
{
6173
private:
@@ -101,6 +113,8 @@ class ThemeRenderer
101113
LRESULT RebarProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
102114
LRESULT ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
103115
LRESULT EditProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
116+
LRESULT ListViewProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
117+
LRESULT HeaderProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
104118

105119
BOOL m_isDarkMode;
106120
ThemeColors m_colors;

0 commit comments

Comments
 (0)