Skip to content

Commit 5ce1e98

Browse files
pierreceliasnaur
authored andcommitted
app: use material.Decorations on undecorated platforms
This patch implements a mechanism for customizing window decorations. If a window is configured with app.Decorated(true), then the widget/material.Decorations are applied. On Wayland, the option is automatically set when the server does not provide window decorations. Server side decorations are no longer requested. The Decorated flag is set according to the server's requests. Wayland is now the default driver for UNIX platforms. References: https://todo.sr.ht/~eliasnaur/gio/318 Signed-off-by: Pierre Curto <[email protected]>
1 parent 90ad001 commit 5ce1e98

12 files changed

+246
-16
lines changed

app/os.go

+14
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ type Config struct {
4343
CustomRenderer bool
4444
// center is a flag used to center the window. Set by option.
4545
center bool
46+
// Decorated reports whether window decorations are provided automatically.
47+
Decorated bool
4648
}
4749

4850
// ConfigEvent is sent whenever the configuration of a Window changes.
@@ -177,6 +179,9 @@ type driver interface {
177179

178180
// Wakeup wakes up the event loop and sends a WakeupEvent.
179181
Wakeup()
182+
183+
// Perform actions on the window.
184+
Perform(system.Action)
180185
}
181186

182187
type windowRendezvous struct {
@@ -218,3 +223,12 @@ func newWindowRendezvous() *windowRendezvous {
218223

219224
func (wakeupEvent) ImplementsEvent() {}
220225
func (ConfigEvent) ImplementsEvent() {}
226+
227+
func walkActions(actions system.Action, do func(system.Action)) {
228+
for a := system.Action(1); actions != 0; a <<= 1 {
229+
if actions&a != 0 {
230+
actions &^= a
231+
do(a)
232+
}
233+
}
234+
}

app/os_android.go

+8
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,9 @@ func (w *window) Configure(options []Option) {
11661166
prev := w.config
11671167
cnf := w.config
11681168
cnf.apply(unit.Metric{}, options)
1169+
// Decorations are never disabled.
1170+
cnf.Decorated = true
1171+
11691172
if prev.Orientation != cnf.Orientation {
11701173
w.config.Orientation = cnf.Orientation
11711174
setOrientation(env, w.view, cnf.Orientation)
@@ -1188,12 +1191,17 @@ func (w *window) Configure(options []Option) {
11881191
w.config.Mode = Windowed
11891192
}
11901193
}
1194+
if cnf.Decorated != prev.Decorated {
1195+
w.config.Decorated = cnf.Decorated
1196+
}
11911197
if w.config != prev {
11921198
w.callbacks.Event(ConfigEvent{Config: w.config})
11931199
}
11941200
})
11951201
}
11961202

1203+
func (w *window) Perform(system.Action) {}
1204+
11971205
func (w *window) Raise() {}
11981206

