Skip to content

Commit bf9e17a

Browse files
authored
[WEP] Customise Window Titlebars (#3508)
* Add proposal. Reference Mac implementation * Add windows support. Update proposal. * Update example * Rename Active->Enable,Inactive->Disabled. Ensure window can get controls back after hiding close on windows. Added guide. Updated example. * Add ExStyle option for setting titlebar style. * Fix linux builds * Tidy up
1 parent 2baca5d commit bf9e17a

File tree

12 files changed

+518
-90
lines changed

12 files changed

+518
-90
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Customising Window Controls in Wails
2+
3+
Wails provides an API to control the appearance and functionality of the controls of a window.
4+
This functionality is available on Windows and macOS, but not on Linux.
5+
6+
## Setting the Window Button States
7+
8+
The button states are defined by the `ButtonState` enum:
9+
10+
```go
11+
type ButtonState int
12+
13+
const (
14+
ButtonEnabled ButtonState = 0
15+
ButtonDisabled ButtonState = 1
16+
ButtonHidden ButtonState = 2
17+
)
18+
```
19+
20+
- `ButtonEnabled`: The button is enabled and visible.
21+
- `ButtonDisabled`: The button is visible but disabled (grayed out).
22+
- `ButtonHidden`: The button is hidden from the titlebar.
23+
24+
The button states can be set during window creation or at runtime.
25+
26+
### Setting Button States During Window Creation
27+
28+
When creating a new window, you can set the initial state of the buttons using the `WebviewWindowOptions` struct:
29+
30+
```go
31+
package main
32+
33+
import (
34+
"github.com/wailsapp/wails/v3/pkg/application"
35+
)
36+
37+
func main() {
38+
app := application.New(application.Options{
39+
Name: "My Application",
40+
})
41+
42+
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
43+
MinimiseButtonState: application.ButtonHidden,
44+
MaximiseButtonState: application.ButtonDisabled,
45+
CloseButtonState: application.ButtonEnabled,
46+
})
47+
48+
app.Run()
49+
}
50+
```
51+
52+
In the example above, the minimise button is hidden, the maximise button is inactive (grayed out), and the close button is active.
53+
54+
### Setting Button States at Runtime
55+
56+
You can also change the button states at runtime using the following methods on the `Window` interface:
57+
58+
```go
59+
window.SetMinimiseButtonState(wails.ButtonHidden)
60+
window.SetMaximiseButtonState(wails.ButtonEnabled)
61+
window.SetCloseButtonState(wails.ButtonDisabled)
62+
```
63+
64+
### Platform Differences
65+
66+
The button state functionality behaves slightly differently on Windows and macOS:
67+
68+
| | Windows | Mac |
69+
|-----------------------|------------------------|------------------------|
70+
| Disable Min/Max/Close | Disables Min/Max/Close | Disables Min/Max/Close |
71+
| Hide Min | Disables Min | Hides Min button |
72+
| Hide Max | Disables Max | Hides Max button |
73+
| Hide Close | Hides all controls | Hides Close |
74+
75+
Note: On Windows, it is not possible to hide the Min/Max buttons individually.
76+
However, disabling both will hide both of the controls and only show the
77+
close button.
78+
79+
### Controlling Window Style (Windows)
80+
81+
To control the style of the titlebar on Windows, you can use the `ExStyle` field in the `WebviewWindowOptions` struct:
82+
83+
Example:
84+
```go
85+
package main
86+
87+
import (
88+
"github.com/wailsapp/wails/v3/pkg/application"
89+
"github.com/wailsapp/wails/v3/pkg/w32"
90+
)
91+
92+
func main() {
93+
app := application.New(application.Options{
94+
Name: "My Application",
95+
})
96+
97+
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
98+
Windows: application.WindowsWindow{
99+
ExStyle: w32.WS_EX_TOOLWINDOW | w32.WS_EX_NOREDIRECTIONBITMAP | w32.WS_EX_TOPMOST,
100+
},
101+
})
102+
103+
app.Run()
104+
}
105+
```
106+
107+
Other options that affect the Extended Style of a window will be overridden by this setting:
108+
- HiddenOnTaskbar
109+
- AlwaysOnTop
110+
- IgnoreMouseEvents
111+
- BackgroundType

mkdocs-website/mkdocs.yml

+2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ nav:
142142
- Learn More:
143143
- Runtime: learn/runtime.md
144144
- Plugins: learn/plugins.md
145+
- Guides:
146+
- Customising Windows: learn/guides/customising-windows.md
145147
- Feedback: getting-started/feedback.md
146148
- Feedback: getting-started/feedback.md
147149
- What's New in v3?: whats-new.md

v3/examples/window/main.go

+100-28
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
_ "embed"
55
"fmt"
6+
"github.com/wailsapp/wails/v3/pkg/w32"
67
"log"
78
"math/rand"
89
"runtime"
@@ -64,12 +65,7 @@ func main() {
6465
myMenu.Add("New WebviewWindow (Disable Minimise)").
6566
OnClick(func(ctx *application.Context) {
6667
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
67-
Windows: application.WindowsWindow{
68-
DisableMinimiseButton: true,
69-
},
70-
Mac: application.MacWindow{
71-
DisableMinimiseButton: true,
72-
},
68+
MinimiseButtonState: application.ButtonDisabled,
7369
}).
7470
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
7571
SetRelativePosition(rand.Intn(1000), rand.Intn(800)).
@@ -80,12 +76,29 @@ func main() {
8076
myMenu.Add("New WebviewWindow (Disable Maximise)").
8177
OnClick(func(ctx *application.Context) {
8278
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
83-
Windows: application.WindowsWindow{
84-
DisableMaximiseButton: true,
85-
},
86-
Mac: application.MacWindow{
87-
DisableMaximiseButton: true,
88-
},
79+
MaximiseButtonState: application.ButtonDisabled,
80+
}).
81+
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
82+
SetRelativePosition(rand.Intn(1000), rand.Intn(800)).
83+
SetURL("https://wails.io").
84+
Show()
85+
windowCounter++
86+
})
87+
myMenu.Add("New WebviewWindow (Hide Minimise)").
88+
OnClick(func(ctx *application.Context) {
89+
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
90+
MinimiseButtonState: application.ButtonHidden,
91+
}).
92+
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
93+
SetRelativePosition(rand.Intn(1000), rand.Intn(800)).
94+
SetURL("https://wails.io").
95+
Show()
96+
windowCounter++
97+
})
98+
myMenu.Add("New WebviewWindow (Hide Maximise)").
99+
OnClick(func(ctx *application.Context) {
100+
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
101+
MaximiseButtonState: application.ButtonHidden,
89102
}).
90103
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
91104
SetRelativePosition(rand.Intn(1000), rand.Intn(800)).
@@ -94,13 +107,22 @@ func main() {
94107
windowCounter++
95108
})
96109
}
97-
if runtime.GOOS == "darwin" {
110+
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
98111
myMenu.Add("New WebviewWindow (Disable Close)").
99112
OnClick(func(ctx *application.Context) {
100113
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
101-
Mac: application.MacWindow{
102-
DisableCloseButton: true,
103-
},
114+
CloseButtonState: application.ButtonDisabled,
115+
}).
116+
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
117+
SetRelativePosition(rand.Intn(1000), rand.Intn(800)).
118+
SetURL("https://wails.io").
119+
Show()
120+
windowCounter++
121+
})
122+
myMenu.Add("New WebviewWindow (Hide Close)").
123+
OnClick(func(ctx *application.Context) {
124+
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
125+
CloseButtonState: application.ButtonHidden,
104126
}).
105127
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
106128
SetRelativePosition(rand.Intn(1000), rand.Intn(800)).
@@ -110,6 +132,22 @@ func main() {
110132
})
111133

