From 5a0e5a9261c251dba54afb8cc0b6b32ae9d95510 Mon Sep 17 00:00:00 2001
From: Zhao Yuhang <2546789017@qq.com>
Date: 周二, 12 12月 2023 19:50:34 +0800
Subject: [PATCH] add theme win32 message

---
 src/core/contexts/win32windowcontext.cpp |  337 +++++++++++++++++++++++++++++++------------------------
 1 files changed, 190 insertions(+), 147 deletions(-)

diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
index 0f1e770..372d0c5 100644
--- a/src/core/contexts/win32windowcontext.cpp
+++ b/src/core/contexts/win32windowcontext.cpp
@@ -4,7 +4,6 @@
 
 #include <QtCore/QHash>
 #include <QtCore/QScopeGuard>
-#include <QtCore/QDateTime>
 #include <QtGui/QGuiApplication>
 #include <QtGui/QPainter>
 #include <QtGui/QPalette>
@@ -28,6 +27,9 @@
 #include <timeapi.h>
 
 #include "nativeeventfilter.h"
+#include "qwkglobal_p.h"
+
+#include "win10borderhandler_p.h"
 
 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
 Q_DECLARE_METATYPE(QMargins)
@@ -35,37 +37,42 @@
 
 namespace QWK {
 
-    using _DWMWINDOWATTRIBUTE = enum _DWMWINDOWATTRIBUTE {
-        _DWMWA_USE_HOSTBACKDROPBRUSH =
-            17, // [set] BOOL, Allows the use of host backdrop brushes for the window.
-        _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 =
-            19, // Undocumented, the same with DWMWA_USE_IMMERSIVE_DARK_MODE, but available on
-                // systems before Win10 20H1.
-        _DWMWA_USE_IMMERSIVE_DARK_MODE =
-            20, // [set] BOOL, Allows a window to either use the accent color, or dark, according to
-                // the user Color Mode preferences.
-        _DWMWA_WINDOW_CORNER_PREFERENCE = 33, // [set] WINDOW_CORNER_PREFERENCE, Controls the policy
-                                              // that rounds top-level window corners
-        _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS =
-            37,  // [get] UINT, width of the visible border around a thick frame window
-        _DWMWA_SYSTEMBACKDROP_TYPE =
-            38,  // [get, set] SYSTEMBACKDROP_TYPE, Controls the system-drawn backdrop material of a
-                 // window, including behind the non-client area.
-        _DWMWA_MICA_EFFECT =
-            1029 // Undocumented, use this value to enable Mica material on Win11 21H2. You should
-                 // use DWMWA_SYSTEMBACKDROP_TYPE instead on Win11 22H2 and newer.
+    enum _DWMWINDOWATTRIBUTE {
+        // [set] BOOL, Allows the use of host backdrop brushes for the window.
+        _DWMWA_USE_HOSTBACKDROPBRUSH = 17,
+
+        // Undocumented, the same with DWMWA_USE_IMMERSIVE_DARK_MODE, but available on systems
+        // before Win10 20H1.
+        _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19,
+
+        // [set] BOOL, Allows a window to either use the accent color, or dark, according to the
+        // user Color Mode preferences.
+        _DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
+
+        // [set] WINDOW_CORNER_PREFERENCE, Controls the policy that rounds top-level window corners
+        _DWMWA_WINDOW_CORNER_PREFERENCE = 33,
+
+        // [get] UINT, width of the visible border around a thick frame window
+        _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37,
+
+        // [get, set] SYSTEMBACKDROP_TYPE, Controls the system-drawn backdrop material of a window,
+        // including behind the non-client area.
+        _DWMWA_SYSTEMBACKDROP_TYPE = 38,
+
+        // Undocumented, use this value to enable Mica material on Win11 21H2. You should use
+        // DWMWA_SYSTEMBACKDROP_TYPE instead on Win11 22H2 and newer.
+        _DWMWA_MICA_EFFECT = 1029
     };
 
     // The thickness of an auto-hide taskbar in pixels.
-    static constexpr const auto kAutoHideTaskBarThickness = quint8{2};
+    static constexpr const quint8 kAutoHideTaskBarThickness = 2;
 
-    static inline constexpr const auto kFrameBorderActiveColorLight =
-        QColor{110, 110, 110};                                                           // #6E6E6E
-    static inline constexpr const auto kFrameBorderActiveColorDark = QColor{51, 51, 51}; // #333333
-    static inline constexpr const auto kFrameBorderInactiveColorLight =
-        QColor{167, 167, 167};                                                           // #A7A7A7
-    static inline constexpr const auto kFrameBorderInactiveColorDark =
-        QColor{61, 61, 62};                                                              // #3D3D3E
+    QWK_USED static constexpr const struct {
+        const uint32_t activeLight = MAKE_RGBA_COLOR(110, 110, 110, 255);   // #6E6E6E
+        const uint32_t activeDark = MAKE_RGBA_COLOR(51, 51, 51, 255);       // #333333
+        const uint32_t inactiveLight = MAKE_RGBA_COLOR(167, 167, 167, 255); // #A7A7A7
+        const uint32_t inactiveDark = MAKE_RGBA_COLOR(61, 61, 62, 255);     // #3D3D3E
+    } kWindowsColorSet;
 
     // hWnd -> context
     using WndProcHash = QHash<HWND, Win32WindowContext *>;
@@ -352,44 +359,37 @@
         }
     }
 