11991207
func (w *window) SetCursor(name pointer.CursorName) {

app/os_ios.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ type window struct {
9898

9999
visible bool
100100
cursor pointer.CursorName
101+
config Config
101102

102103
pointerMap []C.CFTypeRef
103104
}
@@ -273,7 +274,16 @@ func (w *window) WriteClipboard(s string) {
273274
C.writeClipboard(chars, C.NSUInteger(len(u16)))
274275
}
275276

276-
func (w *window) Configure([]Option) {}
277+
func (w *window) Configure([]Option) {
278+
prev := w.config
279+
// Decorations are never disabled.
280+
w.config.Decorated = true
281+
if w.config != prev {
282+
w.w.Event(ConfigEvent{Config: w.config})
283+
}
284+
}
285+
286+
func (w *window) Perform(system.Action) {}
277287

278288
func (w *window) Raise() {}
279289

app/os_js.go

+8
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,9 @@ func (w *window) Configure(options []Option) {
513513
prev := w.config
514514
cnf := w.config
515515
cnf.apply(unit.Metric{}, options)
516+
// Decorations are never disabled.
517+
cnf.Decorated = true
518+
516519
if prev.Title != cnf.Title {
517520
w.config.Title = cnf.Title
518521
w.document.Set("title", cnf.Title)
@@ -528,11 +531,16 @@ func (w *window) Configure(options []Option) {
528531
w.config.Orientation = cnf.Orientation
529532
w.orientation(cnf.Orientation)
530533
}
534+
if cnf.Decorated != prev.Decorated {
535+
w.config.Decorated = cnf.Decorated
536+
}
531537
if w.config != prev {
532538
w.w.Event(ConfigEvent{Config: w.config})
533539
}
534540
}
535541

542+
func (w *window) Perform(system.Action) {}
543+
536544
func (w *window) Raise() {}
537545

538546
func (w *window) SetCursor(name pointer.CursorName) {

app/os_macos.go

+7
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ func (w *window) Configure(options []Option) {
261261
cnf.Size = cnf.Size.Div(int(screenScale))
262262
cnf.MinSize = cnf.MinSize.Div(int(screenScale))
263263
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
264+
// Decorations are never disabled.
265+
cnf.Decorated = true
264266

265267
switch cnf.Mode {
266268
case Fullscreen:
@@ -325,6 +327,9 @@ func (w *window) Configure(options []Option) {
325327
C.setScreenFrame(w.window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y))
326328
}
327329
}
330+
if cnf.Decorated != prev.Decorated {
331+
w.config.Decorated = cnf.Decorated
332+
}
328333
if w.config != prev {
329334
w.w.Event(ConfigEvent{Config: w.config})
330335
}
@@ -339,6 +344,8 @@ func (w *window) setTitle(prev, cnf Config) {
339344
}
340345
}
341346

347+
func (w *window) Perform(system.Action) {}
348+
342349
func (w *window) SetCursor(name pointer.CursorName) {
343350
w.cursor = windowSetCursor(w.cursor, name)
344351
}

app/os_unix.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ var wlDriver, x11Driver windowDriver
2929

3030
func newWindow(window *callbacks, options []Option) error {
3131
var errFirst error
32-
for _, d := range []windowDriver{x11Driver, wlDriver} {
32+
for _, d := range []windowDriver{wlDriver, x11Driver} {
3333
if d == nil {
3434
continue
3535
}

app/os_wayland.c

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <wayland-client.h>
88
#include "wayland_xdg_shell.h"
9+
#include "wayland_xdg_decoration.h"
910
#include "wayland_text_input.h"
1011
#include "_cgo_export.h"
1112

@@ -29,6 +30,10 @@ const struct xdg_toplevel_listener gio_xdg_toplevel_listener = {
2930
.close = gio_onToplevelClose,
3031
};
3132

33+
const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener = {
34+
.configure = gio_onToplevelDecorationConfigure,
35+
};
36+
3237
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
3338
xdg_wm_base_pong(wm, serial);
3439
}

app/os_wayland.go

+96-7
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ extern const struct wl_registry_listener gio_registry_listener;
6464
extern const struct wl_surface_listener gio_surface_listener;
6565
extern const struct xdg_surface_listener gio_xdg_surface_listener;
6666
extern const struct xdg_toplevel_listener gio_xdg_toplevel_listener;
67+
extern const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener;
6768
extern const struct xdg_wm_base_listener gio_xdg_wm_base_listener;
6869
extern const struct wl_callback_listener gio_callback_listener;
6970
extern const struct wl_output_listener gio_output_listener;
@@ -149,6 +150,7 @@ type repeatState struct {
149150
type window struct {
150151
w *callbacks
151152
disp *wlDisplay
153+
seat *wlSeat
152154
surf *C.struct_wl_surface
153155
wmSurf *C.struct_xdg_surface
154156
topLvl *C.struct_xdg_toplevel
@@ -188,9 +190,10 @@ type window struct {
188190
newScale bool
189191
scale int
190192
// size is the unscaled window size (unlike config.Size which is scaled).
191-
size image.Point
192-
config Config
193-
wsize image.Point // window config size before going fullscreen
193+
size image.Point
194+
config Config
195+
wsize image.Point // window config size before going fullscreen or maximized
196+
inCompositor bool // window is moving or being resized
194197

195198
wakeups chan struct{}
196199
}
@@ -212,7 +215,7 @@ type wlOutput struct {
212215
}
213216

214217
// callbackMap maps Wayland native handles to corresponding Go
215-
// references. It is necessary because the the Wayland client API
218+
// references. It is necessary because the Wayland client API
216219
// forces the use of callbacks and storing pointers to Go values
217220
// in C is forbidden.
218221
var callbackMap sync.Map
@@ -369,9 +372,8 @@ func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
369372
C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf))
370373

371374
if d.decor != nil {
372-
// Request server side decorations.
373375
w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(d.decor, w.topLvl)
374-
C.zxdg_toplevel_decoration_v1_set_mode(w.decor, C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE)
376+
C.zxdg_toplevel_decoration_v1_add_listener(w.decor, &C.gio_zxdg_toplevel_decoration_v1_listener, unsafe.Pointer(w.surf))
375377
}
376378
w.updateOpaqueRegion()
377379
return w, nil
@@ -499,6 +501,24 @@ func gio_onToplevelConfigure(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel,
499501
w.size = image.Pt(int(width), int(height))
500502
w.updateOpaqueRegion()
501503
}
504+
w.needAck = true
505+
}
506+
507+
//export gio_onToplevelDecorationConfigure
508+
func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_toplevel_decoration_v1, mode C.uint32_t) {
509+
w := callbackLoad(data).(*window)
510+
decorated := w.config.Decorated
511+
switch mode {
512+
case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
513+
w.config.Decorated = false
514+
case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
515+
w.config.Decorated = true
516+
}
517+
if decorated != w.config.Decorated {
518+
w.w.Event(ConfigEvent{Config: w.config})
519+
}
520+
w.needAck = true
521+
w.draw(true)
502522
}
503523

504524
//export gio_onOutputMode
@@ -772,15 +792,22 @@ func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, seria
772792
s := callbackLoad(data).(*wlSeat)
773793
s.serial = serial
774794
w := callbackLoad(unsafe.Pointer(surf)).(*window)
795+
w.seat = s
775796
s.pointerFocus = w
776797
w.setCursor(pointer, serial)
777798
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
778799
}
779800

780801
//export gio_onPointerLeave
781-
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surface *C.struct_wl_surface) {
802+
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) {
803+
w := callbackLoad(unsafe.Pointer(surf)).(*window)
804+
w.seat = nil
782805
s := callbackLoad(data).(*wlSeat)
783806
s.serial = serial
807+
if w.inCompositor {
808+
w.inCompositor = false
809+
w.w.Event(pointer.Event{Type: pointer.Cancel})
810+
}
784811
}
785812

