wails/v3/examples/liquid-glass/main.go
Lea Anthony 53c2275fea
fix(v3): overhaul drag-and-drop for Linux reliability and simplify Windows implementation (#4848)
* fix(v3): overhaul drag-and-drop for Linux reliability and simplify Windows

This commit fixes drag-and-drop reliability on Linux and simplifies the
Windows implementation.

## Linux
- Rewrite GTK drag handlers to properly intercept external file drops
- Fix HTML5 internal drag-and-drop being broken when file drop enabled
- Add hover effects during file drag operations
- Fix multiple app instances interfering with each other

## Windows
- Remove native IDropTarget in favor of JavaScript approach (matches v2)
- File drops now handled via chrome.webview.postMessageWithAdditionalObjects

## All Platforms
- Rename EnableDragAndDrop to EnableFileDrop
- Rename data-wails-drop-target to data-file-drop-target
- Rename wails-drop-target-active to file-drop-target-active
- Add comprehensive drag-and-drop documentation

## Breaking Changes
- EnableDragAndDrop -> EnableFileDrop
- data-wails-dropzone -> data-file-drop-target
- wails-dropzone-hover -> file-drop-target-active
- DropZoneDetails -> DropTargetDetails
- Remove WindowDropZoneFilesDropped event (use WindowFilesDropped)

* feat(macos): optimize drag event performance with debouncing and caching

- Add 50ms debouncing to limit drag events to 20/sec (was 120/sec)
- Implement window implementation caching to avoid repeated lookups
- Maintain existing 5-pixel threshold for immediate response
- Keep zero-allocation path with pre-allocated buffers
- Rename linuxDragActive to nativeDragActive for clarity
- Update IMPLEMENTATION.md with optimization details and Windows guidance

Performance improvements:
- 83% reduction in event frequency
- ~6x reduction in CPU/memory usage during drag operations
- Maintains smooth visual feedback with InvokeSync for timer callbacks

* fix(windows): implement proper file drop support for Windows

- Remove incorrect AllowExternalDrag(false) call that was blocking file drops
- Fix message prefix from 'FilesDropped' to 'file:drop:' to match JS runtime
- Fix coordinate parsing for 'file:drop:x:y' format (indices 2,3 not 1,2)
- Add enableFileDrop flag injection to JS runtime during navigation
- Update JS runtime to check enableFileDrop flag before processing drops
- Always call preventDefault() to stop browser navigation on file drags
- Show 'no drop' cursor when file drops are disabled
- Update example to filter file drags from HTML drop zone handlers
- Add documentation for combining file drop with HTML drag-and-drop

* fix(v3): block file drops on Linux when EnableFileDrop is false

- Add disableDND() to intercept and reject external file drags at GTK level
- Show 'no drop' cursor when files are dragged over window
- Allow internal HTML5 drag-and-drop to work normally
- Initialize _wails.flags object in runtime core to prevent undefined errors
- Inject enableFileDrop flag on Linux and macOS (matching Windows)
- Fix bare _wails reference to use window._wails
- Update docs with info about blocked drops and combining with HTML DnD

* fix(darwin): add missing fmt import in webview_window_darwin.go

* fix(macOS): implement hover effects for file drag-and-drop with optimizations

- Added draggingUpdated: handler to track mouse movement during drag operations
- Implemented macosOnDragEnter/Exit/Over export functions for real-time hover state
- Fixed JS function call from '_wails.handlePlatformFileDrop' to correct 'wails.Window.HandlePlatformFileDrop'
- Added EnableFileDrop flag checks to prevent hover effects when file drops are disabled
- Renamed linuxDragActive to nativeDragActive for cross-platform consistency

Performance optimizations:
- Added 50ms debounce to reduce event frequency from ~120/sec to ~20/sec
- Implemented 5-pixel movement threshold for immediate response
- Added window caching with sync.Map to avoid repeated lookups
- Zero-allocation JavaScript calls with pre-allocated 128-byte buffer
- Reduced memory usage to ~18 bytes per event (6x reduction)

Build improvements:
- Updated runtime Taskfile to include documentation generation
- Added docs:build task to runtime build process
- Fixed build order: events → docs → runtime

Documentation:
- Added IMPLEMENTATION.md with optimization details
- Included guidance for Windows implementation

* chore(v3/examples): remove html-dnd-api example

The drag-n-drop example now demonstrates both external file drops
and internal HTML5 drag-and-drop, making this separate example redundant.

* docs(v3): move drag-and-drop implementation details to runtime-internals

- Add drag-and-drop section to contributing/runtime-internals.mdx
- Remove IMPLEMENTATION.md from example (content now in proper docs)
- Covers platform differences, debugging tips, and key files

* fix(v3): remove html-dnd-api from example build list

* fix(v3): remove duplicate json import in application_darwin.go

* fix(v3): address CodeRabbit review feedback

- Fix docs to use app.Window.NewWithOptions() instead of deprecated API
- Add mutex protection to dragOverJSBuffer to prevent race conditions
- Add mutex protection to dragThrottleState fields for thread safety

* docs: add coderabbit pre-push requirement to AGENTS.md

* fix(v3/test): use correct CSS class name file-drop-target-active

* chore(v3/test): remove dnd-test directory

This was a development test file that shouldn't be in the PR.
The drag-n-drop example serves as the proper test case.

* docs(v3): update Windows file drop comment to reflect implemented fix

Remove stale TODO - enableFileDrop flag is now injected in navigationCompleted

* refactor(v3): make handleDragAndDropMessage unexported

Internal method only called by application event loop, not part of public API.
2026-01-04 11:08:29 +11:00

237 lines
7.7 KiB
Go

package main
import (
_ "embed"
"encoding/base64"
"fmt"
"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
app.Dialog.Info().
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()
fmt.Println("The Liquid Glass effect is a macOS-specific feature. This demo is not available on", runtime.GOOS)
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,
EnableFileDrop: 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,
EnableFileDrop: 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,
EnableFileDrop: 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,
EnableFileDrop: 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,
EnableFileDrop: 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,
EnableFileDrop: 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,
EnableFileDrop: 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)
}
}