-    static inline quint32 getWindowFrameBorderThickness(HWND hwnd) {
-        UINT result{0};
+    static inline quint32 getSystemMetricsForDpi(int index, quint32 dpi) {
         const DynamicApis &apis = DynamicApis::instance();
-        if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS,
-                                                  &result, sizeof(result)))) {
-            return result;
-        } else {
-            const quint32 dpi = getDpiForWindow(hwnd);
-            result = quint32(std::round(qreal(1) * qreal(dpi) / qreal(USER_DEFAULT_SCREEN_DPI)));
+        if (apis.pGetSystemMetricsForDpi) {
+            return ::GetSystemMetricsForDpi(index, dpi);
+        }
+        return ::GetSystemMetrics(index);
+    }
+
+    static inline quint32 getWindowFrameBorderThickness(HWND hwnd) {
+        const DynamicApis &apis = DynamicApis::instance();
+        if (UINT result = 0; SUCCEEDED(apis.pDwmGetWindowAttribute(
+                hwnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &result, sizeof(result)))) {
             return result;
         }
+        return getSystemMetricsForDpi(SM_CXBORDER, getDpiForWindow(hwnd));
     }
 
     static inline quint32 getResizeBorderThickness(HWND hwnd) {
-        const DynamicApis &apis = DynamicApis::instance();
-        if (apis.pGetSystemMetricsForDpi) {
-            const quint32 dpi = getDpiForWindow(hwnd);
-            return apis.pGetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) +
-                   apis.pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
-        } else {
-            return ::GetSystemMetrics(SM_CXSIZEFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER);
-        }
+        const quint32 dpi = getDpiForWindow(hwnd);
+        return getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) +
+               getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
     }
 
     static inline quint32 getTitleBarHeight(HWND hwnd) {
-        const auto captionHeight = [hwnd]() -> int {
-            const DynamicApis &apis = DynamicApis::instance();
-            if (apis.pGetSystemMetricsForDpi) {
-                const quint32 dpi = getDpiForWindow(hwnd);
-                return apis.pGetSystemMetricsForDpi(SM_CYCAPTION, dpi);
-            } else {
-                return ::GetSystemMetrics(SM_CYCAPTION);
-            }
-        }();
-        return captionHeight + getResizeBorderThickness(hwnd);
+        const quint32 dpi = getDpiForWindow(hwnd);
+        return getSystemMetricsForDpi(SM_CYCAPTION, dpi) +
+               getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) +
+               getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
     }
 