786813
//export gio_onPointerMotion
@@ -818,6 +845,8 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
818845
case 0:
819846
w.pointerBtns &^= btn
820847
typ = pointer.Release
848+
// Move or resize gestures no longer applies.
849+
w.inCompositor = false
821850
case 1:
822851
w.pointerBtns |= btn
823852
typ = pointer.Press
@@ -978,6 +1007,9 @@ func (w *window) Configure(options []Option) {
9781007
C.xdg_toplevel_set_max_size(w.topLvl, C.int32_t(cnf.MaxSize.X), C.int32_t(cnf.MaxSize.Y))
9791008
}
9801009
}
1010+
if cnf.Decorated != prev.Decorated {
1011+
w.config.Decorated = cnf.Decorated
1012+
}
9811013
if w.config != prev {
9821014
w.w.Event(ConfigEvent{Config: w.config})
9831015
}
@@ -992,6 +1024,63 @@ func (w *window) setTitle(prev, cnf Config) {
9921024
}
9931025
}
9941026

1027+
func (w *window) Perform(actions system.Action) {
1028+
walkActions(actions, func(action system.Action) {
1029+
switch action {
1030+
case system.ActionMinimize:
1031+
w.Configure([]Option{Minimized.Option()})
1032+
case system.ActionMaximize:
1033+
w.Configure([]Option{Maximized.Option()})
1034+
case system.ActionUnmaximize:
1035+
w.Configure([]Option{Windowed.Option()})
1036+
case system.ActionClose:
1037+
w.Close()
1038+
case system.ActionMove:
1039+
w.move()
1040+
default:
1041+
w.resize(action)
1042+
}
1043+
})
1044+
}
1045+
1046+
func (w *window) move() {
1047+
if !w.inCompositor && w.seat != nil {
1048+
w.inCompositor = true
1049+
s := w.seat
1050+
C.xdg_toplevel_move(w.topLvl, s.seat, s.serial)
1051+
}
1052+
}
1053+
1054+
func (w *window) resize(a system.Action) {
1055+
if w.inCompositor || w.seat == nil {
1056+
return
1057+
}
1058+
var edge int
1059+
switch a {
1060+
case system.ActionResizeNorth:
1061+
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_TOP
1062+
case system.ActionResizeSouth:
1063+
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM
1064+
case system.ActionResizeEast:
1065+
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_LEFT
1066+
case system.ActionResizeWest:
1067+
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_RIGHT
1068+
case system.ActionResizeNorthWest:
1069+
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT
1070+
case system.ActionResizeNorthEast:
1071+
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT
1072+
case system.ActionResizeSouthEast:
1073+
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT
1074+
case system.ActionResizeSouthWest:
1075+
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT
1076+
default:
1077+
return
1078+
}
1079+
w.inCompositor = true
1080+
s := w.seat
1081+
C.xdg_toplevel_resize(w.topLvl, s.seat, s.serial, C.uint32_t(edge))
1082+
}
1083+
9951084
func (w *window) Raise() {
9961085
// NB. there is no way for a minimized window to be unminimized.
9971086
// https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:set_minimized

app/os_windows.go

+4
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,8 @@ func (w *window) Configure(options []Option) {
531531
metric := configForDPI(dpi)
532532
w.config.apply(metric, options)
533533
windows.SetWindowText(w.hwnd, w.config.Title)
534+
// Decorations are never disabled.
535+
w.config.Decorated = true
534536

535537
switch w.config.Mode {
536538
case Minimized:
@@ -691,6 +693,8 @@ func (w *window) Close() {
691693
windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
692694
}
693695

696+
func (w *window) Perform(system.Action) {}
697+
694698
func (w *window) Raise() {
695699
windows.SetForegroundWindow(w.hwnd)
696700
windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0,

app/os_x11.go

+7
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ func (w *x11Window) Configure(options []Option) {
164164
prev := w.config
165165
cnf := w.config
166166
cnf.apply(w.metric, options)
167+
// Decorations are never disabled.
168+
cnf.Decorated = true
167169

168170
switch cnf.Mode {
169171
case Fullscreen:
@@ -245,6 +247,9 @@ func (w *x11Window) Configure(options []Option) {
245247
C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y))
246248
}
247249
}
250+
if cnf.Decorated != prev.Decorated {
251+
w.config.Decorated = cnf.Decorated
252+
}
248253
if w.config != prev {
249254
w.w.Event(ConfigEvent{Config: w.config})
250255
}
@@ -268,6 +273,8 @@ func (w *x11Window) setTitle(prev, cnf Config) {
268273
}
269274
}
270275

276+
func (w *x11Window) Perform(system.Action) {}
277+
271278
func (w *x11Window) Raise() {
272279
var xev C.XEvent
273280
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))

0 commit comments

Comments
 (0)