From 5bfd136473edc506b860177bf88626e35a5a1ba4 Mon Sep 17 00:00:00 2001
From: SineStriker <trueful@163.com>
Date: 摹曛, 28 12月 2023 12:06:54 +0800
Subject: [PATCH] Add mac blur view

---
 src/widgets/widgetwindowagent_mac.cpp   |    2 
 src/core/contexts/cocoawindowcontext.mm |   94 ++++++++++++++++++++++++++++++
 src/core/windowagentbase.cpp            |    6 +
 examples/mainwindow/mainwindow.cpp      |   58 ++++++++++++++++---
 4 files changed, 147 insertions(+), 13 deletions(-)

diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp
index 206b7f2..154aa62 100644
--- a/examples/mainwindow/mainwindow.cpp
+++ b/examples/mainwindow/mainwindow.cpp
@@ -140,7 +140,7 @@
 #ifdef Q_OS_WIN
         auto dwmBlurAction = new QAction(tr("Enable DWM blur"), menuBar);
         dwmBlurAction->setCheckable(true);
-        connect(dwmBlurAction, &QAction::triggered, this, [this](bool checked) {
+        connect(dwmBlurAction, &QAction::toggled, this, [this](bool checked) {
             if (!windowAgent->setWindowAttribute(QStringLiteral("dwm-blur"), checked)) {
                 return;
             }
@@ -150,7 +150,7 @@
 
         auto acrylicAction = new QAction(tr("Enable acrylic material"), menuBar);
         acrylicAction->setCheckable(true);
-        connect(acrylicAction, &QAction::triggered, this, [this](bool checked) {
+        connect(acrylicAction, &QAction::toggled, this, [this](bool checked) {
             if (!windowAgent->setWindowAttribute(QStringLiteral("acrylic-material"), true)) {
                 return;
             }
@@ -160,7 +160,7 @@
 
         auto micaAction = new QAction(tr("Enable mica"), menuBar);
         micaAction->setCheckable(true);
-        connect(micaAction, &QAction::triggered, this, [this](bool checked) {
+        connect(micaAction, &QAction::toggled, this, [this](bool checked) {
             if (!windowAgent->setWindowAttribute(QStringLiteral("mica"), checked)) {
                 return;
             }
@@ -170,30 +170,70 @@
 
         auto micaAltAction = new QAction(tr("Enable mica alt"), menuBar);
         micaAltAction->setCheckable(true);
-        connect(micaAltAction, &QAction::triggered, this, [this](bool checked) {
+        connect(micaAltAction, &QAction::toggled, this, [this](bool checked) {
             if (!windowAgent->setWindowAttribute(QStringLiteral("mica-alt"), checked)) {
                 return;
             }
             setProperty("custom-style", checked);
             style()->polish(this);
         });
+#elif defined(Q_OS_MAC)
+        auto darkBlurAction = new QAction(tr("Dark blur"), menuBar);
+        darkBlurAction->setCheckable(true);
+        connect(darkBlurAction, &QAction::toggled, this, [this](bool checked) {
+            if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), "dark")) {
+                return;
+            }
+            if (checked) {
+                setProperty("custom-style", true);
+                style()->polish(this);
+            }
+        });
 
-        auto winStyleGroup = new QActionGroup(menuBar);
-        winStyleGroup->addAction(dwmBlurAction);
-        winStyleGroup->addAction(acrylicAction);
-        winStyleGroup->addAction(micaAction);
-        winStyleGroup->addAction(micaAltAction);
+        auto lightBlurAction = new QAction(tr("Light blur"), menuBar);
+        lightBlurAction->setCheckable(true);
+        connect(lightBlurAction, &QAction::toggled, this, [this](bool checked) {
+            if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), "light")) {
+                return;
+            }
+            if (checked) {
+                setProperty("custom-style", true);
+                style()->polish(this);
+            }
+        });
+
+        auto noBlurAction = new QAction(tr("No blur"), menuBar);
+        noBlurAction->setCheckable(true);
+        connect(noBlurAction, &QAction::toggled, this, [this](bool checked) {
+            if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), "none")) {
+                return;
+            }
+            if (checked) {
+                setProperty("custom-style", false);
+                style()->polish(this);
+            }
+        });
+
+        auto macStyleGroup = new QActionGroup(menuBar);
+        macStyleGroup->addAction(darkBlurAction);
+        macStyleGroup->addAction(lightBlurAction);
+        macStyleGroup->addAction(noBlurAction);
 #endif
 
         // Real menu
         auto settings = new QMenu(tr("Settings(&S)"), menuBar);
         settings->addAction(darkAction);
+
 #ifdef Q_OS_WIN
         settings->addSeparator();
         settings->addAction(dwmBlurAction);
         settings->addAction(acrylicAction);
         settings->addAction(micaAction);
         settings->addAction(micaAltAction);
+#elif defined(Q_OS_MAC)
+        settings->addAction(darkBlurAction);
+        settings->addAction(lightBlurAction);
+        settings->addAction(noBlurAction);
 #endif
 
         menuBar->addMenu(file);
diff --git a/src/core/contexts/cocoawindowcontext.mm b/src/core/contexts/cocoawindowcontext.mm
index faa75bb..54a3dbe 100644
--- a/src/core/contexts/cocoawindowcontext.mm
+++ b/src/core/contexts/cocoawindowcontext.mm
@@ -118,12 +118,21 @@
 namespace QWK {
 
     struct NSWindowProxy : public QWK_NSWindowDelegate {
+        enum class BlurMode {
+            Dark,
+            Light,
+            None,
+        };
+
         NSWindowProxy(NSWindow *macWindow) {
             nswindow = macWindow;
             g_proxyIndexes->insert(nswindow, this);
         }
 
         ~NSWindowProxy() override {
+            if (blurEffect) {
+                setBlurEffect(BlurMode::None);
+            }
             g_proxyIndexes->remove(nswindow);
         }
 
@@ -155,6 +164,10 @@
         }
 
         void windowDidResize() override {
+            if (blurEffect) {
+                updateBlurEffectSize();
+            }
+
             if (systemButtonRect.isEmpty() || !systemButtonVisible) {
                 return;
             }
@@ -223,6 +236,47 @@
             NSButton *minimizeBtn = [nswindow standardWindowButton:NSWindowMiniaturizeButton];
             NSButton *zoomBtn = [nswindow standardWindowButton:NSWindowZoomButton];
             return {closeBtn, minimizeBtn, zoomBtn};
+        }
+
+        void setBlurEffect(BlurMode option) {
+            if (option == BlurMode::None) {
+                if (!blurEffect) {
+                    return;
+                }
+                [blurEffect removeFromSuperview];
+                [blurEffect release];
+                blurEffect = nil;
+            } else {
+                if (!blurEffect) {
+                    NSView *const view = [nswindow contentView];
+                    NSVisualEffectView *const blurView =
+                        [[visualEffectViewClass alloc] initWithFrame:view.bounds];
+                    blurView.material = NSVisualEffectMaterialUnderWindowBackground;
+                    blurView.blendingMode = NSVisualEffectBlendingModeBehindWindow;
+                    blurView.state = NSVisualEffectStateFollowsWindowActiveState;
+
+#if 1
+                    const NSView *const parent = [view superview];
+                    [parent addSubview:blurView positioned:NSWindowBelow relativeTo:view];
+#endif
+
+                    blurEffect = blurView;
+                    updateBlurEffectSize();
+                }
+
+                auto view = static_cast<const NSVisualEffectView *>(blurEffect);
+                if (option == BlurMode::Dark) {
+                    view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantDark"];
+                } else {
+                    view.appearance =
+                        [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantLight"];
+                }
+            }
+        }
+
+        void updateBlurEffectSize() {
+            const NSView *const view = [nswindow contentView];
+            blurEffect.frame = view.frame;
         }
 
         void setSystemTitleBarVisible(const bool visible) {
@@ -311,6 +365,10 @@
             windowObserver = nil;
         }
 
+        static inline const Class windowClass = [NSWindow class];
+
+        static inline const Class visualEffectViewClass = NSClassFromString(@"NSVisualEffectView");
+
     protected:
         static BOOL canBecomeKeyWindow(id obj, SEL sel) {
             if (g_proxyIndexes->contains(reinterpret_cast<NSWindow *>(obj))) {
@@ -377,12 +435,11 @@
 #endif
         }
 
-        static inline const Class windowClass = [NSWindow class];
-
     private:
         Q_DISABLE_COPY(NSWindowProxy)
 
         NSWindow *nswindow = nil;
+        NSView *blurEffect = nil;
 
         bool systemButtonVisible = true;
         QRect systemButtonRect;
@@ -622,6 +679,39 @@
             ensureWindowProxy(windowId)->setSystemButtonVisible(!attribute.toBool());
             return true;
         }
+
+        if (key == QStringLiteral("blur-effect")) {
+            // Class not available
+            if (!NSWindowProxy::visualEffectViewClass) {
+                return false;
+            }
+
+            auto option = NSWindowProxy::BlurMode::None;
+            if (attribute.type() == QVariant::Bool) {
+                if (attribute.toBool()) {
+                    NSString *osxMode =
+                        [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
+                    option = [osxMode isEqualToString:@"Dark"] ? NSWindowProxy::BlurMode::Dark
+                                                               : NSWindowProxy::BlurMode::Light;
+                }
+            } else if (attribute.type() == QVariant::String) {
+                auto value = attribute.toString();
+                if (value == QStringLiteral("dark")) {
+                    option = NSWindowProxy::BlurMode::Dark;
+                } else if (value == QStringLiteral("light")) {
+                    option = NSWindowProxy::BlurMode::Light;
+                } else if (value == QStringLiteral("none")) {
+                    // ...
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+            ensureWindowProxy(windowId)->setBlurEffect(option);
+            return true;
+        }
+
         return false;
     }
 
diff --git a/src/core/windowagentbase.cpp b/src/core/windowagentbase.cpp
index 336f2b7..ef8b0fb 100644
--- a/src/core/windowagentbase.cpp
+++ b/src/core/windowagentbase.cpp
@@ -99,7 +99,11 @@
                    internal state.
 
         On macOS,
-            \li \c no-system-buttons: Specify a boolean value to set the system buttons' visibility.
+            \li \c no-system-buttons: Specify a boolean value to set the system buttons'
+                   visibility.
+            \li \c blur-effect: You can specify a string value, "dark" to set dark mode, "light"
+                   to set light mode, "none" to disable. You can also specify a boolean value,
+                   \c true to enable and set the current theme, \c false to disable.
     */
     bool WindowAgentBase::setWindowAttribute(const QString &key, const QVariant &attribute) {
         Q_D(WindowAgentBase);
diff --git a/src/widgets/widgetwindowagent_mac.cpp b/src/widgets/widgetwindowagent_mac.cpp
index 42f8dad..97b6028 100644
--- a/src/widgets/widgetwindowagent_mac.cpp
+++ b/src/widgets/widgetwindowagent_mac.cpp
@@ -5,7 +5,7 @@
 namespace QWK {
 
     static inline QRect getWidgetSceneRect(QWidget *widget) {
-        return {widget->mapTo(widget->window(), {}), widget->size()};
+        return {widget->mapTo(widget->window(), QPoint()), widget->size()};
     }
 
     class SystemButtonAreaWidgetEventFilter : public QObject {

--
Gitblit v1.9.1