xref: /reactos/base/shell/explorer/traydeskbtn.cpp (revision 0c2cdcae)
1 /*
2  * PROJECT:     ReactOS Explorer
3  * LICENSE:     LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4  * PURPOSE:     Show Desktop tray button implementation
5  * COPYRIGHT:   Copyright 2006-2007 Thomas Weidenmueller <w3seek@reactos.org>
6  *              Copyright 2018-2022 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
7  *              Copyright 2023 Ethan Rodensky <splitwirez@gmail.com>
8  */
9 
10 #include "precomp.h"
11 #include <commoncontrols.h>
12 #include <uxtheme.h>
13 
14 #define IDI_SHELL32_DESKTOP 35
15 #define IDI_IMAGERES_DESKTOP 110
16 
17 #define SHOW_DESKTOP_TIMER_ID 999
18 #define SHOW_DESKTOP_TIMER_INTERVAL 200
19 
20 CTrayShowDesktopButton::CTrayShowDesktopButton() :
21     m_nClickedTime(0),
22     m_inset({2, 2}),
23     m_icon(NULL),
24     m_highContrastMode(FALSE),
25     m_drawWithDedicatedBackground(FALSE),
26     m_bHovering(FALSE),
27     m_hWndTaskbar(NULL),
28     m_bPressed(FALSE),
29     m_bHorizontal(FALSE)
30 {
31 }
32 
33 INT CTrayShowDesktopButton::WidthOrHeight() const
34 {
35     if (IsThemeActive() && !m_highContrastMode)
36     {
37         if (m_drawWithDedicatedBackground)
38         {
39             if (GetSystemMetrics(SM_TABLETPC))
40             {
41                 //TODO: DPI scaling - return logical-to-physical conversion of 24, not fixed value
42                 return 24;
43             }
44             else
45                 return 15;
46         }
47         else
48         {
49             INT CurMargin = m_bHorizontal
50                 ? (m_ContentMargins.cxLeftWidth + m_ContentMargins.cxRightWidth)
51                 : (m_ContentMargins.cyTopHeight + m_ContentMargins.cyBottomHeight);
52             return max(16 + CurMargin, 18) + 6;
53         }
54     }
55     else
56     {
57         return max(2 * GetSystemMetrics(SM_CXBORDER) + GetSystemMetrics(SM_CXSMICON),
58                    2 * GetSystemMetrics(SM_CYBORDER) + GetSystemMetrics(SM_CYSMICON));
59     }
60 }
61 
62 HRESULT CTrayShowDesktopButton::DoCreate(HWND hwndParent)
63 {
64     const DWORD style = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | BS_DEFPUSHBUTTON;
65     Create(hwndParent, NULL, NULL, style);
66 
67     if (!m_hWnd)
68         return E_FAIL;
69 
70     // Get desktop icon
71     bool bIconRetrievalFailed = ExtractIconExW(L"imageres.dll", -IDI_IMAGERES_DESKTOP, NULL, &m_icon, 1) == UINT_MAX;
72     if (bIconRetrievalFailed || !m_icon)
73         ExtractIconExW(L"shell32.dll", -IDI_SHELL32_DESKTOP, NULL, &m_icon, 1);
74 
75     // Get appropriate size at which to display desktop icon
76     m_szIcon.cx = GetSystemMetrics(SM_CXSMICON);
77     m_szIcon.cy = GetSystemMetrics(SM_CYSMICON);
78 
79     // Prep visual style
80     EnsureWindowTheme(TRUE);
81 
82     // Get HWND of Taskbar
83     m_hWndTaskbar = ::GetParent(hwndParent);
84     if (!::IsWindow(m_hWndTaskbar))
85         return E_FAIL;
86 
87     return S_OK;
88 }
89 
90 LRESULT CTrayShowDesktopButton::OnClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
91 {
92     // The actual action can be delayed as an expected behaviour.
93     // But a too late action is an unexpected behaviour.
94     LONG nTime0 = m_nClickedTime;
95     LONG nTime1 = ::GetMessageTime();
96     if (nTime1 - nTime0 >= 600) // Ignore after 0.6 sec
97         return 0;
98 
99     // Show/Hide Desktop
100     ::SendMessage(m_hWndTaskbar, WM_COMMAND, TRAYCMD_TOGGLE_DESKTOP, 0);
101 
102     return 0;
103 }
104 
105 // This function is called from OnLButtonDown and parent.
106 VOID CTrayShowDesktopButton::Click()
107 {
108     // The actual action can be delayed as an expected behaviour.
109     m_nClickedTime = ::GetMessageTime();
110     PostMessage(TSDB_CLICK, 0, 0);
111 }
112 
113 LRESULT CTrayShowDesktopButton::OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
114 {
115     m_bPressed = FALSE;
116     ReleaseCapture();
117     Invalidate(TRUE);
118 
119     POINT pt;
120     ::GetCursorPos(&pt);
121     if (PtInButton(&pt))
122         Click(); // Left-click
123     return 0;
124 }
125 
126 LRESULT CTrayShowDesktopButton::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
127 {
128     m_bPressed = TRUE;
129     SetCapture();
130     Invalidate(TRUE);
131     return 0;
132 }
133 
134 LRESULT CTrayShowDesktopButton::OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
135 {
136     LRESULT ret = OnThemeChanged(uMsg, wParam, lParam, bHandled);
137     EnsureWindowTheme(TRUE);
138     return ret;
139 }
140 
141 LRESULT CTrayShowDesktopButton::OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
142 {
143     HIGHCONTRAST hcInfo;
144     hcInfo.cbSize = sizeof(hcInfo);
145     if (SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(hcInfo), &hcInfo, FALSE))
146         m_highContrastMode = (hcInfo.dwFlags & HCF_HIGHCONTRASTON);
147 
148     if (m_hTheme)
149     {
150         ::CloseThemeData(m_hTheme);
151         m_hTheme = NULL;
152     }
153     if (m_hFallbackTheme)
154     {
155         ::CloseThemeData(m_hFallbackTheme);
156         m_hFallbackTheme = NULL;
157     }
158 
159     EnsureWindowTheme(FALSE);
160 
161     Invalidate(TRUE);
162     return 0;
163 }
164 
165 LRESULT CTrayShowDesktopButton::OnWindowPosChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
166 {
167     EnsureWindowTheme(TRUE);
168     return 0;
169 }
170 
171 VOID CTrayShowDesktopButton::EnsureWindowTheme(BOOL setTheme)
172 {
173     if (setTheme)
174         SetWindowTheme(m_hWnd, m_bHorizontal ? L"ShowDesktop" : L"VerticalShowDesktop", NULL);
175 
176     if (::IsWindow(m_hWndTaskbar))
177     {
178         m_hFallbackTheme = ::OpenThemeData(m_hWndTaskbar, L"Toolbar");
179         GetThemeMargins(m_hFallbackTheme, NULL, TP_BUTTON, 0, TMT_CONTENTMARGINS, NULL, &m_ContentMargins);
180     }
181     else
182     {
183         m_hFallbackTheme = NULL;
184     }
185 
186     MARGINS contentMargins;
187     if (GetThemeMargins(GetWindowTheme(GetParent().m_hWnd), NULL, TNP_BACKGROUND, 0, TMT_CONTENTMARGINS, NULL, &contentMargins) == S_OK)
188     {
189         m_inset.cx = max(0, contentMargins.cxRightWidth - 5);
190         m_inset.cy = max(0, contentMargins.cyBottomHeight - 5);
191     }
192     else
193     {
194         m_inset.cx = 2;
195         m_inset.cy = 2;
196     }
197 
198     m_drawWithDedicatedBackground = FALSE;
199     if (IsThemeActive())
200     {
201         m_hTheme = OpenThemeData(m_hWnd, L"Button");
202         if (m_hTheme != NULL)
203             m_drawWithDedicatedBackground = !IsThemePartDefined(m_hTheme, BP_PUSHBUTTON, 0);
204     }
205 }
206 
207 LRESULT CTrayShowDesktopButton::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
208 {
209     RECT rc;
210     GetClientRect(&rc);
211 
212     PAINTSTRUCT ps;
213     HDC hdc = BeginPaint(&ps);
214     OnDraw(hdc, &rc);
215     EndPaint(&ps);
216 
217     return 0;
218 }
219 
220 LRESULT CTrayShowDesktopButton::OnPrintClient(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
221 {
222     if ((lParam & PRF_CHECKVISIBLE) && !IsWindowVisible())
223         return 0;
224 
225     RECT rc;
226     GetClientRect(&rc);
227 
228     HDC hdc = (HDC)wParam;
229     OnDraw(hdc, &rc);
230 
231     return 0;
232 }
233 
234 BOOL CTrayShowDesktopButton::PtInButton(LPPOINT ppt) const
235 {
236     if (!ppt || !IsWindow())
237         return FALSE;
238 
239     RECT rc;
240     GetWindowRect(&rc);
241     INT cxEdge = ::GetSystemMetrics(SM_CXEDGE), cyEdge = ::GetSystemMetrics(SM_CYEDGE);
242     ::InflateRect(&rc, max(cxEdge, 1), max(cyEdge, 1));
243 
244     return m_bHorizontal
245         ? (ppt->x > rc.left)
246         : (ppt->y > rc.top);
247 }
248 
249 VOID CTrayShowDesktopButton::StartHovering()
250 {
251     if (m_bHovering)
252         return;
253 
254     m_bHovering = TRUE;
255     Invalidate(TRUE);
256 
257     SetTimer(SHOW_DESKTOP_TIMER_ID, SHOW_DESKTOP_TIMER_INTERVAL, NULL);
258 
259     ::PostMessage(m_hWndTaskbar, WM_NCPAINT, 0, 0);
260 }
261 
262 LRESULT CTrayShowDesktopButton::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
263 {
264     StartHovering();
265     return 0;
266 }
267 
268 LRESULT CTrayShowDesktopButton::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
269 {
270     if (wParam != SHOW_DESKTOP_TIMER_ID || !m_bHovering)
271         return 0;
272 
273     POINT pt;
274     ::GetCursorPos(&pt);
275     if (!PtInButton(&pt)) // The end of hovering?
276     {
277         m_bHovering = FALSE;
278         KillTimer(SHOW_DESKTOP_TIMER_ID);
279         Invalidate(TRUE);
280 
281         ::PostMessage(m_hWndTaskbar, WM_NCPAINT, 0, 0);
282     }
283 
284     return 0;
285 }
286 
287 LRESULT CTrayShowDesktopButton::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
288 {
289     if (m_hTheme)
290     {
291         ::CloseThemeData(m_hTheme);
292         m_hTheme = NULL;
293     }
294     if (m_hFallbackTheme)
295     {
296         ::CloseThemeData(m_hFallbackTheme);
297         m_hFallbackTheme = NULL;
298     }
299 
300     return 0;
301 }
302 
303 VOID CTrayShowDesktopButton::OnDraw(HDC hdc, LPRECT prc)
304 {
305     RECT rc = { prc->left, prc->top, prc->right, prc->bottom };
306     LPRECT lpRc = &rc;
307     HBRUSH hbrBackground = NULL;
308 
309     if (m_hTheme)
310     {
311         HTHEME theme;
312         int part = 0;
313         int state = 0;
314         if (m_drawWithDedicatedBackground)
315         {
316             theme = m_hTheme;
317 
318             if (m_bPressed)
319                 state = PBS_PRESSED;
320             else if (m_bHovering)
321                 state = PBS_HOT;
322             else
323                 state = PBS_NORMAL;
324         }
325         else
326         {
327             part = TP_BUTTON;
328             theme = m_hFallbackTheme;
329 
330             if (m_bPressed)
331                 state = TS_PRESSED;
332             else if (m_bHovering)
333                 state = TS_HOT;
334             else
335                 state = TS_NORMAL;
336 
337             if (m_bHorizontal)
338                 rc.right -= m_inset.cx;
339             else
340                 rc.bottom -= m_inset.cy;
341         }
342 
343         if (::IsThemeBackgroundPartiallyTransparent(theme, part, state))
344             ::DrawThemeParentBackground(m_hWnd, hdc, NULL);
345 
346         ::DrawThemeBackground(theme, hdc, part, state, lpRc, lpRc);
347     }
348     else
349     {
350         hbrBackground = ::GetSysColorBrush(COLOR_3DFACE);
351         ::FillRect(hdc, lpRc, hbrBackground);
352 
353         if (m_bPressed || m_bHovering)
354         {
355             UINT edge = m_bPressed ? BDR_SUNKENOUTER : BDR_RAISEDINNER;
356             DrawEdge(hdc, lpRc, edge, BF_RECT);
357         }
358     }
359 
360     if (m_highContrastMode || !m_drawWithDedicatedBackground)
361     {
362         /* Prepare to draw icon */
363 
364         // Determine X-position of icon's top-left corner
365         int iconX = rc.left;
366         iconX += (rc.right - iconX) / 2;
367         iconX -= m_szIcon.cx / 2;
368 
369         // Determine Y-position of icon's top-left corner
370         int iconY = rc.top;
371         iconY += (rc.bottom - iconY) / 2;
372         iconY -= m_szIcon.cy / 2;
373 
374         // Ok now actually draw the icon itself
375         if (m_icon)
376         {
377             DrawIconEx(hdc, iconX, iconY,
378                        m_icon, 0, 0,
379                        0, hbrBackground, DI_NORMAL);
380         }
381     }
382 }
383