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