112134
}
135+
if runtime.GOOS == "windows" {
136+
myMenu.Add("New WebviewWindow (Custom ExStyle)").
137+
OnClick(func(ctx *application.Context) {
138+
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
139+
Windows: application.WindowsWindow{
140+
DisableMenu: true,
141+
ExStyle: w32.WS_EX_TOOLWINDOW | w32.WS_EX_NOREDIRECTIONBITMAP | w32.WS_EX_TOPMOST,
142+
},
143+
}).
144+
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
145+
SetRelativePosition(rand.Intn(1000), rand.Intn(800)).
146+
SetURL("https://wails.io").
147+
Show()
148+
windowCounter++
149+
})
150+
}
113151

114152
myMenu.Add("New WebviewWindow (Hides on Close one time)").
115153
SetAccelerator("CmdOrCtrl+H").
@@ -278,12 +316,6 @@ func main() {
278316
w.SetMinSize(200, 200)
279317
})
280318
})
281-
sizeMenu.Add("Set Max Size (600,600)").OnClick(func(ctx *application.Context) {
282-
currentWindow(func(w *application.WebviewWindow) {
283-
w.SetFullscreenButtonEnabled(false)
284-
w.SetMaxSize(600, 600)
285-
})
286-
})
287319
sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) {
288320
currentWindow(func(w *application.WebviewWindow) {
289321
width, height := w.Size()
@@ -297,12 +329,6 @@ func main() {
297329
})
298330
})
299331

