Reactos
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
20CTrayShowDesktopButton::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
33INT 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
62HRESULT 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
103LRESULT 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.
119VOID 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
126LRESULT 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
139LRESULT 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
147LRESULT 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
154LRESULT 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
178LRESULT CTrayShowDesktopButton::OnWindowPosChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
179{
180 EnsureWindowTheme(TRUE);
181 return 0;
182}
183
184VOID 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
220LRESULT 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
233LRESULT 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
247BOOL 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
262VOID 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
275LRESULT CTrayShowDesktopButton::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
276{
277 StartHovering();
278 return 0;
279}
280
281LRESULT 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
300LRESULT 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
316VOID 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}