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/core/contexts/cocoawindowcontext.mm |  199 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 185 insertions(+), 14 deletions(-)

diff --git a/src/core/contexts/cocoawindowcontext.mm b/src/core/contexts/cocoawindowcontext.mm
index 21616d5..54a3dbe 100644
--- a/src/core/contexts/cocoawindowcontext.mm
+++ b/src/core/contexts/cocoawindowcontext.mm
@@ -118,33 +118,165 @@
 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);
         }
 
         void windowWillEnterFullScreen() override {
-            qDebug() << "windowWillEnterFullScreen";
         }
 
         void windowDidEnterFullScreen() override {
-            qDebug() << "windowDidEnterFullScreen";
         }
 
         void windowWillExitFullScreen() override {
-            qDebug() << "windowWillExitFullScreen";
+            if (systemButtonRect.isEmpty() || !systemButtonVisible)
+                return;
+
+            // The system buttons will stuck at their default positions when the exit-fullscreen
+            // animation is running, we need to hide them until the animation finishes
+            for (const auto &button : systemButtons()) {
+                button.hidden = true;
+            }
         }
 
         void windowDidExitFullScreen() override {
-            qDebug() << "windowDidExitFullScreen";
+            if (systemButtonRect.isEmpty() || !systemButtonVisible)
+                return;
+
+            for (const auto &button : systemButtons()) {
+                button.hidden = false;
+            }
+            updateSystemButtonRect();
         }
 
         void windowDidResize() override {
-            qDebug() << "windowDidResize";
+            if (blurEffect) {
+                updateBlurEffectSize();
+            }
+
+            if (systemButtonRect.isEmpty() || !systemButtonVisible) {
+                return;
+            }
+            updateSystemButtonRect();
+        }
+
+        void setSystemButtonVisible(bool visible) {
+            systemButtonVisible = visible;
+            for (const auto &button : systemButtons()) {
+                button.hidden = !visible;
+            }
+
+            if (systemButtonRect.isEmpty() || !visible) {
+                return;
+            }
+            updateSystemButtonRect();
+        }
+
+        void setSystemButtonRect(const QRect &rect) {
+            systemButtonRect = rect;
+
+            if (rect.isEmpty() || !systemButtonVisible) {
+                return;
+            }
+            updateSystemButtonRect();
+        }
+
+        void updateSystemButtonRect() {
+            // https://forgetsou.github.io/2020/11/06/macos%E5%BC%80%E5%8F%91-%E5%85%B3%E9%97%AD-%E6%9C%80%E5%B0%8F%E5%8C%96-%E5%85%A8%E5%B1%8F%E5%B1%85%E4%B8%AD%E5%A4%84%E7%90%86(%E4%BB%BFMac%20QQ)/
+
+            const auto &buttons = systemButtons();
+            const auto &leftButton = buttons[0];
+            const auto &midButton = buttons[1];
+            const auto &rightButton = buttons[2];
+
+            auto spacing = midButton.frame.origin.x - leftButton.frame.origin.x;
+            auto width = midButton.frame.size.width;
+            auto height = midButton.frame.size.height;
+
+            QPoint center = systemButtonRect.center();
+
+            // Mid button
+            NSPoint centerOrigin = {
+                center.x() - width / 2,
+                center.y() - height / 2,
+            };
+            [midButton setFrameOrigin:centerOrigin];
+
+            // Left button
+            NSPoint leftOrigin = {
+                centerOrigin.x - spacing,
+                centerOrigin.y,
+            };
+            [leftButton setFrameOrigin:leftOrigin];
+
+            // Right button
+            NSPoint rightOrigin = {
+                centerOrigin.x + spacing,
+                centerOrigin.y,
+            };
+            [rightButton setFrameOrigin:rightOrigin];
+        }
+
+        inline std::array<NSButton *, 3> systemButtons() {
+            NSButton *closeBtn = [nswindow standardWindowButton:NSWindowCloseButton];
+            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) {
@@ -233,7 +365,11 @@
             windowObserver = nil;
         }
 
-    private:
+        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))) {
                 return YES;
@@ -299,12 +435,14 @@
 #endif
         }
 
-        static inline const Class windowClass = [NSWindow class];
-
     private:
         Q_DISABLE_COPY(NSWindowProxy)
 
         NSWindow *nswindow = nil;
+        NSView *blurEffect = nil;
+
+        bool systemButtonVisible = true;
+        QRect systemButtonRect;
 
         static inline QWK_NSWindowObserver *windowObserver = nil;
 
@@ -505,7 +643,7 @@
     void CocoaWindowContext::virtual_hook(int id, void *data) {
         switch (id) {
             case SystemButtonAreaChangedHook: {
-                // TODO: mac system button rect updated
+                ensureWindowProxy(windowId)->setSystemButtonRect(m_systemButtonArea);
                 return;
             }
 
@@ -533,14 +671,47 @@
 
     bool CocoaWindowContext::windowAttributeChanged(const QString &key, const QVariant &attribute,
                                                     const QVariant &oldAttribute) {
+        Q_UNUSED(oldAttribute)
+
         if (key == QStringLiteral("no-system-buttons")) {
-            if (attribute.toBool()) {
-                // TODO: set off
-            } else {
-                // TODO: set on
-            }
+            if (attribute.type() != QVariant::Bool)
+                return false;
+            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;
     }
 

--
Gitblit v1.9.1