mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 22:55:48 +01:00
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:
parent
8115b88a71
commit
4bfc52f0b5
11 changed files with 725 additions and 9 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
1
v3/examples/liquid-glass/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
liquid-glass-demo
|
||||
70
v3/examples/liquid-glass/README.md
Normal file
70
v3/examples/liquid-glass/README.md
Normal 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},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
50
v3/examples/liquid-glass/index.html
Normal file
50
v3/examples/liquid-glass/index.html
Normal 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>
|
||||
235
v3/examples/liquid-glass/main.go
Normal file
235
v3/examples/liquid-glass/main.go
Normal 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)
|
||||
}
|
||||
}
|
||||
BIN
v3/examples/liquid-glass/wails-logo.png
Normal file
BIN
v3/examples/liquid-glass/wails-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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]];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue