Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions docs/src/content/docs/features/windows/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -745,12 +745,12 @@ To perform cleanup when a window closes, use `OnWindowEvent` with the `WindowClo
window.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) {
// Cleanup code runs here
fmt.Printf("Window %s is closing\n", window.Name())

// Close database connection
if db != nil {
db.Close()
}

// Remove from window list
removeWindow(window.ID())
})
Expand All @@ -777,13 +777,13 @@ func ShowSettings(app *application.App) {
Width: 600,
Height: 400,
})

// Cleanup on close
settingsWindow.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) {
settingsWindow = nil
})
}

// Show and focus
settingsWindow.Show()
settingsWindow.Focus()
Expand All @@ -806,6 +806,7 @@ Mac: application.MacWindow{
InvisibleTitleBarHeight: 50,
WindowLevel: application.MacWindowLevelNormal,
CollectionBehavior: application.MacWindowCollectionBehaviorDefault,
TabbingMode: application.MacWindowTabbingModeDisallowed,
},
```

Expand Down Expand Up @@ -878,6 +879,32 @@ Mac: application.MacWindow{
},
```

**TabbingMode** (`MacWindowTabbingMode`)

Controls window tabbing behavior on macOS 10.12 and later. Window tabbing allows multiple windows to be grouped as tabs.

**Options:**
- `MacWindowTabbingModeDefault` - Zero-value sentinel (not explicitly set). At runtime, defaults to disallowing tabbing
- `MacWindowTabbingModeAutomatic` - System determines tabbing behavior
- `MacWindowTabbingModePreferred` - Window prefers to be in tabbing mode
- `MacWindowTabbingModeDisallowed` - Disables window tabbing

**Example - Disable window tabbing:**

```go
Mac: application.MacWindow{
TabbingMode: application.MacWindowTabbingModeDisallowed,
},
```

**Example - Prefer window tabbing:**

```go
Mac: application.MacWindow{
TabbingMode: application.MacWindowTabbingModePreferred,
},
```

**WebviewPreferences** (`MacWebviewPreferences`)

Fine-grained control over the underlying `WKWebView` configuration. All fields are optional — unset fields leave the WebKit default unchanged.
Expand Down
6 changes: 6 additions & 0 deletions v3/examples/mac-window-tabs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.task
bin
frontend/dist
frontend/node_modules
build/linux/appimage/build
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
39 changes: 39 additions & 0 deletions v3/examples/mac-window-tabs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# mac-window-tabs

This example showcases macOS window tabbing using `MacWindowTabbingMode`.

Window tabbing is a macOS-only feature (NSWindow tabbing, 10.12+), so this
example is macOS only.

## Running

```bash
task dev
```

This uses the `wails3` CLI (via the Taskfile) to generate bindings, build the
frontend, and run the app with live reload. `task run` builds and runs a
non-dev binary instead.

> The `go.mod` includes a `replace` directive pointing at the local Wails
> module, because `MacWindowTabbingMode` is not yet in a published release.
> `go run .` on its own will not work: it skips binding generation and the
> frontend build.

## What to Expect

A single window opens on launch. It uses `MacWindowTabbingModePreferred`, so it
is willing to accept new tabs. Two buttons drive the demo:

- **Open tabbed window** opens a window with `MacWindowTabbingModePreferred`. On
macOS 10.12+ it merges into the current window as a new tab.
- **Open non-tabbed window** opens a window with `MacWindowTabbingModeDisallowed`.
It always opens as a separate window and never tabs, even via Window > Merge
All Windows.

Open a mix of both to see the difference: tabbed windows stack into one titled
tab bar, while non-tabbed windows stay independent.

## Relevant Code

See the macOS window options in [main.go](main.go).
69 changes: 69 additions & 0 deletions v3/examples/mac-window-tabs/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
version: '3'

includes:
common: ./build/Taskfile.yml
windows: ./build/windows/Taskfile.yml
darwin: ./build/darwin/Taskfile.yml
linux: ./build/linux/Taskfile.yml

# This example is a standalone Go module (its own go.mod, with a `replace`
# pointing at the local Wails checkout because MacWindowTabbingMode is not in a
# published release yet). It physically lives under v3/, which is a member of
# the repo's root go.work. An active workspace would shadow this module: binding
# generation would find 0 services and `go mod tidy` would target v3 instead.
# Force workspace mode off so the example always builds as itself. This is
# inherited by the wails3 dev/build subprocess tree and is a harmless no-op
# outside the monorepo, where there is no go.work.
env:
GOWORK: "off"

vars:
APP_NAME: "mac-window-tabs"
BIN_DIR: "bin"
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'

tasks:
build:
summary: Builds the application
cmds:
- task: "{{OS}}:build"

package:
summary: Packages a production build of the application
cmds:
- task: "{{OS}}:package"

run:
summary: Runs the application
cmds:
- task: "{{OS}}:run"

dev:
summary: Runs the application in development mode
cmds:
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}

setup:docker:
summary: Builds Docker image for cross-compilation (~800MB download)
cmds:
- task: common:setup:docker

build:server:
summary: Builds the application in server mode (no GUI, HTTP server only)
cmds:
- task: common:build:server

run:server:
summary: Runs the application in server mode
cmds:
- task: common:run:server

build:docker:
summary: Builds a Docker image for server mode deployment
cmds:
- task: common:build:docker

run:docker:
summary: Builds and runs the Docker image
cmds:
- task: common:run:docker
203 changes: 203 additions & 0 deletions v3/examples/mac-window-tabs/build/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
version: '3'

# See the note in ../Taskfile.yml: this example is a standalone module nested
# under v3/, so an active go.work would shadow it. Force workspace mode off here
# too, so the Go-tooling tasks (go mod tidy, generate bindings, go build) behave
# correctly even when invoked directly rather than via the root dev/build tasks.
env:
GOWORK: "off"

tasks:
go:mod:tidy:
summary: Runs `go mod tidy`
internal: true
cmds:
- go mod tidy

install:frontend:deps:
summary: Install frontend dependencies
dir: frontend
sources:
- package.json
- package-lock.json
generates:
- node_modules
preconditions:
- sh: npm version
msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/"
cmds:
- npm install

build:frontend:
label: build:frontend (DEV={{.DEV}})
summary: Build the frontend project
dir: frontend
sources:
- "**/*"
generates:
- dist/**/*
deps:
- task: install:frontend:deps
- task: generate:bindings
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
cmds:
- npm run {{.BUILD_COMMAND}} -q
env:
PRODUCTION: '{{if eq .DEV "true"}}false{{else}}true{{end}}'
vars:
BUILD_COMMAND: '{{if eq .DEV "true"}}build:dev{{else}}build{{end}}'


frontend:vendor:puppertino:
summary: Fetches Puppertino CSS into frontend/public for consistent mobile styling
sources:
- frontend/public/puppertino/puppertino.css
generates:
- frontend/public/puppertino/puppertino.css
cmds:
- |
set -euo pipefail
mkdir -p frontend/public/puppertino
# If bundled Puppertino exists, prefer it. Otherwise, try to fetch, but don't fail build on error.
if [ ! -f frontend/public/puppertino/puppertino.css ]; then
echo "No bundled Puppertino found. Attempting to fetch from GitHub..."
if curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/dist/css/full.css -o frontend/public/puppertino/puppertino.css; then
curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/LICENSE -o frontend/public/puppertino/LICENSE || true
echo "Puppertino CSS downloaded to frontend/public/puppertino/puppertino.css"
else
echo "Warning: Could not fetch Puppertino CSS. Proceeding without download since template may bundle it."
fi
else
echo "Using bundled Puppertino at frontend/public/puppertino/puppertino.css"
fi
# Ensure index.html includes Puppertino CSS and button classes
INDEX_HTML=frontend/index.html
if [ -f "$INDEX_HTML" ]; then
if ! grep -q 'href="/puppertino/puppertino.css"' "$INDEX_HTML"; then
# Insert Puppertino link tag after style.css link
awk '
/href="\/style.css"\/?/ && !x { print; print " <link rel=\"stylesheet\" href=\"/puppertino/puppertino.css\"/>"; x=1; next }1
' "$INDEX_HTML" > "$INDEX_HTML.tmp" && mv "$INDEX_HTML.tmp" "$INDEX_HTML"
fi
# Replace default .btn with Puppertino primary button classes if present
sed -E -i'' 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" || true
fi


generate:bindings:
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
summary: Generates bindings for the frontend
deps:
- task: go:mod:tidy
sources:
- "**/*.[jt]s"
- exclude: frontend/**/*
- frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output
- "**/*.go"
- go.mod
- go.sum
generates:
- frontend/bindings/**/*
cmds:
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true

generate:icons:
summary: Generates Windows `.ico` and Mac `.icns` from an image; on macOS, `-iconcomposerinput appicon.icon -macassetdir darwin` also produces `Assets.car` from a `.icon` file (skipped on other platforms).
dir: build
sources:
- "appicon.png"
- "appicon.icon"
generates:
- "darwin/icons.icns"
- "windows/icon.ico"
cmds:
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico -iconcomposerinput appicon.icon -macassetdir darwin

dev:frontend:
summary: Runs the frontend in development mode
dir: frontend
deps:
- task: install:frontend:deps
cmds:
# --host 127.0.0.1 forces vite to bind IPv4. Recent Node resolves the
# localhost hostname to ::1 first, but the wails3 dev proxy dials tcp4
# (to dodge IPv6 issues on Windows), so an IPv6-only vite causes
# "connection refused" in the WebView.
- npm run dev -- --port {{.VITE_PORT}} --strictPort --host 127.0.0.1

update:build-assets:
summary: Updates the build assets
dir: build
cmds:
- wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir .

build:server:
summary: Builds the application in server mode (no GUI, HTTP server only)
desc: |
Builds the application with the server build tag enabled.
Server mode runs as a pure HTTP server without native GUI dependencies.
Usage: task build:server
deps:
- task: build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
cmds:
- go build -tags server {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}}
vars:
BUILD_FLAGS: "{{.BUILD_FLAGS}}"

run:server:
summary: Builds and runs the application in server mode
deps:
- task: build:server
cmds:
- ./{{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}}

build:docker:
summary: Builds a Docker image for server mode deployment
desc: |
Creates a minimal Docker image containing the server mode binary.
The image is based on distroless for security and small size.
Usage: task build:docker [TAG=myapp:latest]
cmds:
- docker build -t {{.TAG | default (printf "%s:latest" .APP_NAME)}} -f build/docker/Dockerfile.server .
vars:
TAG: "{{.TAG}}"
preconditions:
- sh: docker info > /dev/null 2>&1
msg: "Docker is required. Please install Docker first."
- sh: test -f build/docker/Dockerfile.server
msg: "Dockerfile.server not found. Run 'wails3 update build-assets' to generate it."

run:docker:
summary: Builds and runs the Docker image
desc: |
Builds the Docker image and runs it, exposing port 8080.
Usage: task run:docker [TAG=myapp:latest] [PORT=8080]
Note: The internal container port is always 8080. The PORT variable
only changes the host port mapping. Ensure your app uses port 8080
or modify the Dockerfile to match your ServerOptions.Port setting.
deps:
- task: build:docker
vars:
TAG:
ref: .TAG
cmds:
- docker run --rm -p {{.PORT | default "8080"}}:8080 {{.TAG | default (printf "%s:latest" .APP_NAME)}}
vars:
TAG: "{{.TAG}}"
PORT: "{{.PORT}}"

setup:docker:
summary: Builds Docker image for cross-compilation (~800MB download)
desc: |
Builds the Docker image needed for cross-compiling to any platform.
Run this once to enable cross-platform builds from any OS.
cmds:
- docker build -t wails-cross -f build/docker/Dockerfile.cross build/docker/
preconditions:
- sh: docker info > /dev/null 2>&1
msg: "Docker is required. Please install Docker first."
Loading
Loading