Add native Liquid Glass effect support for macOS (#4534)

* feat: Implement native Liquid Glass effect for macOS

feat: Add platform check for Liquid Glass demo

Show informative dialog on Windows/Linux explaining that the Liquid Glass
effect is a macOS-specific feature. The demo will exit gracefully on
non-macOS platforms.

docs: Add Liquid Glass feature to unreleased changelog

feat: Enhanced Liquid Glass effect with NSVisualEffectMaterial support

Major improvements to the Liquid Glass implementation for macOS:

- Added comprehensive NSVisualEffectMaterial support with 15+ material options
- Removed debug NSLog statements for cleaner production code
- Created multi-window demo showcasing 7 different glass effects:
  * Light Style - Clean light appearance
  * Dark Style - Dark themed glass
  * Vibrant Style - Enhanced transparency
  * Blue Tint - Custom RGBA tint color example
  * Sheet Material - NSVisualEffectMaterialSheet
  * HUD Window - Ultra-light HUD material
  * Content Background - With warm tint color
- Added Material field to MacLiquidGlass struct for fine-grained control
- Improved demo design with proper Title Case and cleaner layout
- Fixed logo sizing to prevent blur
- All windows fully draggable with InvisibleTitleBarHeight
- Added comprehensive README documentation

The implementation now provides developers with complete control over the
glass effect appearance, supporting both native NSGlassEffectView (macOS 15.0+)
and NSVisualEffectView fallback for older systems.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

feat: Implement native Liquid Glass effect for macOS

- Add support for NSGlassEffectView on macOS 15.0+
- Implement runtime detection of native glass APIs
- Add fallback to enhanced NSVisualEffectView for older systems
- Update liquid glass demo with frameless windows for better visibility
- Support all NSGlassEffectView properties (cornerRadius, tintColor, style)
- Properly handle webview layering with glass effect
- Remove binary from version control

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Address CodeRabbit review feedback

- Comment out unimplemented ReduceMotion and StaticMode fields
- Remove overly broad draggable CSS properties
- Add corner radius validation
- Improve CSS with proper pointer-events and user-select
- Add clarifying comments about future features

* fix: Remove unimplemented ReduceMotion and StaticMode fields

Completely remove the commented-out performance optimization fields
as they are not implemented and have no timeline for implementation.

* fix: Update windowRemoveVisualEffects to also remove NSGlassEffectView instances

The cleanup function now properly removes both NSVisualEffectView and
NSGlassEffectView instances to prevent orphaned effect layers. Uses
NSClassFromString to avoid hard references to NSGlassEffectView which
is only available on macOS 15.0+.

* fix changelog

* Update v3/pkg/application/webview_window_darwin.m

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update v3/pkg/application/webview_window_darwin.m

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: Fix compilation errors in windowSetLiquidGlass

- Add missing NSGlassEffectViewStyle enum definition
- Fix undefined tintColor variable by creating NSColor before use
- Add autorelease to prevent memory leaks for allocated views

These issues were causing CI build failures while working locally due to different compiler settings.

* Update Taskfile.yaml

* feat: Implement groupID and groupSpacing for NSGlassEffectView

- Add runtime detection for groupIdentifier/groupName selectors
- Apply groupID via performSelector if supported
- Apply groupSpacing via KVC if supported
- Parameters are now functional when NSGlassEffectView supports them
- Maintains backward compatibility by checking selector availability

* test: Add liquid-glass example to test suite

- Add liquid-glass to EXAMPLEDIRS in Taskfile.yaml
- Ensures the example is tested during CI builds
- Validates compilation on different platforms

Addresses review comment about missing test coverage

* fix: Correct NSGlassEffectView availability to macOS 26.0

- Update @available checks from macOS 15.0 to 26.0 for NSGlassEffectView
- NSGlassEffectView is a private API introduced in macOS 26.0
- Update README to reflect correct version requirement
- Keep NSVisualEffectMaterial checks at 15.0 as those are different APIs

* fix: Prevent exceptions from unsafe WebView reparenting

- Remove early WebView addition to glassView.contentView
- Consolidate all WebView reparenting in one safe location
- Always call removeFromSuperview before adding to new parent
- Set frame and autoresizing mask after safe reparenting
- Prevents NSInternalInconsistencyException from multiple parents

* fix: Make WebView reparenting more robust and thread-safe

- Always call removeFromSuperview before adding to new parent
- Remove brittle superview check, always detach and reattach
- Check both webView and glassContentView are non-nil before operations
- Ensure all UI operations run on main thread with dispatch_sync
- Set frame and autoresizing mask after safe reparenting

* Tidy up

* Update changelog

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Lea Anthony 2025-08-24 07:16:19 +10:00 committed by GitHub
commit 4bfc52f0b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 725 additions and 9 deletions

View file

@ -184,6 +184,7 @@ tasks:
html-dnd-api
ignore-mouse
keybindings
liquid-glass
menu
notifications
panic-handling
@ -308,6 +309,7 @@ tasks:
html-dnd-api
ignore-mouse
keybindings
liquid-glass
menu
notifications
panic-handling

View file

@ -17,6 +17,7 @@ After processing, the content will be moved to the main changelog and this file
## Added
<!-- New features, capabilities, or enhancements -->
- Add native Liquid Glass effect support for macOS with NSGlassEffectView (macOS 15.0+) and NSVisualEffectView fallback, including comprehensive material customization options by @leaanthony in [#4534](https://github.com/wailsapp/wails/pull/4534)
## Changed
<!-- Changes in existing functionality -->

1
v3/examples/liquid-glass/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
liquid-glass-demo

View file

@ -0,0 +1,70 @@
# Liquid Glass Demo for Wails v3
This demo showcases the native Liquid Glass effect available in macOS 15.0+ with fallback to NSVisualEffectView for older systems.
## Features Demonstrated
### Window Styles
1. **Light Glass** - Clean, light appearance with no tint
2. **Dark Glass** - Dark themed glass effect
3. **Vibrant Glass** - Enhanced vibrant effect for maximum transparency
4. **Tinted Glass** - Blue tinted glass with custom RGBA color
5. **Sheet Material** - Using specific NSVisualEffectMaterialSheet
6. **HUD Window** - Ultra-light HUD window material
7. **Content Background** - Content background material with warm tint
### Customization Options
- **Style**: `LiquidGlassStyleAutomatic`, `LiquidGlassStyleLight`, `LiquidGlassStyleDark`, `LiquidGlassStyleVibrant`
- **Material**: Direct NSVisualEffectMaterial selection (when NSGlassEffectView is not available)
- `NSVisualEffectMaterialAppearanceBased`
- `NSVisualEffectMaterialLight`
- `NSVisualEffectMaterialDark`
- `NSVisualEffectMaterialSheet`
- `NSVisualEffectMaterialHUDWindow`
- `NSVisualEffectMaterialContentBackground`
- `NSVisualEffectMaterialUnderWindowBackground`
- `NSVisualEffectMaterialUnderPageBackground`
- And more...
- **CornerRadius**: Rounded corners (0 for square corners)
- **TintColor**: Custom RGBA tint overlay
- **GroupID**: For grouping multiple glass windows (future feature)
- **GroupSpacing**: Spacing between grouped windows (future feature)
### Running the Demo
```bash
go build -o liquid-glass-demo .
./liquid-glass-demo
```
### Requirements
- macOS 10.14+ (best experience on macOS 26.0+ with native NSGlassEffectView)
- Wails v3
### Implementation Details
The implementation uses:
- Native `NSGlassEffectView` on macOS 26.0+ for authentic glass effect
- Falls back to `NSVisualEffectView` on older systems
- Runtime detection using `NSClassFromString` for compatibility
- Key-Value Coding (KVC) for dynamic property setting
### Example Usage
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Mac: application.MacWindow{
Backdrop: application.MacBackdropLiquidGlass,
InvisibleTitleBarHeight: 500, // Make window draggable
LiquidGlass: application.MacLiquidGlass{
Style: application.LiquidGlassStyleLight,
Material: application.NSVisualEffectMaterialHUDWindow,
CornerRadius: 20.0,
TintColor: &application.RGBA{Red: 0, Green: 100, Blue: 200, Alpha: 50},
},
},
})
```

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wails Liquid Glass</title>
<style>
body {
margin: 0;
padding: 0;
background: transparent;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
/* Draggable is handled by InvisibleTitleBarHeight in Go code */
}
.container {
text-align: center;
}
.logo {
opacity: 0.75;
/* Images should not interfere with drag */
pointer-events: none;
user-select: none;
}
.title {
color: white;
margin-top: 30px;
font-size: 20px;
font-weight: 400;
letter-spacing: 0.5px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
font-family: -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
user-select: none;
}
</style>
</head>
<body>
<div class="container">
<img src="wails-logo.png" alt="Wails" class="logo">
<h1 class="title">LIQUID GLASS</h1>
</div>
</body>
</html>

View file

@ -0,0 +1,235 @@
package main
import (
_ "embed"
"encoding/base64"
"log"
"runtime"
"strings"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed index.html
var indexHTML string
//go:embed wails-logo.png
var wailsLogo []byte
func main() {
app := application.New(application.Options{
Name: "Wails Liquid Glass Demo",
Description: "Demonstrates the native Liquid Glass effect on macOS",
})
// Check if running on macOS
if runtime.GOOS != "darwin" {
// Show dialog for non-macOS platforms
application.InfoDialog().
SetTitle("macOS Only Demo").
SetMessage("The Liquid Glass effect is a macOS-specific feature that uses native NSGlassEffectView (macOS 15.0+) or NSVisualEffectView.\n\nThis demo is not available on " + runtime.GOOS + ".").
Show()
return
}
// Convert logo to base64 data URI
logoDataURI := "data:image/png;base64," + base64.StdEncoding.EncodeToString(wailsLogo)
// Create different HTML for each window
lightHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1)
lightHTML = strings.Replace(lightHTML, "LIQUID GLASS", "Light Style", 1)
darkHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1)
darkHTML = strings.Replace(darkHTML, "LIQUID GLASS", "Dark Style", 1)
vibrantHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1)
vibrantHTML = strings.Replace(vibrantHTML, "LIQUID GLASS", "Vibrant Style", 1)
tintedHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1)
tintedHTML = strings.Replace(tintedHTML, "LIQUID GLASS", "Blue Tint", 1)
sheetHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1)
sheetHTML = strings.Replace(sheetHTML, "LIQUID GLASS", "Sheet Material", 1)
hudHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1)
hudHTML = strings.Replace(hudHTML, "LIQUID GLASS", "HUD Window", 1)
contentHTML := strings.Replace(indexHTML, "wails-logo.png", logoDataURI, 1)
contentHTML = strings.Replace(contentHTML, "LIQUID GLASS", "Content Background", 1)
// Window 1: Light style with no tint
window1 := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Light Glass",
Width: 350,
Height: 280,
X: 100,
Y: 100,
Frameless: true,
EnableDragAndDrop: false,
HTML: lightHTML,
InitialPosition: application.WindowXY,
Mac: application.MacWindow{
Backdrop: application.MacBackdropLiquidGlass,
InvisibleTitleBarHeight: 500,
LiquidGlass: application.MacLiquidGlass{
Style: application.LiquidGlassStyleLight,
Material: application.NSVisualEffectMaterialAuto,
CornerRadius: 20.0,
TintColor: nil,
},
},
})
// Window 2: Dark style
window2 := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Dark Glass",
Width: 350,
Height: 280,
X: 500,
Y: 100,
Frameless: true,
EnableDragAndDrop: false,
HTML: darkHTML,
InitialPosition: application.WindowXY,
Mac: application.MacWindow{
Backdrop: application.MacBackdropLiquidGlass,
InvisibleTitleBarHeight: 500,
LiquidGlass: application.MacLiquidGlass{
Style: application.LiquidGlassStyleDark,
Material: application.NSVisualEffectMaterialAuto,
CornerRadius: 20.0,
TintColor: nil,
},
},
})
// Window 3: Vibrant style
window3 := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Vibrant Glass",
Width: 350,
Height: 280,
X: 900,
Y: 100,
Frameless: true,
EnableDragAndDrop: false,
HTML: vibrantHTML,
InitialPosition: application.WindowXY,
Mac: application.MacWindow{
Backdrop: application.MacBackdropLiquidGlass,
InvisibleTitleBarHeight: 500,
LiquidGlass: application.MacLiquidGlass{
Style: application.LiquidGlassStyleVibrant,
Material: application.NSVisualEffectMaterialAuto,
CornerRadius: 20.0,
TintColor: nil,
},
},
})
// Window 4: Blue tinted glass
window4 := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Tinted Glass",
Width: 350,
Height: 280,
X: 300,
Y: 420,
Frameless: true,
EnableDragAndDrop: false,
HTML: tintedHTML,
InitialPosition: application.WindowXY,
Mac: application.MacWindow{
Backdrop: application.MacBackdropLiquidGlass,
InvisibleTitleBarHeight: 500,
LiquidGlass: application.MacLiquidGlass{
Style: application.LiquidGlassStyleLight,
Material: application.NSVisualEffectMaterialAuto,
CornerRadius: 25.0, // Different corner radius
TintColor: &application.RGBA{Red: 0, Green: 100, Blue: 200, Alpha: 50}, // Blue tint
},
},
})
// Window 5: Using specific NSVisualEffectMaterial
window5 := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Sheet Material",
Width: 350,
Height: 280,
X: 700,
Y: 420,
Frameless: true,
EnableDragAndDrop: false,
HTML: sheetHTML,
InitialPosition: application.WindowXY,
Mac: application.MacWindow{
Backdrop: application.MacBackdropLiquidGlass,
InvisibleTitleBarHeight: 500,
LiquidGlass: application.MacLiquidGlass{
Style: application.LiquidGlassStyleAutomatic, // Automatic style
Material: application.NSVisualEffectMaterialSheet, // Specific material
CornerRadius: 15.0, // Different corner radius
TintColor: nil,
},
},
})
// Window 6: HUD Window Material (very light, translucent)
window6 := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "HUD Window",
Width: 350,
Height: 280,
X: 100,
Y: 740,
Frameless: true,
EnableDragAndDrop: false,
HTML: hudHTML,
InitialPosition: application.WindowXY,
Mac: application.MacWindow{
Backdrop: application.MacBackdropLiquidGlass,
InvisibleTitleBarHeight: 500,
LiquidGlass: application.MacLiquidGlass{
Style: application.LiquidGlassStyleAutomatic,
Material: application.NSVisualEffectMaterialHUDWindow, // HUD Window material - very light
CornerRadius: 30.0, // Larger corner radius
TintColor: nil,
},
},
})
// Window 7: Content Background Material
window7 := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Content Background",
Width: 350,
Height: 280,
X: 500,
Y: 740,
Frameless: true,
EnableDragAndDrop: false,
HTML: contentHTML,
InitialPosition: application.WindowXY,
Mac: application.MacWindow{
Backdrop: application.MacBackdropLiquidGlass,
InvisibleTitleBarHeight: 500,
LiquidGlass: application.MacLiquidGlass{
Style: application.LiquidGlassStyleAutomatic,
Material: application.NSVisualEffectMaterialContentBackground, // Content background
CornerRadius: 10.0, // Smaller corner radius
TintColor: &application.RGBA{Red: 0, Green: 200, Blue: 100, Alpha: 30}, // Warm tint
},
},
})
// Show all windows
window1.Show()
window2.Show()
window3.Show()
window4.Show()
window5.Show()
window6.Show()
window7.Show()
// Run the application
err := app.Run()
if err != nil {
log.Fatal(err)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -4,7 +4,7 @@ package application
/*
#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c
#cgo LDFLAGS: -framework Cocoa -framework WebKit
#cgo LDFLAGS: -framework Cocoa -framework WebKit -framework QuartzCore
#include "application_darwin.h"
#include "webview_window_darwin.h"
@ -1247,6 +1247,8 @@ func (w *macosWebviewWindow) run() {
case MacBackdropTranslucent:
C.windowSetTranslucent(w.nsWindow)
C.webviewSetTransparent(w.nsWindow)
case MacBackdropLiquidGlass:
w.applyLiquidGlass()
case MacBackdropNormal:
}
@ -1351,6 +1353,55 @@ func (w *macosWebviewWindow) setBackgroundColour(colour RGBA) {
C.windowSetBackgroundColour(w.nsWindow, C.int(colour.Red), C.int(colour.Green), C.int(colour.Blue), C.int(colour.Alpha))
}
func (w *macosWebviewWindow) applyLiquidGlass() {
options := w.parent.options.Mac.LiquidGlass
// Validate corner radius
if options.CornerRadius < 0 {
options.CornerRadius = 0
}
globalApplication.debug("Applying Liquid Glass effect", "window", w.parent.id)
// Check if liquid glass is supported
if !C.isLiquidGlassSupported() {
// Fallback to translucent
C.windowSetTranslucent(w.nsWindow)
C.webviewSetTransparent(w.nsWindow)
globalApplication.debug("Liquid Glass not supported on this macOS version, falling back to translucent", "window", w.parent.id)
return
}
// Prepare tint color values (already clamped by uint8 type)
var r, g, b, a C.int
if options.TintColor != nil {
r = C.int(options.TintColor.Red)
g = C.int(options.TintColor.Green)
b = C.int(options.TintColor.Blue)
a = C.int(options.TintColor.Alpha)
}
// Prepare group ID
var groupIDCStr *C.char
if options.GroupID != "" {
groupIDCStr = C.CString(options.GroupID)
defer C.free(unsafe.Pointer(groupIDCStr))
}
// Apply liquid glass effect
C.windowSetLiquidGlass(
w.nsWindow,
C.int(options.Style),
C.int(options.Material),
C.double(options.CornerRadius),
r, g, b, a,
groupIDCStr,
C.double(options.GroupSpacing),
)
globalApplication.debug("Applied Liquid Glass effect", "window", w.parent.id, "style", options.Style)
}
func (w *macosWebviewWindow) relativePosition() (int, int) {
var x, y C.int
InvokeSync(func() {

View file

@ -34,4 +34,12 @@
void windowSetScreen(void* window, void* screen, int yOffset);
// Liquid Glass support functions
bool isLiquidGlassSupported();
void windowSetLiquidGlass(void* window, int style, int material, double cornerRadius,
int r, int g, int b, int a,
const char* groupID, double groupSpacing);
void windowRemoveVisualEffects(void* window);
void configureWebViewForLiquidGlass(void* window);
#endif /* WebviewWindowDelegate_h */

View file

@ -1,6 +1,7 @@
//go:build darwin
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>
#import "webview_window_darwin.h"
#import "../events/events_darwin.h"
extern void processMessage(unsigned int, const char*);
@ -9,6 +10,14 @@ extern void processDragItems(unsigned int windowId, char** arr, int length, int
extern void processWindowKeyDownEvent(unsigned int, const char*);
extern bool hasListeners(unsigned int);
extern bool windowShouldUnconditionallyClose(unsigned int);
// Define NSGlassEffectView style constants (these match the Go constants)
typedef NS_ENUM(NSInteger, NSGlassEffectViewStyle) {
NSGlassEffectViewStyleAutomatic = 0,
NSGlassEffectViewStyleLight = 1,
NSGlassEffectViewStyleDark = 2,
NSGlassEffectViewStyleVibrant = 3
};
@implementation WebviewWindow
- (WebviewWindow*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation;
{
@ -321,14 +330,11 @@ extern bool windowShouldUnconditionallyClose(unsigned int);
- (BOOL)windowShouldClose:(NSWindow *)sender {
WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[sender delegate];
NSLog(@"[DEBUG] windowShouldClose called for window %d", delegate.windowId);
// Check if this window should close unconditionally (called from Close() method)
if (windowShouldUnconditionallyClose(delegate.windowId)) {
NSLog(@"[DEBUG] Window %d closing unconditionally (Close() method called)", delegate.windowId);
return true;
}
// For user-initiated closes, emit WindowClosing event and let the application decide
NSLog(@"[DEBUG] Window %d close requested by user - emitting WindowClosing event", delegate.windowId);
processWindowEvent(delegate.windowId, EventWindowShouldClose);
return false;
}
@ -544,13 +550,11 @@ extern bool windowShouldUnconditionallyClose(unsigned int);
}
}
- (void)windowDidOrderOffScreen:(NSNotification *)notification {
NSLog(@"[DEBUG] Window %d ordered OFF screen (hidden)", self.windowId);
if( hasListeners(EventWindowDidOrderOffScreen) ) {
processWindowEvent(self.windowId, EventWindowDidOrderOffScreen);
}
}
- (void)windowDidOrderOnScreen:(NSNotification *)notification {
NSLog(@"[DEBUG] Window %d ordered ON screen (shown)", self.windowId);
if( hasListeners(EventWindowDidOrderOnScreen) ) {
processWindowEvent(self.windowId, EventWindowDidOrderOnScreen);
}
@ -626,7 +630,6 @@ extern bool windowShouldUnconditionallyClose(unsigned int);
}
}
- (void)windowWillClose:(NSNotification *)notification {
NSLog(@"[DEBUG] Window %d WILL close (window is actually closing)", self.windowId);
if( hasListeners(EventWindowWillClose) ) {
processWindowEvent(self.windowId, EventWindowWillClose);
}
@ -672,13 +675,11 @@ extern bool windowShouldUnconditionallyClose(unsigned int);
}
}
- (void)windowWillOrderOffScreen:(NSNotification *)notification {
NSLog(@"[DEBUG] Window %d WILL order off screen (about to hide)", self.windowId);
if( hasListeners(EventWindowWillOrderOffScreen) ) {
processWindowEvent(self.windowId, EventWindowWillOrderOffScreen);
}
}
- (void)windowWillOrderOnScreen:(NSNotification *)notification {
NSLog(@"[DEBUG] Window %d WILL order on screen (about to show)", self.windowId);
if( hasListeners(EventWindowWillOrderOnScreen) ) {
processWindowEvent(self.windowId, EventWindowWillOrderOnScreen);
}
@ -815,3 +816,234 @@ void windowSetScreen(void* window, void* screen, int yOffset) {
// Set the frame which moves the window to the new screen
[nsWindow setFrame:frame display:YES];
}
// Check if Liquid Glass is supported on this system
bool isLiquidGlassSupported() {
// Check for macOS 26.0+ and NSGlassEffectView availability
if (@available(macOS 26.0, *)) {
return NSClassFromString(@"NSGlassEffectView") != nil;
}
return false;
}
// Remove any existing visual effects from the window
void windowRemoveVisualEffects(void* nsWindow) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
NSView* contentView = [window contentView];
// Get NSGlassEffectView class if available (avoid hard reference)
Class glassEffectViewClass = nil;
if (@available(macOS 26.0, *)) {
glassEffectViewClass = NSClassFromString(@"NSGlassEffectView");
}
// Remove all NSVisualEffectView and NSGlassEffectView subviews
NSArray* subviews = [contentView subviews];
for (NSView* subview in subviews) {
if ([subview isKindOfClass:[NSVisualEffectView class]] ||
(glassEffectViewClass && [subview isKindOfClass:glassEffectViewClass])) {
[subview removeFromSuperview];
}
}
}
// Configure WebView for liquid glass effect
void configureWebViewForLiquidGlass(void* nsWindow) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
WKWebView* webView = window.webView;
// Make WebView background transparent
[webView setValue:@NO forKey:@"drawsBackground"];
if (@available(macOS 10.12, *)) {
[webView setValue:[NSColor clearColor] forKey:@"backgroundColor"];
}
// Ensure WebView is above glass layer
if (webView.layer) {
webView.layer.zPosition = 1.0;
webView.layer.shouldRasterize = YES;
webView.layer.rasterizationScale = [[NSScreen mainScreen] backingScaleFactor];
}
}
// Apply Liquid Glass effect to window
void windowSetLiquidGlass(void* nsWindow, int style, int material, double cornerRadius,
int r, int g, int b, int a,
const char* groupID, double groupSpacing) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
// Ensure we're on the main thread for UI operations
if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
windowSetLiquidGlass(nsWindow, style, material, cornerRadius, r, g, b, a, groupID, groupSpacing);
});
return;
}
// Remove any existing visual effects
windowRemoveVisualEffects(nsWindow);
// Try to use NSGlassEffectView if available
NSView* glassView = nil;
if (@available(macOS 26.0, *)) {
Class NSGlassEffectViewClass = NSClassFromString(@"NSGlassEffectView");
if (NSGlassEffectViewClass) {
// Create NSGlassEffectView (autoreleased)
glassView = [[[NSGlassEffectViewClass alloc] init] autorelease];
// Set corner radius if the property exists
if (cornerRadius > 0 && [glassView respondsToSelector:@selector(setCornerRadius:)]) {
[glassView setValue:@(cornerRadius) forKey:@"cornerRadius"];
}
// Set tint color if the property exists and color is specified
if (a > 0 && [glassView respondsToSelector:@selector(setTintColor:)]) {
NSColor* tintColor = [NSColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a/255.0];
// Use performSelector to safely set tintColor if the setter exists
[glassView performSelector:@selector(setTintColor:) withObject:tintColor];
}
// Set style if the property exists
if ([glassView respondsToSelector:@selector(setStyle:)]) {
// For vibrant style, try to use Light style for a lighter effect
int lightStyle = (style == NSGlassEffectViewStyleVibrant) ? NSGlassEffectViewStyleLight : style;
[glassView setValue:@(lightStyle) forKey:@"style"];
}
// Set group identifier if the property exists and groupID is specified
if (groupID && strlen(groupID) > 0) {
if ([glassView respondsToSelector:@selector(setGroupIdentifier:)]) {
NSString* groupIDString = [NSString stringWithUTF8String:groupID];
[glassView performSelector:@selector(setGroupIdentifier:) withObject:groupIDString];
} else if ([glassView respondsToSelector:@selector(setGroupName:)]) {
NSString* groupIDString = [NSString stringWithUTF8String:groupID];
[glassView performSelector:@selector(setGroupName:) withObject:groupIDString];
}
}
// Set group spacing if the property exists and spacing is specified
if (groupSpacing > 0 && [glassView respondsToSelector:@selector(setGroupSpacing:)]) {
[glassView setValue:@(groupSpacing) forKey:@"groupSpacing"];
}
}
}
// Fallback to NSVisualEffectView if NSGlassEffectView is not available
if (!glassView) {
NSVisualEffectView* effectView = [[[NSVisualEffectView alloc] init] autorelease];
glassView = effectView; // Use effectView as glassView for the rest of the function
// If a custom material is specified, use it directly
if (material >= 0) {
[effectView setMaterial:(NSVisualEffectMaterial)material];
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
} else {
// Configure the visual effect based on style
switch(style) {
case 1: // Light
if (@available(macOS 15.0, *)) {
[effectView setMaterial:NSVisualEffectMaterialUnderPageBackground];
} else if (@available(macOS 10.14, *)) {
[effectView setMaterial:NSVisualEffectMaterialHUDWindow];
} else {
[effectView setMaterial:NSVisualEffectMaterialLight];
}
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
break;
case 2: // Dark
if (@available(macOS 15.0, *)) {
[effectView setMaterial:NSVisualEffectMaterialHeaderView];
} else if (@available(macOS 10.14, *)) {
[effectView setMaterial:NSVisualEffectMaterialFullScreenUI];
} else {
[effectView setMaterial:NSVisualEffectMaterialDark];
}
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
break;
case 3: // Vibrant
if (@available(macOS 11.0, *)) {
// Use the lightest material available - similar to dock
[effectView setMaterial:NSVisualEffectMaterialHUDWindow];
} else if (@available(macOS 10.14, *)) {
[effectView setMaterial:NSVisualEffectMaterialSheet];
} else {
[effectView setMaterial:NSVisualEffectMaterialLight];
}
// Use behind window for true transparency
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
break;
default: // Automatic
if (@available(macOS 10.14, *)) {
// Use content background for lighter automatic effect
[effectView setMaterial:NSVisualEffectMaterialContentBackground];
} else {
[effectView setMaterial:NSVisualEffectMaterialAppearanceBased];
}
[effectView setBlendingMode:NSVisualEffectBlendingModeWithinWindow];
break;
}
}
// Use followsWindowActiveState for automatic adjustment
[effectView setState:NSVisualEffectStateFollowsWindowActiveState];
// Don't emphasize - it makes the effect too dark
if (@available(macOS 10.12, *)) {
[effectView setEmphasized:NO];
}
// Apply corner radius if specified
if (cornerRadius > 0) {
[effectView setWantsLayer:YES];
effectView.layer.cornerRadius = cornerRadius;
effectView.layer.masksToBounds = YES;
}
}
// Get the content view
NSView* contentView = [window contentView];
// Set up the glass view
[glassView setFrame:contentView.bounds];
[glassView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
// Check if this is a real NSGlassEffectView with contentView property
BOOL hasContentView = [glassView respondsToSelector:@selector(contentView)];
if (hasContentView) {
// NSGlassEffectView: Add it to window and webView goes in its contentView
[contentView addSubview:glassView positioned:NSWindowBelow relativeTo:nil];
// Safely reparent the webView to the glass view's contentView
WKWebView* webView = window.webView;
NSView* glassContentView = [glassView valueForKey:@"contentView"];
// Only proceed if both webView and glassContentView are non-nil
if (webView && glassContentView) {
// Always remove from current superview to avoid exceptions
[webView removeFromSuperview];
// Add to the glass view's contentView
[glassContentView addSubview:webView];
[webView setFrame:glassContentView.bounds];
[webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
}
} else {
// NSVisualEffectView: Add glass as bottom layer, webView on top
[contentView addSubview:glassView positioned:NSWindowBelow relativeTo:nil];
WKWebView* webView = window.webView;
if (webView) {
[webView removeFromSuperview];
[contentView addSubview:webView positioned:NSWindowAbove relativeTo:glassView];
}
}
// Configure WebView for liquid glass
configureWebViewForLiquidGlass(nsWindow);
// Make window transparent
[window setOpaque:NO];
[window setBackgroundColor:[NSColor clearColor]];
}

View file

@ -383,6 +383,8 @@ const (
MacBackdropTransparent
// MacBackdropTranslucent - The window will have a translucent background, with the content underneath it being "fuzzy" or "frosted"
MacBackdropTranslucent
// MacBackdropLiquidGlass - The window will use Apple's Liquid Glass effect (macOS 15.0+ with fallback to translucent)
MacBackdropLiquidGlass
)
// MacToolbarStyle is the style of toolbar for macOS
@ -401,6 +403,67 @@ const (
MacToolbarStyleUnifiedCompact
)
// MacLiquidGlassStyle defines the style of the Liquid Glass effect
type MacLiquidGlassStyle int
const (
// LiquidGlassStyleAutomatic - System determines the best style
LiquidGlassStyleAutomatic MacLiquidGlassStyle = iota
// LiquidGlassStyleLight - Light glass appearance
LiquidGlassStyleLight
// LiquidGlassStyleDark - Dark glass appearance
LiquidGlassStyleDark
// LiquidGlassStyleVibrant - Vibrant glass with enhanced effects
LiquidGlassStyleVibrant
)
// NSVisualEffectMaterial represents the NSVisualEffectMaterial enum for macOS
type NSVisualEffectMaterial int
const (
// NSVisualEffectMaterial values from macOS SDK
NSVisualEffectMaterialAppearanceBased NSVisualEffectMaterial = 0
NSVisualEffectMaterialLight NSVisualEffectMaterial = 1
NSVisualEffectMaterialDark NSVisualEffectMaterial = 2
NSVisualEffectMaterialTitlebar NSVisualEffectMaterial = 3
NSVisualEffectMaterialSelection NSVisualEffectMaterial = 4
NSVisualEffectMaterialMenu NSVisualEffectMaterial = 5
NSVisualEffectMaterialPopover NSVisualEffectMaterial = 6
NSVisualEffectMaterialSidebar NSVisualEffectMaterial = 7
NSVisualEffectMaterialHeaderView NSVisualEffectMaterial = 10
NSVisualEffectMaterialSheet NSVisualEffectMaterial = 11
NSVisualEffectMaterialWindowBackground NSVisualEffectMaterial = 12
NSVisualEffectMaterialHUDWindow NSVisualEffectMaterial = 13
NSVisualEffectMaterialFullScreenUI NSVisualEffectMaterial = 15
NSVisualEffectMaterialToolTip NSVisualEffectMaterial = 17
NSVisualEffectMaterialContentBackground NSVisualEffectMaterial = 18
NSVisualEffectMaterialUnderWindowBackground NSVisualEffectMaterial = 21
NSVisualEffectMaterialUnderPageBackground NSVisualEffectMaterial = 22
NSVisualEffectMaterialAuto NSVisualEffectMaterial = -1 // Use auto-selection based on Style
)
// MacLiquidGlass contains configuration for the Liquid Glass effect
type MacLiquidGlass struct {
// Style of the glass effect
Style MacLiquidGlassStyle
// Material to use for NSVisualEffectView (when NSGlassEffectView is not available)
// Set to NSVisualEffectMaterialAuto to use automatic selection based on Style
Material NSVisualEffectMaterial
// Corner radius for the glass effect (0 for square corners)
CornerRadius float64
// Tint color for the glass (optional, nil for no tint)
TintColor *RGBA
// Group identifier for merging multiple glass windows
GroupID string
// Spacing between grouped glass elements (in points)
GroupSpacing float64
}
// MacWindow contains macOS specific options for Webview Windows
type MacWindow struct {
// Backdrop is the backdrop type for the window
@ -425,6 +488,9 @@ type MacWindow struct {
// WindowLevel sets the window level to control the order of windows in the screen
WindowLevel MacWindowLevel
// LiquidGlass contains configuration for the Liquid Glass effect
LiquidGlass MacLiquidGlass
}
type MacWindowLevel string