docs: Migrate /learn content and reorganize documentation structure
Completes the documentation reorganization by migrating all /learn content to proper locations and removing the legacy /learn directory. Changes: - Migrated 10 unique files from /learn to appropriate sections: - features/notifications, keyboard, environment, browser, platform - concepts/manager-api - contributing/architecture/bindings - reference/runtime - guides/build/customization - Removed /learn directory and 22 legacy files - Updated sidebar navigation: - Removed "Learn" section - Added Manager API to Core Concepts - Reorganized Features with new subdirectories - Added Build Customization to Guides - Added Binding System to Contributing/Architecture - Fixed missing Card/CardGrid imports in 12 feature files - Cleaned up stale d2 SVG files All /features content verified to be more comprehensive than /learn versions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
|
@ -92,19 +92,13 @@ export default defineConfig({
|
|||
autogenerate: { directory: "tutorials" },
|
||||
},
|
||||
|
||||
// Learn - Discrete features
|
||||
{
|
||||
label: "Learn",
|
||||
collapsed: true,
|
||||
autogenerate: { directory: "learn" },
|
||||
},
|
||||
|
||||
// Core Concepts
|
||||
{
|
||||
label: "Core Concepts",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ label: "How Wails Works", link: "/concepts/architecture" },
|
||||
{ label: "Manager API", link: "/concepts/manager-api" },
|
||||
{ label: "Application Lifecycle", link: "/concepts/lifecycle" },
|
||||
{ label: "Go-Frontend Bridge", link: "/concepts/bridge" },
|
||||
{ label: "Build System", link: "/concepts/build-system" },
|
||||
|
|
@ -165,21 +159,41 @@ export default defineConfig({
|
|||
{ label: "Custom Dialogs", link: "/features/dialogs/custom" },
|
||||
],
|
||||
},
|
||||
{ label: "Clipboard", link: "/features/clipboard" },
|
||||
{
|
||||
label: "Clipboard",
|
||||
collapsed: true,
|
||||
autogenerate: { directory: "features/clipboard" },
|
||||
},
|
||||
{
|
||||
label: "Browser",
|
||||
collapsed: true,
|
||||
autogenerate: { directory: "features/browser" },
|
||||
},
|
||||
{ label: "Drag & Drop", link: "/features/drag-drop" },
|
||||
{ label: "Keyboard Shortcuts", link: "/features/keyboard" },
|
||||
{ label: "Notifications", link: "/features/notifications" },
|
||||
{ label: "Screens API", link: "/features/screens" },
|
||||
{ label: "Environment", link: "/features/environment" },
|
||||
{
|
||||
label: "Keyboard",
|
||||
collapsed: true,
|
||||
autogenerate: { directory: "features/keyboard" },
|
||||
},
|
||||
{
|
||||
label: "Notifications",
|
||||
collapsed: true,
|
||||
autogenerate: { directory: "features/notifications" },
|
||||
},
|
||||
{
|
||||
label: "Screens",
|
||||
collapsed: true,
|
||||
autogenerate: { directory: "features/screens" },
|
||||
},
|
||||
{
|
||||
label: "Environment",
|
||||
collapsed: true,
|
||||
autogenerate: { directory: "features/environment" },
|
||||
},
|
||||
{
|
||||
label: "Platform-Specific",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ label: "macOS Dock", link: "/features/platform/macos-dock" },
|
||||
{ label: "macOS Toolbar", link: "/features/platform/macos-toolbar" },
|
||||
{ label: "Windows UAC", link: "/features/platform/windows-uac" },
|
||||
{ label: "Linux Desktop", link: "/features/platform/linux" },
|
||||
],
|
||||
autogenerate: { directory: "features/platform" },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -204,6 +218,7 @@ export default defineConfig({
|
|||
collapsed: true,
|
||||
items: [
|
||||
{ label: "Building Applications", link: "/guides/build/building" },
|
||||
{ label: "Build Customization", link: "/guides/build/customization" },
|
||||
{ label: "Cross-Platform Builds", link: "/guides/build/cross-platform" },
|
||||
{ label: "Code Signing", link: "/guides/build/signing" },
|
||||
{ label: "Windows Packaging", link: "/guides/build/windows" },
|
||||
|
|
@ -305,6 +320,7 @@ export default defineConfig({
|
|||
{ label: "Runtime Layer", link: "/contributing/architecture/runtime" },
|
||||
{ label: "Platform Layer", link: "/contributing/architecture/platform" },
|
||||
{ label: "Build System", link: "/contributing/architecture/build" },
|
||||
{ label: "Binding System", link: "/contributing/architecture/bindings" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
title: Manager API
|
||||
description: Organized API structure with focused manager interfaces
|
||||
sidebar:
|
||||
order: 25
|
||||
order: 2
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
title: Binding System Internals
|
||||
title: Binding System
|
||||
description: How the binding system collects, processes, and generates JavaScript/TypeScript code
|
||||
sidebar:
|
||||
order: 21
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
title: Advanced Binding Techniques
|
||||
title: Advanced Binding
|
||||
description: Advanced binding techniques including directives, code injection, and custom IDs
|
||||
sidebar:
|
||||
order: 22
|
||||
order: 3
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
|
@ -5,7 +5,7 @@ sidebar:
|
|||
order: 1
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
import { FileTree, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Type-Safe Go-JavaScript Bindings
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ sidebar:
|
|||
order: 3
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Data Model Bindings
|
||||
|
||||
Wails **automatically generates JavaScript/TypeScript classes** from Go structs, providing full type safety when passing complex data between backend and frontend. Write Go structs, generate bindings, and get fully-typed frontend models complete with constructors, type annotations, and JSDoc comments.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
title: Browser Integration
|
||||
description: Open URLs and files in the user's default web browser
|
||||
sidebar:
|
||||
order: 58
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
|
@ -5,6 +5,8 @@ sidebar:
|
|||
order: 1
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Clipboard Operations
|
||||
|
||||
Wails provides a **unified clipboard API** that works across all platforms. Copy and paste text with simple, consistent methods on Windows, macOS, and Linux.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ sidebar:
|
|||
order: 4
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Custom dialogs
|
||||
|
||||
Create **custom dialog windows** using regular Wails windows with dialog-like behaviour. Build custom forms, complex input validation, branded appearance, and rich content (images, videos) whilst maintaining familiar dialog patterns.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ sidebar:
|
|||
order: 3
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## File dialogs
|
||||
|
||||
Wails provides **native file dialogs** with platform-appropriate appearance for opening files, saving files, and selecting folders. Simple API with file type filtering, multiple selection support, and default locations.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ sidebar:
|
|||
order: 2
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Message dialogs
|
||||
|
||||
Wails provides **native message dialogs** with platform-appropriate appearance: info, warning, error, and question dialogs with customisable titles, messages, and buttons. Simple API, native behaviour, accessible by default.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ sidebar:
|
|||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Native dialogs
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ sidebar:
|
|||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Event System
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
title: Key Bindings
|
||||
title: Keyboard Shortcuts
|
||||
description: Register global keyboard shortcuts for quick access to functionality
|
||||
sidebar:
|
||||
order: 56
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
|
@ -5,7 +5,7 @@ sidebar:
|
|||
order: 3
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## System Tray Menus
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
---
|
||||
title: Notifications
|
||||
description: Display native system notifications with action buttons and text input
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
---
|
||||
title: Dock
|
||||
title: Dock & Taskbar
|
||||
description: Manage dock icon visibility and display badges on macOS and Windows
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
|
@ -5,6 +5,8 @@ sidebar:
|
|||
order: 1
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Screen Information
|
||||
|
||||
Wails provides a **unified screen API** that works across all platforms. Get screen information, detect multiple monitors, query screen properties (size, position, DPI), identify the primary display, and handle DPI scaling with consistent code.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ sidebar:
|
|||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Window Management
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ sidebar:
|
|||
order: 4
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Frameless Windows
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
title: Build System
|
||||
title: Build Customization
|
||||
description: Customize your build process using Task and Taskfile.yml
|
||||
sidebar:
|
||||
order: 40
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
|
@ -1,261 +0,0 @@
|
|||
---
|
||||
title: Application Menu
|
||||
sidebar:
|
||||
order: 53
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Application Menus
|
||||
|
||||
Wails provides native application menus with full support for menu bars, submenus, separators, keyboard shortcuts, and dynamic updates. Create professional menus that respect platform conventions across macOS, Windows, and Linux.
|
||||
|
||||
Create a new application menu using the `New` method from the Menu manager:
|
||||
|
||||
```go
|
||||
menu := app.Menu.New()
|
||||
```
|
||||
## Setting the Menu
|
||||
|
||||
The way to set the menu varies on the platform:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS, there is only one menu bar per application. Set the menu using the `Set` method of the Menu manager:
|
||||
|
||||
```go
|
||||
app.Menu.Set(menu)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows, there is a menu bar per window. Set the menu using the `SetMenu` method of the window:
|
||||
|
||||
```go
|
||||
app.Window.Current().SetMenu(menu)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux, the menu bar is typically per window. Set the menu using the `SetMenu` method of the window:
|
||||
|
||||
```go
|
||||
app.Window.Current().SetMenu(menu)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Menu Roles
|
||||
|
||||
Wails provides predefined menu roles that automatically create platform-appropriate menu structures:
|
||||
|
||||
```go
|
||||
// Add standard application menu on macOS
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu)
|
||||
}
|
||||
|
||||
// Add standard menus
|
||||
menu.AddRole(application.FileMenu)
|
||||
menu.AddRole(application.EditMenu)
|
||||
menu.AddRole(application.WindowMenu)
|
||||
menu.AddRole(application.HelpMenu)
|
||||
```
|
||||
|
||||
:::note[Platform Behaviour]
|
||||
The AppMenu role is specific to macOS and provides the standard application menu containing About, Preferences, and Quit items.
|
||||
:::
|
||||
|
||||
### Available Roles
|
||||
|
||||
| Role | Description | Platform Notes |
|
||||
|------|-------------|----------------|
|
||||
| `AppMenu` | Standard application menu | macOS only |
|
||||
| `FileMenu` | File operations menu | All platforms |
|
||||
| `EditMenu` | Text editing operations | All platforms |
|
||||
| `WindowMenu` | Window management | All platforms |
|
||||
| `HelpMenu` | Help and information | All platforms |
|
||||
|
||||
## Custom Menus
|
||||
|
||||
Create custom menus by adding items directly:
|
||||
|
||||
```go
|
||||
// Add a custom menu
|
||||
customMenu := menu.AddSubmenu("Tools")
|
||||
customMenu.Add("Settings").OnClick(func(ctx *application.Context) {
|
||||
// Show settings dialog
|
||||
})
|
||||
```
|
||||
|
||||
:::tip[Menu Items]
|
||||
For detailed information about available menu item types and properties, refer to the [Menu Reference](./menu-reference) documentation.
|
||||
:::
|
||||
|
||||
## Window Control
|
||||
|
||||
Menu items can control the application windows:
|
||||
|
||||
```go
|
||||
viewMenu := menu.AddSubmenu("View")
|
||||
viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) {
|
||||
window := app.Window.Current()
|
||||
if window.Fullscreen() {
|
||||
window.SetFullscreen(false)
|
||||
} else {
|
||||
window.SetFullscreen(true)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Dynamic Menus
|
||||
|
||||
Menus can be updated dynamically based on application state:
|
||||
|
||||
```go
|
||||
projectMenu := menu.AddSubmenu("Project")
|
||||
saveItem := projectMenu.Add("Save Project")
|
||||
|
||||
// Update based on state
|
||||
saveItem.OnClick(func(ctx *application.Context) {
|
||||
if projectSaved {
|
||||
saveItem.SetEnabled(false)
|
||||
saveItem.SetLabel("Project Saved")
|
||||
}
|
||||
menu.Update()
|
||||
})
|
||||
```
|
||||
|
||||
## Platform-Specific Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS, menus are deeply integrated with the system:
|
||||
|
||||
- Menus appear in the system menu bar at the top of the screen
|
||||
- The application menu (⌘) is required and should be added using `menu.AddRole(application.AppMenu)`
|
||||
- Standard keyboard shortcuts are automatically handled
|
||||
- Menu styling follows system appearance
|
||||
- The "About" menu item appears in the application menu
|
||||
- Preferences are typically placed in the application menu
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows, menus follow the traditional Windows UI guidelines:
|
||||
|
||||
- Menus appear in the application window's title bar
|
||||
- Standard keyboard shortcuts should be explicitly set using `SetAccelerator`
|
||||
- Menu styling matches the Windows theme
|
||||
- The "About" menu item typically appears in the Help menu
|
||||
- Settings/Preferences are typically placed in the Tools menu
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux, menu behaviour depends on the desktop environment:
|
||||
|
||||
- Menu appearance adapts to the desktop environment's theme
|
||||
- Some desktop environments (like Unity) support global menu bars
|
||||
- Menu placement follows the desktop environment's conventions
|
||||
- Keyboard shortcuts should be explicitly set
|
||||
- Settings are typically placed in the Edit menu
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Use standard menu roles where appropriate
|
||||
2. Follow platform-specific menu conventions
|
||||
3. Provide keyboard shortcuts for common actions
|
||||
4. Keep menu structures shallow and organised
|
||||
5. Update menu items to reflect application state
|
||||
6. Use clear, concise menu labels
|
||||
7. Group related items logically
|
||||
|
||||
:::danger[Warning]
|
||||
Always test menu functionality across all target platforms to ensure consistent behaviour and appearance.
|
||||
:::
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Consider using the `app.Window.Current()` method in menu handlers to affect the active window, rather than storing window references.
|
||||
:::
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a comprehensive example demonstrating various menu features:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Menu Demo",
|
||||
})
|
||||
|
||||
// Create main menu
|
||||
menu := app.Menu.New()
|
||||
|
||||
// Add platform-specific application menu
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu)
|
||||
}
|
||||
|
||||
// Add standard menus
|
||||
fileMenu := menu.AddRole(application.FileMenu)
|
||||
menu.AddRole(application.EditMenu)
|
||||
menu.AddRole(application.WindowMenu)
|
||||
menu.AddRole(application.HelpMenu)
|
||||
|
||||
// Add custom menu
|
||||
toolsMenu := menu.AddSubmenu("Tools")
|
||||
|
||||
// Add checkbox item
|
||||
toolsMenu.AddCheckbox("Dark Mode", false).OnClick(func(ctx *application.Context) {
|
||||
isDark := ctx.ClickedMenuItem().Checked()
|
||||
// Toggle theme
|
||||
})
|
||||
|
||||
// Add radio group
|
||||
toolsMenu.AddRadio("Small Text", true).OnClick(handleFontSize)
|
||||
toolsMenu.AddRadio("Medium Text", false).OnClick(handleFontSize)
|
||||
toolsMenu.AddRadio("Large Text", false).OnClick(handleFontSize)
|
||||
|
||||
// Add submenu
|
||||
advancedMenu := toolsMenu.AddSubmenu("Advanced")
|
||||
advancedMenu.Add("Configure...").OnClick(func(ctx *application.Context) {
|
||||
// Show configuration
|
||||
})
|
||||
|
||||
// Set the menu
|
||||
app.Menu.Set(menu)
|
||||
|
||||
// Create main window
|
||||
app.Window.New()
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleFontSize(ctx *application.Context) {
|
||||
size := ctx.ClickedMenuItem().Label()
|
||||
// Update font size
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
---
|
||||
title: Binding Best Practices
|
||||
sidebar:
|
||||
order: 23
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
This guide provides best practices and patterns for using the Wails binding system effectively in your applications.
|
||||
|
||||
## Service Design Patterns
|
||||
|
||||
### Service Organization
|
||||
|
||||
Organize your services based on functionality rather than technical concerns. For example, instead of having a single large service, split it into smaller, focused services:
|
||||
|
||||
```go
|
||||
// Instead of this:
|
||||
type AppService struct {}
|
||||
|
||||
func (s *AppService) GetUser() User { /* ... */ }
|
||||
func (s *AppService) UpdateUser(user User) error { /* ... */ }
|
||||
func (s *AppService) GetProducts() []Product { /* ... */ }
|
||||
func (s *AppService) AddProduct(product Product) error { /* ... */ }
|
||||
|
||||
// Do this:
|
||||
type UserService struct {}
|
||||
func (s *UserService) GetUser() User { /* ... */ }
|
||||
func (s *UserService) UpdateUser(user User) error { /* ... */ }
|
||||
|
||||
type ProductService struct {}
|
||||
func (s *ProductService) GetProducts() []Product { /* ... */ }
|
||||
func (s *ProductService) AddProduct(product Product) error { /* ... */ }
|
||||
```
|
||||
|
||||
This makes your code more maintainable and easier to understand.
|
||||
|
||||
### Use JSON Tags
|
||||
|
||||
Use JSON tags to control how your models are serialized:
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"-"` // Exclude from JSON
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
```
|
||||
|
||||
### Separate Frontend and Backend Models
|
||||
|
||||
Consider using different models for the frontend and backend:
|
||||
|
||||
```go
|
||||
// Backend model
|
||||
type User struct {
|
||||
ID int
|
||||
Name string
|
||||
Email string
|
||||
Password string // Sensitive data
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// Frontend model
|
||||
type UserDTO struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (s *UserService) GetUser() UserDTO {
|
||||
user := getUserFromDatabase()
|
||||
return UserDTO{
|
||||
ID: user.ID,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
CreatedAt: user.CreatedAt,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This gives you more control over what data is exposed to the frontend.
|
||||
|
||||
### Use Context for Cancellation
|
||||
|
||||
Use context for cancellation to avoid wasting resources on abandoned requests:
|
||||
|
||||
```go
|
||||
func (s *ProductService) GetProducts(ctx context.Context, req ProductsRequest) (ProductsResponse, error) {
|
||||
// Check if the request has been cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ProductsResponse{}, ctx.Err()
|
||||
default:
|
||||
// Continue processing
|
||||
}
|
||||
|
||||
products, total, err := getProductsFromDatabase(ctx, req.Page, req.PageSize, req.Filter)
|
||||
if err != nil {
|
||||
return ProductsResponse{}, err
|
||||
}
|
||||
|
||||
return ProductsResponse{
|
||||
Products: products,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
|
@ -1,674 +0,0 @@
|
|||
---
|
||||
title: Bindings
|
||||
sidebar:
|
||||
order: 20
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
## Introduction
|
||||
|
||||
One of the key features of Wails is the ability to seamlessly integrate backend
|
||||
Go code with the frontend, enabling efficient communication between the two.
|
||||
This can be done manually by sending messages between the frontend and backend,
|
||||
but this can be cumbersome and error-prone, especially when dealing with complex
|
||||
data types.
|
||||
|
||||
The bindings generator in Wails v3 simplifies this process by automatically
|
||||
generating JavaScript or TypeScript functions and models that reflect the
|
||||
methods and data structures defined in your Go code. This means you can write
|
||||
your backend logic in Go and easily expose it to the frontend without the need
|
||||
for manual binding or complex integration.
|
||||
|
||||
This guide is designed to help you understand and utilize this powerful binding
|
||||
tool.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
In Wails v3, services can be added to your application. These services act as a
|
||||
bridge between the backend and frontend, allowing you to define methods and
|
||||
state that can be accessed and manipulated from the frontend.
|
||||
|
||||
### Services
|
||||
|
||||
1. Services can hold state and expose methods that operate on that state.
|
||||
2. Services can be used similar to controllers in HTTP web applications or as
|
||||
services.
|
||||
3. Only public methods on the service are bound, following Go's convention.
|
||||
|
||||
Here's a simple example of how you can define a service and add it to your Wails
|
||||
application:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type GreetService struct {}
|
||||
|
||||
func (g *GreetService) Greet(name string) string {
|
||||
return "Hello " + name
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&GreetService{}),
|
||||
},
|
||||
})
|
||||
// ....
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we define a `GreetService` services with a public `Greet`
|
||||
method. The `Greet` method takes a `name` parameter and returns a greeting
|
||||
string.
|
||||
|
||||
We then create a new Wails application using `application.New` and add the
|
||||
`GreetService` service to the application using the `Services` option in the
|
||||
`application.Options`. The `application.NewService` method must always be given
|
||||
an _instance_ of the service struct, not the service struct type itself.
|
||||
|
||||
### Generating the Bindings
|
||||
|
||||
By binding the struct, Wails is able to generate the necessary JavaScript or
|
||||
TypeScript code by running the following command in the project directory:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings
|
||||
```
|
||||
|
||||
The bindings generator will scan the project and dependencies for anything that
|
||||
needs generating. Note: It will take longer the very first time you run the
|
||||
bindings generator, as it will be building up a cache of packages to scan. You
|
||||
should see output similar to the following:
|
||||
|
||||
```bash
|
||||
% wails3 generate bindings
|
||||
INFO 347 Packages, 1 Service, 1 Method, 0 Enums, 0 Models in 1.981036s.
|
||||
INFO Output directory: /Users/me/myproject/frontend/bindings
|
||||
```
|
||||
|
||||
If we look in the `frontend/bindings` directory, we should see the following
|
||||
files:
|
||||
|
||||
<FileTree>
|
||||
|
||||
- frontend/bindings
|
||||
- changeme
|
||||
- greetservice.js
|
||||
- index.js
|
||||
|
||||
</FileTree>
|
||||
|
||||
NOTE: The `changeme` directory is the name of the module defined in `go.mod` and
|
||||
is used to namespace the generated files.
|
||||
|
||||
The generated `greetservice.js` file contains the JavaScript code that mirrors
|
||||
the Go struct and its methods:
|
||||
|
||||
```javascript title="greetservice.js"
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, Create as $Create } from "@wailsio/runtime";
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {Promise<string> & { cancel(): void }}
|
||||
*/
|
||||
export function Greet(name) {
|
||||
let $resultPromise = /** @type {any} */ ($Call.ByID(1411160069, name));
|
||||
return $resultPromise;
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, it also generates all the necessary JSDoc type information to
|
||||
ensure type safety in your frontend code.
|
||||
|
||||
### Using the Bindings
|
||||
|
||||
You can import and use this file in your frontend code to interact with the
|
||||
backend.
|
||||
|
||||
```javascript
|
||||
import { Greet } from "./bindings/changeme/greetservice.js";
|
||||
|
||||
console.log(Greet("Alice")); // Output: Hello Alice
|
||||
```
|
||||
|
||||
### Binding Models
|
||||
|
||||
In addition to binding methods, you can also use structs as input or output
|
||||
parameters in your bound methods. When structs are used as parameters, Wails
|
||||
generates corresponding JavaScript versions of those types.
|
||||
|
||||
Let's extend the previous example to use a `Person` type that has a `Name`
|
||||
field:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Person defines a person
|
||||
type Person struct {
|
||||
// Name of the person
|
||||
Name string
|
||||
}
|
||||
|
||||
type GreetService struct{}
|
||||
|
||||
func (g *GreetService) Greet(person Person) string {
|
||||
return "Hello " + person.Name
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&GreetService{}),
|
||||
},
|
||||
})
|
||||
// ....
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
In this updated example, we define a `Person` struct with a `Name` field. The
|
||||
`Greet` method in the `GreetService` service now takes a `Person` as an input
|
||||
parameter.
|
||||
|
||||
When you run the bindings generator, Wails will generate a corresponding
|
||||
JavaScript `Person` type that mirrors the Go struct. This allows you to create
|
||||
instances of the `Person` type in your frontend code and pass them to the bound
|
||||
`Greet` method.
|
||||
|
||||
If we run the bindings generator again, we should see the following output:
|
||||
|
||||
```bash
|
||||
% wails3 generate bindings
|
||||
INFO Processed: 347 Packages, 1 Service, 1 Method, 0 Enums, 1 Model in 1.9943997s.
|
||||
INFO Output directory: /Users/me/myproject/frontend/bindings
|
||||
```
|
||||
|
||||
In the `frontend/bindings/changeme` directory, you should see a new `models.js`
|
||||
file containing the following code:
|
||||
|
||||
```javascript title="models.js"
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Create as $Create } from "@wailsio/runtime";
|
||||
|
||||
/**
|
||||
* Person defines a person
|
||||
*/
|
||||
export class Person {
|
||||
/**
|
||||
* Creates a new Person instance.
|
||||
* @param {Partial<Person>} [$$source = {}] - The source object to create the Person.
|
||||
*/
|
||||
constructor($$source = {}) {
|
||||
if (!("Name" in $$source)) {
|
||||
/**
|
||||
* Name of the person
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["Name"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Person instance from a string or object.
|
||||
* @param {any} [$$source = {}]
|
||||
* @returns {Person}
|
||||
*/
|
||||
static createFrom($$source = {}) {
|
||||
let $$parsedSource =
|
||||
typeof $$source === "string" ? JSON.parse($$source) : $$source;
|
||||
return new Person(/** @type {Partial<Person>} */ ($$parsedSource));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `Person` class is generated with a constructor that takes an optional
|
||||
`source` parameter, which allows you to create a new `Person` instance from an
|
||||
object. It also has a static `createFrom` method that can create a `Person`
|
||||
instance from a string or object.
|
||||
|
||||
You may also notice that comments in the Go struct are kept in the generated
|
||||
JavaScript code! This can be helpful for understanding the purpose of the fields
|
||||
and methods in the generated models and should be picked up by your IDE.
|
||||
|
||||
### Using Bound Models
|
||||
|
||||
Here's an example of how you can use the generated JavaScript `Person` type in
|
||||
your frontend code:
|
||||
|
||||
```javascript
|
||||
import { Greet } from "./bindings/changeme/greetservice.js";
|
||||
import { Person } from "./bindings/changeme/models.js";
|
||||
|
||||
const resultElement = document.getElementById("result");
|
||||
|
||||
async function doGreet() {
|
||||
let person = new Person({ Name: document.getElementById("name").value });
|
||||
if (!person.Name) {
|
||||
person.Name = "anonymous";
|
||||
}
|
||||
resultElement.innerText = await Greet(person);
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we import the generated `Person` type from the `models` module.
|
||||
We create a new instance of `Person`, set its `Name` property, and pass it to
|
||||
the `Greet` method.
|
||||
|
||||
Using bound models allows you to work with complex data structures and
|
||||
seamlessly pass them between the frontend and backend of your Wails application.
|
||||
|
||||
### Index files
|
||||
|
||||
The generator outputs an additional `index.js` file that re-exports all services and models:
|
||||
|
||||
```javascript title="index.js"
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as GreetService from "./greetservice.js";
|
||||
export {
|
||||
GreetService
|
||||
};
|
||||
|
||||
export {
|
||||
Person
|
||||
} from "./models.js";
|
||||
```
|
||||
|
||||
You can take advantage of this feature
|
||||
to aggregate import statements for multiple services and models.
|
||||
If you are building your frontend with a bundler,
|
||||
which is the default for most project templates,
|
||||
you can also simplify the import path:
|
||||
|
||||
```javascript
|
||||
import { GreetService, Person } from "./bindings/changeme";
|
||||
await GreetService.Greet(new Person(/* ... */));
|
||||
```
|
||||
|
||||
### Using Typescript
|
||||
|
||||
To generate TypeScript bindings instead of JavaScript, you can use the `-ts`
|
||||
flag:
|
||||
|
||||
```bash
|
||||
% wails3 generate bindings -ts
|
||||
```
|
||||
|
||||
This will generate TypeScript files in the `frontend/bindings` directory:
|
||||
|
||||
<FileTree>
|
||||
|
||||
- frontend/bindings
|
||||
- main
|
||||
- greetservice.ts
|
||||
- index.ts
|
||||
- models.ts
|
||||
|
||||
</FileTree>
|
||||
|
||||
The generated files include `greetservice.ts`, which contains the TypeScript
|
||||
code for the bound struct and its methods, and `models.ts`, which contains the
|
||||
TypeScript types for the bound models:
|
||||
|
||||
```typescript title="GreetService.ts"
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, Create as $Create } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
export function Greet(
|
||||
person: $models.Person,
|
||||
): Promise<string> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1411160069, person) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript title="models.ts"
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Person defines a person
|
||||
*/
|
||||
export class Person {
|
||||
/**
|
||||
* Name of the person
|
||||
*/
|
||||
"Name": string;
|
||||
|
||||
/** Creates a new Person instance. */
|
||||
constructor(source: Partial<Person> = {}) {
|
||||
if (!("Name" in source)) {
|
||||
this["Name"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, source);
|
||||
}
|
||||
|
||||
/** Creates a new Person instance from a string or object. */
|
||||
static createFrom(source: string | object = {}): Person {
|
||||
let parsedSource = typeof source === "string" ? JSON.parse(source) : source;
|
||||
return new Person(parsedSource as Partial<Person>);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Using TypeScript bindings provides type safety and improved IDE support when
|
||||
working with the generated code in your frontend.
|
||||
|
||||
### Using `context.Context`
|
||||
|
||||
When defining service methods in Go, you can include `context.Context` as the
|
||||
first parameter. The runtime will automatically provide a context when the
|
||||
method is called from the frontend.
|
||||
|
||||
The context provides several powerful features:
|
||||
|
||||
1. **Cancellation Support**: Long-running operations can be cancelled from the
|
||||
frontend, which will raise an error through the Promise chain.
|
||||
|
||||
2. **Window Information**: You can determine which window made the call using
|
||||
the context key `application.WindowKey`.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
```go
|
||||
// Basic context usage with cancellation
|
||||
func (s *MyService) LongRunningTask(ctx context.Context, input string) (string, error) {
|
||||
select {
|
||||
// Check if the context has been cancelled from the frontend
|
||||
case <-ctx.Done():
|
||||
return "", ctx.Err()
|
||||
default:
|
||||
// Process task
|
||||
return "completed", nil
|
||||
}
|
||||
}
|
||||
|
||||
// Getting caller window information
|
||||
func (s *MyService) WindowAwareMethod(ctx context.Context) (string, error) {
|
||||
window := ctx.Value(application.WindowKey).(application.Window)
|
||||
return fmt.Sprintf("Called from window: %s (ID: %s)", window.Name(), window.ID()), nil
|
||||
}
|
||||
```
|
||||
|
||||
From the frontend, these methods can be called normally. If you need to cancel a
|
||||
long-running operation, you can call the special `cancel` method on the promise
|
||||
and it will reject immediately with a special cancellation error;
|
||||
the Go context will be cancelled and the actual result of the call will be discarded:
|
||||
|
||||
```javascript
|
||||
// Call the method
|
||||
const promise = MyService.LongRunningTask("input");
|
||||
|
||||
// Cancel it later if needed
|
||||
// This will cause the context to be cancelled in the Go method
|
||||
promise.cancel();
|
||||
```
|
||||
|
||||
In fact, the runtime returns a special promise wrapper
|
||||
that provides cancellation support for arbitrarily long promise chains.
|
||||
For example:
|
||||
|
||||
```javascript
|
||||
import { CancelError } from "@wailsio/runtime";
|
||||
|
||||
// Call the method and process its output
|
||||
const promise = MyService.LongRunningTask("input").then((result) => {
|
||||
console.log(result);
|
||||
}).catch((err) => {
|
||||
if (err instanceof CancelError) {
|
||||
console.log("Cancelled.", err.cause);
|
||||
} else {
|
||||
console.error("Failed.", err);
|
||||
}
|
||||
});
|
||||
|
||||
// Later...
|
||||
// cancel() accepts an optional cause parameter
|
||||
// that will be attached to the cancellation error:
|
||||
promise.cancel("I'm tired of waiting!").then(() => {
|
||||
// Cancellation has been requested successfully
|
||||
// and all handlers attached above have run.
|
||||
console.log("Ready for the next adventure!");
|
||||
});
|
||||
```
|
||||
|
||||
The `cancel` method returns a promise that fulfills always (and never rejects)
|
||||
after the cancellation request has been submitted successfully
|
||||
and all previously attached handlers have run.
|
||||
|
||||
:::note
|
||||
Calling the `cancel` method on a settled promise is safe and has no effect;
|
||||
if the task completes before the call to `cancel`, the code above is going to log:
|
||||
|
||||
```
|
||||
completed
|
||||
Ready for the next adventure!
|
||||
```
|
||||
|
||||
However, if `cancel` is called before the task finishes, the output will be:
|
||||
|
||||
```
|
||||
Cancelled. I'm tired of waiting!
|
||||
Ready for the next adventure!
|
||||
```
|
||||
:::
|
||||
|
||||
The approach discussed above requires storing and chaining promises manually,
|
||||
which can be cumbersome for code written in `async`/`await` style.
|
||||
If you target plaforms that support the `AbortController`/`AbortSignal` idiom,
|
||||
you can call the `cancelOn` method and tie call cancellation to an `AbortSignal` instead:
|
||||
|
||||
```javascript
|
||||
async function callBinding(signal) {
|
||||
try {
|
||||
await MyService.LongRunningTask("input").cancelOn(signal);
|
||||
} catch (err) {
|
||||
if (err instanceof CancelError) {
|
||||
console.log("Cancelled! Cause: ", err.cause);
|
||||
} else {
|
||||
console.error("Failed! Error: ", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let controller = new AbortController();
|
||||
callBinding(controller.signal);
|
||||
|
||||
// Later...
|
||||
controller.abort("I'm tired of waiting!");
|
||||
```
|
||||
|
||||
:::caution
|
||||
On the macOS platform, `AbortSignal` is only supported from macOS 10.15 Catalina onwards.
|
||||
:::
|
||||
|
||||
### Handling errors
|
||||
|
||||
As you may have noticed above, bound methods can return errors, which are handled specially.
|
||||
When a result field has type `error`, it is omitted by default from the values returned to JS.
|
||||
When such a field is _non-nil_, the promise rejects with a `RuntimeError` exception
|
||||
that wraps the Go error message:
|
||||
|
||||
```go
|
||||
func (*MyService) FailingMethod(name string) error {
|
||||
return fmt.Errorf("Welcome to an imperfect world, %s", name)
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
import { MyService } from './bindings/changeme';
|
||||
|
||||
try {
|
||||
await MyService.FailingMethod("CLU")
|
||||
} catch (err) {
|
||||
if (err.name === 'RuntimeError') {
|
||||
console.log(err.message); // Prints 'Welcome to an imperfect world, CLU'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The exception will be an instance of the `Call.RuntimeError` class from the wails runtime,
|
||||
hence you can also test its type like this:
|
||||
|
||||
```js
|
||||
import { Call } from '@wailsio/runtime';
|
||||
|
||||
try {
|
||||
// ...
|
||||
} catch (err) {
|
||||
if (err instanceof Call.RuntimeError) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the Go error value supports JSON marshaling, the exception's `cause` property
|
||||
will hold the marshaled version of the error:
|
||||
|
||||
```go
|
||||
type ImperfectWorldError struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (err *ImperfectWorldError) Error() {
|
||||
return fmt.Sprintf("Welcome to an imperfect world, %s", err.Name)
|
||||
}
|
||||
|
||||
func (*MyService) FailingMethod(name string) error {
|
||||
return &ImperfectWorldError{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
import { MyService } from './bindings/changeme';
|
||||
|
||||
try {
|
||||
await MyService.FailingMethod("CLU")
|
||||
} catch (err) {
|
||||
if (err.name === 'RuntimeError') {
|
||||
console.log(err.cause.name); // Prints 'CLU'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Generally, many Go error values will only have limited or no support for marshaling to JSON.
|
||||
If you so wish, you can customise the value provided as cause
|
||||
by specifying either a global or per-service error marshaling function:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
MarshalError: func(err error) []byte {
|
||||
// ...
|
||||
},
|
||||
Services: []application.Service{
|
||||
application.NewServiceWithOptions(&MyService{}, application.ServiceOptions{
|
||||
MarshalError: func(err error) []byte {
|
||||
// ...
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Per-service functions override the global function,
|
||||
which in turn overrides the default behaviour of using `json.Marshal`.
|
||||
If a marshaling function returns `nil`, it falls back to the outer function:
|
||||
per-service functions fall back to the global function,
|
||||
which in turn falls back to the default behaviour.
|
||||
|
||||
:::tip
|
||||
If you wish to omit the `cause` property on the resulting exception,
|
||||
let the marshaling function return a falsy JSON value like `[]byte("null")`.
|
||||
:::
|
||||
|
||||
Here's an example marshaling function that unwraps path errors and reports the file path:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
MarshalError: func(err error) []byte {
|
||||
var perr *fs.PathError
|
||||
if !errors.As(err, &perr) {
|
||||
// Not a path error, fall back to default handling.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal path string
|
||||
path, err := json.Marshal(&perr.Path)
|
||||
if err != nil {
|
||||
// String marshaling failed, fall back to default handling.
|
||||
return nil
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf(`{"path":%s}`, path))
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
:::note
|
||||
Error marshaling functions are not allowed to fail.
|
||||
If they are not able to process a given error and return valid JSON,
|
||||
they should return `nil` and fall back to a more generic handler.
|
||||
If no strategy succeeds, the exception will not have a `cause` property.
|
||||
:::
|
||||
|
||||
Binding call promises may also reject with a `TypeError`
|
||||
when the method has been passed the wrong number of arguments,
|
||||
when the conversion of arguments from JSON to their Go types fails,
|
||||
or when the conversion of results to JSON fails.
|
||||
These problems will usually be caught early by the type system.
|
||||
If your code typechecks but you still get type errors,
|
||||
it might be that some of your Go types are not supported by the `encoding/json` package:
|
||||
look for warnings from the binding generator to catch these.
|
||||
|
||||
:::caution
|
||||
If you see a `ReferenceError` complaining about unknown methods,
|
||||
it could mean that your JS bindings have gotten out of sync with Go code
|
||||
and must be regenerated.
|
||||
:::
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
---
|
||||
title: Clipboard
|
||||
sidebar:
|
||||
order: 50
|
||||
---
|
||||
|
||||
The Wails Clipboard API provides a simple interface for interacting with the system clipboard. It allows you to read from and write to the clipboard, whilst supporting text data.
|
||||
|
||||
## Accessing the Clipboard
|
||||
|
||||
The clipboard can be accessed through the application instance:
|
||||
|
||||
```go
|
||||
clipboard := app.Clipboard
|
||||
```
|
||||
|
||||
## Setting Text
|
||||
|
||||
To set text to the clipboard, utilise the `SetText` method:
|
||||
|
||||
```go
|
||||
success := app.Clipboard.SetText("Hello World")
|
||||
if !success {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
The `SetText` method returns a boolean indicating whether the operation was successful.
|
||||
|
||||
:::tip[Empty Text]
|
||||
Setting an empty string (`""`) effectively clears the text content from the clipboard.
|
||||
:::
|
||||
|
||||
## Getting Text
|
||||
|
||||
To retrieve text from the clipboard, utilise the `Text` method:
|
||||
|
||||
```go
|
||||
text, ok := app.Clipboard.Text()
|
||||
if !ok {
|
||||
// Handle error
|
||||
} else {
|
||||
// Use the text
|
||||
}
|
||||
```
|
||||
|
||||
The `Text` method returns two values:
|
||||
- The text content from the clipboard (string)
|
||||
- A boolean indicating whether the operation was successful
|
||||
|
||||
:::note[Platform Behaviour]
|
||||
The clipboard behaviour might vary slightly amongst operating systems. Always check the return values to ensure operations were successful.
|
||||
:::
|
||||
|
||||
## Example
|
||||
|
||||
Here's a complete example showing how to create a menu-driven application that demonstrates clipboard operations:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Clipboard Demo",
|
||||
Description: "A demo of the clipboard API",
|
||||
Assets: application.AlphaAssets,
|
||||
})
|
||||
|
||||
// Create a custom menu
|
||||
menu := app.Menu.New()
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu)
|
||||
}
|
||||
|
||||
// Add clipboard operations to menu
|
||||
setClipboardMenu := menu.AddSubmenu("Set Clipboard")
|
||||
setClipboardMenu.Add("Set Text 'Hello'").OnClick(func(ctx *application.Context) {
|
||||
success := app.Clipboard.SetText("Hello")
|
||||
if !success {
|
||||
app.Dialog.Info().SetMessage("Failed to set clipboard text").Show()
|
||||
}
|
||||
})
|
||||
|
||||
getClipboardMenu := menu.AddSubmenu("Get Clipboard")
|
||||
getClipboardMenu.Add("Get Text").OnClick(func(ctx *application.Context) {
|
||||
result, ok := app.Clipboard.Text()
|
||||
if !ok {
|
||||
app.Dialog.Info().SetMessage("Failed to get clipboard text").Show()
|
||||
} else {
|
||||
app.Dialog.Info().SetMessage("Got:\n\n" + result).Show()
|
||||
}
|
||||
})
|
||||
|
||||
clearClipboardMenu := menu.AddSubmenu("Clear Clipboard")
|
||||
clearClipboardMenu.Add("Clear Text").OnClick(func(ctx *application.Context) {
|
||||
success := app.Clipboard.SetText("")
|
||||
if success {
|
||||
app.Dialog.Info().SetMessage("Clipboard text cleared").Show()
|
||||
} else {
|
||||
app.Dialog.Info().SetMessage("Clipboard text not cleared").Show()
|
||||
}
|
||||
})
|
||||
|
||||
app.Menu.Set(menu)
|
||||
app.Window.New()
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::danger[Warning]
|
||||
Always handle clipboard operation failures gracefully, as they can fail due to various system-level reasons such as permissions or resource constraints.
|
||||
:::
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always check the return values of clipboard operations
|
||||
2. Handle failures gracefully with appropriate user feedback
|
||||
3. Clear sensitive data from the clipboard when your application exits if it was responsible for putting it there
|
||||
4. Consider implementing a timeout mechanism for clipboard operations in critical sections of your application
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Whilst working with the clipboard in a production environment, consider implementing retry logic for critical clipboard operations, as they can occasionally fail due to temporary system conditions.
|
||||
:::
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
---
|
||||
title: Context Menus
|
||||
sidebar:
|
||||
order: 51
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Context Menus
|
||||
|
||||
Wails provides context menus (right-click menus) with full support for context-specific actions, custom data passing, and default menu overrides. Create different menus for text, images, links, and custom elements.()
|
||||
|
||||
The `menu-id` parameter is a unique identifier for the menu that will be used to associate it with HTML elements.
|
||||
|
||||
## Adding Menu Items
|
||||
|
||||
You can add items to your context menu using the same methods as application menus. Here's a simple example:
|
||||
|
||||
```go
|
||||
contextMenu := application.NewContextMenu()
|
||||
contextMenu.Add("Cut").OnClick(func(ctx *application.Context) {
|
||||
// Handle cut action
|
||||
})
|
||||
contextMenu.Add("Copy").OnClick(func(ctx *application.Context) {
|
||||
// Handle copy action
|
||||
})
|
||||
contextMenu.Add("Paste").OnClick(func(ctx *application.Context) {
|
||||
// Handle paste action
|
||||
})
|
||||
|
||||
// Register the context menu with the manager
|
||||
app.ContextMenu.Add("editor-menu", contextMenu)
|
||||
```
|
||||
|
||||
:::tip[Menu Items]
|
||||
For detailed information about available menu item types and properties, refer to the [Menu Reference](./menu-reference) documentation.
|
||||
:::
|
||||
|
||||
## Context Data
|
||||
|
||||
Context menus can receive data from the HTML element that triggered them. This data can be accessed in the click handlers:
|
||||
|
||||
```go
|
||||
contextMenu := app.ContextMenu.New()
|
||||
menuItem := contextMenu.Add("Process Image")
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
imageID := ctx.ContextMenuData()
|
||||
// Process the image using the ID
|
||||
})
|
||||
|
||||
// Register the context menu with the manager
|
||||
app.ContextMenu.Add("image-menu", contextMenu)
|
||||
```
|
||||
|
||||
## Associating with HTML Elements
|
||||
|
||||
To associate a context menu with an HTML element, use the `--custom-contextmenu` and `--custom-contextmenu-data` CSS properties:
|
||||
|
||||
```html
|
||||
<div style="--custom-contextmenu: menu-id; --custom-contextmenu-data: some-data">
|
||||
Right click me!
|
||||
</div>
|
||||
```
|
||||
|
||||
- `--custom-contextmenu`: Specifies the menu ID (must match the ID used in `NewContextMenu`)
|
||||
- `--custom-contextmenu-data`: Optional data that will be passed to the click handlers
|
||||
|
||||
:::note
|
||||
This feature will only work as expected after the runtime [has been initialised](../runtime#initialisation).
|
||||
:::
|
||||
|
||||
## Default Context Menu
|
||||
|
||||
The default context menu is the webview's built-in context menu that provides system-level operations. You can control its visibility using the `--default-contextmenu` CSS property:
|
||||
|
||||
```html
|
||||
<!-- Hide the default context menu -->
|
||||
<div style="--default-contextmenu: hide">
|
||||
No default menu here
|
||||
</div>
|
||||
|
||||
<!-- Show the default context menu -->
|
||||
<div style="--default-contextmenu: show">
|
||||
Default menu always shown
|
||||
</div>
|
||||
|
||||
<!-- Smart context menu (default behaviour) -->
|
||||
<div style="--default-contextmenu: auto">
|
||||
Shows menu when appropriate
|
||||
</div>
|
||||
```
|
||||
|
||||
:::note[Smart Context Menu]
|
||||
The `auto` setting enables "smart" context menu behaviour:
|
||||
- Shows when text is selected
|
||||
- Shows in text input fields
|
||||
- Shows in editable content
|
||||
- Hides in other contexts
|
||||
:::
|
||||
|
||||
## Updating Menu Items
|
||||
|
||||
Menu items can be updated dynamically using the `SetLabel` method and other property setters. After making changes, call `Update` on the menu to apply them:
|
||||
|
||||
```go
|
||||
contextMenu := application.NewContextMenu()
|
||||
menuItem := contextMenu.Add("Initial Label")
|
||||
|
||||
// Register the context menu with the manager
|
||||
app.ContextMenu.Add("dynamic-menu", contextMenu)
|
||||
|
||||
// Later, update the menu item
|
||||
menuItem.SetLabel("New Label")
|
||||
contextMenu.Update()
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS, context menus follow system conventions:
|
||||
|
||||
- Menus use native system animations and transitions
|
||||
- Right-click is automatically mapped to Control+Click
|
||||
- Menu styling automatically adapts to system appearance
|
||||
- Standard text operations appear in the default context menu
|
||||
- Context menus support native macOS scrolling behaviour
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows, context menus integrate with the Windows UI:
|
||||
|
||||
- Menus use the Windows native context menu style
|
||||
- Right-click handling is native
|
||||
- Menu appearance follows the Windows theme
|
||||
- Default context menu includes standard Windows operations
|
||||
- Context menus support Windows touch and pen input
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux, context menu behaviour varies by desktop environment:
|
||||
|
||||
- Menu styling adapts to the current desktop theme
|
||||
- Right-click behaviour follows system settings
|
||||
- Default context menu content may vary by environment
|
||||
- Menu positioning follows desktop environment conventions
|
||||
- GTK/Qt integration depends on the environment
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Consider using different context menus for different types of elements in your application. This allows you to provide context-specific actions that make sense for each element type.
|
||||
:::
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Keep context menus focused and relevant to the clicked element
|
||||
2. Use clear, concise labels for menu items
|
||||
3. Group related items together
|
||||
4. Consider using separators to organise menu items
|
||||
5. Provide keyboard shortcuts for common actions
|
||||
6. Update menu items dynamically based on application state
|
||||
7. Handle errors gracefully when processing context data
|
||||
|
||||
:::danger[Warning]
|
||||
Always validate context data received from the frontend before using it in your application logic, as it could be manipulated by users.
|
||||
:::
|
||||
|
||||
## Example
|
||||
|
||||
Here's a complete example demonstrating context menu features:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Context Menu Demo",
|
||||
})
|
||||
|
||||
// Create a context menu
|
||||
contextMenu := app.ContextMenu.New()
|
||||
|
||||
// Add items that respond to context data
|
||||
clickMe := contextMenu.Add("Click to show context data")
|
||||
dataLabel := contextMenu.Add("Current data: None")
|
||||
|
||||
clickMe.OnClick(func(ctx *application.Context) {
|
||||
data := ctx.ContextMenuData()
|
||||
dataLabel.SetLabel("Current data: " + data)
|
||||
contextMenu.Update()
|
||||
})
|
||||
|
||||
// Register the context menu with the manager
|
||||
app.ContextMenu.Add("test", contextMenu)
|
||||
|
||||
window := app.Window.New()
|
||||
window.SetTitle("Context Menu Demo")
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Associated HTML:
|
||||
|
||||
```html
|
||||
<div class="region" style="--custom-contextmenu: test; --custom-contextmenu-data: item-123">
|
||||
Right click me to see the custom menu!
|
||||
</div>
|
||||
|
||||
<div style="--default-contextmenu: hide">
|
||||
No context menu here
|
||||
</div>
|
||||
|
||||
<div style="--default-contextmenu: auto">
|
||||
<p style="user-select: text">Select this text to see the default menu</p>
|
||||
<input type="text" placeholder="Type here to see the default menu"/>
|
||||
</div>
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
---
|
||||
title: Dialogs
|
||||
sidebar:
|
||||
order: 54
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides a comprehensive dialog system for displaying native system dialogs. These include informational messages, questions, file selection, and more.
|
||||
|
||||
## Dialog Types
|
||||
|
||||
### Information Dialog
|
||||
|
||||
Display simple informational messages to users:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Info()
|
||||
dialog.SetTitle("Welcome")
|
||||
dialog.SetMessage("Welcome to our application!")
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
### Question Dialog
|
||||
|
||||
Present users with questions and customisable buttons:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Question()
|
||||
dialog.SetTitle("Save Changes")
|
||||
dialog.SetMessage("Do you want to save your changes?")
|
||||
dialog.AddButton("Save").OnClick(func() {
|
||||
// Handle save
|
||||
})
|
||||
saveButton := dialog.AddButton("Don't Save")
|
||||
dialog.SetDefaultButton(saveButton)
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
### Error Dialog
|
||||
|
||||
Display error messages:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Error()
|
||||
dialog.SetTitle("Error")
|
||||
dialog.SetMessage("Failed to save file")
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
### File Dialogs
|
||||
|
||||
#### Open File Dialog
|
||||
|
||||
Allow users to select files to open:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.OpenFile()
|
||||
dialog.SetTitle("Select Image")
|
||||
dialog.SetFilters([]*application.FileFilter{
|
||||
{
|
||||
DisplayName: "Images (*.png;*.jpg)",
|
||||
Pattern: "*.png;*.jpg",
|
||||
},
|
||||
})
|
||||
|
||||
// Single file selection
|
||||
if path, err := dialog.PromptForSingleSelection(); err == nil {
|
||||
// Use selected file path
|
||||
}
|
||||
|
||||
// Multiple file selection
|
||||
if paths, err := dialog.PromptForMultipleSelection(); err == nil {
|
||||
// Use selected file paths
|
||||
}
|
||||
```
|
||||
|
||||
#### Save File Dialog
|
||||
|
||||
Allow users to choose where to save files:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.SaveFile()
|
||||
dialog.SetTitle("Save Document")
|
||||
dialog.SetDefaultFilename("document.txt")
|
||||
dialog.SetFilters([]*application.FileFilter{
|
||||
{
|
||||
DisplayName: "Text Files (*.txt)",
|
||||
Pattern: "*.txt",
|
||||
},
|
||||
})
|
||||
|
||||
if path, err := dialog.PromptForSingleSelection(); err == nil {
|
||||
// Save file to selected path
|
||||
}
|
||||
```
|
||||
|
||||
## Dialog Customisation
|
||||
|
||||
### Setting Icons
|
||||
|
||||
Dialogs can use custom icons from the built-in icon set:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Info()
|
||||
dialog.SetIcon(icons.ApplicationDarkMode256)
|
||||
```
|
||||
|
||||
### Window Attachment
|
||||
|
||||
Dialogs can be attached to specific windows:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Question()
|
||||
dialog.AttachToWindow(app.Window.Current())
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
### Button Customisation
|
||||
|
||||
Create buttons with custom labels and actions:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Question()
|
||||
dialog.SetMessage("Choose an action")
|
||||
|
||||
// Add buttons with custom handlers
|
||||
dialog.AddButton("Save").OnClick(func() {
|
||||
// Handle save
|
||||
})
|
||||
dialog.AddButton("Don't Save").OnClick(func() {
|
||||
// Handle don't save
|
||||
})
|
||||
cancelButton := dialog.AddButton("Cancel")
|
||||
dialog.SetDefaultButton(cancelButton) // Set default button
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS, dialogs follow system conventions:
|
||||
|
||||
- Use system-standard dialog appearances
|
||||
- Support keyboard navigation (Tab, Space, Return)
|
||||
- Support standard keyboard shortcuts (⌘+.)
|
||||
- Automatically handle dark/light mode
|
||||
- Support system accessibility features
|
||||
- Position relative to parent window
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows, dialogs integrate with the Windows UI:
|
||||
|
||||
- Use Windows system dialog styles
|
||||
- Support keyboard navigation (Tab, Space, Enter)
|
||||
- Support Windows accessibility features
|
||||
- Follow Windows dialog positioning rules
|
||||
- Adapt to Windows theme settings
|
||||
- Support high DPI displays
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux, dialog behaviour depends on the desktop environment:
|
||||
|
||||
- Use native dialog widgets when available
|
||||
- Follow desktop environment theme
|
||||
- Support keyboard navigation
|
||||
- Adapt to desktop environment settings
|
||||
- Position according to window manager rules
|
||||
- Support desktop environment accessibility
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Directory Selection
|
||||
|
||||
Allow users to select directories:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.OpenFile()
|
||||
dialog.CanChooseDirectories(true)
|
||||
dialog.CanChooseFiles(false)
|
||||
dialog.SetTitle("Select Project Directory")
|
||||
if path, err := dialog.PromptForSingleSelection(); err == nil {
|
||||
// Use selected directory path
|
||||
}
|
||||
```
|
||||
|
||||
## About Dialog
|
||||
|
||||
Display application information:
|
||||
|
||||
```go
|
||||
app.Menu.ShowAbout()
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Use appropriate dialog types for different scenarios:
|
||||
- InfoDialog for general messages
|
||||
- QuestionDialog for user decisions
|
||||
- ErrorDialog for error messages
|
||||
- FileDialog for file operations
|
||||
|
||||
2. Provide clear and concise messages:
|
||||
- Use descriptive titles
|
||||
- Keep messages brief but informative
|
||||
- Clearly state any required user action
|
||||
|
||||
3. Handle dialog responses appropriately:
|
||||
- Check for errors in file dialogs
|
||||
- Provide feedback for user actions
|
||||
- Handle cancellation gracefully
|
||||
|
||||
4. Consider platform conventions:
|
||||
- Follow platform-specific dialog patterns
|
||||
- Use appropriate button ordering
|
||||
- Respect system settings
|
||||
|
||||
:::tip[Pro Tip]
|
||||
When using file dialogs, always set appropriate filters to help users select the correct file types for your application.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Always handle potential errors from file and directory dialogs, as they may fail due to permissions or other system issues.
|
||||
:::
|
||||
|
|
@ -1,676 +0,0 @@
|
|||
---
|
||||
title: Events
|
||||
sidebar:
|
||||
order: 55
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides a flexible event system that enables communication between different parts of your application. This includes both application-level and window-level events.
|
||||
|
||||
## Application Events
|
||||
|
||||
Application events are triggered by application-level state changes such as application startup, theme changes, and power events. You can listen for these events using the `OnApplicationEvent` method:
|
||||
|
||||
```go
|
||||
app.Event.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(event *application.ApplicationEvent) {
|
||||
app.Logger.Info("Application started!")
|
||||
})
|
||||
|
||||
app.Event.OnApplicationEvent(events.Windows.SystemThemeChanged, func(event *application.ApplicationEvent) {
|
||||
app.Logger.Info("System theme changed!")
|
||||
if event.Context().IsDarkMode() {
|
||||
app.Logger.Info("System is now using dark mode!")
|
||||
} else {
|
||||
app.Logger.Info("System is now using light mode!")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Common Application Events
|
||||
|
||||
Common application events are aliases for platform-specific application events. These events are triggered by application-level state
|
||||
changes such as application startup, theme changes, and power events.
|
||||
|
||||
Here is the same example as above, but using common application events to make it work across all platforms:
|
||||
|
||||
```go
|
||||
app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) {
|
||||
app.Logger.Info("Application started!")
|
||||
})
|
||||
|
||||
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
|
||||
if event.Context().IsDarkMode() {
|
||||
app.Logger.Info("System is now using dark mode!")
|
||||
} else {
|
||||
app.Logger.Info("System is now using light mode!")
|
||||
}
|
||||
})
|
||||
```
|
||||
#### Common Application Event List
|
||||
|
||||
| Event Name | Description |
|
||||
|---------------------------|----------------------------------------------------------------------------------------------------------|
|
||||
| ApplicationOpenedWithFile | Application opened with a file. See [File Associations](/guides/file-associations) for more information. |
|
||||
| ApplicationStarted | Application has started |
|
||||
| ThemeChanged | System theme changed |
|
||||
|
||||
### Platform-Specific Application Events
|
||||
|
||||
Below is a list of all platform-specific application events.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
| Event Name | Common Event | Description |
|
||||
|------------|--------------|-------------|
|
||||
| ApplicationDidBecomeActive | - | Application became active |
|
||||
| ApplicationDidChangeBackingProperties | - | Application backing properties changed |
|
||||
| ApplicationDidChangeEffectiveAppearance | ThemeChanged | Application appearance changed |
|
||||
| ApplicationDidChangeIcon | - | Application icon changed |
|
||||
| ApplicationDidChangeOcclusionState | - | Application occlusion state changed |
|
||||
| ApplicationDidChangeScreenParameters | - | Screen parameters changed |
|
||||
| ApplicationDidChangeStatusBarFrame | - | Status bar frame changed |
|
||||
| ApplicationDidChangeStatusBarOrientation | - | Status bar orientation changed |
|
||||
| ApplicationDidChangeTheme | ThemeChanged | System theme changed |
|
||||
| ApplicationDidFinishLaunching | ApplicationStarted | Application finished launching |
|
||||
| ApplicationDidHide | - | Application hidden |
|
||||
| ApplicationDidResignActiveNotification | - | Application resigned active state |
|
||||
| ApplicationDidUnhide | - | Application unhidden |
|
||||
| ApplicationDidUpdate | - | Application updated |
|
||||
| ApplicationShouldHandleReopen | - | Application should handle reopen |
|
||||
| ApplicationWillBecomeActive | - | Application will become active |
|
||||
| ApplicationWillFinishLaunching | - | Application will finish launching |
|
||||
| ApplicationWillHide | - | Application will hide |
|
||||
| ApplicationWillResignActiveNotification | - | Application will resign active state |
|
||||
| ApplicationWillTerminate | - | Application will terminate |
|
||||
| ApplicationWillUnhide | - | Application will unhide |
|
||||
| ApplicationWillUpdate | - | Application will update |
|
||||
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
| Event Name | Common Event | Description |
|
||||
|------------|--------------|-------------|
|
||||
| APMPowerSettingChange | - | Power settings changed |
|
||||
| APMPowerStatusChange | - | Power status changed |
|
||||
| APMResumeAutomatic | - | System resuming automatically |
|
||||
| APMResumeSuspend | - | System resuming from suspend |
|
||||
| APMSuspend | - | System suspending |
|
||||
| ApplicationStarted | ApplicationStarted | Application started |
|
||||
| SystemThemeChanged | ThemeChanged | System theme changed |
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
| Event Name | Common Event | Description |
|
||||
|------------|--------------|-------------|
|
||||
| ApplicationStartup | ApplicationStarted | Application started |
|
||||
| SystemThemeChanged | ThemeChanged | System theme changed |
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Window Events
|
||||
|
||||
Window events are triggered by window-specific actions such as resizing, moving, or changing focus state. You can listen for these events using the `OnWindowEvent` method:
|
||||
|
||||
```go
|
||||
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
app.Logger.Info("Window is closing!")
|
||||
})
|
||||
|
||||
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
|
||||
app.Logger.Info("Window gained focus!")
|
||||
})
|
||||
```
|
||||
|
||||
### Hooks vs Standard Listeners
|
||||
|
||||
Wails provides two ways to handle window events: standard listeners (OnWindowEvent) and hooks (RegisterHook). The key differences are:
|
||||
|
||||
1. **Execution Order**: Hooks are executed first and in the order they are registered, while standard listeners execute after Hooks and have no guaranteed order.
|
||||
2. **Blocking**: Hooks are blocking and must complete before the next hook is executed. Standard listeners are non-blocking.
|
||||
3. **Event Cancellation**: When cancelling an event in a Hook, it prevents it from propagating further. This is useful to prevent
|
||||
default behaviour, such as closing a window. Cancelling an event in a standard listener will only prevent it from being emitted
|
||||
from that point in time.
|
||||
|
||||
In this example, the window will only close after the close button has been clicked three times, demonstrating how hooks can be used to control event flow.
|
||||
|
||||
```go
|
||||
// Hook - runs synchronously. The window will not close until the countdown reaches zero.
|
||||
var countdown = 3
|
||||
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
countdown--
|
||||
if countdown == 0 {
|
||||
app.Logger.Info("Window closing - countdown reached zero!")
|
||||
return
|
||||
}
|
||||
app.Logger.Info("Preventing window from closing - countdown:", countdown)
|
||||
e.Cancel()
|
||||
})
|
||||
```
|
||||
|
||||
This next example demonstrates the execution order of hooks vs standard listeners.
|
||||
|
||||
```go
|
||||
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
|
||||
app.Logger.Info("I always run after hooks!")
|
||||
})
|
||||
|
||||
// Multiple hooks are executed in order
|
||||
window.RegisterHook(events.Common.WindowFocus, func(e *application.WindowEvent) {
|
||||
app.Logger.Info("First focus hook - will always run first!")
|
||||
})
|
||||
window.RegisterHook(events.Common.WindowFocus, func(e *application.WindowEvent) {
|
||||
app.Logger.Info("Second focus hook - will always run second!")
|
||||
})
|
||||
```
|
||||
|
||||
This produces the following output:
|
||||
|
||||
```
|
||||
INF First focus hook - will always run first!
|
||||
INF Second focus hook - will always run second!
|
||||
INF I always run after hooks!
|
||||
```
|
||||
|
||||
### Common Window Events
|
||||
|
||||
| Event Name | Description |
|
||||
|--------------------|---------------------------|
|
||||
| WindowClosing | Window is closing |
|
||||
| WindowDidMove | Window moved |
|
||||
| WindowDidResize | Window resized |
|
||||
| WindowDPIChanged | Window DPI changed |
|
||||
| WindowFilesDropped | Files dropped on window |
|
||||
| WindowFocus | Window gained focus |
|
||||
| WindowFullscreen | Window entered fullscreen |
|
||||
| WindowHide | Window hidden |
|
||||
| WindowLostFocus | Window lost focus |
|
||||
| WindowMaximise | Window maximised |
|
||||
| WindowMinimise | Window minimised |
|
||||
| WindowRestore | Window restored |
|
||||
| WindowRuntimeReady | Window runtime is ready |
|
||||
| WindowShow | Window shown |
|
||||
| WindowUnFullscreen | Window exited fullscreen |
|
||||
| WindowUnMaximise | Window unmaximised |
|
||||
| WindowUnMinimise | Window unminimised |
|
||||
| WindowZoom | Window zoomed |
|
||||
| WindowZoomIn | Window zoomed in |
|
||||
| WindowZoomOut | Window zoomed out |
|
||||
| WindowZoomReset | Window zoom reset |
|
||||
|
||||
### Enhanced Drag and Drop with Targeted Dropzones
|
||||
|
||||
Wails v3 introduces an enhanced drag-and-drop system that allows you to define specific "dropzones" within your application's HTML. This provides finer control over where files can be dropped and offers automatic visual feedback managed by the Wails runtime.
|
||||
|
||||
#### 1. Defining Dropzones in HTML
|
||||
|
||||
To designate an HTML element as a dropzone, add the `data-wails-dropzone` attribute to it. Any element with this attribute will become a valid target for file drops.
|
||||
|
||||
**Example:**
|
||||
```html
|
||||
<div id="myDropArea" class="my-styles" data-wails-dropzone>
|
||||
<p>Drop files here!</p>
|
||||
</div>
|
||||
|
||||
<div id="anotherZone" data-wails-dropzone style="width: 300px; height: 100px; border: 1px solid grey;">
|
||||
Another drop target
|
||||
</div>
|
||||
|
||||
<!-- Advanced example with custom data attributes -->
|
||||
<div class="tree-node folder folder-dropzone"
|
||||
data-wails-dropzone
|
||||
data-folder-id="documents"
|
||||
data-folder-name="Documents"
|
||||
data-path="/home/user/Documents">
|
||||
<span>📁 Documents</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 2. Visual Feedback
|
||||
|
||||
When files are dragged over an element marked with `data-wails-dropzone`, the Wails JavaScript runtime automatically adds the `wails-dropzone-hover` CSS class to that element. You can define styles for this class to provide visual feedback:
|
||||
|
||||
**Example CSS:**
|
||||
```css
|
||||
/* Base style for all dropzones (resting state) */
|
||||
.dropzone {
|
||||
border: 2px dashed #888;
|
||||
background-color: #303030;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Default hover effect applied by the runtime */
|
||||
.dropzone.wails-dropzone-hover {
|
||||
background-color: #3c3c3e;
|
||||
border-style: dotted;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 8px rgba(0, 123, 255, 0.4);
|
||||
}
|
||||
|
||||
/* Example: Customizing hover for a specific dropzone to override the default */
|
||||
#myDropArea.wails-dropzone-hover {
|
||||
background-color: lightgreen;
|
||||
outline: 2px solid green;
|
||||
}
|
||||
```
|
||||
The runtime handles adding and removing the `wails-dropzone-hover` class as files are dragged in and out of the dropzone or the window.
|
||||
|
||||
#### 3. Handling Drops in Go
|
||||
|
||||
On the Go side, listen for the `events.Common.WindowDropZoneFilesDropped` event. This event will be emitted when files are dropped onto an element that has the `data-wails-dropzone` attribute.
|
||||
|
||||
**Example Go Handler:**
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
// Assuming 'win' is your *application.WebviewWindow instance
|
||||
win.OnWindowEvent(events.Common.WindowDropZoneFilesDropped, func(event *application.WindowEvent) {
|
||||
droppedFiles := event.Context().DroppedFiles()
|
||||
log.Printf("Files dropped: %v", droppedFiles)
|
||||
|
||||
details := event.Context().DropZoneDetails()
|
||||
if details != nil {
|
||||
log.Printf("Dropped on Element ID: '%s'", details.ElementID)
|
||||
log.Printf("Element Classes: %v", details.ClassList)
|
||||
log.Printf("Drop Coordinates (relative to window): X=%d, Y=%d", details.X, details.Y)
|
||||
|
||||
// Access custom data attributes from the HTML element
|
||||
if folderName, exists := details.Attributes["data-folder-name"]; exists {
|
||||
log.Printf("Folder name: %s", folderName)
|
||||
}
|
||||
if folderPath, exists := details.Attributes["data-path"]; exists {
|
||||
log.Printf("Target path: %s", folderPath)
|
||||
}
|
||||
|
||||
// Example: Handle different dropzone types based on ElementID
|
||||
switch details.ElementID {
|
||||
case "documents":
|
||||
log.Printf("Files dropped on Documents folder")
|
||||
// Handle document uploads
|
||||
case "downloads":
|
||||
log.Printf("Files dropped on Downloads folder")
|
||||
// Handle download folder drops
|
||||
case "trash":
|
||||
log.Printf("Files dropped on Trash")
|
||||
// Handle file deletion
|
||||
default:
|
||||
log.Printf("Files dropped on unknown target: %s", details.ElementID)
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"files": droppedFiles,
|
||||
"targetID": details.ElementID,
|
||||
"targetClasses": details.ClassList,
|
||||
"dropX": details.X,
|
||||
"dropY": details.Y,
|
||||
"attributes": details.Attributes,
|
||||
}
|
||||
application.Get().EmitEvent("frontend:FileDropInfo", payload) // Emits globally
|
||||
// or win.EmitEvent("frontend:FileDropInfoForWindow", payload) // Emits to this specific window
|
||||
} else {
|
||||
log.Println("Drop occurred, but DropZoneDetails were nil.")
|
||||
}
|
||||
})
|
||||
```
|
||||
The `event.Context().DropZoneDetails()` method returns a pointer to an `application.DropZoneDetails` struct (or `nil` if details aren't available), containing:
|
||||
- `ElementID string`: The id of the element dropped onto
|
||||
- `ClassList []string`: The list of CSS classes of the HTML element that received the drop.
|
||||
- `X int`: The X-coordinate of the drop, relative to the window's content area.
|
||||
- `Y int`: The Y-coordinate of the drop, relative to the window's content area.
|
||||
- `Attributes map[string]string`: A map containing all HTML attributes of the target element, allowing access to custom data attributes like `data-path`, `data-folder-name`, etc.
|
||||
|
||||
The `event.Context().DroppedFiles()` method returns a `[]string` of file paths.
|
||||
|
||||
For a fully runnable demonstration of these features, including multiple styled dropzones, please refer to the example located in the `v3/examples/drag-n-drop` directory within the Wails repository.
|
||||
|
||||
### Platform-Specific Window Events
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
| Event Name | Common Event | Description |
|
||||
|------------|--------------|-------------|
|
||||
| WindowDidBecomeKey | WindowFocus | Window became key window |
|
||||
| WindowDidBecomeMain | - | Window became main window |
|
||||
| WindowDidBeginSheet | - | Sheet began |
|
||||
| WindowDidChangeAlpha | - | Window alpha changed |
|
||||
| WindowDidChangeBackingLocation | - | Window backing location changed |
|
||||
| WindowDidChangeBackingProperties | - | Window backing properties changed |
|
||||
| WindowDidChangeCollectionBehavior | - | Window collection behaviour changed |
|
||||
| WindowDidChangeEffectiveAppearance | - | Window appearance changed |
|
||||
| WindowDidChangeOcclusionState | - | Window occlusion state changed |
|
||||
| WindowDidChangeOrderingMode | - | Window ordering mode changed |
|
||||
| WindowDidChangeScreen | - | Window screen changed |
|
||||
| WindowDidChangeScreenParameters | - | Window screen parameters changed |
|
||||
| WindowDidChangeScreenProfile | - | Window screen profile changed |
|
||||
| WindowDidChangeScreenSpace | - | Window screen space changed |
|
||||
| WindowDidChangeScreenSpaceProperties | - | Window screen space properties changed |
|
||||
| WindowDidChangeSharingType | - | Window sharing type changed |
|
||||
| WindowDidChangeSpace | - | Window space changed |
|
||||
| WindowDidChangeSpaceOrderingMode | - | Window space ordering mode changed |
|
||||
| WindowDidChangeTitle | - | Window title changed |
|
||||
| WindowDidChangeToolbar | - | Window toolbar changed |
|
||||
| WindowDidDeminiaturize | WindowUnMinimise | Window unminimised |
|
||||
| WindowDidEndSheet | - | Sheet ended |
|
||||
| WindowDidEnterFullScreen | WindowFullscreen | Window entered fullscreen |
|
||||
| WindowDidEnterVersionBrowser | - | Window entered version browser |
|
||||
| WindowDidExitFullScreen | WindowUnFullscreen | Window exited fullscreen |
|
||||
| WindowDidExitVersionBrowser | - | Window exited version browser |
|
||||
| WindowDidExpose | - | Window exposed |
|
||||
| WindowDidFocus | WindowFocus | Window gained focus |
|
||||
| WindowDidMiniaturize | WindowMinimise | Window minimised |
|
||||
| WindowDidMove | WindowDidMove | Window moved |
|
||||
| WindowDidOrderOffScreen | - | Window ordered off screen |
|
||||
| WindowDidOrderOnScreen | - | Window ordered on screen |
|
||||
| WindowDidResignKey | - | Window resigned key window |
|
||||
| WindowDidResignMain | - | Window resigned main window |
|
||||
| WindowDidResize | WindowDidResize | Window resized |
|
||||
| WindowDidUpdate | - | Window updated |
|
||||
| WindowDidUpdateAlpha | - | Window alpha updated |
|
||||
| WindowDidUpdateCollectionBehavior | - | Window collection behaviour updated |
|
||||
| WindowDidUpdateCollectionProperties | - | Window collection properties updated |
|
||||
| WindowDidUpdateShadow | - | Window shadow updated |
|
||||
| WindowDidUpdateTitle | - | Window title updated |
|
||||
| WindowDidUpdateToolbar | - | Window toolbar updated |
|
||||
| WindowDidZoom | WindowZoom | Window zoomed |
|
||||
| WindowFileDraggingEntered | - | File dragging entered window |
|
||||
| WindowFileDraggingExited | - | File dragging exited window |
|
||||
| WindowFileDraggingPerformed | - | File dragging performed |
|
||||
| WindowHide | WindowHide | Window hidden |
|
||||
| WindowMaximise | WindowMaximise | Window maximised |
|
||||
| WindowShouldClose | WindowClosing | Window should close |
|
||||
| WindowShow | WindowShow | Window shown |
|
||||
| WindowUnMaximize | WindowUnMaximise | Window unmaximised |
|
||||
| WindowZoomIn | WindowZoomIn | Window zoomed in |
|
||||
| WindowZoomOut | WindowZoomOut | Window zoomed out |
|
||||
| WindowZoomReset | WindowZoomReset | Window zoom reset |
|
||||
|------------|--------------|-------------|
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
| Event Name | Common Event | Description |
|
||||
|------------|--------------|-------------|
|
||||
| WebViewNavigationCompleted | - | WebView navigation completed |
|
||||
| WindowActive | - | Window became active |
|
||||
| WindowBackgroundErase | - | Window background needs erasing |
|
||||
| WindowClickActive | - | Window clicked whilst active |
|
||||
| WindowClosing | WindowClosing | Window closing |
|
||||
| WindowDidMove | WindowDidMove | Window moved |
|
||||
| WindowDidResize | WindowDidResize | Window resized |
|
||||
| WindowEndMove | - | Window finished moving |
|
||||
| WindowEndResize | - | Window finished resising |
|
||||
| WindowFullscreen | WindowFullscreen | Window entered fullscreen |
|
||||
| WindowHide | WindowHide | Window hidden |
|
||||
| WindowInactive | - | Window became inactive |
|
||||
| WindowKillFocus | WindowLostFocus | Window lost focus |
|
||||
| WindowMaximise | WindowMaximise | Window maximised |
|
||||
| WindowMinimise | WindowMinimise | Window minimised |
|
||||
| WindowPaint | - | Window needs painting |
|
||||
| WindowRestore | WindowRestore | Window restored |
|
||||
| WindowSetFocus | WindowFocus | Window gained focus |
|
||||
| WindowShow | WindowShow | Window shown |
|
||||
| WindowStartMove | - | Window started moving |
|
||||
| WindowStartResize | - | Window started resising |
|
||||
| WindowUnFullscreen | WindowUnFullscreen | Window exited fullscreen |
|
||||
| WindowUnMaximise | WindowUnMaximise | Window unmaximised |
|
||||
| WindowUnMinimise | WindowUnMinimise | Window unminimised |
|
||||
| WindowZOrderChanged | - | Window z-order changed |
|
||||
|
||||
#### Input Events
|
||||
| Event Name | Description |
|
||||
|------------|-------------|
|
||||
| WindowDragDrop | Files dragged and dropped |
|
||||
| WindowDragEnter | Drag entered window |
|
||||
| WindowDragLeave | Drag left window |
|
||||
| WindowDragOver | Drag over window |
|
||||
| WindowKeyDown | Key pressed |
|
||||
| WindowKeyUp | Key released |
|
||||
| WindowNonClientHit | Mouse hit in non-client area |
|
||||
| WindowNonClientMouseDown | Mouse down in non-client area |
|
||||
| WindowNonClientMouseLeave | Mouse left non-client area |
|
||||
| WindowNonClientMouseMove | Mouse move in non-client area |
|
||||
| WindowNonClientMouseUp | Mouse up in non-client area |
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
| Event Name | Common Event | Description |
|
||||
|------------|--------------|-------------|
|
||||
| WindowDeleteEvent | WindowClosing | Window delete requested |
|
||||
| WindowDidMove | WindowDidMove | Window moved |
|
||||
| WindowDidResize | WindowDidResize | Window resized |
|
||||
| WindowFocusIn | WindowFocus | Window gained focus |
|
||||
| WindowFocusOut | WindowLostFocus | Window lost focus |
|
||||
| WindowLoadChanged | WindowShow | Window load state changed |
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Menu Events
|
||||
|
||||
Menu events are triggered by menu-specific actions such as opening, closing, and interacting with menu items. These events are useful for creating dynamic menus and responding to menu interactions.
|
||||
|
||||
```go
|
||||
// Listen for menu events
|
||||
app.Event.OnApplicationEvent(events.Mac.MenuDidOpen, func(event *application.ApplicationEvent) {
|
||||
app.Logger.Info("Menu opened!")
|
||||
})
|
||||
|
||||
app.Event.OnApplicationEvent(events.Mac.MenuWillSendAction, func(event *application.ApplicationEvent) {
|
||||
app.Logger.Info("Menu about to send action!")
|
||||
})
|
||||
```
|
||||
|
||||
For more information about menus, see the [Application Menu](/learn/application-menu) and [Context Menu](/learn/context-menu) documentation.
|
||||
|
||||
### Platform-Specific Menu Events
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
| Event Name | Description |
|
||||
|------------|-------------|
|
||||
| MenuDidAddItem | Menu item added |
|
||||
| MenuDidBeginTracking | Menu began tracking |
|
||||
| MenuDidClose | Menu closed |
|
||||
| MenuDidDisplayItem | Menu item displayed |
|
||||
| MenuDidEndTracking | Menu ended tracking |
|
||||
| MenuDidHighlightItem | Menu item highlighted |
|
||||
| MenuDidOpen | Menu opened |
|
||||
| MenuDidPopUp | Menu popped up |
|
||||
| MenuDidRemoveItem | Menu item removed |
|
||||
| MenuDidSendAction | Menu sent action |
|
||||
| MenuDidSendActionToItem | Menu sent action to item |
|
||||
| MenuDidUpdate | Menu updated |
|
||||
| MenuWillAddItem | Menu will add item |
|
||||
| MenuWillBeginTracking | Menu will begin tracking |
|
||||
| MenuWillDisplayItem | Menu will display item |
|
||||
| MenuWillEndTracking | Menu will end tracking |
|
||||
| MenuWillHighlightItem | Menu will highlight item |
|
||||
| MenuWillOpen | Menu will open |
|
||||
| MenuWillPopUp | Menu will pop up |
|
||||
| MenuWillRemoveItem | Menu will remove item |
|
||||
| MenuWillSendAction | Menu will send action |
|
||||
| MenuWillSendActionToItem | Menu will send action to item |
|
||||
| MenuWillUpdate | Menu will update |
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
Windows does not currently provide specific menu events.
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
Linux does not currently provide specific menu events.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## WebView Events
|
||||
|
||||
WebView events are triggered by navigation and loading state changes in the WebView component. These events are useful for tracking page loads and navigation state.
|
||||
|
||||
```go
|
||||
// Listen for WebView navigation events
|
||||
app.Event.OnApplicationEvent(events.Mac.WebViewDidStartProvisionalNavigation, func(event *application.ApplicationEvent) {
|
||||
app.Logger.Info("WebView started loading a page!")
|
||||
})
|
||||
|
||||
app.Event.OnApplicationEvent(events.Mac.WebViewDidFinishNavigation, func(event *application.ApplicationEvent) {
|
||||
app.Logger.Info("WebView finished loading the page!")
|
||||
})
|
||||
|
||||
// On Windows
|
||||
app.Event.OnApplicationEvent(events.Windows.WebViewNavigationCompleted, func(event *application.ApplicationEvent) {
|
||||
app.Logger.Info("WebView navigation completed!")
|
||||
})
|
||||
```
|
||||
|
||||
### Platform-Specific WebView Events
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
| Event Name | Description |
|
||||
|------------|-------------|
|
||||
| WebViewDidCommitNavigation | Navigation committed |
|
||||
| WebViewDidFinishNavigation | Navigation finished |
|
||||
| WebViewDidReceiveServerRedirectForProvisionalNavigation | Server redirect received |
|
||||
| WebViewDidStartProvisionalNavigation | Provisional navigation started |
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
| Event Name | Description |
|
||||
|------------|-------------|
|
||||
| WebViewNavigationCompleted | Navigation completed |
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
Linux does not currently provide specific WebView events.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Custom Events
|
||||
|
||||
You can emit and listen for custom events to enable communication between different parts of your application. Wails v3 provides both a traditional API and a new [Manager API](/learn/manager-api) for better organization.
|
||||
|
||||
### Emitting Events
|
||||
|
||||
You can emit custom events from anywhere in your application:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Manager API (Recommended)">
|
||||
```go
|
||||
// NEW: Using the Event Manager (recommended)
|
||||
app.Event.Emit("myevent", "hello")
|
||||
|
||||
// Emit from a specific window
|
||||
window.EmitEvent("windowevent", "window specific data")
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem label="Traditional API">
|
||||
```go
|
||||
// Traditional API (no longer works)
|
||||
app.EmitEvent("myevent", "hello")
|
||||
|
||||
// Emit from a specific window
|
||||
window.EmitEvent("windowevent", "window specific data")
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Handling Custom Events
|
||||
|
||||
Listen for custom events using the event management methods:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Manager API (Recommended)">
|
||||
```go
|
||||
// NEW: Using the Event Manager (recommended)
|
||||
cancelFunc := app.Event.On("myevent", func(e *application.CustomEvent) {
|
||||
// Access event information
|
||||
name := e.Name // Event name
|
||||
data := e.Data // Event data
|
||||
sender := e.Sender // Name of the window sending the event, or "" if sent from application
|
||||
cancelled := e.IsCancelled() // Event cancellation status
|
||||
})
|
||||
|
||||
// Remove specific event listener
|
||||
app.Event.Off("myevent")
|
||||
|
||||
// Remove all event listeners
|
||||
app.Event.Reset()
|
||||
|
||||
// Listen for events a limited number of times
|
||||
app.Event.OnMultiple("myevent", func(e *application.CustomEvent) {
|
||||
// This will only be called 3 times
|
||||
}, 3)
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem label="Traditional API">
|
||||
```go
|
||||
// Traditional API (still supported)
|
||||
cancelFunc := app.OnEvent("myevent", func(e *application.CustomEvent) {
|
||||
// Access event information
|
||||
name := e.Name // Event name
|
||||
data := e.Data // Event data
|
||||
sender := e.Sender // Name of the window sending the event, or "" if sent from application
|
||||
cancelled := e.IsCancelled() // Event cancellation status
|
||||
})
|
||||
|
||||
// Remove specific event listener
|
||||
app.OffEvent("myevent")
|
||||
|
||||
// Remove all event listeners
|
||||
app.ResetEvents()
|
||||
|
||||
// Listen for events a limited number of times
|
||||
app.OnMultipleEvent("myevent", func(e *application.CustomEvent) {
|
||||
// This will only be called 3 times
|
||||
}, 3)
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Event Cancellation
|
||||
|
||||
Events can be cancelled to prevent their default behaviour or stop propagation to other listeners. This is particularly useful for hooks that need to control window closing, menu actions, or other system events.
|
||||
|
||||
### Cancelling Events
|
||||
|
||||
To cancel an event, call the `Cancel()` method on the event object:
|
||||
|
||||
```go
|
||||
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
// Prevent the window from closing
|
||||
e.Cancel()
|
||||
})
|
||||
```
|
||||
|
||||
### Checking Event Cancellation
|
||||
|
||||
You can check if an event has been cancelled using the `IsCancelled()` method:
|
||||
|
||||
```go
|
||||
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
if e.IsCancelled() {
|
||||
app.Logger.Info("Window closing was cancelled by another hook")
|
||||
return
|
||||
}
|
||||
// Process event
|
||||
})
|
||||
|
||||
// For custom events
|
||||
app.Event.On("myevent", func(e *application.CustomEvent) {
|
||||
if e.IsCancelled() {
|
||||
app.Logger.Info("Event was cancelled")
|
||||
return
|
||||
}
|
||||
// Process event
|
||||
})
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Remember that event cancellation in hooks affects all subsequent hooks and listeners, whilst cancellation in standard listeners only affects listeners that haven't yet been called.
|
||||
:::
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
---
|
||||
title: Menu Reference
|
||||
sidebar:
|
||||
order: 52
|
||||
---
|
||||
|
||||
This reference document covers the common menu item types and properties available in Wails v3. These features are shared between application menus and context menus.
|
||||
|
||||
## Menu Item Types
|
||||
|
||||
### Regular Menu Items
|
||||
|
||||
The most basic type of menu item displays text and triggers an action when clicked:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Click Me")
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
// Handle click
|
||||
})
|
||||
```
|
||||
|
||||
### Checkboxes
|
||||
|
||||
Checkbox menu items provide a toggleable state:
|
||||
|
||||
```go
|
||||
checkbox := menu.AddCheckbox("Enable Feature", true) // true = initially checked
|
||||
checkbox.OnClick(func(ctx *application.Context) {
|
||||
isChecked := ctx.ClickedMenuItem().Checked()
|
||||
// Handle state change
|
||||
})
|
||||
```
|
||||
|
||||
### Radio Groups
|
||||
|
||||
Radio items create mutually exclusive options. Items are grouped automatically when placed adjacently:
|
||||
|
||||
```go
|
||||
menu.AddRadio("Option 1", true) // true = initially selected
|
||||
menu.AddRadio("Option 2", false)
|
||||
menu.AddRadio("Option 3", false)
|
||||
```
|
||||
|
||||
### Submenus
|
||||
|
||||
Submenus allow you to create nested menu structures:
|
||||
|
||||
```go
|
||||
submenu := menu.AddSubmenu("More Options")
|
||||
submenu.Add("Submenu Item 1")
|
||||
submenu.Add("Submenu Item 2")
|
||||
```
|
||||
|
||||
### Separators
|
||||
|
||||
Separators add visual dividers between menu items:
|
||||
|
||||
```go
|
||||
menu.Add("Item 1")
|
||||
menu.AddSeparator()
|
||||
menu.Add("Item 2")
|
||||
```
|
||||
|
||||
## Menu Item Properties
|
||||
|
||||
### Label
|
||||
|
||||
The text displayed for the menu item:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Initial Label")
|
||||
menuItem.SetLabel("New Label")
|
||||
```
|
||||
|
||||
### Enabled State
|
||||
|
||||
Control whether the menu item can be interacted with:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Disabled Item")
|
||||
menuItem.SetEnabled(false)
|
||||
```
|
||||
|
||||
### Checked State
|
||||
|
||||
For checkbox and radio items, control or query their checked state:
|
||||
|
||||
```go
|
||||
checkbox := menu.AddCheckbox("Feature", false)
|
||||
checkbox.SetChecked(true)
|
||||
isChecked := checkbox.Checked()
|
||||
```
|
||||
|
||||
### Accelerators
|
||||
|
||||
Add keyboard shortcuts to menu items:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Save")
|
||||
menuItem.SetAccelerator("CmdOrCtrl+S")
|
||||
```
|
||||
|
||||
Common accelerator modifiers:
|
||||
- `CmdOrCtrl`: Command on macOS, Control on Windows/Linux
|
||||
- `Shift`
|
||||
- `Alt`: Option on macOS
|
||||
- `Ctrl`: Control key on all platforms
|
||||
|
||||
## Event Handling
|
||||
|
||||
### Click Events
|
||||
|
||||
Handle menu item clicks using the `OnClick` method:
|
||||
|
||||
```go
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
// Access the clicked item
|
||||
clickedItem := ctx.ClickedMenuItem()
|
||||
|
||||
// Get current state
|
||||
label := clickedItem.Label()
|
||||
isChecked := clickedItem.Checked()
|
||||
|
||||
// Update the item
|
||||
clickedItem.SetLabel("New Label")
|
||||
})
|
||||
```
|
||||
|
||||
### Shared Event Handlers
|
||||
|
||||
Event handlers can be shared amongst multiple menu items:
|
||||
|
||||
```go
|
||||
handleClick := func(ctx *application.Context) {
|
||||
item := ctx.ClickedMenuItem()
|
||||
// Common handling logic
|
||||
}
|
||||
|
||||
menu.Add("Item 1").OnClick(handleClick)
|
||||
menu.Add("Item 2").OnClick(handleClick)
|
||||
```
|
||||
|
||||
## Dynamic Updates
|
||||
|
||||
Menu items can be updated dynamically during runtime:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Initial State")
|
||||
|
||||
// Later, update the item
|
||||
menuItem.SetLabel("New Label")
|
||||
menuItem.SetEnabled(false)
|
||||
menuItem.SetChecked(true)
|
||||
|
||||
// Apply changes
|
||||
menu.Update()
|
||||
```
|
||||
|
||||
:::note[Update Required]
|
||||
After modifying menu items, call `Update()` on the parent menu to apply the changes.
|
||||
:::
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Use clear, concise labels that describe the action
|
||||
2. Group related items together using separators
|
||||
3. Limit submenu depth to maintain usability
|
||||
4. Provide keyboard shortcuts for common actions
|
||||
5. Keep radio groups focused on a single choice
|
||||
6. Update menu items to reflect application state
|
||||
7. Handle all possible states in click handlers
|
||||
|
||||
:::tip[Pro Tip]
|
||||
When sharing event handlers, use the `ctx.ClickedMenuItem()` method to determine which item triggered the event and handle it accordingly.
|
||||
:::
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
:::note[Platform Behaviour]
|
||||
Menu appearance and behaviour varies by platform:
|
||||
- macOS: Uses native menu styling and supports system roles
|
||||
- Windows: Follows Windows menu conventions
|
||||
- Linux: Adapts to the desktop environment's theme
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Always test menu functionality across all supported platforms, as behaviour and appearance may vary significantly.
|
||||
:::
|
||||
|
|
@ -1,505 +0,0 @@
|
|||
---
|
||||
title: Screen Management
|
||||
sidebar:
|
||||
order: 57
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides comprehensive screen management capabilities for multi-monitor setups and high-DPI displays. The ScreenManager API allows you to query screen information, handle coordinate transformations, and manage window placement across multiple displays.
|
||||
|
||||
## Accessing the Screen Manager
|
||||
|
||||
The screen manager is accessed through the `Screens` property on your application instance:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "Multi-Monitor App",
|
||||
})
|
||||
|
||||
// Access the screen manager
|
||||
screens := app.Screen
|
||||
```
|
||||
|
||||
## Getting Screen Information
|
||||
|
||||
### Get All Screens
|
||||
|
||||
Retrieve information about all connected displays:
|
||||
|
||||
```go
|
||||
allScreens := app.Screen.GetAll()
|
||||
for i, screen := range allScreens {
|
||||
app.Logger.Info("Screen info",
|
||||
"index", i,
|
||||
"name", screen.Name,
|
||||
"id", screen.ID,
|
||||
"primary", screen.IsPrimary,
|
||||
"width", screen.Size.Width,
|
||||
"height", screen.Size.Height,
|
||||
"scaleFactor", screen.ScaleFactor,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Get Primary Screen
|
||||
|
||||
Get the primary (main) display:
|
||||
|
||||
```go
|
||||
primaryScreen := app.Screen.GetPrimary()
|
||||
if primaryScreen != nil {
|
||||
app.Logger.Info("Primary screen",
|
||||
"name", primaryScreen.Name,
|
||||
"resolution", fmt.Sprintf("%dx%d", primaryScreen.Size.Width, primaryScreen.Size.Height),
|
||||
"workArea", primaryScreen.WorkArea,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Screen Properties
|
||||
|
||||
### Screen Structure
|
||||
|
||||
Each screen provides comprehensive information about the display:
|
||||
|
||||
```go
|
||||
type Screen struct {
|
||||
ID string // Unique identifier for the display
|
||||
Name string // Display name (e.g., "Built-in Retina Display")
|
||||
ScaleFactor float32 // DPI scale factor (1.0 = 96 DPI, 2.0 = 192 DPI)
|
||||
X int // X coordinate of top-left corner
|
||||
Y int // Y coordinate of top-left corner
|
||||
Size Size // Logical size of the display
|
||||
Bounds Rect // Display bounds in logical coordinates
|
||||
PhysicalBounds Rect // Physical bounds before scaling
|
||||
WorkArea Rect // Available area (excluding taskbars/docks)
|
||||
PhysicalWorkArea Rect // Physical work area
|
||||
IsPrimary bool // Whether this is the primary display
|
||||
Rotation float32 // Display rotation in degrees
|
||||
}
|
||||
```
|
||||
|
||||
### Coordinate Systems
|
||||
|
||||
Wails handles two coordinate systems:
|
||||
|
||||
- **Logical (DIP)**: Device-independent pixels, scaled for readability
|
||||
- **Physical**: Actual pixel coordinates on the hardware
|
||||
|
||||
```go
|
||||
screen := app.Screen.GetPrimary()
|
||||
|
||||
// Logical coordinates (what you typically work with)
|
||||
logicalWidth := screen.Size.Width
|
||||
logicalHeight := screen.Size.Height
|
||||
|
||||
// Physical coordinates (actual hardware pixels)
|
||||
physicalWidth := screen.PhysicalBounds.Width
|
||||
physicalHeight := screen.PhysicalBounds.Height
|
||||
|
||||
// Scale factor relationship
|
||||
scaleFactor := screen.ScaleFactor // e.g., 2.0 for Retina displays
|
||||
// physicalWidth = logicalWidth * scaleFactor
|
||||
```
|
||||
|
||||
## Coordinate Transformations
|
||||
|
||||
### Point Transformations
|
||||
|
||||
Convert coordinates between logical and physical systems:
|
||||
|
||||
```go
|
||||
// Convert logical point to physical coordinates
|
||||
logicalPoint := application.Point{X: 100, Y: 50}
|
||||
physicalPoint := app.Screen.DipToPhysicalPoint(logicalPoint)
|
||||
|
||||
// Convert physical point to logical coordinates
|
||||
physicalPoint2 := application.Point{X: 200, Y: 100}
|
||||
logicalPoint2 := app.Screen.PhysicalToDipPoint(physicalPoint2)
|
||||
|
||||
app.Logger.Info("Coordinate conversion",
|
||||
"logical", logicalPoint,
|
||||
"physical", physicalPoint,
|
||||
"backToLogical", logicalPoint2,
|
||||
)
|
||||
```
|
||||
|
||||
### Rectangle Transformations
|
||||
|
||||
Convert rectangular areas between coordinate systems:
|
||||
|
||||
```go
|
||||
// Define a logical rectangle
|
||||
logicalRect := application.Rect{
|
||||
X: 100, Y: 100,
|
||||
Width: 400, Height: 300,
|
||||
}
|
||||
|
||||
// Convert to physical coordinates
|
||||
physicalRect := app.Screen.DipToPhysicalRect(logicalRect)
|
||||
|
||||
// Convert back to logical coordinates
|
||||
logicalRect2 := app.Screen.PhysicalToDipRect(physicalRect)
|
||||
|
||||
app.Logger.Info("Rectangle conversion",
|
||||
"original", logicalRect,
|
||||
"physical", physicalRect,
|
||||
"converted", logicalRect2,
|
||||
)
|
||||
```
|
||||
|
||||
## Screen Detection and Positioning
|
||||
|
||||
### Find Screen by Point
|
||||
|
||||
Determine which screen contains a specific point:
|
||||
|
||||
```go
|
||||
// Find screen containing a logical point
|
||||
point := application.Point{X: 500, Y: 300}
|
||||
screen := app.Screen.ScreenNearestDipPoint(point)
|
||||
|
||||
// Find screen containing a physical point
|
||||
physicalPoint := application.Point{X: 1000, Y: 600}
|
||||
screenPhys := app.Screen.ScreenNearestPhysicalPoint(physicalPoint)
|
||||
|
||||
app.Logger.Info("Screen detection",
|
||||
"point", point,
|
||||
"screen", screen.Name,
|
||||
"physicalPoint", physicalPoint,
|
||||
"physicalScreen", screenPhys.Name,
|
||||
)
|
||||
```
|
||||
|
||||
### Find Screen by Rectangle
|
||||
|
||||
Determine which screen best contains or overlaps with a rectangle:
|
||||
|
||||
```go
|
||||
// Find screen for a logical rectangle
|
||||
rect := application.Rect{X: 200, Y: 150, Width: 800, Height: 600}
|
||||
screen := app.Screen.ScreenNearestDipRect(rect)
|
||||
|
||||
// Find screen for a physical rectangle
|
||||
physicalRect := application.Rect{X: 400, Y: 300, Width: 1600, Height: 1200}
|
||||
screenPhys := app.Screen.ScreenNearestPhysicalRect(physicalRect)
|
||||
|
||||
app.Logger.Info("Screen detection by area",
|
||||
"rect", rect,
|
||||
"screen", screen.Name,
|
||||
"physicalRect", physicalRect,
|
||||
"physicalScreen", screenPhys.Name,
|
||||
)
|
||||
```
|
||||
|
||||
## Window Positioning
|
||||
|
||||
### Position Window on Specific Screen
|
||||
|
||||
Place a window on a particular screen:
|
||||
|
||||
```go
|
||||
func positionWindowOnScreen(window *application.WebviewWindow, targetScreen *application.Screen) {
|
||||
// Calculate center position on target screen
|
||||
centerX := targetScreen.WorkArea.X + (targetScreen.WorkArea.Width-800)/2
|
||||
centerY := targetScreen.WorkArea.Y + (targetScreen.WorkArea.Height-600)/2
|
||||
|
||||
// Position the window
|
||||
window.SetPosition(centerX, centerY)
|
||||
window.SetSize(800, 600)
|
||||
}
|
||||
|
||||
// Usage
|
||||
primaryScreen := app.Screen.GetPrimary()
|
||||
if primaryScreen != nil {
|
||||
window := app.Window.New()
|
||||
positionWindowOnScreen(window, primaryScreen)
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Monitor Window Management
|
||||
|
||||
Manage windows across multiple screens:
|
||||
|
||||
```go
|
||||
func distributeWindowsAcrossScreens(app *application.App) {
|
||||
screens := app.Screen.GetAll()
|
||||
if len(screens) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Create one window per screen
|
||||
for i, screen := range screens {
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: fmt.Sprintf("window-%d", i),
|
||||
Title: fmt.Sprintf("Window on %s", screen.Name),
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
})
|
||||
|
||||
// Position window in center of screen's work area
|
||||
x := screen.WorkArea.X + (screen.WorkArea.Width-800)/2
|
||||
y := screen.WorkArea.Y + (screen.WorkArea.Height-600)/2
|
||||
window.SetPosition(x, y)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## High-DPI Support
|
||||
|
||||
### Handling Different Scale Factors
|
||||
|
||||
Properly handle displays with different DPI settings:
|
||||
|
||||
```go
|
||||
func analyzeDisplayScaling(app *application.App) {
|
||||
screens := app.Screen.GetAll()
|
||||
|
||||
for _, screen := range screens {
|
||||
scaleFactor := screen.ScaleFactor
|
||||
|
||||
switch {
|
||||
case scaleFactor == 1.0:
|
||||
app.Logger.Info("Standard DPI display", "screen", screen.Name)
|
||||
case scaleFactor == 1.25:
|
||||
app.Logger.Info("125% scaled display", "screen", screen.Name)
|
||||
case scaleFactor == 1.5:
|
||||
app.Logger.Info("150% scaled display", "screen", screen.Name)
|
||||
case scaleFactor == 2.0:
|
||||
app.Logger.Info("High DPI display (200%)", "screen", screen.Name)
|
||||
default:
|
||||
app.Logger.Info("Custom scale display",
|
||||
"screen", screen.Name,
|
||||
"scale", scaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DPI-Aware Measurements
|
||||
|
||||
Convert measurements appropriately for different displays:
|
||||
|
||||
```go
|
||||
func getDPIAwareSizes(screen *application.Screen, logicalSize int) (int, int) {
|
||||
// Physical size in actual pixels
|
||||
physicalSize := int(float32(logicalSize) * screen.ScaleFactor)
|
||||
|
||||
return logicalSize, physicalSize
|
||||
}
|
||||
|
||||
// Usage
|
||||
screen := app.Screen.GetPrimary()
|
||||
logicalWidth, physicalWidth := getDPIAwareSizes(screen, 400)
|
||||
app.Logger.Info("Size conversion",
|
||||
"logical", logicalWidth,
|
||||
"physical", physicalWidth,
|
||||
"scaleFactor", screen.ScaleFactor,
|
||||
)
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS:
|
||||
|
||||
- Screen coordinates start from bottom-left (different from Windows/Linux)
|
||||
- Built-in Retina displays typically have 2.0 scale factor
|
||||
- External displays may have different scale factors
|
||||
- Mission Control and Spaces affect screen management
|
||||
- Screen arrangement can be configured in System Preferences
|
||||
|
||||
```go
|
||||
// macOS-specific screen handling
|
||||
if runtime.GOOS == "darwin" {
|
||||
// Handle coordinate system differences
|
||||
screen := app.Screen.GetPrimary()
|
||||
// Y coordinates are flipped on macOS
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows:
|
||||
|
||||
- Screen coordinates start from top-left
|
||||
- Scale factors vary: 100%, 125%, 150%, 175%, 200%, etc.
|
||||
- Multiple monitor configurations are common
|
||||
- Taskbar affects work area calculations
|
||||
- Windows display settings control scaling
|
||||
|
||||
```go
|
||||
// Windows-specific screen handling
|
||||
if runtime.GOOS == "windows" {
|
||||
screen := app.Screen.GetPrimary()
|
||||
// Account for taskbar in work area
|
||||
workArea := screen.WorkArea
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux:
|
||||
|
||||
- Screen coordinates start from top-left
|
||||
- Scale factors depend on desktop environment
|
||||
- X11 and Wayland have different behaviors
|
||||
- Panel/dock configurations affect work areas
|
||||
- Multi-monitor support varies by environment
|
||||
|
||||
```go
|
||||
// Linux-specific screen handling
|
||||
if runtime.GOOS == "linux" {
|
||||
screen := app.Screen.GetPrimary()
|
||||
// Handle different desktop environments
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always Check for Screens**: Ensure screens are available before using them:
|
||||
```go
|
||||
screens := app.Screen.GetAll()
|
||||
if len(screens) == 0 {
|
||||
app.Logger.Warn("No screens detected")
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
2. **Use Work Areas**: Position windows within work areas to avoid taskbars/docks:
|
||||
```go
|
||||
screen := app.Screen.GetPrimary()
|
||||
workArea := screen.WorkArea
|
||||
// Position within workArea, not full screen bounds
|
||||
```
|
||||
|
||||
3. **Handle Scale Factors**: Always consider DPI scaling for proper sizing:
|
||||
```go
|
||||
screen := app.Screen.GetPrimary()
|
||||
if screen.ScaleFactor > 1.0 {
|
||||
// Adjust UI elements for high-DPI displays
|
||||
}
|
||||
```
|
||||
|
||||
4. **Monitor Screen Changes**: Listen for screen configuration changes:
|
||||
```go
|
||||
app.Event.OnApplicationEvent(events.Common.SystemDisplayChanged, func(event *application.ApplicationEvent) {
|
||||
// Refresh screen information
|
||||
screens := app.Screen.GetAll()
|
||||
// Reposition windows if needed
|
||||
})
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example demonstrating screen management:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Screen Management Demo",
|
||||
})
|
||||
|
||||
// Analyze connected screens
|
||||
analyzeScreens(app)
|
||||
|
||||
// Create windows strategically positioned
|
||||
createPositionedWindows(app)
|
||||
|
||||
// Setup screen change monitoring
|
||||
monitorScreenChanges(app)
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func analyzeScreens(app *application.App) {
|
||||
screens := app.Screen.GetAll()
|
||||
app.Logger.Info("Screen analysis", "count", len(screens))
|
||||
|
||||
primary := app.Screen.GetPrimary()
|
||||
if primary != nil {
|
||||
app.Logger.Info("Primary screen",
|
||||
"name", primary.Name,
|
||||
"size", fmt.Sprintf("%dx%d", primary.Size.Width, primary.Size.Height),
|
||||
"scaleFactor", primary.ScaleFactor,
|
||||
"workArea", primary.WorkArea,
|
||||
)
|
||||
}
|
||||
|
||||
for i, screen := range screens {
|
||||
app.Logger.Info("Screen details",
|
||||
"index", i,
|
||||
"name", screen.Name,
|
||||
"primary", screen.IsPrimary,
|
||||
"bounds", screen.Bounds,
|
||||
"scaleFactor", screen.ScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func createPositionedWindows(app *application.App) {
|
||||
screens := app.Screen.GetAll()
|
||||
|
||||
for i, screen := range screens {
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: fmt.Sprintf("screen-%d", i),
|
||||
Title: fmt.Sprintf("Window on %s", screen.Name),
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
})
|
||||
|
||||
// Center window in screen's work area
|
||||
x := screen.WorkArea.X + (screen.WorkArea.Width-600)/2
|
||||
y := screen.WorkArea.Y + (screen.WorkArea.Height-400)/2
|
||||
window.SetPosition(x, y)
|
||||
|
||||
app.Logger.Info("Created window",
|
||||
"screen", screen.Name,
|
||||
"position", fmt.Sprintf("%d,%d", x, y),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func monitorScreenChanges(app *application.App) {
|
||||
// Monitor for screen configuration changes
|
||||
app.Event.OnApplicationEvent(events.Common.SystemDisplayChanged, func(event *application.ApplicationEvent) {
|
||||
app.Logger.Info("Screen configuration changed")
|
||||
|
||||
// Re-analyze screens
|
||||
screens := app.Screen.GetAll()
|
||||
app.Logger.Info("Updated screen count", "count", len(screens))
|
||||
|
||||
// Could reposition windows here if needed
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
When developing multi-monitor applications, test on various configurations including different DPI settings and monitor arrangements to ensure proper behavior.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Screen configurations can change during application runtime (monitors connected/disconnected, resolution changes). Always handle these changes gracefully and avoid storing screen references long-term.
|
||||
:::
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
---
|
||||
title: Services
|
||||
sidebar:
|
||||
order: 10
|
||||
---
|
||||
|
||||
Services in Wails v3 provide a powerful way to extend the functionality of your
|
||||
application. They allow you to create modular, reusable components that can be
|
||||
easily integrated into your Wails application.
|
||||
|
||||
## Overview
|
||||
|
||||
Services are designed to encapsulate specific functionality and can be
|
||||
registered with the application at startup. They can handle various tasks such
|
||||
as file serving, database operations, logging, and more. Services can also
|
||||
interact with the application lifecycle and respond to HTTP requests.
|
||||
|
||||
## Creating a Service
|
||||
|
||||
To create a service, you simply define a struct. Here's a basic structure of a
|
||||
service:
|
||||
|
||||
```go
|
||||
type MyService struct {
|
||||
// Your service fields
|
||||
}
|
||||
|
||||
func NewMyService() *MyService {
|
||||
// Initialize and return your service
|
||||
}
|
||||
|
||||
func (s *MyService) Greet(name string) string {
|
||||
return fmt.Sprintf("Hello, %s!", name)
|
||||
}
|
||||
```
|
||||
|
||||
This service has a single method, `Greet`, which accepts a name and returns a
|
||||
greeting.
|
||||
|
||||
## Registering a Service
|
||||
|
||||
To register a service with the application, you need to provide an instance of
|
||||
the service to the `Services` field of the `application.Options` struct.
|
||||
All services need to be wrapped by an `application.NewService` call.
|
||||
Here's an example:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(NewMyService()),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This registers the `NewMyService` function as a service with the application.
|
||||
|
||||
Services may also be registered with additional options:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewServiceWithOptions(NewMyService(), application.ServiceOptions{
|
||||
// ...
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
ServiceOptions has the following fields:
|
||||
- Name - Specify a custom name for the Service
|
||||
- Route - A route to bind the Service to the frontend (more on this below)
|
||||
|
||||
After the application has been created but not yet started,
|
||||
you can register more services using the `RegisterService` method.
|
||||
This is useful when you need to feed a service some value
|
||||
that is only available after the application has been created.
|
||||
For example, let's wire application's logger into your own service:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{})
|
||||
|
||||
app.RegisterService(application.NewService(NewMyService(app.Logger)))
|
||||
|
||||
// ...
|
||||
|
||||
err := app.Run()
|
||||
```
|
||||
|
||||
Services may only be registered before running the application:
|
||||
`RegisterService` will panic if called after the `Run` method.
|
||||
|
||||
## Optional Methods
|
||||
|
||||
Services can implement optional methods to hook into the application lifecycle.
|
||||
|
||||
:::note
|
||||
The `ServiceStartup`, `ServiceShutdown`, `ServiceName` and `ServeHTTP` methods are not included in the
|
||||
bindings generated for a service, so they are not exposed to your frontend.
|
||||
:::
|
||||
|
||||
### ServiceName
|
||||
|
||||
```go
|
||||
func (s *Service) ServiceName() string
|
||||
```
|
||||
|
||||
This method returns the name of the service. By default, it will the struct name of the Service but can be
|
||||
overridden with the `Name` field of the `ServiceOptions`.
|
||||
|
||||
It is used for logging purposes only.
|
||||
|
||||
### ServiceStartup
|
||||
|
||||
```go
|
||||
func (s *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error
|
||||
```
|
||||
|
||||
This method is called when the application is starting up. You can use it to
|
||||
initialize resources, set up connections, or perform any necessary setup tasks.
|
||||
The context is the application context that will be canceled upon shutdown,
|
||||
and the `options` parameter provides additional information about the service.
|
||||
|
||||
Services are initialised in the exact order of registration:
|
||||
first those listed in the `Services` field of the `application.Options` struct,
|
||||
then those added through the `RegisterService` method.
|
||||
|
||||
### ServiceShutdown
|
||||
|
||||
```go
|
||||
func (s *Service) ServiceShutdown() error
|
||||
```
|
||||
|
||||
This method is called when the application is shutting down. Use it to clean up
|
||||
resources, close connections, or perform any necessary cleanup tasks.
|
||||
|
||||
Services are shut down in reverse registration order.
|
||||
The application context will be canceled before `ServiceShutdown` is called.
|
||||
|
||||
### ServeHTTP
|
||||
|
||||
```go
|
||||
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||
```
|
||||
|
||||
If your service needs to handle HTTP requests, implement this method. It allows
|
||||
your service to act as an HTTP handler. The route of the handler is defined in
|
||||
the service options:
|
||||
|
||||
```go
|
||||
application.NewServiceWithOptions(fileserver.New(&fileserver.Config{
|
||||
RootPath: rootPath,
|
||||
}), application.ServiceOptions{
|
||||
Route: "/files",
|
||||
})
|
||||
```
|
||||
|
||||
## Example: File Server Service
|
||||
|
||||
Let's look at a simplified version of the `fileserver` service as an example:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
RootPath string
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
fs http.Handler
|
||||
}
|
||||
|
||||
func NewWithConfig(config *Config) *Service {
|
||||
return &Service{
|
||||
fs: http.FileServer(http.Dir(config.RootPath)),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) ServiceName() string {
|
||||
return "github.com/wailsapp/wails/v3/services/fileserver"
|
||||
}
|
||||
|
||||
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.fs.ServeHTTP(w, r)
|
||||
}
|
||||
```
|
||||
|
||||
We can now use this service in our application:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewServiceWithOptions(fileserver.New(&fileserver.Config{
|
||||
RootPath: rootPath,
|
||||
}), application.ServiceOptions{
|
||||
Route: "/files",
|
||||
}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
All requests to `/files` will be handled by the `fileserver` service.
|
||||
|
||||
## Application Lifecycle and Services
|
||||
|
||||
1. During application initialization, services are registered with the
|
||||
application.
|
||||
2. When the application starts (`app.Run()`), the `ServiceStartup` method of each
|
||||
service is called with the application context and service options.
|
||||
3. Throughout the application's lifetime, services can perform their specific
|
||||
tasks.
|
||||
4. If a service implements `ServeHTTP`, it can handle HTTP requests at the
|
||||
specified path.
|
||||
5. When the application is shutting down, the `ServiceShutdown` method of each
|
||||
service is called as well as the context being cancelled.
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
---
|
||||
title: System Tray
|
||||
description: Learn how to create and use system tray icons in Wails
|
||||
---
|
||||
|
||||
import {Badge} from "@astrojs/starlight/components";
|
||||
|
||||
## Introduction
|
||||
|
||||
The system tray (also known as the notification area) is a section of the desktop environment where applications can display icons and menus. In Wails, you can easily add a system tray icon to your application with full control over its appearance and behavior.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
To create a basic system tray icon:
|
||||
|
||||
```go
|
||||
app := application.New(options)
|
||||
systray := app.SystemTray.New()
|
||||
systray.SetLabel("My App")
|
||||
systray.SetIcon(iconBytes)
|
||||
app.Run()
|
||||
```
|
||||
|
||||
## Setting the Icon
|
||||
|
||||
The system tray icon can be set using embedded image files. First, import the `embed` package and declare your icon files:
|
||||
|
||||
```go
|
||||
import "embed"
|
||||
|
||||
//go:embed assets/icon.png assets/icon-dark.png
|
||||
var iconFS embed.FS
|
||||
```
|
||||
|
||||
Then read and set the icons:
|
||||
|
||||
```go
|
||||
// Read icon data
|
||||
iconBytes, _ := iconFS.ReadFile("assets/icon.png")
|
||||
darkModeIconBytes, _ := iconFS.ReadFile("assets/icon-dark.png")
|
||||
|
||||
// Set icons
|
||||
systray.SetIcon(iconBytes)
|
||||
systray.SetDarkModeIcon(darkModeIconBytes)
|
||||
```
|
||||
|
||||
Supported image formats include PNG and JPEG. For best results, use icons with appropriate sizes:
|
||||
- Windows: 16x16 or 32x32 pixels
|
||||
- macOS: 18x18 to 128x128 pixels
|
||||
- Linux: Varies by desktop environment
|
||||
|
||||
On macOS, you can mark the icon as a template image for automatic dark/light mode adaptation:
|
||||
|
||||
```go
|
||||
systray.SetTemplateIcon(iconBytes)
|
||||
```
|
||||
|
||||
For more details on creating template icons, read this [great article](https://bjango.com/articles/designingmenubarextras/).
|
||||
|
||||
## Setting the Tooltip <Badge text="Windows" variant="note" />
|
||||
|
||||
You can set a tooltip for your system tray icon that appears when hovering over the icon:
|
||||
|
||||
```go
|
||||
systray.SetTooltip("My Application Tooltip")
|
||||
```
|
||||
|
||||
## Setting the Label <Badge text="macOS" variant="success" />
|
||||
|
||||
You can set a text label for your system tray icon:
|
||||
|
||||
```go
|
||||
systray.SetLabel("My App")
|
||||
```
|
||||
|
||||
The label will appear next to the icon in the system tray. On some platforms, this text may be truncated if it's too long.
|
||||
|
||||
:::note
|
||||
On Windows, setting the label is the equivalent of setting the tooltip.
|
||||
:::
|
||||
|
||||
## Adding a Menu
|
||||
|
||||
You can add a menu to your system tray icon:
|
||||
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
menu.Add("Open").OnClick(func(ctx *application.Context) {
|
||||
// Handle click
|
||||
})
|
||||
menu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
systray.SetMenu(menu)
|
||||
```
|
||||
|
||||
## Attaching a Window
|
||||
|
||||
You can attach a window to a system tray icon to gain a number of desirable features:
|
||||
- The attached window will start hidden
|
||||
- Left-clicking on the system tray icon will toggle the visibility of the attached window
|
||||
- Right-clicking on the system tray icon will show the system tray menu, if given
|
||||
|
||||
Here's a complete example:
|
||||
|
||||
```go
|
||||
app := application.New()
|
||||
|
||||
// Create system tray
|
||||
systray := app.SystemTray.New()
|
||||
systray.SetLabel("My App")
|
||||
|
||||
// Create a window
|
||||
window := app.Window.New()
|
||||
|
||||
// Attach the window to the system tray
|
||||
systray.AttachWindow(window)
|
||||
|
||||
// Optional: Set window offset from tray icon
|
||||
systray.WindowOffset(10)
|
||||
|
||||
// Optional: Set debounce time for window show/hide
|
||||
systray.WindowDebounce(200 * time.Millisecond)
|
||||
|
||||
// Add a menu (optional)
|
||||
menu := app.Menu.New()
|
||||
menu.Add("Open").OnClick(func() {
|
||||
window.Show()
|
||||
})
|
||||
menu.Add("Quit").OnClick(func() {
|
||||
app.Quit()
|
||||
})
|
||||
systray.SetMenu(menu)
|
||||
|
||||
app.Run()
|
||||
```
|
||||
|
||||
## Icon Position <Badge text="macOS" variant="success" />
|
||||
|
||||
On macOS, you can control the position of the system tray icon relative to other icons:
|
||||
|
||||
```go
|
||||
systray.SetIconPosition(application.IconPositionRight)
|
||||
```
|
||||
|
||||
Available positions:
|
||||
- `NSImageNone`
|
||||
- `NSImageOnly`
|
||||
- `NSImageLeft`
|
||||
- `NSImageRight`
|
||||
- `NSImageBelow`
|
||||
- `NSImageAbove`
|
||||
- `NSImageOverlaps`
|
||||
- `NSImageLeading`
|
||||
- `NSImageTrailing`
|
||||
|
||||
## Destroying the System Tray
|
||||
|
||||
When you're done with the system tray, you should destroy it to release resources:
|
||||
|
||||
```go
|
||||
systray.Destroy()
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
- **macOS**: Icons support template images for automatic dark/light mode
|
||||
- **Windows**: Icons should be 16x16 or 32x32 pixels
|
||||
- **Linux**: Uses the StatusNotifierItem specification
|
||||
|
||||
## Examples
|
||||
|
||||
Explore these examples for more advanced usage:
|
||||
|
||||
- [Basic System Tray](/examples/systray-basic)
|
||||
- [System Tray with Menu](/examples/systray-menu)
|
||||
- [Custom System Tray](/examples/systray-custom)
|
||||
|
||||
## API Reference
|
||||
|
||||
### Core Methods
|
||||
| Method | Description |
|
||||
|------------------------------------------|--------------------------------------------|
|
||||
| `app.SystemTray.New()` | Creates a new system tray instance |
|
||||
| `Run()` | Starts the system tray |
|
||||
| `SetLabel(label string)` | Sets the text label |
|
||||
| `SetTooltip(tooltip string)` | Sets the tooltip text (Windows) |
|
||||
| `SetIcon(icon []byte)` | Sets the icon image |
|
||||
| `SetDarkModeIcon(icon []byte)` | Sets the dark mode variant of the icon |
|
||||
| `SetTemplateIcon(icon []byte)` | Marks the icon as a template image (macOS) |
|
||||
| `SetIconPosition(position IconPosition)` | Sets the icon position (macOS) |
|
||||
| `Destroy()` | Destroys the system tray |
|
||||
|
||||
### Menu Management
|
||||
| Method | Description |
|
||||
|-----------------------|----------------------------------|
|
||||
| `SetMenu(menu *Menu)` | Attaches a menu to the tray icon |
|
||||
| `OpenMenu()` | Programmatically opens the menu |
|
||||
|
||||
### Event Handlers
|
||||
| Method | Description |
|
||||
|--------------------------------------|-----------------------------------|
|
||||
| `OnClick(handler func())` | Handles left-click events |
|
||||
| `OnRightClick(handler func())` | Handles right-click events |
|
||||
| `OnDoubleClick(handler func())` | Handles left-double-click events |
|
||||
| `OnRightDoubleClick(handler func())` | Handles right-double-click events |
|
||||
| `OnMouseEnter(handler func())` | Handles mouse enter events |
|
||||
| `OnMouseLeave(handler func())` | Handles mouse leave events |
|
||||
|
||||
### Window Attachment
|
||||
|
||||
| Method | Description |
|
||||
|------------------------------------------|---------------------------------------------|
|
||||
| `AttachWindow(window *WebviewWindow)` | Associates a window with the tray icon |
|
||||
| `WindowOffset(offset int)` | Sets the offset between the tray and window |
|
||||
| `WindowDebounce(debounce time.Duration)` | Sets the debounce time for window show/hide |
|
||||
|
||||
### Visibility Control
|
||||
|
||||
| Method | Description |
|
||||
|----------|-----------------------------|
|
||||
| `Show()` | Makes the tray icon visible |
|
||||
| `Hide()` | Hides the tray icon |
|
||||
|
|
@ -1,444 +0,0 @@
|
|||
---
|
||||
title: Windows
|
||||
sidebar:
|
||||
order: 52
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides a comprehensive window management system through the WindowManager API. This allows you to create, manage, and control multiple windows in your application.
|
||||
|
||||
## Accessing the Window Manager
|
||||
|
||||
The window manager is accessed through the `Windows` property on your application instance:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "Multi-Window App",
|
||||
})
|
||||
|
||||
// Access the window manager
|
||||
windowManager := app.Window
|
||||
```
|
||||
|
||||
## Creating Windows
|
||||
|
||||
### Basic Window Creation
|
||||
|
||||
Create a new window with default settings:
|
||||
|
||||
```go
|
||||
window := app.Window.New()
|
||||
```
|
||||
|
||||
### Window Creation with Options
|
||||
|
||||
Create a window with custom configuration:
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Custom Window",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
Mac: application.MacOptions{
|
||||
TitleBarAppearsTransparent: true,
|
||||
},
|
||||
Windows: application.WindowsOptions{
|
||||
DisableWindowIcon: true,
|
||||
},
|
||||
Linux: application.LinuxOptions{
|
||||
Icon: []byte{/* icon data */},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Finding Windows
|
||||
|
||||
### Get Window by Name
|
||||
|
||||
Retrieve a window using its name:
|
||||
|
||||
```go
|
||||
window, exists := app.Window.GetByName("main-window")
|
||||
if exists {
|
||||
// Work with the window
|
||||
window.SetTitle("Found Window")
|
||||
}
|
||||
```
|
||||
|
||||
### Get Window by ID
|
||||
|
||||
Retrieve a window using its unique ID:
|
||||
|
||||
```go
|
||||
window, exists := app.Window.GetByID(123)
|
||||
if exists {
|
||||
// Work with the window
|
||||
}
|
||||
```
|
||||
|
||||
### Get Current Window
|
||||
|
||||
Get the currently active/focused window:
|
||||
|
||||
```go
|
||||
// Get current window (may be nil)
|
||||
currentWindow := app.Window.Current()
|
||||
if currentWindow != nil {
|
||||
currentWindow.SetTitle("Active Window")
|
||||
}
|
||||
|
||||
// Get current window with existence check
|
||||
currentWindow, exists := app.Window.GetCurrent()
|
||||
if exists {
|
||||
currentWindow.SetTitle("Active Window")
|
||||
}
|
||||
```
|
||||
|
||||
### Get All Windows
|
||||
|
||||
Retrieve all windows managed by the application:
|
||||
|
||||
```go
|
||||
allWindows := app.Window.GetAll()
|
||||
for i, window := range allWindows {
|
||||
window.SetTitle(fmt.Sprintf("Window %d", i+1))
|
||||
}
|
||||
```
|
||||
|
||||
## Window Lifecycle Management
|
||||
|
||||
### Window Creation Callbacks
|
||||
|
||||
Register callbacks to be notified when new windows are created:
|
||||
|
||||
```go
|
||||
app.Window.OnCreate(func(window application.Window) {
|
||||
app.Logger.Info("New window created", "name", window.Name())
|
||||
|
||||
// Configure the window
|
||||
if webviewWindow, ok := window.(*application.WebviewWindow); ok {
|
||||
webviewWindow.SetMinSize(400, 300)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Removing Windows
|
||||
|
||||
Remove windows from the manager:
|
||||
|
||||
```go
|
||||
// Remove by ID
|
||||
app.Window.Remove(windowID)
|
||||
|
||||
// Remove by name
|
||||
removed := app.Window.RemoveByName("window-name")
|
||||
if removed {
|
||||
app.Logger.Info("Window removed successfully")
|
||||
}
|
||||
```
|
||||
|
||||
## Window Types
|
||||
|
||||
### WebView Windows
|
||||
|
||||
The primary window type in Wails v3 is the WebView window, which embeds a web browser:
|
||||
|
||||
```go
|
||||
window := app.Window.New()
|
||||
|
||||
// WebView-specific operations
|
||||
window.SetURL("https://example.com")
|
||||
window.SetHTML("<h1>Hello World</h1>")
|
||||
window.Reload()
|
||||
```
|
||||
|
||||
### Window Interface
|
||||
|
||||
All windows implement the `Window` interface, providing common functionality:
|
||||
|
||||
```go
|
||||
type Window interface {
|
||||
ID() uint
|
||||
Name() string
|
||||
SetTitle(title string)
|
||||
SetSize(width, height int)
|
||||
SetPosition(x, y int)
|
||||
Show()
|
||||
Hide()
|
||||
Close()
|
||||
// ... other methods
|
||||
}
|
||||
```
|
||||
|
||||
### Common Window Methods
|
||||
|
||||
Windows provide many methods for controlling their appearance and behavior:
|
||||
|
||||
#### Display Control
|
||||
- `Show()` - Shows the window
|
||||
- `Hide()` - Hides the window
|
||||
- `SetTitle(title string)` - Sets the window title
|
||||
- `SetSize(width, height int)` - Sets the window size
|
||||
- `SetPosition(x, y int)` - Sets the window position
|
||||
- `Center()` - Centers the window on screen
|
||||
- `SetAlwaysOnTop(bool)` - Sets whether window stays on top
|
||||
|
||||
#### Window State
|
||||
- `Minimise()` - Minimizes the window
|
||||
- `Maximise()` - Maximizes the window
|
||||
- `UnMinimise()` - Restores from minimized state
|
||||
- `UnMaximise()` - Restores from maximized state
|
||||
- `Fullscreen()` - Enters fullscreen mode
|
||||
- `UnFullscreen()` - Exits fullscreen mode
|
||||
- `Restore()` - Restores window to normal state
|
||||
- `IsMinimised() bool` - Checks if window is minimized
|
||||
- `IsMaximised() bool` - Checks if window is maximized
|
||||
- `IsFullscreen() bool` - Checks if window is fullscreen
|
||||
|
||||
#### Security and Privacy
|
||||
- `SetContentProtection(bool)` - Enables/disables content protection to prevent screen capture
|
||||
|
||||
#### Platform-Specific Methods
|
||||
|
||||
**Windows Only:**
|
||||
- `Flash(bool)` - Flashes the taskbar button
|
||||
- `SnapAssist()` - Triggers Windows 11 Snap Assist (Win+Z)
|
||||
|
||||
## Content Protection
|
||||
|
||||
Content Protection prevents window contents from being captured by screen recording or screen sharing software. This is useful for applications that display sensitive information.
|
||||
|
||||
### Enabling Content Protection
|
||||
|
||||
You can enable content protection when creating a window:
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Secure Window",
|
||||
ContentProtectionEnabled: true,
|
||||
})
|
||||
```
|
||||
|
||||
Or enable/disable it dynamically at runtime:
|
||||
|
||||
```go
|
||||
// Enable content protection
|
||||
window.SetContentProtection(true)
|
||||
|
||||
// Disable content protection
|
||||
window.SetContentProtection(false)
|
||||
```
|
||||
|
||||
### Platform Support
|
||||
|
||||
- **Windows**: Uses `WDA_EXCLUDEFROMCAPTURE` on Windows 10 version 2004+ (build 19041+). Falls back to `WDA_MONITOR` on older versions (legacy mode providing partial protection that may not block all capture paths).
|
||||
- **macOS**: Sets `NSWindow.sharingType` to `NSWindowSharingNone` when enabled (prevents capture) and `NSWindowSharingReadOnly` when disabled (allows capture).
|
||||
- **Linux**: Currently not implemented (no-op).
|
||||
|
||||
### Use Cases
|
||||
|
||||
Content protection is ideal for:
|
||||
- Banking and financial applications
|
||||
- Password managers
|
||||
- Medical record systems
|
||||
- Document viewers with confidential information
|
||||
- Communication apps with private messages
|
||||
|
||||
### Important Notes
|
||||
|
||||
1. Content protection does not prevent physical photography of the screen
|
||||
2. On Windows, the fallback for older versions provides partial protection
|
||||
3. Some screen capture tools may still be able to capture protected content using low-level APIs
|
||||
4. This feature should be part of a comprehensive security strategy, not the only protection
|
||||
5. Detached DevTools or inspector windows are separate instances and will not inherit content protection automatically
|
||||
|
||||
|
||||
## Advanced Window Management
|
||||
|
||||
### Multi-Window Applications
|
||||
|
||||
Create and manage multiple windows for complex applications:
|
||||
|
||||
```go
|
||||
// Create main window
|
||||
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Main Application",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
})
|
||||
|
||||
// Create settings window
|
||||
settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Settings",
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
WindowState: application.WindowStateHidden, // Start hidden
|
||||
})
|
||||
|
||||
// Show settings when needed
|
||||
settingsWindow.Show()
|
||||
```
|
||||
|
||||
### Window Communication
|
||||
|
||||
Windows can communicate through events:
|
||||
|
||||
```go
|
||||
// In window A
|
||||
app.Event.Emit("data-updated", newData)
|
||||
|
||||
// In window B
|
||||
app.Event.On("data-updated", func(event *application.CustomEvent) {
|
||||
// Update UI with new data
|
||||
data := event.Data
|
||||
// Process data...
|
||||
})
|
||||
```
|
||||
|
||||
## Platform-Specific Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS, windows:
|
||||
|
||||
- Support native title bar customization
|
||||
- Can be configured with transparent title bars
|
||||
- Follow macOS window management conventions
|
||||
- Support native fullscreen mode
|
||||
- Integrate with Mission Control and Spaces
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows, windows:
|
||||
|
||||
- Support custom window icons
|
||||
- Can be configured without system title bars
|
||||
- Follow Windows window management conventions
|
||||
- Support Windows 11 snap layouts via `SnapAssist()` method
|
||||
- Integrate with Windows taskbar features
|
||||
|
||||
**Windows 11 Snap Assist Example:**
|
||||
```go
|
||||
// Trigger Windows 11 Snap Assist
|
||||
window.SnapAssist()
|
||||
|
||||
// This is equivalent to pressing Win+Z
|
||||
// Shows snap layout options for the current window
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux, windows:
|
||||
|
||||
- Support custom icons
|
||||
- Follow desktop environment conventions
|
||||
- Work with various window managers
|
||||
- Support X11 and Wayland protocols
|
||||
- Integrate with desktop environment features
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Window Naming**: Always provide meaningful names for windows to make them easy to find:
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings-window",
|
||||
Title: "Application Settings",
|
||||
})
|
||||
```
|
||||
|
||||
2. **Resource Management**: Properly clean up windows when they're no longer needed:
|
||||
```go
|
||||
window.OnEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
app.Window.Remove(window.ID())
|
||||
})
|
||||
```
|
||||
|
||||
3. **Window State**: Consider starting secondary windows as hidden and showing them when needed:
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
WindowState: application.WindowStateHidden,
|
||||
})
|
||||
```
|
||||
|
||||
4. **Error Handling**: Always check if windows exist before using them:
|
||||
```go
|
||||
if window, exists := app.Window.GetByName("main"); exists {
|
||||
window.SetTitle("New Title")
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example of a multi-window application:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Multi-Window Demo",
|
||||
})
|
||||
|
||||
// Create main window
|
||||
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "main",
|
||||
Title: "Main Window",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
})
|
||||
|
||||
// Create hidden settings window
|
||||
settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings",
|
||||
Title: "Settings",
|
||||
Width: 400,
|
||||
Height: 300,
|
||||
WindowState: application.WindowStateHidden,
|
||||
})
|
||||
|
||||
// Setup window creation callback
|
||||
app.Window.OnCreate(func(window application.Window) {
|
||||
app.Logger.Info("Window created", "name", window.Name())
|
||||
})
|
||||
|
||||
// Setup menu to show settings
|
||||
menu := app.Menu.New()
|
||||
menu.AddRole(application.AppMenu)
|
||||
|
||||
settingsMenu := menu.AddSubmenu("Settings")
|
||||
settingsMenu.Add("Open Settings").OnClick(func(ctx *application.Context) {
|
||||
if window, exists := app.Window.GetByName("settings"); exists {
|
||||
window.Show()
|
||||
window.Focus()
|
||||
}
|
||||
})
|
||||
|
||||
app.Menu.Set(menu)
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Use the window manager's callback system to implement application-wide window policies, such as automatically setting minimum sizes or configuring window behaviors.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Always check if windows exist before performing operations on them, as they may have been closed or removed by the user or system.
|
||||
:::
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
title: Runtime
|
||||
title: Runtime Overview
|
||||
description: The Wails JavaScript runtime package for frontend integration
|
||||
sidebar:
|
||||
order: 30
|
||||
order: 1
|
||||
---
|
||||
|
||||
The Wails runtime is the standard library for Wails applications. It provides a
|
||||