xref: /reactos/base/shell/explorer/traydeskbtn.cpp (revision 541cb0d9)
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     // Create tooltip
80     m_tooltip.Create(m_hWnd, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP);
81 
82     TOOLINFOW ti = { 0 };
83     ti.cbSize = TTTOOLINFOW_V1_SIZE;
84     ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
85     ti.hwnd = m_hWnd;
86     ti.uId = reinterpret_cast<UINT_PTR>(m_hWnd);
87     ti.hinst = hExplorerInstance;
88     ti.lpszText = MAKEINTRESOURCEW(IDS_TRAYDESKBTN_TOOLTIP);
89 
90     m_tooltip.AddTool(&ti);
91 
92     // Prep visual style
93     EnsureWindowTheme(TRUE);
94 
95     // Get HWND of Taskbar
96     m_hWndTaskbar = ::GetParent(hwndParent);
97     if (!::IsWindow(m_hWndTaskbar))
98         return E_FAIL;
99 
100     return S_OK;
101 }
102 
103 LRESULT CTrayShowDesktopButton::OnClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
104 {
105     // The actual action can be delayed as an expected behaviour.
106     // But a too late action is an unexpected behaviour.
107     LONG nTime0 = m_nClickedTime;
108     LONG nTime1 = ::GetMessageTime();
109     if (nTime1 - nTime0 >= 600) // Ignore after 0.6 sec
110         return 0;
111 
112     // Show/Hide Desktop
113     ::SendMessage(m_hWndTaskbar, WM_COMMAND, TRAYCMD_TOGGLE_DESKTOP, 0);
114 
115     return 0;
116 }
117 
118 // This function is called from OnLButtonDown and parent.
119 VOID CTrayShowDesktopButton::Click()
120 {
121     // The actual action can be delayed as an expected behaviour.
122     m_nClickedTime = ::GetMessageTime();
123     PostMessage(TSDB_CLICK, 0, 0);
124 }
125 
126 LRESULT CTrayShowDesktopButton::OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
127 {
128     m_bPressed = FALSE;
129     ReleaseCapture();
130     Invalidate(TRUE);
131 
132     POINT pt;
133     ::GetCursorPos(&pt);
134     if (PtInButton(&pt))
135         Click(); // Left-click
136     return 0;
137 }
138 
139 LRESULT CTrayShowDesktopButton::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
140 {
141     m_bPressed = TRUE;
142     SetCapture();
143     Invalidate(TRUE);
144     return 0;
145 }
146 
147 LRESULT CTrayShowDesktopButton::OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
148 {
149     LRESULT ret = OnThemeChanged(uMsg, wParam, lParam, bHandled);
150     EnsureWindowTheme(TRUE);
151     return ret;
152 }
153 
154 LRESULT CTrayShowDesktopButton::OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
155 {
156     HIGHCONTRAST hcInfo;
157     hcInfo.cbSize = sizeof(hcInfo);
158     if (SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(hcInfo), &hcInfo, FALSE))
159         m_highContrastMode = (hcInfo.dwFlags & HCF_HIGHCONTRASTON);
160 
161     if (m_hTheme)
162     {
163         ::CloseThemeData(m_hTheme);
164         m_hTheme = NULL;
165     }
166     if (m_hFallbackTheme)
167     {
168         ::CloseThemeData(m_hFallbackTheme);
169         m_hFallbackTheme = NULL;
170     }
171 
172     EnsureWindowTheme(FALSE);
173 
174     Invalidate(TRUE);
175     return 0;
176 }
177 
178 LRESULT CTrayShowDesktopButton::OnWindowPosChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
179 {
180     EnsureWindowTheme(TRUE);
181     return 0;
182 }
183 
184 VOID CTrayShowDesktopButton::EnsureWindowTheme(BOOL setTheme)
185 {
186     if (setTheme)
187         SetWindowTheme(m_hWnd, m_bHorizontal ? L"ShowDesktop" : L"VerticalShowDesktop", NULL);
188 
189     if (::IsWindow(m_hWndTaskbar))
190     {
191         m_hFallbackTheme = ::OpenThemeData(m_hWndTaskbar, L"Toolbar");
192         GetThemeMargins(m_hFallbackTheme, NULL, TP_BUTTON, 0, TMT_CONTENTMARGINS, NULL, &m_ContentMargins);
193     }
194     else
195     {
196         m_hFallbackTheme = NULL;
197     }
198 
199     MARGINS contentMargins;
200     if (GetThemeMargins(GetWindowTheme(GetParent().m_hWnd), NULL, TNP_BACKGROUND, 0, TMT_CONTENTMARGINS, NULL, &contentMargins) == S_OK)
201     {
202         m_inset.cx = max(0, contentMargins.cxRightWidth - 5);
203         m_inset.cy = max(0, contentMargins.cyBottomHeight - 5);
204     }
205     else
206     {
207         m_inset.cx = 2;
208         m_inset.cy = 2;
209     }
210 
211     m_drawWithDedicatedBackground = FALSE;
212     if (IsThemeActive())
213     {
214         m_hTheme = OpenThemeData(m_hWnd, L"Button");
215         if (m_hTheme != NULL)
216             m_drawWithDedicatedBackground = !IsThemePartDefined(m_hTheme, BP_PUSHBUTTON, 0);
217     }
218 }
219 
220 LRESULT CTrayShowDesktopButton::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
221 {
222     RECT rc;
223     GetClientRect(&rc);
224 
225     PAINTSTRUCT ps;
226     HDC hdc = BeginPaint(&ps);
227     OnDraw(hdc, &rc);
228     EndPaint(&ps);
229 
230     return 0;
231 }
232 
233 LRESULT CTrayShowDesktopButton::OnPrintClient(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
234 {
235     if ((lParam & PRF_CHECKVISIBLE) && !IsWindowVisible())
236         return 0;
237 
238     RECT rc;
239     GetClientRect(&rc);
240 
241     HDC hdc = (HDC)wParam;
242     OnDraw(hdc, &rc);
243 
244     return 0;
245 }
246 
247 BOOL CTrayShowDesktopButton::PtInButton(LPPOINT ppt) const
248 {
249     if (!ppt || !IsWindow())
250         return FALSE;
251 
252     RECT rc;
253     GetWindowRect(&rc);
254     INT cxEdge = ::GetSystemMetrics(SM_CXEDGE), cyEdge = ::GetSystemMetrics(SM_CYEDGE);
255     ::InflateRect(&rc, max(cxEdge, 1), max(cyEdge, 1));
256 
257     return m_bHorizontal
258         ? (ppt->x > rc.left)
259         : (ppt->y > rc.top);
260 }
261 
262 VOID CTrayShowDesktopButton::StartHovering()
263 {
264     if (m_bHovering)
265         return;
266 
267     m_bHovering = TRUE;
268     Invalidate(TRUE);
269 
270     SetTimer(SHOW_DESKTOP_TIMER_ID, SHOW_DESKTOP_TIMER_INTERVAL, NULL);
271 
272     ::PostMessage(m_hWndTaskbar, WM_NCPAINT, 0, 0);
273 }
274 
275 LRESULT CTrayShowDesktopButton::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
276 {
277     StartHovering();
278     return 0;
279 }
280 
281 LRESULT CTrayShowDesktopButton::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
282 {
283     if (wParam != SHOW_DESKTOP_TIMER_ID || !m_bHovering)
284         return 0;
285 
286     POINT pt;
287     ::GetCursorPos(&pt);
288     if (!PtInButton(&pt)) // The end of hovering?
289     {
290         m_bHovering = FALSE;
291         KillTimer(SHOW_DESKTOP_TIMER_ID);
292         Invalidate(TRUE);
293 
294         ::PostMessage(m_hWndTaskbar, WM_NCPAINT, 0, 0);
295     }
296 
297     return 0;
298 }
299 
300 LRESULT CTrayShowDesktopButton::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
301 {
302     if (m_hTheme)
303     {
304         ::CloseThemeData(m_hTheme);
305         m_hTheme = NULL;
306     }
307     if (m_hFallbackTheme)
308     {
309         ::CloseThemeData(m_hFallbackTheme);
310         m_hFallbackTheme = NULL;
311     }
312 
313     return 0;
314 }
315 
316 VOID CTrayShowDesktopButton::OnDraw(HDC hdc, LPRECT prc)
317 {
318     RECT rc = { prc->left, prc->top, prc->right, prc->bottom };
319     LPRECT lpRc = &rc;
320     HBRUSH hbrBackground = NULL;
321 
322     if (m_hTheme)
323     {
324         HTHEME theme;
325         int part = 0;
326         int state = 0;
327         if (m_drawWithDedicatedBackground)
328         {
329             theme = m_hTheme;
330 
331             if (m_bPressed)
332                 state = PBS_PRESSED;
333             else if (m_bHovering)
334                 state = PBS_HOT;
335             else
336                 state = PBS_NORMAL;
337         }
338         else
339         {
340             part = TP_BUTTON;
341             theme = m_hFallbackTheme;
342 
343             if (m_bPressed)
344                 state = TS_PRESSED;
345             else if (m_bHovering)
346                 state = TS_HOT;
347             else
348                 state = TS_NORMAL;
349 
350             if (m_bHorizontal)
351                 rc.right -= m_inset.cx;
352             else
353                 rc.bottom -= m_inset.cy;
354         }
355 
356         if (::IsThemeBackgroundPartiallyTransparent(theme, part, state))
357             ::DrawThemeParentBackground(m_hWnd, hdc, NULL);
358 
359         ::DrawThemeBackground(theme, hdc, part, state, lpRc, lpRc);
360     }
361     else
362     {
363         hbrBackground = ::GetSysColorBrush(COLOR_3DFACE);
364         ::FillRect(hdc, lpRc, hbrBackground);
365 
366         if (m_bPressed || m_bHovering)
367         {
368             UINT edge = m_bPressed ? BDR_SUNKENOUTER : BDR_RAISEDINNER;
369             DrawEdge(hdc, lpRc, edge, BF_RECT);
370         }
371     }
372 
373     if (m_highContrastMode || !m_drawWithDedicatedBackground)
374     {
375         /* Prepare to draw icon */
376 
377         // Determine X-position of icon's top-left corner
378         int iconX = rc.left;
379         iconX += (rc.right - iconX) / 2;
380         iconX -= m_szIcon.cx / 2;
381 
382         // Determine Y-position of icon's top-left corner
383         int iconY = rc.top;
384         iconY += (rc.bottom - iconY) / 2;
385         iconY -= m_szIcon.cy / 2;
386 
387         // Ok now actually draw the icon itself
388         if (m_icon)
389         {
390             DrawIconEx(hdc, iconX, iconY,
391                        m_icon, 0, 0,
392                        0, hbrBackground, DI_NORMAL);
393         }
394     }
395 }
396