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 <richard.gustavsen@qt.io>
This commit is contained in:
Tor Arne Vestbø 2025-04-02 15:47:59 +02:00
parent db7e2d6fd0
commit 6f67925799
3 changed files with 55 additions and 1 deletions

View File

@ -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

View File

@ -19,11 +19,12 @@
#include <MoltenVK/mvk_vulkan.h>
#endif
#include <QHash>
#include <QtCore/private/qflatmap_p.h>
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<quintptr, NSVisualEffectView*> m_effectViews;
NSView *m_view = nil;
QCocoaNSWindow *m_nsWindow = nil;

View File

@ -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<QContainerLayer*>(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)
{