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