diff --git a/docs/src/content/docs/concepts/architecture.mdx b/docs/src/content/docs/concepts/architecture.mdx index d72affe3b..824ba6ce2 100644 --- a/docs/src/content/docs/concepts/architecture.mdx +++ b/docs/src/content/docs/concepts/architecture.mdx @@ -348,17 +348,27 @@ Shutdown.Save -> End **Lifecycle hooks:** ```go +// Services provide startup/shutdown hooks +type AppService struct{} + +func (s *AppService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Initialise database, load config, etc. + return nil +} + +func (s *AppService) ServiceShutdown() error { + // Save state, close connections, etc. + return nil +} + app := application.New(application.Options{ Name: "My App", - - // Called before windows are created - OnStartup: func(ctx context.Context) { - // Initialise database, load config, etc. + Services: []application.Service{ + application.NewService(&AppService{}), }, - - // Called when app is about to quit + // Called when app is about to quit (before service shutdown) OnShutdown: func() { - // Save state, close connections, etc. + // Additional cleanup }, }) ``` diff --git a/docs/src/content/docs/concepts/build-system.mdx b/docs/src/content/docs/concepts/build-system.mdx index ed5ca9430..78a5fae31 100644 --- a/docs/src/content/docs/concepts/build-system.mdx +++ b/docs/src/content/docs/concepts/build-system.mdx @@ -196,7 +196,7 @@ var assets embed.FS ``` **What happens:** - 1. Starts frontend dev server (Vite on port 5173) + 1. Starts frontend dev server (Vite on port 9245) 2. Compiles Go without optimisations 3. Launches app pointing to dev server 4. Enables hot reload @@ -245,118 +245,87 @@ var assets embed.FS wails3 build ``` -**Output:** `build/bin/myapp[.exe]` +**Output:** Binary in `bin/` directory. -### Build for Specific Platform +`wails3 build` is a wrapper for `wails3 task build`. It runs the `build` task defined in your project's `Taskfile.yml`, which dispatches to the appropriate platform-specific Taskfile. + +### Build with Tags ```bash -# Build for Windows (from any OS) -wails3 build -platform windows/amd64 - -# Build for macOS -wails3 build -platform darwin/amd64 -wails3 build -platform darwin/arm64 - -# Build for Linux -wails3 build -platform linux/amd64 +# Pass additional Go build tags +wails3 build -tags production ``` -**Cross-compilation:** Build for any platform from any platform. +### Build with CLI Variables -### Build with Options +You can pass CLI variables to customize the build: ```bash -# Custom output directory -wails3 build -o ./dist/myapp - -# Skip frontend build (use existing) -wails3 build -skipbindings - -# Clean build (remove cache) -wails3 build -clean - -# Verbose output -wails3 build -v +wails3 build PRODUCTION=true ``` -### Build Modes +These variables are forwarded to the underlying task and can be accessed in your `Taskfile.yml` using Go template syntax. + +### Platform-Specific Tasks + +The default project structure includes platform-specific Taskfiles. The main `Taskfile.yml` dispatches to the correct one based on `{{OS}}`: ```bash -# Debug build (includes symbols) -wails3 build -debug - -# Production build (default, optimised) -wails3 build - -# Development build (fast, unoptimised) -wails3 build -devbuild +# Run platform-specific tasks directly +wails3 task darwin:build +wails3 task windows:build +wails3 task linux:build ``` ## Build Configuration ### Taskfile.yml -Wails uses [Taskfile](https://taskfile.dev/) for build configuration: +Wails uses [Taskfile](https://taskfile.dev/) for build configuration. The main `Taskfile.yml` in the project root includes platform-specific Taskfiles: ```yaml # Taskfile.yml version: '3' +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "myproject" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + tasks: build: - desc: Build the application + summary: Builds the application cmds: - - wails3 build - - build:windows: - desc: Build for Windows + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application cmds: - - wails3 build -platform windows/amd64 - - build:macos: - desc: Build for macOS (Universal) + - task: "{{OS}}:package" + + run: + summary: Runs the application cmds: - - wails3 build -platform darwin/amd64 - - wails3 build -platform darwin/arm64 - - lipo -create -output build/bin/myapp.app build/bin/myapp-amd64.app build/bin/myapp-arm64.app - - build:linux: - desc: Build for Linux + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode cmds: - - wails3 build -platform linux/amd64 + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} ``` **Run tasks:** ```bash -task build:windows -task build:macos -task build:linux -``` - -### Build Options File - -Create `build/build.json` for persistent configuration: - -```json -{ - "name": "My Application", - "version": "1.0.0", - "author": "Your Name", - "description": "Application description", - "icon": "build/appicon.png", - "outputFilename": "myapp", - "platforms": ["windows/amd64", "darwin/amd64", "linux/amd64"], - "frontend": { - "dir": "./frontend", - "install": "npm install", - "build": "npm run build", - "dev": "npm run dev" - }, - "go": { - "ldflags": "-s -w -X main.version={{.Version}}" - } -} +wails3 task build +wails3 task package +wails3 task run ``` ## Asset Embedding @@ -476,7 +445,7 @@ export default { ```bash # After building -upx --best build/bin/myapp.exe +upx --best bin/myapp.exe ``` **Results:** @@ -497,12 +466,7 @@ upx --best build/bin/myapp.exe **Icon:** -```bash -# Specify icon -wails3 build -icon build/appicon.png -``` - -Wails converts PNG to `.ico` automatically. +Icon generation is handled by the platform-specific Taskfile. The default Windows Taskfile generates an `.ico` file from `build/appicon.png` using the `wails3 generate icons` command. **Manifest:** @@ -557,15 +521,10 @@ myapp.app/ **Universal Binary:** -```bash -# Build for both architectures -wails3 build -platform darwin/amd64 -wails3 build -platform darwin/arm64 +The macOS Taskfile supports building universal binaries. You can also use the `wails3 tool lipo` command to combine architecture-specific binaries: -# Combine into universal binary -lipo -create -output myapp-universal \ - build/bin/myapp-amd64 \ - build/bin/myapp-arm64 +```bash +wails3 tool lipo -i bin/myapp-amd64 -i bin/myapp-arm64 -output bin/myapp-universal ``` ### Linux @@ -625,24 +584,7 @@ sudo cp icon.png /usr/share/icons/hicolor/256x256/apps/myapp.png npm run build # Uses cache by default ``` -**2. Skip unchanged steps:** - -```bash -# Skip frontend if unchanged -wails3 build -skipbindings -``` - -**3. Parallel builds:** - -```bash -# Build multiple platforms in parallel -wails3 build -platform windows/amd64 & -wails3 build -platform darwin/amd64 & -wails3 build -platform linux/amd64 & -wait -``` - -**4. Use faster tools:** +**2. Use faster tools:** ```bash # Use esbuild instead of webpack @@ -684,9 +626,7 @@ wait **Solutions:** 1. **Strip debug symbols** (should be automatic) - ```bash - wails3 build # Already includes -ldflags="-s -w" - ``` + The default Taskfiles include `-ldflags="-s -w"` in the build task. 2. **Check embedded assets** ```bash @@ -696,7 +636,7 @@ wait 3. **Use UPX compression** ```bash - upx --best build/bin/myapp.exe + upx --best bin/myapp.exe ``` ### Slow Builds @@ -709,12 +649,7 @@ wait - Go cache is automatic - Frontend cache (Vite) is automatic -2. **Skip unchanged steps** - ```bash - wails3 build -skipbindings - ``` - -3. **Optimise frontend build** +2. **Optimise frontend build** ```javascript // vite.config.js export default { @@ -730,7 +665,7 @@ wait - **Use `wails3 dev` during development** - Fast iteration - **Use `wails3 build` for releases** - Optimised output -- **Version your builds** - Use `-ldflags` to embed version +- **Version your builds** - Use `-ldflags` in your Taskfile to embed version - **Test builds on target platforms** - Cross-compilation isn't perfect - **Keep frontend builds fast** - Optimise bundler config - **Use build cache** - Speeds up subsequent builds diff --git a/docs/src/content/docs/concepts/lifecycle.mdx b/docs/src/content/docs/concepts/lifecycle.mdx index fb44f95ed..425ff3106 100644 --- a/docs/src/content/docs/concepts/lifecycle.mdx +++ b/docs/src/content/docs/concepts/lifecycle.mdx @@ -33,7 +33,7 @@ PreInit: "Pre-Initialisation" { } } -OnStartup: "OnStartup Hook" { +ServiceStartup: "Service Startup" { shape: rectangle style.fill: "#3B82F6" } @@ -59,12 +59,12 @@ QuitSignal: "Quit Signal" { style.fill: "#F59E0B" } -OnBeforeClose: "OnBeforeClose Hook" { +ShouldQuit: "ShouldQuit Check" { shape: rectangle style.fill: "#3B82F6" } -OnShutdown: "OnShutdown Hook" { +OnShutdown: "Shutdown Hooks" { shape: rectangle style.fill: "#3B82F6" } @@ -86,16 +86,16 @@ End: "Application End" { Start -> PreInit.Parse PreInit.Parse -> PreInit.Register PreInit.Register -> PreInit.Validate -PreInit.Validate -> OnStartup -OnStartup -> CreateWindows +PreInit.Validate -> ServiceStartup +ServiceStartup -> CreateWindows CreateWindows -> EventLoop.Process EventLoop.Process -> EventLoop.Handle EventLoop.Handle -> EventLoop.Update EventLoop.Update -> EventLoop.Process: "Loop" EventLoop.Process -> QuitSignal: "User quits" -QuitSignal -> OnBeforeClose: "Can cancel?" -OnBeforeClose -> EventLoop.Process: "Cancelled" -OnBeforeClose -> OnShutdown: "Confirmed" +QuitSignal -> ShouldQuit: "Can cancel?" +ShouldQuit -> EventLoop.Process: "Cancelled" +ShouldQuit -> OnShutdown: "Confirmed" OnShutdown -> Cleanup.Close Cleanup.Close -> Cleanup.Release Cleanup.Release -> End @@ -111,34 +111,42 @@ Before your code runs, Wails: **You don't control this phase** - it happens automatically. -### 2. OnStartup Hook +### 2. Service Startup -Your first opportunity to run code: +Your first opportunity to run code is through the `ServiceStartup` interface. Services that implement this interface receive a startup notification during `app.Run()`: ```go +type AppService struct { + db *sql.DB + config *Config +} + +func (s *AppService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Initialise database + var err error + s.db, err = sql.Open("sqlite3", "app.db") + if err != nil { + return fmt.Errorf("failed to open database: %w", err) + } + + // Load configuration + s.config, err = loadConfig() + if err != nil { + return fmt.Errorf("failed to load config: %w", err) + } + + return nil +} + app := application.New(application.Options{ Name: "My App", - OnStartup: func(ctx context.Context) { - // Initialise database - db, err := sql.Open("sqlite3", "app.db") - if err != nil { - log.Fatal(err) - } - - // Load configuration - config, err := loadConfig() - if err != nil { - log.Fatal(err) - } - - // Store in context for services to access - ctx = context.WithValue(ctx, "db", db) - ctx = context.WithValue(ctx, "config", config) + Services: []application.Service{ + application.NewService(&AppService{}), }, }) ``` -**When it runs:** After Wails initialisation, before windows are created +**When it runs:** During `app.Run()`, before the event loop starts **Use it for:** - Database connections @@ -146,7 +154,7 @@ app := application.New(application.Options{ - Resource initialisation - Authentication checks -**Context:** The `context.Context` is passed to all services and can store shared state. +**Context:** The `context.Context` is valid as long as the application is running and is cancelled right before shutdown. ### 3. Window Creation @@ -185,60 +193,89 @@ User triggers quit via: - Cmd+Q / Alt+F4 / File → Quit - Your code calling `app.Quit()` -### 6. OnBeforeClose Hook +### 6. ShouldQuit / Window Close Prevention -**Optional hook to prevent quit:** +**Application-level quit prevention with `ShouldQuit`:** ```go -window := app.Window.NewWithOptions(application.WebviewWindowOptions{ - OnBeforeClose: func() bool { +app := application.New(application.Options{ + ShouldQuit: func() bool { // Return false to cancel quit // Return true to allow quit - if hasUnsavedChanges() { - result := showConfirmDialog("Unsaved changes. Quit anyway?") - return result == "yes" + return false } return true }, }) ``` +**Window-level close prevention with `RegisterHook`:** + +```go +window := app.Window.New() +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + e.Cancel() // Prevent the window from closing + } +}) +``` + **Use cases:** - Confirm quit with unsaved changes - Prevent accidental closure - Save state before quitting -**Important:** Only works for window close events, not `app.Quit()`. +### 7. Shutdown Hooks -### 7. OnShutdown Hook +There are multiple hooks for shutdown: -Your last opportunity to run code: +**`OnShutdown` (application option)** - runs first during shutdown: ```go app := application.New(application.Options{ OnShutdown: func() { // Save application state saveState() - - // Close database - db.Close() - - // Release resources - cleanup() }, }) ``` -**When it runs:** After quit confirmed, before application exits +**`app.OnShutdown()` (runtime registration)** - add shutdown tasks dynamically: -**Use it for:** -- Saving state -- Closing connections -- Releasing resources -- Final cleanup +```go +app.OnShutdown(func() { + // Additional cleanup + cleanup() +}) +``` -**Important:** Keep it fast (<1 second). OS may force-kill if too slow. +**`ServiceShutdown` (service interface)** - services shut down in reverse registration order: + +```go +func (s *MyService) ServiceShutdown() error { + // Close database, release resources + return s.db.Close() +} +``` + +**`PostShutdown` (application option)** - runs after everything else, just before process exit: + +```go +app := application.New(application.Options{ + PostShutdown: func() { + log.Println("Application has finished shutting down") + }, +}) +``` + +**Shutdown order:** +1. `OnShutdown` callbacks (in order added) +2. `ServiceShutdown` (reverse registration order) +3. Windows closed, system trays destroyed +4. `PostShutdown` callback + +**Important:** Keep shutdown fast. OS may force-kill if too slow. ### 8. Cleanup & Exit @@ -251,34 +288,46 @@ Wails automatically: | Hook | When | Can Cancel? | Use For | |------|------|-------------|---------| -| `OnStartup` | Before windows created | No | Initialisation | -| `OnBeforeClose` | Window closing | Yes | Confirm quit | +| `ServiceStartup` | During `app.Run()`, before event loop | Yes (return error) | Initialisation | +| `ShouldQuit` | App quit requested | Yes (return false) | Confirm quit | +| `ShouldClose` | Window closing | Yes (return false) | Prevent window close | | `OnShutdown` | After quit confirmed | No | Cleanup | +| `PostShutdown` | After shutdown complete | No | Logging, testing | ## Common Patterns ### Pattern 1: Database Lifecycle ```go -var db *sql.DB +type DatabaseService struct { + db *sql.DB +} + +func (d *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + var err error + d.db, err = sql.Open("sqlite3", "app.db") + if err != nil { + return fmt.Errorf("failed to open database: %w", err) + } + + // Run migrations + if err := runMigrations(d.db); err != nil { + return fmt.Errorf("migrations failed: %w", err) + } + + return nil +} + +func (d *DatabaseService) ServiceShutdown() error { + if d.db != nil { + return d.db.Close() + } + return nil +} app := application.New(application.Options{ - OnStartup: func(ctx context.Context) { - var err error - db, err = sql.Open("sqlite3", "app.db") - if err != nil { - log.Fatal(err) - } - - // Run migrations - if err := runMigrations(db); err != nil { - log.Fatal(err) - } - }, - OnShutdown: func() { - if db != nil { - db.Close() - } + Services: []application.Service{ + application.NewService(&DatabaseService{}), }, }) ``` @@ -287,69 +336,92 @@ app := application.New(application.Options{ ```go type Config struct { - Theme string - Language string + Theme string + Language string WindowPos Point } -var config *Config +type ConfigService struct { + config *Config +} + +func (c *ConfigService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + var err error + c.config, err = loadConfig() // Load from disk + if err != nil { + return fmt.Errorf("failed to load config: %w", err) + } + return nil +} + +func (c *ConfigService) ServiceShutdown() error { + return saveConfig(c.config) // Save to disk +} app := application.New(application.Options{ - OnStartup: func(ctx context.Context) { - config = loadConfig() // Load from disk - }, - OnShutdown: func() { - saveConfig(config) // Save to disk + Services: []application.Service{ + application.NewService(&ConfigService{}), }, }) ``` ### Pattern 3: Confirm Quit with Unsaved Changes +Use `RegisterHook` with `events.Common.WindowClosing` to prevent window close, or `ShouldQuit` on the application options: + ```go -type AppState struct { - hasUnsavedChanges bool -} - -var state AppState - -window := app.Window.NewWithOptions(application.WebviewWindowOptions{ - OnBeforeClose: func() bool { - if state.hasUnsavedChanges { - // Show dialog (blocks until user responds) - result := showConfirmdialog("Unsaved changes. Quit anyway?") - return result == "yes" +// Application-level: prevent quit +app := application.New(application.Options{ + ShouldQuit: func() bool { + if hasUnsavedChanges { + // Return false to prevent quit + return false } return true }, }) + +// Window-level: prevent individual window close +window := app.Window.New() +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges { + e.Cancel() // Prevent window from closing + } +}) ``` ### Pattern 4: Background Tasks ```go -app := application.New(application.Options{ - OnStartup: func(ctx context.Context) { - // Start background task - go func() { - ticker := time.NewTicker(5 * time.Minute) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - performBackgroundSync() - case <-ctx.Done(): - // Context cancelled, quit - return - } +type SyncService struct{} + +func (s *SyncService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Start background task + go func() { + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + performBackgroundSync() + case <-ctx.Done(): + // Context cancelled, quit + return } - }() + } + }() + return nil +} + +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&SyncService{}), }, }) ``` -**Important:** Use `ctx.Done()` to know when to stop background tasks. +**Important:** Use `ctx.Done()` to know when to stop background tasks. The context is cancelled right before shutdown. ## Window Lifecycle @@ -382,7 +454,7 @@ CloseRequest: "Close Request" { style.fill: "#F59E0B" } -OnBeforeClose: "OnBeforeClose" { +CloseHook: "WindowClosing Hook" { shape: rectangle style.fill: "#3B82F6" } @@ -401,9 +473,9 @@ Load -> Show Show -> Active.Events Active.Events -> Active.Events: "Loop" Active.Events -> CloseRequest: "User closes" -CloseRequest -> OnBeforeClose -OnBeforeClose -> Active.Events: "Cancelled" -OnBeforeClose -> Destroy: "Confirmed" +CloseRequest -> CloseHook +CloseHook -> Active.Events: "Cancelled" +CloseHook -> Destroy: "Confirmed" Destroy -> End ``` @@ -453,34 +525,38 @@ app := application.New(application.Options{ ### Startup Errors +Return an error from `ServiceStartup` to abort application startup. The error is propagated from `app.Run()`: + ```go -app := application.New(application.Options{ - OnStartup: func(ctx context.Context) { - if err := initialise(); err != nil { - // Show error dialog - showErrordialog("Initialisation failed: " + err.Error()) - - // Quit application - app.Quit() - } - }, -}) +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + if err := initialise(); err != nil { + return fmt.Errorf("initialisation failed: %w", err) + } + return nil +} + +// In main: +err := app.Run() +if err != nil { + log.Fatal("Failed to start:", err) +} ``` ### Shutdown Errors +Errors returned from `ServiceShutdown` are logged but do not prevent shutdown: + ```go -app := application.New(application.Options{ - OnShutdown: func() { - if err := saveState(); err != nil { - // Log error (can't show dialog, app is quitting) - log.Printf("Failed to save state: %v", err) - } - }, -}) +func (s *MyService) ServiceShutdown() error { + if err := saveState(); err != nil { + // Error will be logged by Wails + return fmt.Errorf("failed to save state: %w", err) + } + return nil +} ``` -**Important:** `OnShutdown` runs during quit - don't show dialogs or try to cancel. +**Important:** Shutdown hooks run during quit - don't show dialogs or try to cancel. ## Platform Differences @@ -507,15 +583,14 @@ app := application.New(application.Options{ **Symptom:** Database connections left open, files not closed -**Solution:** Use `OnShutdown`: +**Solution:** Implement `ServiceShutdown` on your services: ```go -app := application.New(application.Options{ - OnShutdown: func() { - log.Println("Cleaning up...") - // Your cleanup code - }, -}) +func (s *MyService) ServiceShutdown() error { + log.Println("Cleaning up...") + // Your cleanup code + return s.db.Close() +} ``` ### Problem: Application Won't Quit @@ -523,28 +598,31 @@ app := application.New(application.Options{ **Symptom:** App hangs when trying to quit **Causes:** -1. `OnBeforeClose` returning `false` -2. `OnShutdown` taking too long -3. Background goroutines not stopping +1. `ShouldQuit` returning `false` +2. Window close hook cancelling close events +3. Shutdown tasks taking too long +4. Background goroutines not stopping **Solution:** ```go -// 1. Check OnBeforeClose logic -OnBeforeClose: func() bool { - log.Println("OnBeforeClose called") - return true // Allow quit -} +// 1. Check ShouldQuit logic +app := application.New(application.Options{ + ShouldQuit: func() bool { + log.Println("ShouldQuit called") + return true // Allow quit + }, +}) -// 2. Keep OnShutdown fast -OnShutdown: func() { - log.Println("OnShutdown started") +// 2. Keep shutdown fast +app.OnShutdown(func() { + log.Println("Shutdown started") // Fast cleanup only - log.Println("OnShutdown finished") -} + log.Println("Shutdown finished") +}) -// 3. Stop background tasks -OnStartup: func(ctx context.Context) { +// 3. Stop background tasks using context +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { go func() { for { select { @@ -556,6 +634,7 @@ OnStartup: func(ctx context.Context) { } } }() + return nil } ``` @@ -563,14 +642,19 @@ OnStartup: func(ctx context.Context) { **Symptom:** App starts but doesn't work correctly -**Solution:** Check errors in `OnStartup`: +**Solution:** Return errors from `ServiceStartup` to abort startup: ```go -OnStartup: func(ctx context.Context) { +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { if err := initialise(); err != nil { - log.Fatal("Initialisation failed:", err) - // Or show dialog and quit + return fmt.Errorf("initialisation failed: %w", err) } + return nil +} + +// In main - app.Run() returns the error: +if err := app.Run(); err != nil { + log.Fatal("Failed to start:", err) } ``` @@ -578,20 +662,20 @@ OnStartup: func(ctx context.Context) { ### ✅ Do -- **Initialise in OnStartup** - Database, config, resources -- **Clean up in OnShutdown** - Close connections, save state -- **Keep OnShutdown fast** - <1 second +- **Initialise in ServiceStartup** - Database, config, resources +- **Clean up in ServiceShutdown** - Close connections, save state +- **Keep shutdown fast** - <1 second - **Use context for cancellation** - Stop background tasks -- **Handle errors gracefully** - Show dialogs, log errors +- **Handle errors gracefully** - Return errors from ServiceStartup - **Test quit scenarios** - Unsaved changes, background tasks ### ❌ Don't -- **Don't block OnStartup** - Keep it fast (<2 seconds) -- **Don't show dialogs in OnShutdown** - App is quitting -- **Don't ignore errors** - Log or show them -- **Don't leak resources** - Always clean up -- **Don't forget background tasks** - Stop them on quit +- **Don't block ServiceStartup** - Keep it fast (<2 seconds) +- **Don't show dialogs during shutdown** - App is quitting +- **Don't ignore errors** - Log or return them +- **Don't leak resources** - Always clean up in ServiceShutdown +- **Don't forget background tasks** - Stop them using ctx.Done() ## Next Steps diff --git a/docs/src/content/docs/features/environment/info.mdx b/docs/src/content/docs/features/environment/info.mdx index d03fb5f70..e6ed82a83 100644 --- a/docs/src/content/docs/features/environment/info.mdx +++ b/docs/src/content/docs/features/environment/info.mdx @@ -107,6 +107,16 @@ func updateApplicationTheme(theme string) { } ``` +### Accent Colour + +Get the system accent colour: + +```go +accentColor := app.Env.GetAccentColor() +app.Logger.Info("System accent colour", "color", accentColor) +// Returns a CSS-compatible colour string, e.g. "rgb(0,122,255)" +``` + ## File Manager Integration ### Open File Manager diff --git a/docs/src/content/docs/features/menus/application.mdx b/docs/src/content/docs/features/menus/application.mdx index 0543e3d54..5f1e0d417 100644 --- a/docs/src/content/docs/features/menus/application.mdx +++ b/docs/src/content/docs/features/menus/application.mdx @@ -150,10 +150,13 @@ Wails provides **predefined menu roles** that create platform-appropriate menu s | Role | Description | Platform Notes | |------|-------------|----------------| -| `AppMenu` | Application menu with About, Preferences, Quit | **macOS only** | +| `AppMenu` | Application menu with About, Services, Hide/Show, and Quit | **macOS only** | | `FileMenu` | File menu with Close Window (macOS) or Quit (Windows/Linux) | All platforms | -| `EditMenu` | Text editing (Undo, Redo, Cut, Copy, Paste) | All platforms | +| `EditMenu` | Text editing (Undo, Redo, Cut, Copy, Paste, etc.) | All platforms | +| `ViewMenu` | View menu with Reload, Zoom, and Fullscreen controls | All platforms | | `WindowMenu` | Window management (Minimise, Zoom, etc.) | All platforms | +| `ServicesMenu` | macOS Services submenu | **macOS only** | +| `SpeechMenu` | Speech menu with Start/Stop Speaking | **macOS only** | | `HelpMenu` | Help and information | All platforms | ### Using Roles @@ -421,7 +424,6 @@ menuItem.OnClick(func(ctx *application.Context) { **Standard locations:** - **About**: Application menu -- **Preferences**: Application menu (⌘,) - **Quit**: Application menu (⌘Q) - **Help**: Help menu @@ -429,8 +431,8 @@ menuItem.OnClick(func(ctx *application.Context) { ```go if runtime.GOOS == "darwin" { - menu.AddRole(application.AppMenu) // Adds About, Preferences, Quit - + menu.AddRole(application.AppMenu) // Adds About, Services, Hide/Show, Quit + // Don't add Quit to File menu on macOS // Don't add About to Help menu on macOS } @@ -522,10 +524,8 @@ func createMenu(app *application.App) { // Tools menu toolsMenu := menu.AddSubmenu("Tools") - // Settings location varies by platform - if runtime.GOOS == "darwin" { - // On macOS, Preferences is in Application menu (added by AppMenu role) - } else { + // Settings location varies by platform convention + if runtime.GOOS != "darwin" { toolsMenu.Add("Settings").SetAccelerator("CmdOrCtrl+,").OnClick(showSettings) } diff --git a/docs/src/content/docs/features/menus/reference.mdx b/docs/src/content/docs/features/menus/reference.mdx index 93cc6b45a..b92495aa3 100644 --- a/docs/src/content/docs/features/menus/reference.mdx +++ b/docs/src/content/docs/features/menus/reference.mdx @@ -232,12 +232,21 @@ quitMenuItem := menu.Add("Quit") quitMenuItem.SetAccelerator("CmdOrCtrl+Q") ``` -**Accelerator format:** -- `CmdOrCtrl` - Cmd on macOS, Ctrl on Windows/Linux -- `Shift`, `Alt`, `Option` - Modifier keys -- `A-Z`, `0-9` - Letter/number keys -- `F1-F12` - Function keys -- `Enter`, `Space`, `Backspace`, etc. - Special keys +**Accelerator format:** `Modifier+Key` joined by `+`. Modifiers are case-insensitive. + +**Modifiers:** +- `CmdOrCtrl` (aliases: `Cmd`, `Command`) - Cmd on macOS, Ctrl on Windows/Linux +- `Ctrl` - Control key on all platforms +- `Alt` (aliases: `Option`, `OptionOrAlt`) - Alt on Windows/Linux, Option on macOS +- `Shift` - Shift key on all platforms +- `Super` - Cmd on macOS, Windows key on Windows/Linux + +**Keys:** +- `A-Z`, `0-9` - Letter and number keys +- `F1-F35` - Function keys +- `Plus` - The `+` character (since `+` is the separator) +- Named keys: `Backspace`, `Tab`, `Return`, `Enter`, `Escape`, `Left`, `Right`, `Up`, `Down`, `Space`, `Delete`, `Home`, `End`, `Page Up`, `Page Down`, `NumLock` +- Any single printable character **Examples:** @@ -306,10 +315,10 @@ menuItem.OnClick(func(ctx *application.Context) { }) ``` -**Context provides:** -- `ctx.ClickedMenuItem()` - The menu item that was clicked -- Window context (if from window menu) -- Application context +**Context methods:** +- `ctx.ClickedMenuItem() *MenuItem` - Returns the menu item that was clicked +- `ctx.IsChecked() bool` - Returns the checked state of the clicked item (for checkbox/radio items) +- `ctx.ContextMenuData() string` - Returns the context data string from the HTML element (context menus only) **Example: Access menu item in handler** @@ -317,7 +326,7 @@ menuItem.OnClick(func(ctx *application.Context) { checkbox := menu.AddCheckbox("Feature", false) checkbox.OnClick(func(ctx *application.Context) { item := ctx.ClickedMenuItem() - isChecked := item.Checked() + isChecked := ctx.IsChecked() fmt.Printf("Feature is now: %v\n", isChecked) }) ``` @@ -433,12 +442,12 @@ onUndoStackChanged(updateEditMenu) **macOS:** - Has "Application" menu (with app name) -- "Preferences" in Application menu +- `AppMenu` role adds: About, Services, Hide/Show, Quit - "Quit" in Application menu **Windows/Linux:** - No Application menu -- "Preferences" in Edit or Tools menu +- "Settings" typically in Edit or Tools menu - "Exit" in File menu **Example: Platform-appropriate structure** @@ -456,13 +465,11 @@ fileMenu := menu.AddSubmenu("File") fileMenu.Add("New") fileMenu.Add("Open") -// Preferences location varies -if runtime.GOOS == "darwin" { - // On macOS, preferences are in Application menu (added by AppMenu role) -} else { +// Settings location varies by platform convention +if runtime.GOOS != "darwin" { // On Windows/Linux, add to Edit or Tools menu editMenu := menu.AddSubmenu("Edit") - editMenu.Add("Preferences") + editMenu.Add("Settings") } ``` diff --git a/docs/src/content/docs/features/notifications/overview.mdx b/docs/src/content/docs/features/notifications/overview.mdx index efacdd74f..78b467250 100644 --- a/docs/src/content/docs/features/notifications/overview.mdx +++ b/docs/src/content/docs/features/notifications/overview.mdx @@ -67,7 +67,7 @@ notifier.SendNotification(notifications.NotificationOptions{ ``` ### Interactive Notifications -Send a notification with action buttons and text inputs. These notifications require a notification category to be resgistered first: +Send a notification with action buttons and text inputs. These notifications require a notification category to be registered first: ```go // Define a unique category id @@ -155,7 +155,7 @@ notifier.SendNotification(notifications.NotificationOptions{ On macOS, notifications: - Require user authorization - - Require the app to be notorized for distribution + - Require the app to be notarized for distribution - Use system-standard notification appearances - Support `subtitle` - Support user text input @@ -178,7 +178,7 @@ notifier.SendNotification(notifications.NotificationOptions{ - On Linux, dialog behaviour depends on the desktop environment: + On Linux, notification behaviour depends on the desktop environment: - Use native notifications when available - Follow desktop environment theme @@ -225,25 +225,25 @@ Explore this example: | `CheckNotificationAuthorization()` | Checks current notification authorization status (macOS) | ### Sending Notifications -| Method | Description | -|------------------------------------------------------------|---------------------------------------------------| -| `SendNotification(options NotificationOptions)` | Sends a basic notification | -| `SendNotificationWithActions(options NotificationOptions)` | Sends an interactive notification with actions | +| Method | Returns | Description | +|------------------------------------------------------------|---------|------------------------------------------------| +| `SendNotification(options NotificationOptions)` | `error` | Sends a basic notification | +| `SendNotificationWithActions(options NotificationOptions)` | `error` | Sends an interactive notification with actions | ### Notification Categories -| Method | Description | -|---------------------------------------------------------------|---------------------------------------------------| -| `RegisterNotificationCategory(category NotificationCategory)` | Registers a reusable notification category | -| `RemoveNotificationCategory(categoryID string)` | Removes a previously registered category | +| Method | Returns | Description | +|---------------------------------------------------------------|---------|------------------------------------------- | +| `RegisterNotificationCategory(category NotificationCategory)` | `error` | Registers a reusable notification category | +| `RemoveNotificationCategory(categoryID string)` | `error` | Removes a previously registered category | ### Managing Notifications -| Method | Description | -|-------------------------------------------------|---------------------------------------------------------------------| -| `RemoveAllPendingNotifications()` | Removes all pending notifications (macOS and Linux only) | -| `RemovePendingNotification(identifier string)` | Removes a specific pending notification (macOS and Linux only) | -| `RemoveAllDeliveredNotifications()` | Removes all delivered notifications (macOS and Linux only) | -| `RemoveDeliveredNotification(identifier string)`| Removes a specific delivered notification (macOS and Linux only) | -| `RemoveNotification(identifier string)` | Removes a notification (Linux-specific) | +| Method | Returns | Description | +|-------------------------------------------------|---------|---------------------------------------------------------------------| +| `RemoveAllPendingNotifications()` | `error` | Removes all pending notifications (macOS and Linux only) | +| `RemovePendingNotification(identifier string)` | `error` | Removes a specific pending notification (macOS and Linux only) | +| `RemoveAllDeliveredNotifications()` | `error` | Removes all delivered notifications (macOS and Linux only) | +| `RemoveDeliveredNotification(identifier string)`| `error` | Removes a specific delivered notification (macOS and Linux only) | +| `RemoveNotification(identifier string)` | `error` | Removes a notification (Linux-specific) | ### Event Handling | Method | Description | diff --git a/docs/src/content/docs/features/screens/info.mdx b/docs/src/content/docs/features/screens/info.mdx index 7e2e59c27..71f3e7d6d 100644 --- a/docs/src/content/docs/features/screens/info.mdx +++ b/docs/src/content/docs/features/screens/info.mdx @@ -15,15 +15,15 @@ Wails provides a **unified screen API** that works across all platforms. Get scr ```go // Get all screens -screens := app.Screens.GetAll() +screens := app.Screen.GetAll() for _, screen := range screens { - fmt.Printf("Screen: %s (%dx%d)\n", - screen.Name, screen.Width, screen.Height) + fmt.Printf("Screen: %s (%dx%d)\n", + screen.Name, screen.Size.Width, screen.Size.Height) } // Get primary screen -primary := app.Screens.GetPrimary() +primary := app.Screen.GetPrimary() fmt.Printf("Primary: %s\n", primary.Name) ``` @@ -34,12 +34,12 @@ fmt.Printf("Primary: %s\n", primary.Name) ### All Screens ```go -screens := app.Screens.GetAll() +screens := app.Screen.GetAll() for _, screen := range screens { fmt.Printf("ID: %s\n", screen.ID) fmt.Printf("Name: %s\n", screen.Name) - fmt.Printf("Size: %dx%d\n", screen.Width, screen.Height) + fmt.Printf("Size: %dx%d\n", screen.Size.Width, screen.Size.Height) fmt.Printf("Position: %d,%d\n", screen.X, screen.Y) fmt.Printf("Scale: %.2f\n", screen.ScaleFactor) fmt.Printf("Primary: %v\n", screen.IsPrimary) @@ -50,61 +50,58 @@ for _, screen := range screens { ### Primary Screen ```go -primary := app.Screens.GetPrimary() +primary := app.Screen.GetPrimary() fmt.Printf("Primary screen: %s\n", primary.Name) -fmt.Printf("Resolution: %dx%d\n", primary.Width, primary.Height) +fmt.Printf("Resolution: %dx%d\n", primary.Size.Width, primary.Size.Height) fmt.Printf("Scale factor: %.2f\n", primary.ScaleFactor) ``` -### Current Screen - -Get the screen containing a window: - -```go -screen := app.Screens.GetCurrent(window) - -fmt.Printf("Window is on: %s\n", screen.Name) -``` - -### Screen by ID - -```go -screen := app.Screens.GetByID("screen-id") -if screen != nil { - fmt.Printf("Found screen: %s\n", screen.Name) -} -``` - ## Screen Properties ### Screen Structure ```go type Screen struct { - ID string // Unique identifier - Name string // Display name - X int // X position - Y int // Y position - Width int // Width in pixels - Height int // Height in pixels - ScaleFactor float32 // DPI scale (1.0, 1.5, 2.0, etc.) - IsPrimary bool // Is this the primary screen? + ID string // A unique identifier for the display + Name string // The name of the display + ScaleFactor float32 // The scale factor of the display (DPI/96) + X int // The x-coordinate of the top-left corner + Y int // The y-coordinate of the top-left corner + Size Size // The size of the display + Bounds Rect // The bounds of the display + PhysicalBounds Rect // The physical bounds (before scaling) + WorkArea Rect // The work area of the display + PhysicalWorkArea Rect // The physical work area (before scaling) + IsPrimary bool // Whether this is the primary display + Rotation float32 // The rotation of the display +} + +type Rect struct { + X int + Y int + Width int + Height int +} + +type Size struct { + Width int + Height int } ``` ### Physical vs Logical Pixels ```go -screen := app.Screens.GetPrimary() +screen := app.Screen.GetPrimary() -// Logical pixels (what you use) -logicalWidth := screen.Width -logicalHeight := screen.Height +// Logical pixels (DIP - what you use) +logicalWidth := screen.Bounds.Width +logicalHeight := screen.Bounds.Height // Physical pixels (actual display) -physicalWidth := int(float32(screen.Width) * screen.ScaleFactor) -physicalHeight := int(float32(screen.Height) * screen.ScaleFactor) +physicalWidth := screen.PhysicalBounds.Width +physicalHeight := screen.PhysicalBounds.Height fmt.Printf("Logical: %dx%d\n", logicalWidth, logicalHeight) fmt.Printf("Physical: %dx%d\n", physicalWidth, physicalHeight) @@ -125,10 +122,10 @@ fmt.Printf("Scale: %.2f\n", screen.ScaleFactor) ```go func centreOnScreen(window *application.WebviewWindow, screen *Screen) { windowWidth, windowHeight := window.Size() - - x := screen.X + (screen.Width-windowWidth)/2 - y := screen.Y + (screen.Height-windowHeight)/2 - + + x := screen.X + (screen.Size.Width-windowWidth)/2 + y := screen.Y + (screen.Size.Height-windowHeight)/2 + window.SetPosition(x, y) } ``` @@ -137,7 +134,7 @@ func centreOnScreen(window *application.WebviewWindow, screen *Screen) { ```go func moveToScreen(window *application.WebviewWindow, screenIndex int) { - screens := app.Screens.GetAll() + screens := app.Screen.GetAll() if screenIndex < 0 || screenIndex >= len(screens) { return @@ -161,15 +158,15 @@ func positionTopLeft(window *application.WebviewWindow, screen *Screen) { // Top-right corner func positionTopRight(window *application.WebviewWindow, screen *Screen) { windowWidth, _ := window.Size() - window.SetPosition(screen.X+screen.Width-windowWidth-10, screen.Y+10) + window.SetPosition(screen.X+screen.Size.Width-windowWidth-10, screen.Y+10) } // Bottom-right corner func positionBottomRight(window *application.WebviewWindow, screen *Screen) { windowWidth, windowHeight := window.Size() window.SetPosition( - screen.X+screen.Width-windowWidth-10, - screen.Y+screen.Height-windowHeight-10, + screen.X+screen.Size.Width-windowWidth-10, + screen.Y+screen.Size.Height-windowHeight-10, ) } ``` @@ -180,11 +177,11 @@ func positionBottomRight(window *application.WebviewWindow, screen *Screen) { ```go func hasMultipleMonitors() bool { - return len(app.Screens.GetAll()) > 1 + return len(app.Screen.GetAll()) > 1 } func getMonitorCount() int { - return len(app.Screens.GetAll()) + return len(app.Screen.GetAll()) } ``` @@ -192,7 +189,7 @@ func getMonitorCount() int { ```go func listMonitors() { - screens := app.Screens.GetAll() + screens := app.Screen.GetAll() fmt.Printf("Found %d monitor(s):\n", len(screens)) @@ -203,7 +200,7 @@ func listMonitors() { } fmt.Printf("%d. %s%s\n", i+1, screen.Name, primary) - fmt.Printf(" Resolution: %dx%d\n", screen.Width, screen.Height) + fmt.Printf(" Resolution: %dx%d\n", screen.Size.Width, screen.Size.Height) fmt.Printf(" Position: %d,%d\n", screen.X, screen.Y) fmt.Printf(" Scale: %.2fx\n", screen.ScaleFactor) } @@ -214,7 +211,7 @@ func listMonitors() { ```go func chooseMonitor() (*Screen, error) { - screens := app.Screens.GetAll() + screens := app.Screen.GetAll() if len(screens) == 1 { return screens[0], nil @@ -229,7 +226,7 @@ func chooseMonitor() (*Screen, error) { } options = append(options, fmt.Sprintf("%d. %s%s - %dx%d", - i+1, screen.Name, primary, screen.Width, screen.Height)) + i+1, screen.Name, primary, screen.Size.Width, screen.Size.Height)) } // Use dialog to select @@ -257,7 +254,7 @@ func NewMultiMonitorManager(app *application.App) *MultiMonitorManager { } func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error { - screens := m.app.Screens.GetAll() + screens := m.app.Screen.GetAll() if screenIndex < 0 || screenIndex >= len(screens) { return errors.New("invalid screen index") @@ -273,8 +270,8 @@ func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error { }) // Centre on screen - x := screen.X + (screen.Width-800)/2 - y := screen.Y + (screen.Height-600)/2 + x := screen.X + (screen.Size.Width-800)/2 + y := screen.Y + (screen.Size.Height-600)/2 window.SetPosition(x, y) window.Show() @@ -284,7 +281,7 @@ func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error { } func (m *MultiMonitorManager) CreateWindowOnEachScreen() { - screens := m.app.Screens.GetAll() + screens := m.app.Screen.GetAll() for i := range screens { m.CreateWindowOnScreen(i) @@ -304,7 +301,7 @@ type ScreenMonitor struct { func NewScreenMonitor(app *application.App) *ScreenMonitor { return &ScreenMonitor{ app: app, - lastScreens: app.Screens.GetAll(), + lastScreens: app.Screen.GetAll(), } } @@ -323,7 +320,7 @@ func (sm *ScreenMonitor) Start() { } func (sm *ScreenMonitor) checkScreens() { - current := sm.app.Screens.GetAll() + current := sm.app.Screen.GetAll() if len(current) != len(sm.lastScreens) { sm.lastScreens = current @@ -341,22 +338,22 @@ func createDPIAwareWindow(screen *Screen) *application.WebviewWindow { // Base size at 1.0 scale baseWidth := 800 baseHeight := 600 - + // Adjust for DPI width := int(float32(baseWidth) * screen.ScaleFactor) height := int(float32(baseHeight) * screen.ScaleFactor) - + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "DPI-Aware Window", Width: width, Height: height, }) - + // Centre on screen - x := screen.X + (screen.Width-width)/2 - y := screen.Y + (screen.Height-height)/2 + x := screen.X + (screen.Size.Width-width)/2 + y := screen.Y + (screen.Size.Height-height)/2 window.SetPosition(x, y) - + return window } ``` @@ -365,7 +362,7 @@ func createDPIAwareWindow(screen *Screen) *application.WebviewWindow { ```go func visualiseScreenLayout() string { - screens := app.Screens.GetAll() + screens := app.Screen.GetAll() var layout strings.Builder layout.WriteString("Screen Layout:\n\n") @@ -378,11 +375,11 @@ func visualiseScreenLayout() string { layout.WriteString(fmt.Sprintf("Screen %d: %s%s\n", i+1, screen.Name, primary)) layout.WriteString(fmt.Sprintf(" Position: (%d, %d)\n", screen.X, screen.Y)) - layout.WriteString(fmt.Sprintf(" Size: %dx%d\n", screen.Width, screen.Height)) + layout.WriteString(fmt.Sprintf(" Size: %dx%d\n", screen.Size.Width, screen.Size.Height)) layout.WriteString(fmt.Sprintf(" Scale: %.2fx\n", screen.ScaleFactor)) - layout.WriteString(fmt.Sprintf(" Physical: %dx%d\n", - int(float32(screen.Width)*screen.ScaleFactor), - int(float32(screen.Height)*screen.ScaleFactor))) + layout.WriteString(fmt.Sprintf(" Physical: %dx%d\n", + screen.PhysicalBounds.Width, + screen.PhysicalBounds.Height)) layout.WriteString("\n") } diff --git a/docs/src/content/docs/features/windows/basics.mdx b/docs/src/content/docs/features/windows/basics.mdx index 2078318ff..b30a66bb8 100644 --- a/docs/src/content/docs/features/windows/basics.mdx +++ b/docs/src/content/docs/features/windows/basics.mdx @@ -386,8 +386,9 @@ Windows can communicate via the event system. See [Multiple Windows](/features/w **Backdrop types:** - `MacBackdropNormal` - Standard window - - `MacBackdropTranslucent` - Translucent background - `MacBackdropTransparent` - Fully transparent + - `MacBackdropTranslucent` - Translucent background + - `MacBackdropLiquidGlass` - Liquid Glass effect (macOS 15.0+) **Native fullscreen:** macOS fullscreen creates a new Space (virtual desktop). diff --git a/docs/src/content/docs/features/windows/events.mdx b/docs/src/content/docs/features/windows/events.mdx index 3d198f269..3f474165f 100644 --- a/docs/src/content/docs/features/windows/events.mdx +++ b/docs/src/content/docs/features/windows/events.mdx @@ -202,17 +202,27 @@ window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEv | Event | Description | |-------|-------------| | `events.Common.WindowClosing` | Window is about to close | -| `events.Common.WindowFocus` | Window gained focus | -| `events.Common.WindowLostFocus` | Window lost focus | | `events.Common.WindowDidMove` | Window moved | | `events.Common.WindowDidResize` | Window resized | -| `events.Common.WindowMinimise` | Window minimised | -| `events.Common.WindowUnMinimise` | Window restored from minimised | -| `events.Common.WindowMaximise` | Window maximised | -| `events.Common.WindowUnMaximise` | Window restored from maximised | -| `events.Common.WindowFullscreen` | Window entered fullscreen | -| `events.Common.WindowUnFullscreen` | Window exited fullscreen | +| `events.Common.WindowDPIChanged` | DPI scaling changed | | `events.Common.WindowFilesDropped` | Files were dropped onto the window | +| `events.Common.WindowFocus` | Window gained focus | +| `events.Common.WindowFullscreen` | Window entered fullscreen | +| `events.Common.WindowHide` | Window was hidden | +| `events.Common.WindowLostFocus` | Window lost focus | +| `events.Common.WindowMaximise` | Window maximised | +| `events.Common.WindowMinimise` | Window minimised | +| `events.Common.WindowToggleFrameless` | Frameless mode toggled | +| `events.Common.WindowRestore` | Window restored from min/max | +| `events.Common.WindowRuntimeReady` | Wails runtime loaded in window | +| `events.Common.WindowShow` | Window became visible | +| `events.Common.WindowUnFullscreen` | Window exited fullscreen | +| `events.Common.WindowUnMaximise` | Window restored from maximised | +| `events.Common.WindowUnMinimise` | Window restored from minimised | +| `events.Common.WindowZoom` | Window zoom changed | +| `events.Common.WindowZoomIn` | Zoom level increased | +| `events.Common.WindowZoomOut` | Zoom level decreased | +| `events.Common.WindowZoomReset` | Zoom reset to default | ## Complete Example diff --git a/docs/src/content/docs/features/windows/multiple.mdx b/docs/src/content/docs/features/windows/multiple.mdx index c5ca3c94a..23099c5aa 100644 --- a/docs/src/content/docs/features/windows/multiple.mdx +++ b/docs/src/content/docs/features/windows/multiple.mdx @@ -444,17 +444,14 @@ func CreateWindow(name string) { } ``` -### Closing vs Destroying +### Closing Windows ```go // Close - triggers WindowClosing event, can be cancelled via RegisterHook window.Close() - -// Destroy - immediate, cannot be cancelled -window.Destroy() ``` -**Best practice:** Use `Close()` for user-initiated closes, `Destroy()` for cleanup. +**Note:** `Close()` emits a `WindowClosing` event. Use `RegisterHook` to intercept and optionally cancel the close. ### Resource Cleanup @@ -464,14 +461,14 @@ type ManagedWindow struct { resources []io.Closer } -func (mw *ManagedWindow) Destroy() { +func (mw *ManagedWindow) CleanupAndClose() { // Close all resources for _, resource := range mw.resources { resource.Close() } - - // Destroy window - mw.window.Destroy() + + // Close window + mw.window.Close() } ``` diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx index 70c2dc37d..22c3eedd7 100644 --- a/docs/src/content/docs/features/windows/options.mdx +++ b/docs/src/content/docs/features/windows/options.mdx @@ -514,22 +514,6 @@ HTML: ` `, ``` -### Assets - -**Type:** `AssetOptions` -**Default:** Inherited from application -**Platform:** All - -```go -Assets: application.AssetOptions{ - Handler: application.AssetFileServerFS(assets), -}, -``` - -**Purpose:** Serve embedded frontend assets. - -**See [Build System](/concepts/build-system) for details.** - ## Security Options ### ContentProtectionEnabled @@ -638,10 +622,14 @@ Mac: application.MacWindow{ - `MacTitleBarHiddenInset` - Hidden with inset traffic lights - `MacTitleBarHiddenInsetUnified` - Hidden with unified toolbar style +**DisableShadow** (`bool`) +- Disable the window shadow + **Backdrop** (`MacBackdrop`) -- `MacBackdropNormal` - Standard -- `MacBackdropTranslucent` - Blurred translucent +- `MacBackdropNormal` - Standard opaque background - `MacBackdropTransparent` - Fully transparent +- `MacBackdropTranslucent` - Blurred translucent +- `MacBackdropLiquidGlass` - Apple's Liquid Glass effect (macOS 15.0+, falls back to translucent) **InvisibleTitleBarHeight** (`int`) - Height of invisible title bar (for dragging) @@ -650,6 +638,35 @@ Mac: application.MacWindow{ - `DefaultAppearance` - System default - `NSAppearanceNameAqua` - Light appearance - `NSAppearanceNameDarkAqua` - Dark appearance +- `NSAppearanceNameVibrantLight` - Light vibrant appearance +- `NSAppearanceNameAccessibilityHighContrastAqua` - High-contrast light +- `NSAppearanceNameAccessibilityHighContrastDarkAqua` - High-contrast dark +- `NSAppearanceNameAccessibilityHighContrastVibrantLight` - High-contrast vibrant light +- `NSAppearanceNameAccessibilityHighContrastVibrantDark` - High-contrast vibrant dark + +**EnableFraudulentWebsiteWarnings** (`bool`) +- Enable warnings for fraudulent websites + +**WebviewPreferences** (`MacWebviewPreferences`) +- `TabFocusesLinks` - Enable tabbing to links +- `TextInteractionEnabled` - Enable text interaction +- `FullscreenEnabled` - Enable fullscreen +- `AllowsBackForwardNavigationGestures` - Enable horizontal swipe gestures for back/forward navigation + +**WindowLevel** (`MacWindowLevel`) +- Controls the window level ordering. Values: `MacWindowLevelNormal`, `MacWindowLevelFloating`, `MacWindowLevelTornOffMenu`, `MacWindowLevelModalPanel`, `MacWindowLevelMainMenu`, `MacWindowLevelStatus`, `MacWindowLevelPopUpMenu`, `MacWindowLevelScreenSaver` + +**CollectionBehavior** (`MacWindowCollectionBehavior`) +- Controls how the window behaves across macOS Spaces and fullscreen. Values can be combined with bitwise OR. Includes: `MacWindowCollectionBehaviorCanJoinAllSpaces`, `MacWindowCollectionBehaviorMoveToActiveSpace`, `MacWindowCollectionBehaviorFullScreenPrimary`, `MacWindowCollectionBehaviorFullScreenAuxiliary`, etc. + +**EventMapping** (`map[events.WindowEventType]events.WindowEventType`) +- Maps platform-specific events to common event types + +**LiquidGlass** (`MacLiquidGlass`) +- Configuration for the Liquid Glass effect: `Style`, `Material`, `CornerRadius`, `TintColor`, `GroupID`, `GroupSpacing` + +**ShowToolbarWhenFullscreen** (in `MacTitleBar`) +- Keep the toolbar visible when the window is in fullscreen mode **Example:** @@ -696,6 +713,42 @@ Windows: application.WindowsWindow{ - Disable default frameless decorations (shadow and rounded corners) - For custom window chrome +**WindowMask** (`[]byte`) +- Set the window shape using a PNG with an alpha channel + +**WindowMaskDraggable** (`bool`) +- Make the window draggable by clicking on the window mask + +**ResizeDebounceMS** (`uint16`) +- Amount of time in milliseconds to debounce redraws of webview2 when resizing + +**WindowDidMoveDebounceMS** (`uint16`) +- Amount of time in milliseconds to debounce the WindowDidMove event when moving + +**EventMapping** (`map[events.WindowEventType]events.WindowEventType`) +- Maps platform-specific events to common event types + +**HiddenOnTaskbar** (`bool`) +- Hide the window from the taskbar + +**EnableSwipeGestures** (`bool`) +- Enable swipe gestures for the window + +**Menu** (`*Menu`) +- The menu to use for the window + +**Permissions** (`map[CoreWebView2PermissionKind]CoreWebView2PermissionState`) +- WebView2 permissions map. If empty, default permissions will be granted. + +**ExStyle** (`int`) +- Extended window style + +**GeneralAutofillEnabled** (`bool`) +- Enable general autofill + +**PasswordAutosaveEnabled** (`bool`) +- Enable autosaving passwords + **Example:** ```go @@ -728,6 +781,16 @@ Linux: application.LinuxWindow{ - `WebviewGpuPolicyOnDemand` - Enabled/disabled as requested by web contents - `WebviewGpuPolicyNever` - Hardware acceleration always disabled +**WindowDidMoveDebounceMS** (`uint16`) +- Debounce time in milliseconds for the WindowDidMove event + +**Menu** (`*Menu`) +- The window's menu + +**MenuStyle** (`LinuxMenuStyle`) - GTK4 only, ignored on GTK3 +- `LinuxMenuStyleMenuBar` - Traditional menu bar below the title bar (default) +- `LinuxMenuStylePrimaryMenu` - Primary menu button in the header bar (GNOME style) + **Example:** ```go diff --git a/docs/src/content/docs/guides/building.mdx b/docs/src/content/docs/guides/building.mdx index cf6e9ef97..d711724b4 100644 --- a/docs/src/content/docs/guides/building.mdx +++ b/docs/src/content/docs/guides/building.mdx @@ -9,7 +9,7 @@ import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components"; ## Overview -Wails provides simple commands to build your application for development and production. +Wails uses [Task](https://taskfile.dev) as its build system. The `wails3 build`, `wails3 package`, and `wails3 dev` commands are wrappers around tasks defined in your project's `Taskfile.yml`. You can customize the build process by editing the Taskfile. ## Development Build @@ -28,16 +28,22 @@ wails3 dev ### Dev Options ```bash -# Specify frontend dev server -wails3 dev -devserver http://localhost:5173 +# Custom config file +wails3 dev -config ./build/config.yml -# Skip frontend dev server -wails3 dev -nofrontend +# Custom vite dev server port +wails3 dev -port 3000 -# Custom build flags -wails3 dev -tags dev +# Enable HTTPS for the dev server +wails3 dev -s ``` +The dev server port defaults to `9245`. You can also set the port via the `WAILS_VITE_PORT` environment variable. + + :::note + `wails3 dev` is equivalent to running `wails3 task dev` and runs the `dev` task in the project's main Taskfile. You can customise this by editing the `Taskfile.yml` file. + ::: + ## Production Build ### Basic Build @@ -46,105 +52,103 @@ wails3 dev -tags dev wails3 build ``` -**Output:** Optimized binary in `build/bin/` +**Output:** Binary in `bin/` directory. -### Build Options +### Build with Tags ```bash -# Build for specific platform -wails3 build -platform windows/amd64 - -# Custom output directory -wails3 build -o ./dist/myapp - -# Skip frontend build -wails3 build -nofrontend - -# Production optimizations -wails3 build -ldflags "-s -w" +# Pass additional Go build tags +wails3 build -tags production,debug ``` -## Build Configuration +### Build with CLI Variables -### wails.json +You can pass CLI variables to customize the build via the underlying Taskfile: -```json -{ - "name": "myapp", - "frontend": { - "dir": "./frontend", - "install": "npm install", - "build": "npm run build", - "dev": "npm run dev", - "devServerUrl": "http://localhost:5173" - }, - "build": { - "output": "myapp", - "ldflags": "-s -w" - } -} +```bash +wails3 build PRODUCTION=true ``` +These variables can be accessed in your `Taskfile.yml` using Go template syntax. + + :::note + `wails3 build` is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. Any CLI variables are forwarded to the underlying task. You can customise the build process by editing the `Taskfile.yml` file. + ::: + ## Platform-Specific Builds +Platform-specific build tasks are defined in the Taskfiles under `build//Taskfile.yml`. The main `Taskfile.yml` dispatches to the correct platform automatically based on the current OS. + + The Windows Taskfile handles: + - Building the binary with appropriate flags + - Generating `.ico` icon file + - Generating Windows `.syso` file + - Creating NSIS installers for packaging + ```bash - # Windows executable - wails3 build -platform windows/amd64 - - # With icon - wails3 build -platform windows/amd64 -icon icon.ico - - # Console app (shows terminal) - wails3 build -platform windows/amd64 -windowsconsole + # Build for Windows (runs automatically on Windows) + wails3 build ``` + The macOS Taskfile handles: + - Building binaries for amd64, arm64, and universal architectures + - Generating `.icns` icon file + - Creating `.app` bundles + - Ad-hoc code signing + ```bash - # macOS app bundle - wails3 build -platform darwin/amd64 - - # Universal binary (Intel + Apple Silicon) - wails3 build -platform darwin/universal - - # With icon - wails3 build -platform darwin/amd64 -icon icon.icns + # Build for macOS (runs automatically on macOS) + wails3 build ``` + The Linux Taskfile handles: + - Building the binary with appropriate flags + - Generating `.desktop` files + - Creating AppImage, deb, rpm, and Arch Linux packages + ```bash - # Linux executable - wails3 build -platform linux/amd64 - - # With icon - wails3 build -platform linux/amd64 -icon icon.png + # Build for Linux (runs automatically on Linux) + wails3 build ``` +## Packaging + +```bash +wails3 package +``` + +This creates platform-specific packages for distribution. Like `build`, it is a wrapper around `wails3 task package`. + +| Platform | Package Types | +|----------|-------------------------------------------| +| Windows | `.exe`, NSIS installer | +| macOS | `.app` bundle | +| Linux | `.AppImage`, `.deb`, `.rpm`, `.archlinux` | + ## Optimization ### Binary Size ```bash -# Strip debug symbols -wails3 build -ldflags "-s -w" - # UPX compression (external tool) -upx --best --lzma build/bin/myapp +upx --best --lzma bin/myapp ``` -### Performance +The default Taskfiles already include `-ldflags "-s -w"` to strip debug symbols. + +### Build Tags ```bash -# Enable optimizations +# Pass build tags to the Go compiler wails3 build -tags production - -# Disable debug features -wails3 build -ldflags "-X main.debug=false" ``` ## Troubleshooting @@ -157,20 +161,19 @@ wails3 build -ldflags "-X main.debug=false" - Check `go.mod` is up to date - Run `go mod tidy` - Verify frontend builds: `cd frontend && npm run build` -- Check wails.json configuration +- Run `wails3 doctor` to check your environment ### Large Binary Size **Problem:** Binary is too large **Solutions:** -- Use `-ldflags "-s -w"` to strip symbols +- Ensure Taskfile includes `-ldflags "-s -w"` to strip symbols (default Taskfiles do this) - Remove unused dependencies - Use UPX compression - Check embedded assets size ## Next Steps -- [Cross-Platform Building](/guides/cross-platform) - Build for multiple platforms -- [Distribution](/guides/installers) - Create installers and packages -- [Build System](/concepts/build-system) - Understand the build system +- [Build Customization](/guides/build/customization) - Customize the build process with Taskfiles +- [Build System](/concepts/build-system) - Understand how the build system works diff --git a/docs/src/content/docs/guides/cli.mdx b/docs/src/content/docs/guides/cli.mdx index fb9f23969..5335fc7ab 100644 --- a/docs/src/content/docs/guides/cli.mdx +++ b/docs/src/content/docs/guides/cli.mdx @@ -37,7 +37,11 @@ wails3 init [flags] | `-productcopyright` | Copyright notice | ` now, My Company` | | `-productcomments` | File comments | `This is a comment` | | `-productidentifier` | Product identifier | | +| `-s` | Skip warning for remote templates | `false` | | `-skipgomodtidy` | Skip go mod tidy | `false` | +| `-mod` | Go module path | | + +The `-mod` flag sets the Go module path for the project. If omitted and `-git` is provided, the module path will be computed from the Git URL. The `-git` flag accepts various Git URL formats: - HTTPS: `https://github.com/username/project` @@ -77,12 +81,22 @@ wails3 dev [flags] Builds a debug version of your application. It defaults to building for the current platform and architecture. ```bash -wails3 build [CLI variables...] +wails3 build [flags] [CLI variables...] ``` +#### Flags +| Flag | Description | Default | +|-----------|----------------------------------------------------------|---------| +| `-tags` | Additional build tags to pass to the Go compiler (comma-separated) | | + You can pass CLI variables to customize the build: ```bash wails3 build PLATFORM=linux CONFIG=production +``` + +You can also pass additional Go build tags: +```bash +wails3 build -tags production,debug ``` :::note @@ -215,11 +229,12 @@ wails3 generate bindings [flags] [patterns...] | `-i` | Use TS interfaces | `false` | | `-b` | Use bundled runtime | `false` | | `-names` | Use names instead of IDs | `false` | +| `-noevents` | Skip custom event types | `false` | | `-noindex` | Skip index files | `false` | | `-dry` | Dry run | `false` | | `-silent` | Silent mode | `false` | | `-v` | Debug output | `false` | -| `-clean` | Clean output directory | `false` | +| `-clean` | Clean output directory | `true` | ### `generate build-assets` Generates build assets for your application. @@ -308,9 +323,15 @@ wails3 generate runtime Generates JavaScript constants from Go code. ```bash -wails3 generate constants +wails3 generate constants [flags] ``` +#### Flags +| Flag | Description | Default | +|------|--------------------------------|----------------| +| `-f` | The Go constants filename | `constants.go` | +| `-o` | The output JavaScript file | `constants.js` | + ### `generate appimage` Generates a Linux AppImage. @@ -435,20 +456,60 @@ wails3 tool package [flags] ``` #### Flags -| Flag | Description | Default | -|-----------|--------------------------------------|----------| -| `-format` | Package format (deb, rpm, archlinux) | `deb` | -| `-name` | Executable name | Required | -| `-config` | Config file path | Required | -| `-out` | Output directory | `.` | +| Flag | Description | Default | +|----------------|------------------------------------------------|---------| +| `-format` | Package format (deb, rpm, archlinux, dmg) | `deb` | +| `-name` | Executable name | `myapp` | +| `-config` | Config file path | | +| `-out` | Output directory | `.` | +| `-background` | Path to optional background image for DMG | | +| `-create-dmg` | Create a DMG file (macOS only) | `false` | + +### `tool lipo` +Creates a macOS universal binary from multiple architecture-specific binaries. + +```bash +wails3 tool lipo [flags] +``` #### Flags -| Flag | Description | Default | -|-----------|--------------------------------------|---------| -| `-format` | Package format (deb, rpm, archlinux) | `deb` | -| `-name` | Executable name | `myapp` | -| `-config` | Config file path | | -| `-out` | Output directory | `.` | +| Flag | Description | Default | +|------------|-------------------------------------------------|---------| +| `-output` (`-o`) | Output path for the universal binary | | +| `-input` (`-i`) | Input binaries to combine (specify multiple times) | | + +### `tool capabilities` +Checks system build capabilities (GTK4/GTK3 availability on Linux). + +```bash +wails3 tool capabilities +``` + +### `tool sign` +Signs a binary or package directly. + +```bash +wails3 tool sign [flags] +``` + +#### Flags +| Flag | Description | Default | +|----------------------|------------------------------------------------------|---------| +| `-input` | Path to the file to sign | | +| `-output` | Output path (defaults to in-place signing) | | +| `-verbose` | Enable verbose output | `false` | +| `-certificate` | Path to PKCS#12 (.pfx/.p12) certificate file | | +| `-password` | Certificate password | | +| `-thumbprint` | Certificate thumbprint in Windows certificate store | | +| `-timestamp` | Timestamp server URL | | +| `-identity` | macOS signing identity | | +| `-entitlements` | Path to entitlements plist file | | +| `-hardened-runtime` | Enable hardened runtime | `false` | +| `-notarize` | Submit for Apple notarization after signing | `false` | +| `-keychain-profile` | Keychain profile for notarization credentials | | +| `-pgp-key` | Path to PGP private key file | | +| `-pgp-password` | PGP key password | | +| `-role` | DEB signing role (origin, maint, archive, builder) | | Base command: `wails3 update` @@ -537,3 +598,99 @@ Opens the Wails sponsorship page in your default browser. ```bash wails3 sponsor +``` + +### `doctor-ng` +System status report using the new TUI interface. + +```bash +wails3 doctor-ng +``` + +## Setup Commands + +Setup commands provide project configuration wizards. All setup commands use the base command: `wails3 setup `. + +### `setup signing` +Configures code signing for your project. + +```bash +wails3 setup signing [flags] +``` + +#### Flags +| Flag | Description | Default | +|--------------|--------------------------------------------------------------|---------| +| `-platform` | Platform(s) to configure (darwin, windows, linux). Auto-detects if not specified. | | + +### `setup entitlements` +Configures macOS entitlements for your project. + +```bash +wails3 setup entitlements [flags] +``` + +#### Flags +| Flag | Description | Default | +|------------|--------------------------------------|-------------------------------------| +| `-output` | Output path for entitlements.plist | `build/darwin/entitlements.plist` | + +## Sign Command + +### `sign` +Signs binaries and packages for the current or specified platform. This is a wrapper that calls platform-specific signing tasks. + +```bash +wails3 sign [flags] [args...] +``` + +## Generate Commands (continued) + +### `generate template` +Generates a new Wails template from an existing project. + +```bash +wails3 generate template [flags] +``` + +#### Flags +| Flag | Description | Default | +|-----------------|--------------------------|----------| +| `-name` | Template name | | +| `-shortname` | Short name | | +| `-author` | Template author | | +| `-description` | Template description | | +| `-helpurl` | Help URL | | +| `-version` | Template version | `v0.0.1` | +| `-dir` | Output directory | `.` | +| `-frontend` | Frontend directory to migrate | | + +### `generate webview2bootstrapper` +Generates the WebView2 bootstrapper executable for Windows. + +```bash +wails3 generate webview2bootstrapper [flags] +``` + +#### Flags +| Flag | Description | Default | +|--------|---------------------------------|---------| +| `-dir` | Directory to write the file to | | + +## iOS Commands + +iOS commands provide tooling for iOS development. All iOS commands use the base command: `wails3 ios `. + +### `ios overlay:gen` +Generates a Go overlay for the iOS bridge shim. + +```bash +wails3 ios overlay:gen +``` + +### `ios xcode:gen` +Generates an Xcode project in the output directory. + +```bash +wails3 ios xcode:gen +``` diff --git a/docs/src/content/docs/guides/distribution/custom-protocols.mdx b/docs/src/content/docs/guides/distribution/custom-protocols.mdx index 044a2ea3e..344c4aafc 100644 --- a/docs/src/content/docs/guides/distribution/custom-protocols.mdx +++ b/docs/src/content/docs/guides/distribution/custom-protocols.mdx @@ -35,13 +35,6 @@ func main() { app := application.New(application.Options{ Name: "My Application", Description: "My awesome application", - Protocols: []application.Protocol{ - { - Scheme: "myapp", - Description: "My Application Protocol", - Role: "Editor", // macOS only - }, - }, }) // Register handler for protocol events @@ -60,6 +53,12 @@ func handleCustomURL(url string) { } ``` +:::note +Custom protocol schemes are registered at the OS level through your application's build assets +(Info.plist on macOS, NSIS installer on Windows, .desktop files on Linux), +not through `application.Options`. See the [Platform Registration](#platform-registration) section below for details. +::: + ## Protocol Handler Listen for protocol events to handle incoming URLs: @@ -123,7 +122,7 @@ Custom protocols are registered differently on each platform. #### Automatic Registration When you build your application with `wails3 build`, the NSIS installer: -1. Automatically registers all protocols defined in `application.Options.Protocols` +1. Automatically registers all protocols defined in your build assets configuration 2. Associates protocols with your application executable 3. Sets up proper registry entries 4. Removes protocol associations during uninstall @@ -136,7 +135,7 @@ The NSIS template includes built-in macros: - `wails.associateCustomProtocols` - Registers protocols during installation - `wails.unassociateCustomProtocols` - Removes protocols during uninstall -These macros are automatically called based on your `Protocols` configuration. +These macros are automatically called based on your build assets protocol configuration. #### Manual Registry (Advanced) @@ -174,7 +173,7 @@ On macOS, protocols are registered via your `Info.plist` file. Wails automatically generates the `Info.plist` with your protocols when you build with `wails3 build`. -The protocols from `application.Options.Protocols` are added to: +The protocols from your build assets configuration are added to: ```xml CFBundleURLTypes @@ -278,13 +277,6 @@ func main() { app := application.New(application.Options{ Name: "DeepLink Demo", Description: "Custom protocol demonstration", - Protocols: []application.Protocol{ - { - Scheme: "deeplink", - Description: "DeepLink Demo Protocol", - Role: "Editor", - }, - }, }) myApp := &App{app: app} @@ -556,8 +548,10 @@ Create a test HTML page: **Check logs:** ```go +import "log/slog" + app := application.New(application.Options{ - Logger: application.NewLogger(application.LogLevelDebug), + LogLevel: slog.LevelDebug, // ... }) ``` diff --git a/docs/src/content/docs/guides/events-reference.mdx b/docs/src/content/docs/guides/events-reference.mdx index 4fd29e659..fa6d29534 100644 --- a/docs/src/content/docs/guides/events-reference.mdx +++ b/docs/src/content/docs/guides/events-reference.mdx @@ -168,22 +168,45 @@ Events.On('common:ThemeChanged', (event) => { ### 3. Handling File Drops -Make your app accept dragged files: +Handle file drops in Go using the `WindowEventContext`: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +window.OnWindowEvent(events.Common.WindowFilesDropped, func(e *application.WindowEvent) { + files := e.Context().DroppedFiles() + for _, file := range files { + fmt.Println("File dropped:", file) + } + + // Optionally get drop target details + details := e.Context().DropTargetDetails() + if details != nil { + fmt.Println("Dropped on element:", details.ID) + } + + // Notify the frontend + window.EmitEvent("files-dropped", files) +}) +``` + +Then listen in JavaScript: ```javascript import { Events } from '@wailsio/runtime'; -Events.On('common:WindowFilesDropped', (event) => { - const files = event.data.files; - - files.forEach(file => { +Events.On('files-dropped', (event) => { + event.data.forEach(file => { console.log('File dropped:', file); - // Process the dropped files handleFileUpload(file); }); }); ``` +:::note +The `common:WindowFilesDropped` system event is dispatched to the JS frontend but does not include the file paths in its data. Use a Go event handler to access the dropped files via `event.Context().DroppedFiles()` and forward them to the frontend with a custom event. +::: + ### 4. Window Lifecycle Management Respond to window state changes: @@ -194,9 +217,6 @@ import { Events } from '@wailsio/runtime'; Events.On('common:WindowClosing', () => { // Save user data before closing saveApplicationState(); - - // You could also prevent closing by returning false - // from a registered window close handler }); Events.On('common:WindowMaximise', () => { @@ -327,12 +347,8 @@ func init() { :::caution `RegisterEvent` is meant to be called at init time and will panic if: -- Arguments are not valid -- The same event name is registered twice with different data types -::: - -:::note -It is safe to register the same event multiple times as long as the data type is always the same. This can be useful to ensure an event is registered when any of multiple packages is loaded. +- The event name conflicts with a known system event name +- The same event name is registered more than once ::: ### Benefits of Event Registration @@ -421,18 +437,32 @@ These events work on all platforms: | Event | Description | When to Use | |-------|-------------|-------------| +| `common:ApplicationOpenedWithFile` | App launched with a file | Open file passed at launch | | `common:ApplicationStarted` | Application has fully started | Initialize your app, load saved state | -| `common:WindowRuntimeReady` | Wails runtime is ready | Start making Wails API calls | +| `common:ApplicationLaunchedWithUrl` | App launched via URL scheme | Handle custom URL schemes | | `common:ThemeChanged` | System theme changed | Update app appearance | -| `common:WindowFocus` | Window gained focus | Resume activities, refresh data | -| `common:WindowLostFocus` | Window lost focus | Pause activities, save state | -| `common:WindowMinimise` | Window was minimized | Pause rendering, reduce resource usage | -| `common:WindowMaximise` | Window was maximized | Adjust layout for full screen | -| `common:WindowRestore` | Window restored from min/max | Return to normal layout | | `common:WindowClosing` | Window is about to close | Save data, cleanup resources | -| `common:WindowFilesDropped` | Files dropped on window | Handle file imports | -| `common:WindowDidResize` | Window was resized | Adjust layout, rerender charts | | `common:WindowDidMove` | Window was moved | Update position-dependent features | +| `common:WindowDidResize` | Window was resized | Adjust layout, rerender charts | +| `common:WindowDPIChanged` | DPI scaling changed | Adjust rendering for new DPI | +| `common:WindowFilesDropped` | Files dropped on window | Handle file imports | +| `common:WindowFocus` | Window gained focus | Resume activities, refresh data | +| `common:WindowFullscreen` | Window entered fullscreen | Adjust layout for fullscreen | +| `common:WindowHide` | Window was hidden | Pause rendering | +| `common:WindowLostFocus` | Window lost focus | Pause activities, save state | +| `common:WindowMaximise` | Window was maximized | Adjust layout for full screen | +| `common:WindowMinimise` | Window was minimized | Pause rendering, reduce resource usage | +| `common:WindowToggleFrameless` | Frameless mode toggled | Update window chrome | +| `common:WindowRestore` | Window restored from min/max | Return to normal layout | +| `common:WindowRuntimeReady` | Wails runtime is ready | Start making Wails API calls | +| `common:WindowShow` | Window became visible | Resume rendering | +| `common:WindowUnFullscreen` | Window exited fullscreen | Restore normal layout | +| `common:WindowUnMaximise` | Window restored from maximized | Restore normal layout | +| `common:WindowUnMinimise` | Window restored from minimized | Resume rendering | +| `common:WindowZoom` | Window zoom changed | Adjust content scaling | +| `common:WindowZoomIn` | Zoom level increased | Update zoom indicator | +| `common:WindowZoomOut` | Zoom level decreased | Update zoom indicator | +| `common:WindowZoomReset` | Zoom reset to default | Update zoom indicator | ### Platform-Specific Events diff --git a/docs/src/content/docs/migration/v2-to-v3.mdx b/docs/src/content/docs/migration/v2-to-v3.mdx index 58e357518..e164d2b94 100644 --- a/docs/src/content/docs/migration/v2-to-v3.mdx +++ b/docs/src/content/docs/migration/v2-to-v3.mdx @@ -533,7 +533,7 @@ menu.Append(menu.Text("File", nil, []*menu.MenuItem{ ```go menu := application.NewMenu() fileMenu := menu.AddSubmenu("File") -fileMenu.Add("Quit").OnClick(func(ctx *application.Context) { +fileMenu.Add("Quit").OnClick(func(_ *application.Context) { app.Quit() }) ``` diff --git a/docs/src/content/docs/reference/dialogs.mdx b/docs/src/content/docs/reference/dialogs.mdx index 81b45879d..c82e81217 100644 --- a/docs/src/content/docs/reference/dialogs.mdx +++ b/docs/src/content/docs/reference/dialogs.mdx @@ -112,6 +112,62 @@ Sets whether hidden files are shown. func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct ``` +#### ResolvesAliases() + +Sets whether aliases (symlinks) are resolved. + +```go +func (d *OpenFileDialogStruct) ResolvesAliases(resolvesAliases bool) *OpenFileDialogStruct +``` + +#### AllowsOtherFileTypes() + +Sets whether file types other than those in filters are allowed. + +```go +func (d *OpenFileDialogStruct) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialogStruct +``` + +#### HideExtension() + +Sets whether the file extension is hidden. + +```go +func (d *OpenFileDialogStruct) HideExtension(hideExtension bool) *OpenFileDialogStruct +``` + +#### TreatsFilePackagesAsDirectories() + +Sets whether file packages are treated as directories (macOS). + +```go +func (d *OpenFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *OpenFileDialogStruct +``` + +#### CanSelectHiddenExtension() + +Sets whether hidden extensions can be selected. + +```go +func (d *OpenFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialogStruct +``` + +#### SetMessage() + +Sets the dialog message text. + +```go +func (d *OpenFileDialogStruct) SetMessage(message string) *OpenFileDialogStruct +``` + +#### SetButtonText() + +Sets the text for the dialog's confirmation button. + +```go +func (d *OpenFileDialogStruct) SetButtonText(text string) *OpenFileDialogStruct +``` + #### AttachToWindow() Attaches the dialog to a specific window (sheet on macOS). @@ -200,6 +256,14 @@ if err != nil { outputDir = folder ``` +### app.Dialog.OpenFileWithOptions() + +Creates a file open dialog with pre-set options. + +```go +func (dm *DialogManager) OpenFileWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct +``` + ### app.Dialog.SaveFile() Creates a file save dialog. @@ -213,16 +277,16 @@ func (dm *DialogManager) SaveFile() *SaveFileDialogStruct dialog := app.Dialog.SaveFile() ``` -### SaveFileDialogStruct Methods +### app.Dialog.SaveFileWithOptions() -#### SetTitle() - -Sets the dialog title. +Creates a file save dialog with pre-set options. ```go -func (d *SaveFileDialogStruct) SetTitle(title string) *SaveFileDialogStruct +func (dm *DialogManager) SaveFileWithOptions(options *SaveFileDialogOptions) *SaveFileDialogStruct ``` +### SaveFileDialogStruct Methods + #### SetFilename() Sets the default filename. @@ -252,6 +316,78 @@ Sets the initial directory. func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct ``` +#### SetMessage() + +Sets the dialog message text. + +```go +func (d *SaveFileDialogStruct) SetMessage(message string) *SaveFileDialogStruct +``` + +#### SetButtonText() + +Sets the text for the dialog's confirmation button. + +```go +func (d *SaveFileDialogStruct) SetButtonText(text string) *SaveFileDialogStruct +``` + +#### CanCreateDirectories() + +Sets whether new directories can be created in the dialog. + +```go +func (d *SaveFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialogStruct +``` + +#### ShowHiddenFiles() + +Sets whether hidden files are shown. + +```go +func (d *SaveFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialogStruct +``` + +#### CanSelectHiddenExtension() + +Sets whether hidden extensions can be selected. + +```go +func (d *SaveFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialogStruct +``` + +#### HideExtension() + +Sets whether the file extension is hidden. + +```go +func (d *SaveFileDialogStruct) HideExtension(hideExtension bool) *SaveFileDialogStruct +``` + +#### TreatsFilePackagesAsDirectories() + +Sets whether file packages are treated as directories (macOS). + +```go +func (d *SaveFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *SaveFileDialogStruct +``` + +#### AllowsOtherFileTypes() + +Sets whether file types other than those in filters are allowed. + +```go +func (d *SaveFileDialogStruct) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialogStruct +``` + +#### AttachToWindow() + +Attaches the dialog to a specific window (sheet on macOS). + +```go +func (d *SaveFileDialogStruct) AttachToWindow(window Window) *SaveFileDialogStruct +``` + #### PromptForSingleSelection() Shows the dialog and returns the save path. @@ -353,6 +489,14 @@ func (d *MessageDialog) AddButton(s string) *Button **Returns:** `*Button` - The created button +#### AddButtons() + +Sets all buttons for the dialog at once, replacing any existing buttons. + +```go +func (d *MessageDialog) AddButtons(buttons []*Button) *MessageDialog +``` + #### SetDefaultButton() Sets the default button (activated by pressing Enter). diff --git a/docs/src/content/docs/reference/events.mdx b/docs/src/content/docs/reference/events.mdx index 5c7241aa4..fdb3485b9 100644 --- a/docs/src/content/docs/reference/events.mdx +++ b/docs/src/content/docs/reference/events.mdx @@ -118,83 +118,83 @@ app.Event.Emit("global-update", data) ## Event Methods (Frontend) -### On() +### Events.On() Listens for events from Go. ```javascript -import { On } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' -On(eventName, callback) +Events.On(eventName, callback) ``` **Parameters:** - `eventName` - Name of the event to listen for -- `callback` - Function called when event is received +- `callback` - Function called with a `WailsEvent` object (`{ name, data, sender }`) **Returns:** Cleanup function **Example:** ```javascript -import { On } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' // Listen for events -const cleanup = On('data-updated', (data) => { - console.log('Count:', data.count) - console.log('Status:', data.status) - updateUI(data) +const cleanup = Events.On('data-updated', (event) => { + console.log('Count:', event.data.count) + console.log('Status:', event.data.status) + updateUI(event.data) }) // Later, remove listener cleanup() ``` -### Once() +### Events.Once() Listens for a single event occurrence. ```javascript -import { Once } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' -Once(eventName, callback) +Events.Once(eventName, callback) ``` **Example:** ```javascript -import { Once } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' // Listen for first occurrence only -Once('initialization-complete', (data) => { - console.log('App initialized!', data) +Events.Once('initialization-complete', (event) => { + console.log('App initialized!', event.data) // This will only fire once }) ``` -### Off() +### Events.Off() -Removes event listeners. +Removes event listeners for the specified event names. ```javascript -import { Off } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' -Off(...eventNames) +Events.Off(...eventNames) ``` **Example:** ```javascript -import { Off } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' -Off('my-event', 'other-event') +Events.Off('my-event', 'other-event') ``` -### OffAll() +### Events.OffAll() Removes all event listeners. ```javascript -import { OffAll } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' -OffAll() +Events.OffAll() ``` ## Application Events @@ -326,14 +326,14 @@ func (s *Service) ProcessFiles(files []string) error { **JavaScript:** ```javascript -import { On, Once } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' -On('progress', (data) => { - progressBar.style.width = `${data.percent}%` - statusText.textContent = `Processing ${data.file}... (${data.current}/${data.total})` +Events.On('progress', (event) => { + progressBar.style.width = `${event.data.percent}%` + statusText.textContent = `Processing ${event.data.file}... (${event.data.current}/${event.data.total})` }) -Once('processing-complete', () => { +Events.Once('processing-complete', () => { progressBar.style.width = '100%' statusText.textContent = 'Complete!' }) @@ -354,10 +354,10 @@ preferencesWindow.EmitEvent("settings-updated", settings) **JavaScript:** ```javascript -import { On } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' -On('theme-changed', (theme) => { - document.body.className = theme +Events.On('theme-changed', (event) => { + document.body.className = event.data }) ``` @@ -388,11 +388,11 @@ func (s *StateService) UpdateState(key string, value any) { **JavaScript:** ```javascript -import { On } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' -On('state-updated', (data) => { - localState[data.key] = data.value - updateUI(data.key, data.value) +Events.On('state-updated', (event) => { + localState[event.data.key] = event.data.value + updateUI(event.data.key, event.data.value) }) ``` @@ -462,7 +462,7 @@ app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.App | `WindowClosing` | Window is about to close | Yes | | `WindowDidMove` | Window moved | No | | `WindowDidResize` | Window was resized | No | -| `WindowDPIChanged` | DPI scaling changed (Windows) | No | +| `WindowDPIChanged` | DPI scaling changed | No | | `WindowFilesDropped` | Files dropped onto window | No | | `WindowFocus` | Window gained focus | No | | `WindowFullscreen` | Entered fullscreen | No | @@ -470,12 +470,14 @@ app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.App | `WindowLostFocus` | Window lost focus | No | | `WindowMaximise` | Window maximized | Yes (macOS) | | `WindowMinimise` | Window minimized | Yes (macOS) | +| `WindowToggleFrameless` | Frameless mode toggled | No | | `WindowRestore` | Restored from min/max | No | | `WindowRuntimeReady` | Wails runtime loaded | No | | `WindowShow` | Window became visible | No | | `WindowUnFullscreen` | Exited fullscreen | No | | `WindowUnMaximise` | Exited maximized state | Yes (macOS) | | `WindowUnMinimise` | Exited minimized state | Yes (macOS) | +| `WindowZoom` | Zoom level changed | No | | `WindowZoomIn` | Zoom increased | Yes (macOS) | | `WindowZoomOut` | Zoom decreased | Yes (macOS) | | `WindowZoomReset` | Zoom reset to 100% | Yes (macOS) | @@ -584,26 +586,26 @@ func main() { **JavaScript:** ```javascript -import { On, Once } from '@wailsio/runtime' +import { Events } from '@wailsio/runtime' import { StartLongTask, BroadcastMessage } from './bindings/EventDemoService' // Task events -On('task-started', () => { +Events.On('task-started', () => { document.getElementById('status').textContent = 'Running...' }) -On('task-progress', (data) => { +Events.On('task-progress', (event) => { const progressBar = document.getElementById('progress') - progressBar.style.width = `${data.percent}%` + progressBar.style.width = `${event.data.percent}%` }) -Once('task-completed', (data) => { - document.getElementById('status').textContent = data.message +Events.Once('task-completed', (event) => { + document.getElementById('status').textContent = event.data.message }) // Window state events -On('window-state', (state) => { - document.body.dataset.windowState = state +Events.On('window-state', (event) => { + document.body.dataset.windowState = event.data }) // Trigger long task diff --git a/docs/src/content/docs/reference/menu.mdx b/docs/src/content/docs/reference/menu.mdx index d4966bd5b..4a180e920 100644 --- a/docs/src/content/docs/reference/menu.mdx +++ b/docs/src/content/docs/reference/menu.mdx @@ -37,6 +37,30 @@ You can also create a menu via the manager: menu := app.Menu.New() ``` +### NewMenuFromItems() + +Creates a menu from one or more existing menu items. + +```go +func NewMenuFromItems(item *MenuItem, items ...*MenuItem) *Menu +``` + +### NewSubmenu() + +Creates a submenu menu item with an existing menu as its content. + +```go +func NewSubmenu(label string, items *Menu) *MenuItem +``` + +### NewContextMenu() + +Creates a new context menu and auto-registers it by name. + +```go +func NewContextMenu(name string) *ContextMenu +``` + ## Menu Methods ### Add() @@ -167,7 +191,19 @@ editMenu.AddRole(application.Paste) editMenu.AddRole(application.SelectAll) ``` -**Available Roles:** `AppMenu`, `EditMenu`, `FileMenu`, `ViewMenu`, `WindowMenu`, `HelpMenu`, `Undo`, `Redo`, `Cut`, `Copy`, `Paste`, `PasteAndMatchStyle`, `SelectAll`, `Delete`, `Quit`, `CloseWindow`, `About`, `Reload`, `ForceReload`, `ToggleFullscreen`, `OpenDevTools`, `ResetZoom`, `ZoomIn`, `ZoomOut`, `Minimise`, `Print`, and more. +**Available Roles:** + +| Category | Roles | +|----------|-------| +| **Complete Menus** | `AppMenu`, `EditMenu`, `FileMenu`, `ViewMenu`, `WindowMenu`, `SpeechMenu`, `HelpMenu`, `ServicesMenu` | +| **Window Actions** | `CloseWindow`, `Minimise`, `Zoom`, `FullScreen`, `ToggleFullscreen`, `Front`, `ShowAll`, `BringAllToFront` | +| **Edit Actions** | `Undo`, `Redo`, `Cut`, `Copy`, `Paste`, `PasteAndMatchStyle`, `SelectAll`, `Delete` | +| **File Actions** | `NewFile`, `Open`, `Save`, `SaveAs`, `Revert`, `Print`, `PageLayout` | +| **View Actions** | `Reload`, `ForceReload`, `OpenDevTools`, `ResetZoom`, `ZoomIn`, `ZoomOut` | +| **App Actions** | `About`, `Quit`, `Hide`, `HideOthers`, `UnHide` | +| **Find Actions** | `Find`, `FindAndReplace`, `FindNext`, `FindPrevious` | +| **Speech Actions** | `StartSpeaking`, `StopSpeaking` | +| **Other** | `Help`, `NoRole` | ### Update() @@ -341,9 +377,10 @@ func (mi *MenuItem) SetAccelerator(shortcut string) *MenuItem **Parameters:** - `shortcut` - Keyboard shortcut string -**Accelerator format:** -- **Modifiers:** `Ctrl`, `Cmd`, `Alt`, `Shift`, `CmdOrCtrl` -- **Keys:** `A-Z`, `0-9`, `F1-F12`, `Enter`, `Backspace`, etc. +**Accelerator format:** `Modifier+Key` joined by `+`. Modifiers are case-insensitive. + +- **Modifiers:** `CmdOrCtrl` (aliases: `Cmd`, `Command`), `Ctrl`, `Alt` (aliases: `Option`, `OptionOrAlt`), `Shift`, `Super` +- **Keys:** `A-Z`, `0-9`, `F1-F35`, `Plus`, and named keys: `Backspace`, `Tab`, `Return`, `Enter`, `Escape`, `Space`, `Delete`, `Home`, `End`, `Page Up`, `Page Down`, `Left`, `Right`, `Up`, `Down`, `NumLock` - **Cross-platform:** Use `CmdOrCtrl` for `Cmd` on macOS and `Ctrl` on Windows/Linux **Example:** @@ -612,6 +649,16 @@ Sets the menu for the system tray. Returns `*SystemTray` for chaining. func (s *SystemTray) SetMenu(menu *Menu) *SystemTray ``` +#### SetIconPosition() + +Sets the icon position relative to the label (macOS). Returns `*SystemTray` for chaining. + +```go +func (s *SystemTray) SetIconPosition(iconPosition IconPosition) *SystemTray +``` + +**Icon position constants:** `NSImageNone`, `NSImageOnly`, `NSImageLeft`, `NSImageRight`, `NSImageBelow`, `NSImageAbove`, `NSImageOverlaps`, `NSImageLeading` (default), `NSImageTrailing` + #### SetLabel() Sets the tray label text (shown next to icon on macOS). @@ -620,6 +667,14 @@ Sets the tray label text (shown next to icon on macOS). func (s *SystemTray) SetLabel(label string) ``` +#### Label() + +Returns the current label text. + +```go +func (s *SystemTray) Label() string +``` + #### SetTooltip() Sets the tooltip shown when hovering over the tray icon. @@ -928,7 +983,7 @@ func (e *Editor) updateMenuState() { - Application menu automatically added with app name - Use `Cmd` for accelerators (or `CmdOrCtrl` for cross-platform) -- "About", "Preferences", and "Quit" in application menu by default +- "About", "Services", "Hide/Show", and "Quit" in application menu by default - Use `AddRole(application.AppMenu)` for the standard app menu ### Windows/Linux diff --git a/docs/src/content/docs/reference/window.mdx b/docs/src/content/docs/reference/window.mdx index ce9296200..bcd473bc4 100644 --- a/docs/src/content/docs/reference/window.mdx +++ b/docs/src/content/docs/reference/window.mdx @@ -290,6 +290,22 @@ window.Fullscreen() window.UnFullscreen() ``` +### ToggleFullscreen() + +Toggles the window between fullscreen and normal. + +```go +func (w *WebviewWindow) ToggleFullscreen() +``` + +### ToggleMaximise() + +Toggles the window between maximised and normal. + +```go +func (w *WebviewWindow) ToggleMaximise() +``` + ### Restore() Restores the window to its previous state if it was minimised, maximised, or fullscreen. @@ -404,6 +420,90 @@ Reloads the current window content. func (w *WebviewWindow) Reload() ``` +### ForceReload() + +Forces the window to reload the page assets. + +```go +func (w *WebviewWindow) ForceReload() +``` + +## Size Helpers + +### Width() / Height() + +Returns the current window width or height individually. + +```go +func (w *WebviewWindow) Width() int +func (w *WebviewWindow) Height() int +``` + +### Bounds() / SetBounds() + +Gets or sets the DIP (device-independent pixel) bounds of the window as a `Rect`. + +```go +func (w *WebviewWindow) Bounds() Rect +func (w *WebviewWindow) SetBounds(bounds Rect) +``` + +### PhysicalBounds() / SetPhysicalBounds() + +Gets or sets the physical pixel bounds of the window. + +```go +func (w *WebviewWindow) PhysicalBounds() Rect +func (w *WebviewWindow) SetPhysicalBounds(physicalBounds Rect) +``` + +### GetBorderSizes() + +Returns the border sizes of the window. + +```go +func (w *WebviewWindow) GetBorderSizes() *LRTB +``` + +### Resizable() + +Returns `true` if the window is resizable. + +```go +func (w *WebviewWindow) Resizable() bool +``` + +### DisableSizeConstraints() / EnableSizeConstraints() + +Temporarily disables or re-enables the min/max size constraints (used internally during maximise/fullscreen). + +```go +func (w *WebviewWindow) DisableSizeConstraints() +func (w *WebviewWindow) EnableSizeConstraints() +``` + +## Window Frame + +### ToggleFrameless() + +Toggles the window between frameless and normal. + +```go +func (w *WebviewWindow) ToggleFrameless() +``` + +## Menu Bar + +### ShowMenuBar() / HideMenuBar() / ToggleMenuBar() + +Controls the visibility of the window menu bar. + +```go +func (w *WebviewWindow) ShowMenuBar() +func (w *WebviewWindow) HideMenuBar() +func (w *WebviewWindow) ToggleMenuBar() +``` + ## Window Events Wails provides two methods for handling window events: @@ -501,7 +601,7 @@ func (w *WebviewWindow) EmitEvent(name string, data ...any) bool - `name` - Event name - `data` - Optional data to send with the event -**Returns:** `true` if the event was cancelled +**Returns:** `true` if the event was emitted successfully (not cancelled by a hook) **Example:** ```go @@ -600,12 +700,13 @@ performLongOperation() window.SetEnabled(true) ``` -### SetIgnoreMouseEvents() +### SetIgnoreMouseEvents() / IsIgnoreMouseEvents() Makes the window ignore mouse events (click-through). Returns `Window` for chaining. Windows and Mac only. ```go func (w *WebviewWindow) SetIgnoreMouseEvents(ignore bool) Window +func (w *WebviewWindow) IsIgnoreMouseEvents() bool ``` ### SetContentProtection() @@ -656,6 +757,14 @@ Flashes the window's taskbar button/icon. Windows only. func (w *WebviewWindow) Flash(enabled bool) ``` +### SnapAssist() + +Triggers the Windows Snap Assist feature by simulating Win+Z key combination. On Windows 11, this opens the snap layout options. On Linux and macOS, this is a no-op. + +```go +func (w *WebviewWindow) SnapAssist() +``` + ### GetScreen() Returns the screen that the window is on. diff --git a/docs/src/content/docs/tutorials/01-creating-a-service.mdx b/docs/src/content/docs/tutorials/01-creating-a-service.mdx index 935f33129..255502ebb 100644 --- a/docs/src/content/docs/tutorials/01-creating-a-service.mdx +++ b/docs/src/content/docs/tutorials/01-creating-a-service.mdx @@ -459,7 +459,7 @@ In this tutorial, we'll create a QR code generator service to demonstrate these Description: "A demo of using raw HTML & CSS", LogLevel: slog.LevelDebug, Services: []application.Service{ - application.NewService(NewQRService(), application.ServiceOptions{ + application.NewServiceWithOptions(NewQRService(), application.ServiceOptions{ Route: "/qrservice", }), }, @@ -549,7 +549,7 @@ In this tutorial, we'll create a QR code generator service to demonstrate these ```go title="main.go" ins={3} // ... - application.NewService(NewQRService(), application.ServiceOptions{ + application.NewServiceWithOptions(NewQRService(), application.ServiceOptions{ Route: "/services/qr", }), // ... diff --git a/docs/src/content/docs/tutorials/03-notes-vanilla.mdx b/docs/src/content/docs/tutorials/03-notes-vanilla.mdx index 329827c3a..4d9fdd494 100644 --- a/docs/src/content/docs/tutorials/03-notes-vanilla.mdx +++ b/docs/src/content/docs/tutorials/03-notes-vanilla.mdx @@ -45,6 +45,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat package main import ( + "context" "encoding/json" "errors" "os" @@ -63,11 +64,13 @@ In this tutorial, you'll build a notes application that demonstrates file operat type NotesService struct { notes []Note + app *application.App } - func NewNotesService() *NotesService { + func NewNotesService(app *application.App) *NotesService { return &NotesService{ notes: make([]Note, 0), + app: app, } } @@ -115,7 +118,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat // SaveToFile saves notes to a file func (n *NotesService) SaveToFile() error { - path, err := application.SaveFileDialog(). + path, err := n.app.Dialog.SaveFile(). SetFilename("notes.json"). AddFilter("JSON Files", "*.json"). PromptForSingleSelection() @@ -133,7 +136,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat return err } - application.InfoDialog(). + n.app.Dialog.Info(). SetTitle("Success"). SetMessage("Notes saved successfully!"). Show() @@ -143,7 +146,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat // LoadFromFile loads notes from a file func (n *NotesService) LoadFromFile() error { - path, err := application.OpenFileDialog(). + path, err := n.app.Dialog.OpenFile(). AddFilter("JSON Files", "*.json"). PromptForSingleSelection() @@ -163,7 +166,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat n.notes = notes - application.InfoDialog(). + n.app.Dialog.Info(). SetTitle("Success"). SetMessage("Notes loaded successfully!"). Show() @@ -180,8 +183,8 @@ In this tutorial, you'll build a notes application that demonstrates file operat - **Note struct**: Defines the data structure with JSON tags (lowercase) for proper serialization - **CRUD operations**: GetAll, Create, Update, and Delete for managing notes in memory - - **File dialogs**: Uses package-level functions `application.SaveFileDialog()` and `application.OpenFileDialog()` - - **Info dialogs**: Shows success messages using `application.InfoDialog()` + - **File dialogs**: Uses `app.Dialog.SaveFile()` and `app.Dialog.OpenFile()` via the DialogManager + - **Info dialogs**: Shows success messages using `app.Dialog.Info()` - **ID generation**: Simple timestamp-based ID generator 3. **Update main.go** @@ -206,9 +209,6 @@ In this tutorial, you'll build a notes application that demonstrates file operat app := application.New(application.Options{ Name: "Notes App", Description: "A simple notes application", - Services: []application.Service{ - application.NewService(NewNotesService()), - }, Assets: application.AssetOptions{ Handler: application.AssetFileServerFS(assets), }, @@ -217,6 +217,9 @@ In this tutorial, you'll build a notes application that demonstrates file operat }, }) + // Register service after app creation so we can pass the app reference + app.RegisterService(application.NewService(NewNotesService(app))) + app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Notes App", Width: 1000, @@ -234,7 +237,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat **What's happening here:** - - Registers `NotesService` with the application + - Creates the application, then registers `NotesService` with `app.RegisterService()` so the service can access the app for dialogs - Creates a window with dimensions (1000x700) mimicking Apple Notes - Sets up proper macOS behavior to quit when the last window closes @@ -650,19 +653,22 @@ In this tutorial, you'll build a notes application that demonstrates file operat ## Key Concepts -### Package-Level Dialog Functions +### Dialog Functions -In Wails v3, file and message dialogs are package-level functions, not methods on the app instance: +In Wails v3, file and message dialogs are accessed through the app's `Dialog` manager: ```go -// Correct - package-level function -path, err := application.SaveFileDialog(). +// Use the DialogManager on the app instance +path, err := app.Dialog.SaveFile(). SetFilename("notes.json"). AddFilter("JSON Files", "*.json"). PromptForSingleSelection() -// Wrong - not available -path, err := app.Dialog.SaveFile() // This doesn't exist +// Info dialog +app.Dialog.Info(). + SetTitle("Success"). + SetMessage("Notes saved successfully!"). + Show() ``` ### JSON Tag Mapping