-    static inline void updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) {
+    static void updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) {
         const auto margins = [hwnd]() -> QMargins {
             const auto titleBarHeight = int(getTitleBarHeight(hwnd));
             if (isWin10OrGreater()) {
@@ -455,7 +455,7 @@
 #endif
     }
 
-    static inline void syncPaintEventWithDwm() {
+    static void syncPaintEventWithDwm() {
         // No need to sync with DWM if DWM composition is disabled.
         if (!isDwmCompositionEnabled()) {
             return;
@@ -500,9 +500,9 @@
         apis.ptimeEndPeriod(ms_granularity);
     }
 
-    static inline void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry,
-                                       const bool fixedSize) {
-        const HMENU hMenu = ::GetSystemMenu(hWnd, FALSE);
+    static void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry,
+                                const bool fixedSize) {
+        HMENU hMenu = ::GetSystemMenu(hWnd, FALSE);
         if (!hMenu) {
             // The corresponding window doesn't have a system menu, most likely due to the
             // lack of the "WS_SYSMENU" window style. This situation should not be treated
@@ -750,33 +750,13 @@
             return ::DefWindowProcW(hWnd, message, wParam, lParam);
         }
 
-        switch (message) {
-            case WM_NCCALCSIZE: {
-                // Since Qt does the necessary processing of the WM_NCCALCSIZE message, we need to
-                // forward it right away and process it in our native event filter.
-                WindowsNativeEventFilter::lastMessageContext = ctx;
-                LRESULT result = ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
-                WindowsNativeEventFilter::lastMessageContext = nullptr;
-                return result;
-            }
-
-            case WM_WINDOWPOSCHANGING: {
-                // When toggling the "Show theme color in title bar and window border" setting in
-                // Windows Settings, or calling `DrawMenuBar()`, Windows sends a message of
-                // WM_WINDOWPOSCHANGING with flags 0x37. If we do not process this message,
-                // the client area as a whole will shift to the left, which looks very abnormal if
-                // we don't repaint it. This exception disappears if we add SWP_NOCOPYBITS flag.
-                // But I don't know what caused the problem, or why this would solve it.
-                const auto windowPos = reinterpret_cast<LPWINDOWPOS>(lParam);
-                if (windowPos->flags ==
-                    (SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED)) {
-                    windowPos->flags |= SWP_NOCOPYBITS;
-                }
-                return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
-            }
-
-            default:
-                break;
+        // Since Qt does the necessary processing of the WM_NCCALCSIZE message, we need to
+        // forward it right away and process it in our native event filter.
+        if (message == WM_NCCALCSIZE) {
+            WindowsNativeEventFilter::lastMessageContext = ctx;
+            LRESULT result = ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
+            WindowsNativeEventFilter::lastMessageContext = nullptr;
+            return result;
         }
 
         // Try hooked procedure and save result
@@ -815,6 +795,7 @@
                 moveToDesktopCenter(hwnd);
                 return;
             }
+
             case ShowSystemMenuHook: {
                 const auto &pos = *static_cast<const QPoint *>(data);
                 auto hWnd = reinterpret_cast<HWND>(m_windowHandle->winId());
@@ -822,72 +803,33 @@
                                 m_delegate->isHostSizeFixed(m_host));
                 return;
             }
-            case NeedsDrawBordersHook: {
-                auto &result = *static_cast<bool *>(data);
-                result = isWin10OrGreater() && !isWin11OrGreater();
+
+            case DefaultColorsHook: {
+                auto &map = *static_cast<QMap<QString, QColor> *>(data);
+                map.clear();
+                map.insert(QStringLiteral("activeLight"), kWindowsColorSet.activeLight);
+                map.insert(QStringLiteral("activeDark"), kWindowsColorSet.activeDark);
+                map.insert(QStringLiteral("inactiveLight"), kWindowsColorSet.inactiveLight);
+                map.insert(QStringLiteral("inactiveDark"), kWindowsColorSet.inactiveDark);
                 return;
             }
-            case DrawBordersHook: {
-                auto args = static_cast<void **>(data);
-                auto &painter = *static_cast<QPainter *>(args[0]);
-                const auto &rect = *static_cast<const QRect *>(args[1]);
-                const auto &region = *static_cast<const QRegion *>(args[2]);
-                const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId());
-                QPen pen;
-                const auto borderThickness = int(QHighDpi::fromNativePixels(
-                    getWindowFrameBorderThickness(hwnd), m_windowHandle));
-                pen.setWidth(borderThickness * 2);
-                const bool active = m_host->isWidgetType()
-                                        ? m_host->property("isActiveWindow").toBool()
-                                        : m_host->property("active").toBool();
-                const bool dark = isDarkThemeActive() && isDarkWindowFrameEnabled(hwnd);
-                if (active) {
-                    if (isWindowFrameBorderColorized()) {
-                        pen.setColor(getAccentColor());
-                    } else {
-                        if (dark) {
-                            pen.setColor(kFrameBorderActiveColorDark);
-                        } else {
-                            pen.setColor(kFrameBorderActiveColorLight);
-                        }
-                    }
-                } else {
-                    if (dark) {
-                        pen.setColor(kFrameBorderInactiveColorDark);
-                    } else {
-                        pen.setColor(kFrameBorderInactiveColorLight);
-                    }
-                }
-                painter.save();
-                painter.setRenderHint(
-                    QPainter::Antialiasing); // ### TODO: do we need to enable or disable it?
-                painter.setPen(pen);
-                painter.drawLine(QLine{
-                    QPoint{0,            0},
-                    QPoint{rect.width(), 0}
-                });
-                painter.restore();
-                return;
-            }
-            case QueryBorderThicknessHook: {
-                auto args = static_cast<void **>(data);
-                const bool requireNative = *static_cast<const bool *>(args[0]);
-                quint32 &thickness = *static_cast<quint32 *>(args[1]);
-                const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId());
-                const auto nativeThickness = getWindowFrameBorderThickness(hwnd);
-                if (requireNative) {
-                    thickness = nativeThickness;
-                } else {
-                    thickness = QHighDpi::fromNativePixels(nativeThickness, m_windowHandle);
-                }
-                return;
-            }
+
             default: {
                 // unreachable
                 break;
             }
         }
         AbstractWindowContext::virtual_hook(id, data);
+    }
+
+    bool Win32WindowContext::needWin10BorderHandler() const {
+        return isWin10OrGreater() && !isWin11OrGreater();
+    }
+
+    void Win32WindowContext::setWin10BorderHandler(Win10BorderHandler *handler) {
+        win10BorderHandler.reset(handler);
+        handler->setBorderThickness(
+            int(getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId))));
     }
 
     bool Win32WindowContext::setupHost() {
@@ -950,14 +892,19 @@
             return true;
         }
 