300-
sizeMenu.Add("Reset Max Size").OnClick(func(ctx *application.Context) {
301-
currentWindow(func(w *application.WebviewWindow) {
302-
w.SetMaxSize(0, 0)
303-
w.SetFullscreenButtonEnabled(true)
304-
})
305-
})
306332
positionMenu := menu.AddSubmenu("Position")
307333
positionMenu.Add("Set Relative Position (0,0)").OnClick(func(ctx *application.Context) {
308334
currentWindow(func(w *application.WebviewWindow) {
@@ -346,6 +372,52 @@ func main() {
346372
w.Center()
347373
})
348374
})
375+
titleBarMenu := menu.AddSubmenu("Controls")
376+
titleBarMenu.Add("Disable Minimise").OnClick(func(ctx *application.Context) {
377+
currentWindow(func(w *application.WebviewWindow) {
378+
w.SetMinimiseButtonState(application.ButtonDisabled)
379+
})
380+
})
381+
titleBarMenu.Add("Enable Minimise").OnClick(func(ctx *application.Context) {
382+
currentWindow(func(w *application.WebviewWindow) {
383+
w.SetMinimiseButtonState(application.ButtonEnabled)
384+
})
385+
})
386+
titleBarMenu.Add("Hide Minimise").OnClick(func(ctx *application.Context) {
387+
currentWindow(func(w *application.WebviewWindow) {
388+
w.SetMinimiseButtonState(application.ButtonHidden)
389+
})
390+
})
391+
titleBarMenu.Add("Disable Maximise").OnClick(func(ctx *application.Context) {
392+
currentWindow(func(w *application.WebviewWindow) {
393+
w.SetMaximiseButtonState(application.ButtonDisabled)
394+
})
395+
})
396+
titleBarMenu.Add("Enable Maximise").OnClick(func(ctx *application.Context) {
397+
currentWindow(func(w *application.WebviewWindow) {
398+
w.SetMaximiseButtonState(application.ButtonEnabled)
399+
})
400+
})
401+
titleBarMenu.Add("Hide Maximise").OnClick(func(ctx *application.Context) {
402+
currentWindow(func(w *application.WebviewWindow) {
403+
w.SetMaximiseButtonState(application.ButtonHidden)
404+
})
405+
})
406+
titleBarMenu.Add("Disable Close").OnClick(func(ctx *application.Context) {
407+
currentWindow(func(w *application.WebviewWindow) {
408+
w.SetCloseButtonState(application.ButtonDisabled)
409+
})
410+
})
411+
titleBarMenu.Add("Enable Close").OnClick(func(ctx *application.Context) {
412+
currentWindow(func(w *application.WebviewWindow) {
413+
w.SetCloseButtonState(application.ButtonEnabled)
414+
})
415+
})
416+
titleBarMenu.Add("Hide Close").OnClick(func(ctx *application.Context) {
417+
currentWindow(func(w *application.WebviewWindow) {
418+
w.SetCloseButtonState(application.ButtonHidden)
419+
})
420+
})
349421
stateMenu := menu.AddSubmenu("State")
350422
stateMenu.Add("Minimise (for 2 secs)").OnClick(func(ctx *application.Context) {
351423
currentWindow(func(w *application.WebviewWindow) {

v3/pkg/application/messageprocessor_window.go

-8
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,6 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
249249
}
250250
window.SetFrameless(*frameless)
251251
m.ok(rw)
252-
case WindowSetFullscreenButtonEnabled:
253-
enabled := args.Bool("enabled")
254-
if enabled == nil {
255-
m.Error("Invalid SetFullscreenButtonEnabled Message: 'enabled' value required")
256-
return
257-
}
258-
window.SetFullscreenButtonEnabled(*enabled)
259-
m.ok(rw)
260252
case WindowSetMaxSize:
261253
width := args.Int("width")
262254
if width == nil {

v3/pkg/application/webview_window.go

+12-12
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,6 @@ type (
7171
isNormal() bool
7272
isVisible() bool
7373
isFocused() bool
74-
setFullscreenButtonEnabled(enabled bool)
75-
setMinimiseButtonEnabled(enabled bool)
76-
setMaximiseButtonEnabled(enabled bool)
7774
focus()
7875
show()
7976
hide()
@@ -90,6 +87,9 @@ type (
9087
flash(enabled bool)
9188
handleKeyEvent(acceleratorString string)
9289
getBorderSizes() *LRTB
90+
setMinimiseButtonState(state ButtonState)
91+
setMaximiseButtonState(state ButtonState)
92+
setCloseButtonState(state ButtonState)
9393
}
9494
)
9595

@@ -540,31 +540,31 @@ func (w *WebviewWindow) Fullscreen() Window {
540540
return w
541541
}
542542

543-
func (w *WebviewWindow) SetFullscreenButtonEnabled(enabled bool) Window {
544-
w.options.FullscreenButtonEnabled = enabled
543+
func (w *WebviewWindow) SetMinimiseButtonState(state ButtonState) Window {
544+
w.options.MinimiseButtonState = state
545545
if w.impl != nil {
546546
InvokeSync(func() {
547-
w.impl.setFullscreenButtonEnabled(enabled)
547+
w.impl.setMinimiseButtonState(state)
548548
})
549549
}
550550
return w
551551
}
552552

553-
func (w *WebviewWindow) SetMinimiseButtonEnabled(enabled bool) Window {
554-
w.options.FullscreenButtonEnabled = enabled
553+
func (w *WebviewWindow) SetMaximiseButtonState(state ButtonState) Window {
554+
w.options.MaximiseButtonState = state
555555
if w.impl != nil {
556556
InvokeSync(func() {
557-
w.impl.setMinimiseButtonEnabled(enabled)
557+
w.impl.setMaximiseButtonState(state)
558558
})
559559
}
560560
return w
561561
}
562562

563-
func (w *WebviewWindow) SetMaximiseButtonEnabled(enabled bool) Window {
564-
w.options.FullscreenButtonEnabled = enabled
563+
func (w *WebviewWindow) SetCloseButtonState(state ButtonState) Window {
564+
w.options.CloseButtonState = state
565565
if w.impl != nil {
566566
InvokeSync(func() {
567-
w.impl.setMaximiseButtonEnabled(enabled)
567+
w.impl.setCloseButtonState(state)
568568
})
569569
}
570570
return w

0 commit comments

Comments
 (0)