diff --git a/README.md b/README.md index a639a0c..a2b4f9b 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,23 @@ You can share your findings, thoughts and ideas on improving / implementing QWin ![image](./docs/images/win10.png) -### macOS & Linux +### macOS -| macOS | Linux (Ubuntu 20.04) | -|:-------------------------------:|:---------------------------------:| -| ![image](./docs/images/mac.png) | ![image](./docs/images/linux.png) | +![image](./docs/images/mac.png) + +| default | glass - regular | +|:---------------------------------------------------------------------:|:---------------------------------------------------------------------------------:| +| ![default](./docs/images/macos/01%20default.png) | ![glass - regular](./docs/images/macos/02%20glass%20-%20regular.png) | +| glass - clear | glass - regular, rounded | +| ![glass - clear](./docs/images/macos/03%20glass%20-%20clear.png) | ![glass - regular, rounded](./docs/images/macos/04%20glass%20-%20regular,%20rounded.png) | +| glass - regular, dark tint | glass - regular, light tint | +| ![glass - regular, dark tint](./docs/images/macos/05%20glass%20-%20regular,%20dark%20tint.png) | ![glass - regular, light tint](./docs/images/macos/06%20glass%20-%20regular,%20light%20tint.png) | +| legacy - dark blur | legacy - light blur | +| ![legacy - dark blur](./docs/images/macos/07%20legacy%20-%20dark%20blur.png) | ![legacy - light blur](./docs/images/macos/08%20legacy%20-%20light%20blur.png) | + +### Linux + +![image](./docs/images/linux.png) ## Requirements diff --git a/docs/images/macos/01 default.png b/docs/images/macos/01 default.png new file mode 100644 index 0000000..d81580b Binary files /dev/null and b/docs/images/macos/01 default.png differ diff --git a/docs/images/macos/02 glass - regular.png b/docs/images/macos/02 glass - regular.png new file mode 100644 index 0000000..2b35c3e Binary files /dev/null and b/docs/images/macos/02 glass - regular.png differ diff --git a/docs/images/macos/03 glass - clear.png b/docs/images/macos/03 glass - clear.png new file mode 100644 index 0000000..d3e92a0 Binary files /dev/null and b/docs/images/macos/03 glass - clear.png differ diff --git a/docs/images/macos/04 glass - regular, rounded.png b/docs/images/macos/04 glass - regular, rounded.png new file mode 100644 index 0000000..89aa04d Binary files /dev/null and b/docs/images/macos/04 glass - regular, rounded.png differ diff --git a/docs/images/macos/05 glass - regular, dark tint.png b/docs/images/macos/05 glass - regular, dark tint.png new file mode 100644 index 0000000..68b28b3 Binary files /dev/null and b/docs/images/macos/05 glass - regular, dark tint.png differ diff --git a/docs/images/macos/06 glass - regular, light tint.png b/docs/images/macos/06 glass - regular, light tint.png new file mode 100644 index 0000000..967fde8 Binary files /dev/null and b/docs/images/macos/06 glass - regular, light tint.png differ diff --git a/docs/images/macos/07 legacy - dark blur.png b/docs/images/macos/07 legacy - dark blur.png new file mode 100644 index 0000000..42ae003 Binary files /dev/null and b/docs/images/macos/07 legacy - dark blur.png differ diff --git a/docs/images/macos/08 legacy - light blur.png b/docs/images/macos/08 legacy - light blur.png new file mode 100644 index 0000000..837caa1 Binary files /dev/null and b/docs/images/macos/08 legacy - light blur.png differ diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp index d5408a2..18de806 100644 --- a/examples/mainwindow/mainwindow.cpp +++ b/examples/mainwindow/mainwindow.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -227,46 +229,108 @@ void MainWindow::installWindowAgent() { // - false: Show native system buttons (default behavior) windowAgent->setWindowAttribute(QStringLiteral("no-system-buttons"), false); - 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")) { + auto polishMacStyle = [this](bool customStyle) { + setProperty("custom-style", customStyle); + style()->polish(this); + }; + + auto applyMacBlurEffect = [this, polishMacStyle](const QString &effect) { + windowAgent->setWindowAttribute(QStringLiteral("glass-effect"), QStringLiteral("none")); + if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), effect)) { + return; + } + polishMacStyle(effect != QStringLiteral("none")); + }; + + auto applyMacGlassEffect = [this, polishMacStyle](const QString &effect, + qreal radius = 0, + const QVariant &tintColor = {}) { + windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), QStringLiteral("none")); + windowAgent->setWindowAttribute(QStringLiteral("glass-corner-radius"), radius); + windowAgent->setWindowAttribute(QStringLiteral("glass-tint-color"), + tintColor.isValid() ? tintColor : QVariant(QStringLiteral("none"))); + if (!windowAgent->setWindowAttribute(QStringLiteral("glass-effect"), effect)) { return; } + polishMacStyle(effect != QStringLiteral("none")); + }; + + auto glassRegularAction = new QAction(tr("Glass: regular"), menuBar); + glassRegularAction->setCheckable(true); + connect(glassRegularAction, &QAction::toggled, this, [applyMacGlassEffect](bool checked) { if (checked) { - setProperty("custom-style", true); - style()->polish(this); + applyMacGlassEffect(QStringLiteral("regular")); } }); - 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; + auto glassClearAction = new QAction(tr("Glass: clear"), menuBar); + glassClearAction->setCheckable(true); + connect(glassClearAction, &QAction::toggled, this, [applyMacGlassEffect](bool checked) { + if (checked) { + applyMacGlassEffect(QStringLiteral("clear")); } + }); + + auto glassRegularRoundedAction = new QAction(tr("Glass: regular, rounded"), menuBar); + glassRegularRoundedAction->setCheckable(true); + connect(glassRegularRoundedAction, &QAction::toggled, this, + [applyMacGlassEffect](bool checked) { + if (checked) { + applyMacGlassEffect(QStringLiteral("regular"), 24); + } + }); + + auto glassRegularDarkTintAction = new QAction(tr("Glass: regular, dark tint"), menuBar); + glassRegularDarkTintAction->setCheckable(true); + connect(glassRegularDarkTintAction, &QAction::toggled, this, + [applyMacGlassEffect](bool checked) { + if (checked) { + applyMacGlassEffect(QStringLiteral("regular"), 0, QColor(0, 0, 0, 46)); + } + }); + + auto glassRegularLightTintAction = new QAction(tr("Glass: regular, light tint"), menuBar); + glassRegularLightTintAction->setCheckable(true); + connect(glassRegularLightTintAction, &QAction::toggled, this, + [applyMacGlassEffect](bool checked) { + if (checked) { + applyMacGlassEffect(QStringLiteral("regular"), 0, QColor(255, 255, 255, 46)); + } + }); + + auto darkBlurAction = new QAction(tr("Dark blur"), menuBar); + darkBlurAction->setCheckable(true); + connect(darkBlurAction, &QAction::toggled, this, [applyMacBlurEffect](bool checked) { if (checked) { - setProperty("custom-style", true); - style()->polish(this); + applyMacBlurEffect(QStringLiteral("dark")); } }); - 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; + auto lightBlurAction = new QAction(tr("Light blur"), menuBar); + lightBlurAction->setCheckable(true); + connect(lightBlurAction, &QAction::toggled, this, [applyMacBlurEffect](bool checked) { + if (checked) { + applyMacBlurEffect(QStringLiteral("light")); } + }); + + auto noEffectAction = new QAction(tr("No effect"), menuBar); + noEffectAction->setCheckable(true); + connect(noEffectAction, &QAction::toggled, this, [applyMacGlassEffect](bool checked) { if (checked) { - setProperty("custom-style", false); - style()->polish(this); + applyMacGlassEffect(QStringLiteral("none")); } }); auto macStyleGroup = new QActionGroup(menuBar); + macStyleGroup->addAction(glassRegularAction); + macStyleGroup->addAction(glassClearAction); + macStyleGroup->addAction(glassRegularRoundedAction); + macStyleGroup->addAction(glassRegularDarkTintAction); + macStyleGroup->addAction(glassRegularLightTintAction); macStyleGroup->addAction(darkBlurAction); macStyleGroup->addAction(lightBlurAction); - macStyleGroup->addAction(noBlurAction); + macStyleGroup->addAction(noEffectAction); #endif // Real menu @@ -283,9 +347,15 @@ void MainWindow::installWindowAgent() { settings->addSeparator(); settings->addAction(borderColorAction); #elif defined(Q_OS_MAC) + settings->addAction(glassRegularAction); + settings->addAction(glassClearAction); + settings->addAction(glassRegularRoundedAction); + settings->addAction(glassRegularDarkTintAction); + settings->addAction(glassRegularLightTintAction); + settings->addSeparator(); settings->addAction(darkBlurAction); settings->addAction(lightBlurAction); - settings->addAction(noBlurAction); + settings->addAction(noEffectAction); #endif menuBar->addMenu(file); diff --git a/examples/qml/FramelessWindow.qml b/examples/qml/FramelessWindow.qml index 592c90a..3404820 100644 --- a/examples/qml/FramelessWindow.qml +++ b/examples/qml/FramelessWindow.qml @@ -7,6 +7,7 @@ import QWindowKit 1.0 Window { property bool showWhenReady: true property alias titleBar: titleBar + readonly property bool isMacOS: Qt.platform.os === "osx" id: window width: 800 @@ -30,6 +31,20 @@ Window { readonly property color windowBackgroundColor: "#1E1E1E" } + function applyMacBlurEffect(effect) { + window.color = effect === "none" ? darkStyle.windowBackgroundColor : "transparent" + windowAgent.setWindowAttribute("glass-effect", "none") + windowAgent.setWindowAttribute("blur-effect", effect) + } + + function applyMacGlassEffect(effect, tintColor, radius) { + window.color = effect === "none" ? darkStyle.windowBackgroundColor : "transparent" + windowAgent.setWindowAttribute("blur-effect", "none") + windowAgent.setWindowAttribute("glass-corner-radius", radius === undefined ? 0 : radius) + windowAgent.setWindowAttribute("glass-tint-color", tintColor === undefined ? "none" : tintColor) + windowAgent.setWindowAttribute("glass-effect", effect) + } + Timer { interval: 100 running: true @@ -63,30 +78,41 @@ Window { anchors { verticalCenter: parent.verticalCenter left: parent.left - leftMargin: 10 + leftMargin: window.isMacOS ? 0 : 10 } - width: 18 - height: 18 + visible: !window.isMacOS + width: window.isMacOS ? 0 : 18 + height: width mipmap: true source: "qrc:///app/example.png" fillMode: Image.PreserveAspectFit - Component.onCompleted: windowAgent.setSystemButton(WindowAgent.WindowIcon, iconButton) + Component.onCompleted: { + if (!window.isMacOS) { + windowAgent.setSystemButton(WindowAgent.WindowIcon, iconButton) + } + } } Text { anchors { verticalCenter: parent.verticalCenter left: iconButton.right - leftMargin: 10 + leftMargin: window.isMacOS ? 0 : 10 + right: captionButtonRow.left + rightMargin: 10 } horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight text: window.title font.pixelSize: 14 color: "#ECECEC" } Row { + id: captionButtonRow + visible: !window.isMacOS + width: visible ? implicitWidth : 0 anchors { top: parent.top right: parent.right @@ -98,7 +124,11 @@ Window { height: parent.height source: "qrc:///window-bar/minimize.svg" onClicked: window.showMinimized() - Component.onCompleted: windowAgent.setSystemButton(WindowAgent.Minimize, minButton) + Component.onCompleted: { + if (!window.isMacOS) { + windowAgent.setSystemButton(WindowAgent.Minimize, minButton) + } + } } QWKButton { @@ -112,7 +142,11 @@ Window { window.showMaximized() } } - Component.onCompleted: windowAgent.setSystemButton(WindowAgent.Maximize, maxButton) + Component.onCompleted: { + if (!window.isMacOS) { + windowAgent.setSystemButton(WindowAgent.Maximize, maxButton) + } + } } QWKButton { @@ -134,7 +168,11 @@ Window { } } onClicked: window.close() - Component.onCompleted: windowAgent.setSystemButton(WindowAgent.Close, closeButton) + Component.onCompleted: { + if (!window.isMacOS) { + windowAgent.setSystemButton(WindowAgent.Close, closeButton) + } + } } } } @@ -255,6 +293,66 @@ Window { windowAgent.setWindowAttribute("mica-alt", true) } } + + MenuSeparator { + visible: Qt.platform.os === "osx" + } + + MenuItem { + enabled: Qt.platform.os === "osx" + text: qsTr("Glass: regular") + checkable: true + onTriggered: window.applyMacGlassEffect("regular") + } + + MenuItem { + enabled: Qt.platform.os === "osx" + text: qsTr("Glass: clear") + checkable: true + onTriggered: window.applyMacGlassEffect("clear") + } + + MenuItem { + enabled: Qt.platform.os === "osx" + text: qsTr("Glass: regular, rounded") + checkable: true + onTriggered: window.applyMacGlassEffect("regular", undefined, 24) + } + + MenuItem { + enabled: Qt.platform.os === "osx" + text: qsTr("Glass: regular, dark tint") + checkable: true + onTriggered: window.applyMacGlassEffect("regular", Qt.rgba(0, 0, 0, 0.18)) + } + + MenuItem { + enabled: Qt.platform.os === "osx" + text: qsTr("Glass: regular, light tint") + checkable: true + onTriggered: window.applyMacGlassEffect("regular", Qt.rgba(1, 1, 1, 0.18)) + } + + MenuItem { + enabled: Qt.platform.os === "osx" + text: qsTr("Dark blur") + checkable: true + onTriggered: window.applyMacBlurEffect("dark") + } + + MenuItem { + enabled: Qt.platform.os === "osx" + text: qsTr("Light blur") + checkable: true + onTriggered: window.applyMacBlurEffect("light") + } + + MenuItem { + enabled: Qt.platform.os === "osx" + text: qsTr("No effect") + checkable: true + onTriggered: window.applyMacGlassEffect("none") + } } } } diff --git a/src/core/contexts/cocoawindowcontext.mm b/src/core/contexts/cocoawindowcontext.mm index c757bf4..954bb6e 100644 --- a/src/core/contexts/cocoawindowcontext.mm +++ b/src/core/contexts/cocoawindowcontext.mm @@ -4,12 +4,15 @@ #include "cocoawindowcontext_p.h" +#include + #include #include #include #include +#include #include "qwkglobal_p.h" #include "systemwindow_p.h" @@ -154,6 +157,12 @@ - (void)detach; None, }; + enum class GlassMode { + Regular, + Clear, + None, + }; + NSWindowProxy(NSView *macView) { nsview = macView; @@ -319,34 +328,68 @@ inline int titleBarHeight() const { } // Blur effect - bool setBlurEffect(BlurMode mode) { + static NSString *blurEffectViewIdentifier() { return @"QWindowKitBlurEffectView"; } + + NSVisualEffectView *findBlurEffectView(bool create) { static Class visualEffectViewClass = NSClassFromString(@"NSVisualEffectView"); if (!visualEffectViewClass) - return false; + return nil; + + NSView *container = [nsview superview]; + if (!container) { + return nil; + } - NSVisualEffectView *effectView = nil; - for (NSView *subview in [[nsview superview] subviews]) { - if ([subview isKindOfClass:visualEffectViewClass]) { - effectView = reinterpret_cast(subview); + const auto identifier = NSWindowProxy::blurEffectViewIdentifier(); + for (NSView *subview in [container subviews]) { + if ([subview.identifier isEqualToString:identifier] && + [subview isKindOfClass:visualEffectViewClass]) { + return reinterpret_cast(subview); } } - if (effectView == nil) { - return false; + + if (!create) { + return nil; } - static const auto originalMaterial = effectView.material; - static const auto originalBlendingMode = effectView.blendingMode; - static const auto originalState = effectView.state; + NSVisualEffectView *effectView = + [[visualEffectViewClass alloc] initWithFrame:container.bounds]; + effectView.identifier = identifier; + effectView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + effectView.wantsLayer = YES; + effectView.layer.opaque = NO; + + [container addSubview:effectView positioned:NSWindowBelow relativeTo:nsview]; + [effectView release]; + return effectView; + } + + bool setBlurEffect(BlurMode mode) { + auto effectView = findBlurEffectView(mode != BlurMode::None); + if (!effectView) { + return mode == BlurMode::None; + } + effectView.frame = [effectView.superview bounds]; if (mode == BlurMode::None) { - effectView.material = originalMaterial; - effectView.blendingMode = originalBlendingMode; - effectView.state = originalState; - effectView.appearance = nil; + effectView.hidden = YES; } else { + auto nswindow = [nsview window]; + if (!nswindow) { + return false; + } + + nswindow.opaque = NO; + nswindow.backgroundColor = NSColor.clearColor; + + nsview.wantsLayer = YES; + nsview.layer.opaque = NO; + nsview.layer.backgroundColor = NSColor.clearColor.CGColor; + + effectView.hidden = NO; effectView.material = NSVisualEffectMaterialUnderWindowBackground; effectView.blendingMode = NSVisualEffectBlendingModeBehindWindow; - effectView.state = NSVisualEffectStateFollowsWindowActiveState; + effectView.state = NSVisualEffectStateActive; if (mode == BlurMode::Dark) { effectView.appearance = @@ -359,6 +402,169 @@ bool setBlurEffect(BlurMode mode) { return true; } + // Glass effect + static Class glassEffectViewClass() { + if (@available(macOS 26.0, *)) { + static Class glassEffectViewClass = NSClassFromString(@"NSGlassEffectView"); + return glassEffectViewClass; + } + return nil; + } + + static bool isGlassEffectAvailable() { return glassEffectViewClass() != nil; } + + NSView *findGlassEffectView(bool create) { + const auto glassEffectViewClass = NSWindowProxy::glassEffectViewClass(); + if (!glassEffectViewClass) { + return nil; + } + + NSView *container = [nsview superview]; + if (!container) { + return nil; + } + + static NSString *glassEffectViewIdentifier = @"QWindowKitGlassEffectView"; + for (NSView *subview in [container subviews]) { + if ([subview.identifier isEqualToString:glassEffectViewIdentifier] && + [subview isKindOfClass:glassEffectViewClass]) { + return subview; + } + } + + if (!create) { + return nil; + } + + NSView *glassView = [[glassEffectViewClass alloc] initWithFrame:container.bounds]; + glassView.identifier = glassEffectViewIdentifier; + glassView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + glassView.wantsLayer = YES; + glassView.layer.opaque = NO; + + NSView *contentView = [[NSView alloc] initWithFrame:glassView.bounds]; + contentView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + [glassView setValue:contentView forKey:@"contentView"]; + [contentView release]; + + [container addSubview:glassView positioned:NSWindowBelow relativeTo:nsview]; + [glassView release]; + return glassView; + } + + NSColor *glassTintNSColor() const { + if (!glassTintColor.isValid()) { + return nil; + } + return [NSColor colorWithSRGBRed:glassTintColor.redF() + green:glassTintColor.greenF() + blue:glassTintColor.blueF() + alpha:glassTintColor.alphaF()]; + } + + void updateGlassContainerCornerRadius(NSView *glassView) { + NSView *container = glassView ? glassView.superview : [nsview superview]; + if (!container) { + return; + } + + if (!glassContainerLayerStateCaptured) { + originalGlassContainerWantsLayer = container.wantsLayer; + if (container.layer) { + originalGlassContainerCornerRadius = container.layer.cornerRadius; + originalGlassContainerMasksToBounds = container.layer.masksToBounds; + } + glassContainerLayerStateCaptured = true; + } + + const auto needsCustomRadius = glassMode != GlassMode::None && glassCornerRadius > 0; + container.wantsLayer = needsCustomRadius || originalGlassContainerWantsLayer; + + auto layer = container.layer; + if (!layer) { + return; + } + + layer.cornerRadius = needsCustomRadius ? glassCornerRadius + : originalGlassContainerCornerRadius; + layer.masksToBounds = needsCustomRadius ? YES : originalGlassContainerMasksToBounds; + + if (!needsCustomRadius && !originalGlassContainerWantsLayer) { + container.wantsLayer = NO; + } + } + + bool applyGlassEffectSettings(NSView *glassView) { + if (!glassView) { + return false; + } + + glassView.frame = [glassView.superview bounds]; + glassView.hidden = glassMode == GlassMode::None; + if (glassMode == GlassMode::None) { + updateGlassContainerCornerRadius(glassView); + return true; + } + + const auto glassEffectViewClass = NSWindowProxy::glassEffectViewClass(); + if (!glassEffectViewClass || ![glassView isKindOfClass:glassEffectViewClass]) { + return false; + } + + auto nswindow = [nsview window]; + if (!nswindow) { + return false; + } + + nswindow.opaque = NO; + nswindow.backgroundColor = NSColor.clearColor; + + nsview.wantsLayer = YES; + nsview.layer.opaque = NO; + nsview.layer.backgroundColor = NSColor.clearColor.CGColor; + + updateGlassContainerCornerRadius(glassView); + [glassView setValue:@(glassMode == GlassMode::Clear ? 1 : 0) forKey:@"style"]; + [glassView setValue:@(glassCornerRadius) forKey:@"cornerRadius"]; + [glassView setValue:glassTintNSColor() forKey:@"tintColor"]; + return true; + } + + bool setGlassEffect(GlassMode mode) { + if (mode == GlassMode::None) { + glassMode = mode; + if (auto glassView = findGlassEffectView(false)) { + return applyGlassEffectSettings(glassView); + } + return true; + } + + if (!isGlassEffectAvailable()) { + return false; + } + + glassMode = mode; + return applyGlassEffectSettings(findGlassEffectView(true)); + } + + bool setGlassCornerRadius(qreal radius) { + glassCornerRadius = std::max(0, radius); + + if (glassMode == GlassMode::None) { + return true; + } + return applyGlassEffectSettings(findGlassEffectView(true)); + } + + bool setGlassTintColor(const QColor &color) { + glassTintColor = color; + + if (glassMode == GlassMode::None) { + return true; + } + return applyGlassEffectSettings(findGlassEffectView(true)); + } + // System title bar void setSystemTitleBarVisible(const bool visible) { auto nswindow = [nsview window]; @@ -538,6 +744,14 @@ static void sendEvent(id obj, SEL sel, NSEvent *event) { bool checkButton = true; ScreenRectCallback screenRectCallback; + GlassMode glassMode = GlassMode::None; + qreal glassCornerRadius = 0; + QColor glassTintColor; + bool glassContainerLayerStateCaptured = false; + BOOL originalGlassContainerWantsLayer = NO; + CGFloat originalGlassContainerCornerRadius = 0; + BOOL originalGlassContainerMasksToBounds = NO; + static inline QWK_NSWindowObserver *windowObserver = nil; // NSEvent *lastMouseDownEvent = nil; @@ -828,6 +1042,71 @@ static inline void releaseWindowProxy(const WId windowId) { } return ensureWindowProxy(m_windowId)->setBlurEffect(mode); } + + if (key == QStringLiteral("glass-effect")) { + auto mode = NSWindowProxy::GlassMode::None; +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + if (attribute.type() == QVariant::Bool) { +#else + if (attribute.typeId() == QMetaType::Type::Bool) { +#endif + mode = attribute.toBool() ? NSWindowProxy::GlassMode::Regular + : NSWindowProxy::GlassMode::None; +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + } else if (attribute.type() == QVariant::String) { +#else + } else if (attribute.typeId() == QMetaType::Type::QString) { +#endif + const auto value = attribute.toString(); + if (value == QStringLiteral("regular")) { + mode = NSWindowProxy::GlassMode::Regular; + } else if (value == QStringLiteral("clear")) { + mode = NSWindowProxy::GlassMode::Clear; + } else if (value == QStringLiteral("none")) { + // ... + } else { + return false; + } + } else { + return false; + } + return ensureWindowProxy(m_windowId)->setGlassEffect(mode); + } + + if (key == QStringLiteral("glass-corner-radius")) { + bool ok = false; + const auto radius = attribute.toDouble(&ok); + if (!ok) { + return false; + } + return ensureWindowProxy(m_windowId)->setGlassCornerRadius(radius); + } + + if (key == QStringLiteral("glass-tint-color")) { + if (attribute.isNull()) { + return ensureWindowProxy(m_windowId)->setGlassTintColor(QColor()); + } + + QColor color; + if (attribute.canConvert()) { + color = attribute.value(); + } + + if (!color.isValid() && attribute.canConvert()) { + const auto value = attribute.toString(); + if (value.isEmpty() || value == QStringLiteral("none") || + value == QStringLiteral("transparent")) { + return ensureWindowProxy(m_windowId)->setGlassTintColor(QColor()); + } + color = QColor(value); + } + + if (!color.isValid()) { + return false; + } + + return ensureWindowProxy(m_windowId)->setGlassTintColor(color); + } return false; } @@ -906,4 +1185,4 @@ - (void)observeValueForKeyPath:(NSString *)keyPath _proxy->setButtonsVisible(_proxy->hasButtonVisible()); } -@end \ No newline at end of file +@end diff --git a/src/core/windowagentbase.cpp b/src/core/windowagentbase.cpp index 1cbca12..c36c36e 100644 --- a/src/core/windowagentbase.cpp +++ b/src/core/windowagentbase.cpp @@ -125,6 +125,15 @@ namespace QWK { \li \c blur-effect: You can specify a string value, "dark" to enable dark mode, "light" to set enable mode, "none" to disable. You can also specify a boolean value, \c true to enable current theme mode, \c false to disable. + \li \c glass-effect: Specify a string value, "regular" or "clear" to enable the + Liquid Glass effect, "none" to disable. You can also specify a boolean value, + \c true to enable regular glass, \c false to disable. This attribute is only + available on macOS 26 and later; enabling it returns \c false when unavailable. + \li \c glass-corner-radius: Specify a real value to set the Liquid Glass corner radius. + This attribute is only available on macOS 26 and later. + \li \c glass-tint-color: Specify a \c QColor to set the Liquid Glass tint color, or + an invalid value to clear the tint. This attribute is only available on macOS + 26 and later. \li \c title-bar-height: Returns the system title bar height, the system button display area will be limited to this height. (Readonly) */