From 6f67925799407216b999709d86ac180c1106fa9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Wed, 2 Apr 2025 15:47:59 +0200 Subject: [PATCH] macOS: Add native interface API to manage visual effect view NSVisualEffectView is the only supported way to add effects to views, such as showing a blurred representation of the content under the window, or within the window. Now that we render the content of our QNSView into a sublayer of the view's root layer, we can add NSVisualEffectViews as child views without obscuring the view's own content, by making sure that the NSVisualEffectView layer has a lower Z order in the layer tree of our view's root layer. This works because adding a layer-backed or -hosted view as a subview will also add its layer as a sublayer of the view's layer, making the effect layer and our content layer sibling layers. Change-Id: Iab822e8462f54025559b3e3f26c7df668c885d75 Reviewed-by: Richard Moe Gustavsen --- src/gui/kernel/qplatformwindow_p.h | 7 ++++ src/plugins/platforms/cocoa/qcocoawindow.h | 8 +++- src/plugins/platforms/cocoa/qcocoawindow.mm | 41 +++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/gui/kernel/qplatformwindow_p.h b/src/gui/kernel/qplatformwindow_p.h index e240da5838c..5ee14c90e8a 100644 --- a/src/gui/kernel/qplatformwindow_p.h +++ b/src/gui/kernel/qplatformwindow_p.h @@ -30,6 +30,10 @@ struct wl_surface; #if defined(Q_OS_MACOS) Q_FORWARD_DECLARE_OBJC_CLASS(CALayer); +typedef long NSInteger; +enum NSVisualEffectMaterial : NSInteger; +enum NSVisualEffectBlendingMode : NSInteger; +enum NSVisualEffectState: NSInteger; #endif QT_BEGIN_NAMESPACE @@ -63,6 +67,9 @@ struct Q_GUI_EXPORT QCocoaWindow virtual void setContentBorderEnabled(bool enable) = 0; virtual QPoint bottomLeftClippedByNSWindowOffset() const = 0; virtual CALayer *contentLayer() const = 0; + virtual void manageVisualEffectArea(quintptr identifier, const QRect &rect, + NSVisualEffectMaterial material, NSVisualEffectBlendingMode blendMode, + NSVisualEffectState activationState) = 0; }; #endif diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index a3ce192e6e3..c7a5d55344a 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -19,11 +19,12 @@ #include #endif -#include +#include Q_FORWARD_DECLARE_OBJC_CLASS(NSWindow); Q_FORWARD_DECLARE_OBJC_CLASS(NSView); Q_FORWARD_DECLARE_OBJC_CLASS(NSCursor); +Q_FORWARD_DECLARE_OBJC_CLASS(NSVisualEffectView); #if !defined(__OBJC__) using NSInteger = long; @@ -227,6 +228,11 @@ public: // for QNSView CALayer *contentLayer() const override; + void manageVisualEffectArea(quintptr identifier, const QRect &rect, + NSVisualEffectMaterial material, NSVisualEffectBlendingMode blendMode, + NSVisualEffectState activationState) override; + QFlatMap m_effectViews; + NSView *m_view = nil; QCocoaNSWindow *m_nsWindow = nil; diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 9a8e5246b80..d0abb8e38c4 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -2224,6 +2224,47 @@ CALayer *QCocoaWindow::contentLayer() const return layer; } +void QCocoaWindow::manageVisualEffectArea(quintptr identifier, const QRect &rect, + NSVisualEffectMaterial material, NSVisualEffectBlendingMode blendMode, + NSVisualEffectState activationState) +{ + if (!qt_objc_cast(m_view.layer)) { + qCWarning(lcQpaWindow) << "Can not manage visual effect areas" + << "in views without a container layer"; + return; + } + + qCDebug(lcQpaWindow) << "Updating visual effect area" << identifier + << "to" << rect << "with material" << material << "blend mode" + << blendMode << "and activation state" << activationState; + + NSVisualEffectView *effectView = nullptr; + if (m_effectViews.contains(identifier)) { + effectView = m_effectViews.value(identifier); + if (rect.isEmpty()) { + [effectView removeFromSuperview]; + m_effectViews.remove(identifier); + return; + } + } else if (!rect.isEmpty()) { + effectView = [NSVisualEffectView new]; + // Ensure that the visual effect layer is stacked well + // below our content layer (which defaults to a z of 0). + effectView.wantsLayer = YES; + effectView.layer.zPosition = -FLT_MAX; + [m_view addSubview:effectView]; + m_effectViews.insert(identifier, effectView); + } + + if (!effectView) + return; + + effectView.frame = rect.toCGRect(); + effectView.material = material; + effectView.blendingMode = blendMode; + effectView.state = activationState; +} + #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug debug, const QCocoaWindow *window) {