+        // Whether to show system menu
         if (systemMenuHandler(hWnd, message, wParam, lParam, result)) {
+            return true;
+        }
+
+        if (themeStuffHandler(hWnd, message, wParam, lParam, result)) {
             return true;
         }
 
         return false; // Not handled
     }
 
-    static constexpr const struct {
+    QWK_USED static constexpr const struct {
         const WPARAM wParam = 0xF1C9ADD4;
         const LPARAM lParam = 0xAFB6F4C6;
     } kMessageTag;
@@ -1101,7 +1048,7 @@
                                                LPARAM lParam, LRESULT *result) {
         switch (message) {
             case WM_MOUSELEAVE: {
-                if (wParam == kMessageTag.wParam) {
+                if (wParam != kMessageTag.wParam) {
                     // Qt will call TrackMouseEvent() to get the WM_MOUSELEAVE message when it
                     // receives WM_MOUSEMOVE messages, and since we are converting every
                     // WM_NCMOUSEMOVE message to WM_MOUSEMOVE message and send it back to the window
@@ -1251,6 +1198,7 @@
                 }
                 break;
             }
+
             case WM_NCHITTEST: {
                 // 鍘熺敓Win32绐楀彛鍙湁椤惰竟鏄湪绐楀彛鍐呴儴resize鐨勶紝鍏朵綑涓夎竟閮芥槸鍦ㄧ獥鍙�
                 // 澶栭儴杩涜resize鐨勶紝鍏跺師鐞嗘槸锛學S_THICKFRAME杩欎釜绐楀彛鏍峰紡浼氬湪绐�
@@ -1363,7 +1311,7 @@
                     // this is also the normal behavior of a native Win32 window (but only when the
                     // window is not maximized/fullscreen/minimized, of course).
                     if (isWindowNoState(hWnd)) {
-                        static constexpr const auto kBorderSize = quint8{2};
+                        static constexpr const quint8 kBorderSize = 2;
                         bool isTop = (nativeLocalPos.y <= kBorderSize);
                         bool isLeft = nativeLocalPos.x <= kBorderSize;
                         bool isRight = (nativeLocalPos.x >= (clientWidth - kBorderSize));
@@ -1539,9 +1487,27 @@
                     return true;
                 }
             }
+
+            case WM_WINDOWPOSCHANGING: {
+                // ### FIXME: How does this problem happen and why is it solved?
+                // When toggling the "Show theme color in title bar and window border" setting in
+                // Windows Settings, or calling `DrawMenuBar()`, Windows sends a message of
+                // WM_WINDOWPOSCHANGING with flags 0x37. If we do not process this message,
+                // the client area as a whole will shift to the left, which looks very abnormal if
+                // we don't repaint it. This exception disappears if we add SWP_NOCOPYBITS flag.
+                // But I don't know what caused the problem, or why this would solve it.
+                const auto windowPos = reinterpret_cast<LPWINDOWPOS>(lParam);
+                if (windowPos->flags ==
+                    (SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED)) {
+                    windowPos->flags |= SWP_NOCOPYBITS;
+                }
+                break;
+            }
+
             default:
                 break;
         }
+
         if (!isWin10OrGreater()) {
             switch (message) {
                 case WM_NCUAHDRAWCAPTION:
@@ -1921,4 +1887,81 @@
         return false;
     }
 
+    bool Win32WindowContext::themeStuffHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam,
+                           LRESULT *resul)
+    {
+        switch (message) {
+            case WM_DPICHANGED: {
+                const auto dpiX = UINT(LOWORD(wParam));
+                const auto dpiY = UINT(HIWORD(wParam));
+                break;
+            }
+            case WM_THEMECHANGED:
+            case WM_SYSCOLORCHANGE: {
+                break;
+            }
+            case WM_DWMCOLORIZATIONCOLORCHANGED: {
+                const QColor color = QColor::fromRgba(wParam);
+                const auto blendWithOpacity = *reinterpret_cast<LPBOOL>(lParam);
+                break;
+            }
+            case WM_SETTINGCHANGE: {
+                if (!wParam && lParam && std::wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0) {
+                    const QColor color = getAccentColor();
+                }
+                break;
+            }
+            case WM_SIZE: {
+                const bool max = wParam == SIZE_MAXIMIZED;
+                const bool min = wParam == SIZE_MINIMIZED;
+                const bool full = isFullScreen(hWnd);
+                break;
+            }
+            case WM_ACTIVATE: {
+                const auto state = LOWORD(wParam);
+                const bool active = state == WA_ACTIVE || state == WA_CLICKACTIVE;
+                break;
+            }
+            default:
+                break;
+        }
+        return false;
+    }
+
+    void Win10BorderHandler::paintBorder(QPainter &painter, const QRect &rect,
+                                         const QRegion &region) {
+        Q_UNUSED(rect)
+        Q_UNUSED(region)
+
+        QPen pen;
+        pen.setWidth(m_borderThickness * 2);
+
+        const bool dark = isDarkThemeActive() &&
+                          isDarkWindowFrameEnabled(reinterpret_cast<HWND>(m_window->winId()));
+        if (isActive()) {
+            if (isWindowFrameBorderColorized()) {
+                pen.setColor(getAccentColor());
+            } else {
+                static QColor frameBorderActiveColorLight(kWindowsColorSet.activeLight);
+                static QColor frameBorderActiveColorDark(kWindowsColorSet.activeDark);
+                pen.setColor(dark ? frameBorderActiveColorDark : frameBorderActiveColorLight);
+            }
+        } else {
+            static QColor frameBorderInactiveColorLight(kWindowsColorSet.inactiveLight);
+            static QColor frameBorderInactiveColorDark(kWindowsColorSet.inactiveDark);
+            pen.setColor(dark ? frameBorderInactiveColorDark : frameBorderInactiveColorLight);
+        }
+        painter.save();
+
+        // ### TODO: do we need to enable or disable it?
+        painter.setRenderHint(QPainter::Antialiasing);
+
+        painter.setPen(pen);
+        painter.drawLine(QLine{
+            QPoint{0,                 0},
+            QPoint{m_window->width(), 0}
+        });
+        painter.restore();
+    }
+
 }

--
Gitblit v1.9.1