[EXPLORER] Add 'Show Desktop' button at right edge of taskbar (#4715)

This PR adds a tiny button of window class "TrayShowDesktopButtonWClass" at right/bottom edge of taskbar.
This button allows the user to access "Show/Restore Desktop" feature by mouse.
You can toggle visibility of this button by registry value "TaskbarSd" in key "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced".
The button is themed when theme is available.
CORE-15369
This commit is contained in:
Katayama Hirofumi MZ 2022-09-22 12:18:59 +09:00 committed by GitHub
parent 34635e1585
commit 26efda4d8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -2,7 +2,7 @@
* ReactOS Explorer
*
* Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
* Copyright 2018 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
* Copyright 2018-2022 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
*
* this library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -264,6 +264,200 @@ public:
};
// This window class name is CONFIRMED on Win10 by WinHier.
static const WCHAR szTrayShowDesktopButton[] = L"TrayShowDesktopButtonWClass";
// The 'Show Desktop' button at edge of taskbar
class CTrayShowDesktopButton :
public CWindowImpl<CTrayShowDesktopButton, CWindow, CControlWinTraits>
{
LONG m_nClickedTime;
BOOL m_bHovering;
HTHEME m_hTheme;
public:
DECLARE_WND_CLASS_EX(szTrayShowDesktopButton, CS_HREDRAW | CS_VREDRAW, COLOR_3DFACE)
CTrayShowDesktopButton() : m_nClickedTime(0), m_bHovering(FALSE)
{
}
INT WidthOrHeight() const
{
#define SHOW_DESKTOP_MINIMUM_WIDTH 3
INT cxy = 2 * ::GetSystemMetrics(SM_CXEDGE);
return max(cxy, SHOW_DESKTOP_MINIMUM_WIDTH);
}
HRESULT DoCreate(HWND hwndParent)
{
DWORD style = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS;
Create(hwndParent, NULL, NULL, style);
if (!m_hWnd)
return E_FAIL;
::SetWindowTheme(m_hWnd, L"TaskBar", NULL);
return S_OK;
}
LRESULT OnClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// The actual action can be delayed as an expected behaviour.
// But a too late action is an unexpected behaviour.
LONG nTime0 = m_nClickedTime;
LONG nTime1 = ::GetMessageTime();
if (nTime1 - nTime0 >= 600) // Ignore after 0.6 sec
return 0;
// Show/Hide Desktop
::SendMessageW(::GetParent(m_hWnd), WM_COMMAND, TRAYCMD_TOGGLE_DESKTOP, 0);
return 0;
}
#define TSDB_CLICK (WM_USER + 100)
// This function is called from OnLButtonDown and parent.
VOID Click()
{
// The actual action can be delayed as an expected behaviour.
m_nClickedTime = ::GetMessageTime();
PostMessage(TSDB_CLICK, 0, 0);
}
LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
Click(); // Left-click
return 0;
}
LRESULT OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (m_hTheme)
::CloseThemeData(m_hTheme);
m_hTheme = ::OpenThemeData(m_hWnd, L"TaskBar");
InvalidateRect(NULL, TRUE);
return 0;
}
// This function is called from OnPaint and parent.
VOID OnDraw(HDC hdc, LPRECT prc);
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
RECT rc;
GetClientRect(&rc);
PAINTSTRUCT ps;
HDC hdc = BeginPaint(&ps);
OnDraw(hdc, &rc);
EndPaint(&ps);
return 0;
}
BOOL PtInButton(POINT pt)
{
RECT rc;
GetWindowRect(&rc);
INT cxEdge = ::GetSystemMetrics(SM_CXEDGE), cyEdge = ::GetSystemMetrics(SM_CYEDGE);
::InflateRect(&rc, max(cxEdge, 1), max(cyEdge, 1));
return ::PtInRect(&rc, pt);
}
#define SHOW_DESKTOP_TIMER_ID 999
#define SHOW_DESKTOP_TIMER_INTERVAL 200
VOID StartHovering()
{
if (m_bHovering)
return;
m_bHovering = TRUE;
SetTimer(SHOW_DESKTOP_TIMER_ID, SHOW_DESKTOP_TIMER_INTERVAL, NULL);
InvalidateRect(NULL, TRUE);
::SendMessageW(::GetParent(m_hWnd), WM_NCPAINT, 0, 0);
}
LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
StartHovering();
return 0;
}
LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (wParam != SHOW_DESKTOP_TIMER_ID || !m_bHovering)
return 0;
POINT pt;
::GetCursorPos(&pt);
if (!PtInButton(pt)) // The end of hovering?
{
m_bHovering = FALSE;
KillTimer(SHOW_DESKTOP_TIMER_ID);
InvalidateRect(NULL, TRUE);
::SendMessageW(::GetParent(m_hWnd), WM_NCPAINT, 0, 0);
}
return 0;
}
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (m_hTheme)
{
CloseThemeData(m_hTheme);
m_hTheme = NULL;
}
return 0;
}
BEGIN_MSG_MAP(CTrayShowDesktopButton)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged)
MESSAGE_HANDLER(WM_THEMECHANGED, OnSettingChanged)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_TIMER, OnTimer)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(TSDB_CLICK, OnClick)
END_MSG_MAP()
};
VOID CTrayShowDesktopButton::OnDraw(HDC hdc, LPRECT prc)
{
if (m_hTheme)
{
if (m_bHovering) // Draw a hot button
{
HTHEME hButtonTheme = ::OpenThemeData(m_hWnd, L"Button");
::DrawThemeBackground(hButtonTheme, hdc, BP_PUSHBUTTON, PBS_NORMAL, prc, prc);
::CloseThemeData(hButtonTheme);
}
else // Draw a taskbar background
{
::DrawThemeBackground(m_hTheme, hdc, TBP_BACKGROUNDTOP, 0, prc, prc);
}
}
else
{
RECT rc = *prc;
if (m_bHovering) // Draw a hot button
{
::DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_ADJUSTRECT);
HBRUSH hbrHot = ::CreateSolidBrush(RGB(255, 255, 191));
::FillRect(hdc, &rc, hbrHot);
::DeleteObject(hbrHot);
}
else // Draw a flattish button
{
::DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONPUSH);
::InflateRect(&rc, -1, -1);
::FillRect(hdc, &rc, ::GetSysColorBrush(COLOR_3DFACE));
}
}
}
class CTrayWindow :
public CComCoClass<CTrayWindow>,
public CComObjectRootEx<CComMultiThreadModelNoCS>,
@ -274,6 +468,7 @@ class CTrayWindow :
public IContextMenu
{
CStartButton m_StartButton;
CTrayShowDesktopButton m_ShowDesktopButton;
CComPtr<IMenuBand> m_StartMenuBand;
CComPtr<IMenuPopup> m_StartMenuPopup;
@ -327,6 +522,7 @@ public:
public:
CTrayWindow() :
m_StartButton(),
m_ShowDesktopButton(),
m_Theme(NULL),
m_Font(NULL),
m_DesktopWnd(NULL),
@ -1617,7 +1813,7 @@ ChangePos:
/* We're about to resize/move the start button, the rebar control and
the tray notification control */
dwp = BeginDeferWindowPos(3);
dwp = BeginDeferWindowPos(4);
if (dwp == NULL)
{
ERR("BeginDeferWindowPos failed. lastErr=%d\n", GetLastError());
@ -1655,6 +1851,35 @@ ChangePos:
}
}
if (m_ShowDesktopButton.m_hWnd)
{
// Get rectangle from rcClient
RECT rc = rcClient;
INT cxyShowDesktop = m_ShowDesktopButton.WidthOrHeight();
if (Horizontal)
{
rc.left = rc.right - cxyShowDesktop;
rc.right += 5; // excessive
}
else
{
rc.top = rc.bottom - cxyShowDesktop;
rc.bottom += 5; // excessive
}
/* Resize and reposition the button */
dwp = m_ShowDesktopButton.DeferWindowPos(dwp, NULL,
rc.left, rc.top,
rc.right - rc.left, rc.bottom - rc.top,
SWP_NOZORDER | SWP_NOACTIVATE);
// Adjust rcClient
if (Horizontal)
rcClient.right -= cxyShowDesktop + ::GetSystemMetrics(SM_CXEDGE);
else
rcClient.bottom -= cxyShowDesktop + ::GetSystemMetrics(SM_CYEDGE);
}
/* Determine the size that the tray notification window needs */
if (Horizontal)
{
@ -2209,6 +2434,15 @@ ChangePos:
return m_ContextMenu->GetCommandString(idCmd, uType, pwReserved, pszName, cchMax);
}
BOOL IsShowDesktopButtonNeeded() // Read the registry value
{
return SHRegGetBoolUSValueW(
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced",
L"TaskbarSd",
FALSE,
TRUE);
}
/**********************************************************
* ##### message handling #####
*/
@ -2224,6 +2458,10 @@ ChangePos:
/* Create the Start button */
m_StartButton.Create(m_hWnd);
/* Create the 'Show Desktop' button if necessary */
if (IsShowDesktopButtonNeeded())
m_ShowDesktopButton.DoCreate(m_hWnd);
/* Load the saved tray window settings */
RegLoadSettings();
@ -2364,19 +2602,34 @@ ChangePos:
return FALSE;
}
// We have to draw non-client area because the 'Show Desktop' button is beyond client area.
void DrawShowDesktopButton()
{
// Get the rectangle in window coordinates
RECT rcButton, rcWnd;
GetWindowRect(&rcWnd);
m_ShowDesktopButton.GetWindowRect(&rcButton);
::OffsetRect(&rcButton, -rcWnd.left, -rcWnd.top);
HDC hdc = GetDCEx(NULL, DCX_WINDOW | DCX_CACHE | DCX_NORESETATTRS);
m_ShowDesktopButton.OnDraw(hdc, &rcButton); // Draw the button
ReleaseDC(hdc);
}
LRESULT OnNcPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (!m_Theme)
{
bHandled = FALSE;
return 0;
}
else if (g_TaskbarSettings.bLock)
DefWindowProc(uMsg, wParam, lParam);
bHandled = TRUE;
if (!m_Theme || g_TaskbarSettings.bLock)
{
DrawShowDesktopButton(); // We have to draw non-client area
return 0;
}
return DrawSizerWithTheme((HRGN) wParam);
DrawSizerWithTheme((HRGN) wParam);
DrawShowDesktopButton(); // We have to draw non-client area
return 0;
}
LRESULT OnCtlColorBtn(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
@ -2601,11 +2854,15 @@ ChangePos:
POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
WINDOWINFO wi = {sizeof(WINDOWINFO)};
RECT rcStartBtn;
bHandled = FALSE;
if (CheckShowDesktopButtonClick(lParam, bHandled))
return TRUE;
RECT rcStartBtn;
m_StartButton.GetWindowRect(&rcStartBtn);
GetWindowInfo(m_hWnd, &wi);
switch (m_Position)
@ -2632,9 +2889,7 @@ ChangePos:
case ABE_BOTTOM:
{
if (pt.x > rcStartBtn.right || pt.y < rcStartBtn.top)
{
return 0;
}
if (rcStartBtn.bottom + (int)wi.cyWindowBorders * 2 + 1 < wi.rcWindow.bottom &&
pt.y > rcStartBtn.bottom)
@ -2764,8 +3019,24 @@ HandleTrayContextMenu:
return Ret;
}
BOOL CheckShowDesktopButtonClick(LPARAM lParam, BOOL& bHandled)
{
POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
if (m_ShowDesktopButton.PtInButton(pt)) // Did you click the button?
{
m_ShowDesktopButton.Click();
bHandled = TRUE;
return TRUE;
}
return FALSE;
}
LRESULT OnNcLButtonDblClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (CheckShowDesktopButtonClick(lParam, bHandled))
return TRUE;
/* Let the clock handle the double click */
::SendMessageW(m_TrayNotify, uMsg, wParam, lParam);
@ -2931,6 +3202,11 @@ HandleTrayContextMenu:
LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
POINT pt;
::GetCursorPos(&pt);
if (m_ShowDesktopButton.PtInButton(pt))
m_ShowDesktopButton.StartHovering();
if (g_TaskbarSettings.sr.AutoHide)
{
SetTimer(TIMER_ID_MOUSETRACK, MOUSETRACK_INTERVAL, NULL);
@ -2954,6 +3230,14 @@ HandleTrayContextMenu:
return TRUE;
}
LRESULT OnNcActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DefWindowProc(uMsg, wParam, lParam);
DrawShowDesktopButton(); // We have to draw non-client area
bHandled = TRUE;
return 0;
}
LRESULT OnNcCalcSize(INT code, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
RECT *rc = NULL;
@ -3124,6 +3408,7 @@ HandleTrayContextMenu:
MESSAGE_HANDLER(WM_DISPLAYCHANGE, OnDisplayChange)
MESSAGE_HANDLER(WM_COPYDATA, OnCopyData)
MESSAGE_HANDLER(WM_NCPAINT, OnNcPaint)
MESSAGE_HANDLER(WM_NCACTIVATE, OnNcActivate)
MESSAGE_HANDLER(WM_CTLCOLORBTN, OnCtlColorBtn)
MESSAGE_HANDLER(WM_MOVING, OnMoving)
MESSAGE_HANDLER(WM_SIZING, OnSizing)