[EXPLORER] Add 'Show Desktop' button at right edge of taskbar (#4715)
authorKatayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
Thu, 22 Sep 2022 03:18:59 +0000 (12:18 +0900)
committerGitHub <noreply@github.com>
Thu, 22 Sep 2022 03:18:59 +0000 (12:18 +0900)
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

base/shell/explorer/traywnd.cpp

index da62c75..f1ff624 100644 (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)