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 <pierre.curto@gmail.com>
This commit is contained in:
Pierre Curto
2022-01-23 21:54:14 +01:00
committed by Elias Naur
parent 90ad0010ec
commit 5ce1e98282
12 changed files with 246 additions and 16 deletions
+14
View File
@@ -43,6 +43,8 @@ type Config struct {
CustomRenderer bool CustomRenderer bool
// center is a flag used to center the window. Set by option. // center is a flag used to center the window. Set by option.
center bool center bool
// Decorated reports whether window decorations are provided automatically.
Decorated bool
} }
// ConfigEvent is sent whenever the configuration of a Window changes. // ConfigEvent is sent whenever the configuration of a Window changes.
@@ -177,6 +179,9 @@ type driver interface {
// Wakeup wakes up the event loop and sends a WakeupEvent. // Wakeup wakes up the event loop and sends a WakeupEvent.
Wakeup() Wakeup()
// Perform actions on the window.
Perform(system.Action)
} }
type windowRendezvous struct { type windowRendezvous struct {
@@ -218,3 +223,12 @@ func newWindowRendezvous() *windowRendezvous {
func (wakeupEvent) ImplementsEvent() {} func (wakeupEvent) ImplementsEvent() {}
func (ConfigEvent) ImplementsEvent() {} func (ConfigEvent) ImplementsEvent() {}
func walkActions(actions system.Action, do func(system.Action)) {
for a := system.Action(1); actions != 0; a <<= 1 {
if actions&a != 0 {
actions &^= a
do(a)
}
}
}
+8
View File
@@ -1166,6 +1166,9 @@ func (w *window) Configure(options []Option) {
prev := w.config prev := w.config
cnf := w.config cnf := w.config
cnf.apply(unit.Metric{}, options) cnf.apply(unit.Metric{}, options)
// Decorations are never disabled.
cnf.Decorated = true
if prev.Orientation != cnf.Orientation { if prev.Orientation != cnf.Orientation {
w.config.Orientation = cnf.Orientation w.config.Orientation = cnf.Orientation
setOrientation(env, w.view, cnf.Orientation) setOrientation(env, w.view, cnf.Orientation)
@@ -1188,12 +1191,17 @@ func (w *window) Configure(options []Option) {
w.config.Mode = Windowed w.config.Mode = Windowed
} }
} }
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
if w.config != prev { if w.config != prev {
w.callbacks.Event(ConfigEvent{Config: w.config}) w.callbacks.Event(ConfigEvent{Config: w.config})
} }
}) })
} }
func (w *window) Perform(system.Action) {}
func (w *window) Raise() {} func (w *window) Raise() {}
func (w *window) SetCursor(name pointer.CursorName) { func (w *window) SetCursor(name pointer.CursorName) {
+11 -1
View File
@@ -98,6 +98,7 @@ type window struct {
visible bool visible bool
cursor pointer.CursorName cursor pointer.CursorName
config Config
pointerMap []C.CFTypeRef pointerMap []C.CFTypeRef
} }
@@ -273,7 +274,16 @@ func (w *window) WriteClipboard(s string) {
C.writeClipboard(chars, C.NSUInteger(len(u16))) C.writeClipboard(chars, C.NSUInteger(len(u16)))
} }
func (w *window) Configure([]Option) {} func (w *window) Configure([]Option) {
prev := w.config
// Decorations are never disabled.
w.config.Decorated = true
if w.config != prev {
w.w.Event(ConfigEvent{Config: w.config})
}
}
func (w *window) Perform(system.Action) {}
func (w *window) Raise() {} func (w *window) Raise() {}
+8
View File
@@ -513,6 +513,9 @@ func (w *window) Configure(options []Option) {
prev := w.config prev := w.config
cnf := w.config cnf := w.config
cnf.apply(unit.Metric{}, options) cnf.apply(unit.Metric{}, options)
// Decorations are never disabled.
cnf.Decorated = true
if prev.Title != cnf.Title { if prev.Title != cnf.Title {
w.config.Title = cnf.Title w.config.Title = cnf.Title
w.document.Set("title", cnf.Title) w.document.Set("title", cnf.Title)
@@ -528,11 +531,16 @@ func (w *window) Configure(options []Option) {
w.config.Orientation = cnf.Orientation w.config.Orientation = cnf.Orientation
w.orientation(cnf.Orientation) w.orientation(cnf.Orientation)
} }
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
if w.config != prev { if w.config != prev {
w.w.Event(ConfigEvent{Config: w.config}) w.w.Event(ConfigEvent{Config: w.config})
} }
} }
func (w *window) Perform(system.Action) {}
func (w *window) Raise() {} func (w *window) Raise() {}
func (w *window) SetCursor(name pointer.CursorName) { func (w *window) SetCursor(name pointer.CursorName) {
+7
View File
@@ -261,6 +261,8 @@ func (w *window) Configure(options []Option) {
cnf.Size = cnf.Size.Div(int(screenScale)) cnf.Size = cnf.Size.Div(int(screenScale))
cnf.MinSize = cnf.MinSize.Div(int(screenScale)) cnf.MinSize = cnf.MinSize.Div(int(screenScale))
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale)) cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
// Decorations are never disabled.
cnf.Decorated = true
switch cnf.Mode { switch cnf.Mode {
case Fullscreen: case Fullscreen:
@@ -325,6 +327,9 @@ func (w *window) Configure(options []Option) {
C.setScreenFrame(w.window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y)) C.setScreenFrame(w.window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y))
} }
} }
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
if w.config != prev { if w.config != prev {
w.w.Event(ConfigEvent{Config: w.config}) w.w.Event(ConfigEvent{Config: w.config})
} }
@@ -339,6 +344,8 @@ func (w *window) setTitle(prev, cnf Config) {
} }
} }
func (w *window) Perform(system.Action) {}
func (w *window) SetCursor(name pointer.CursorName) { func (w *window) SetCursor(name pointer.CursorName) {
w.cursor = windowSetCursor(w.cursor, name) w.cursor = windowSetCursor(w.cursor, name)
} }
+1 -1
View File
@@ -29,7 +29,7 @@ var wlDriver, x11Driver windowDriver
func newWindow(window *callbacks, options []Option) error { func newWindow(window *callbacks, options []Option) error {
var errFirst error var errFirst error
for _, d := range []windowDriver{x11Driver, wlDriver} { for _, d := range []windowDriver{wlDriver, x11Driver} {
if d == nil { if d == nil {
continue continue
} }
+5
View File
@@ -6,6 +6,7 @@
#include <wayland-client.h> #include <wayland-client.h>
#include "wayland_xdg_shell.h" #include "wayland_xdg_shell.h"
#include "wayland_xdg_decoration.h"
#include "wayland_text_input.h" #include "wayland_text_input.h"
#include "_cgo_export.h" #include "_cgo_export.h"
@@ -29,6 +30,10 @@ const struct xdg_toplevel_listener gio_xdg_toplevel_listener = {
.close = gio_onToplevelClose, .close = gio_onToplevelClose,
}; };
const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener = {
.configure = gio_onToplevelDecorationConfigure,
};
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) { static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
xdg_wm_base_pong(wm, serial); xdg_wm_base_pong(wm, serial);
} }
+96 -7
View File
@@ -64,6 +64,7 @@ extern const struct wl_registry_listener gio_registry_listener;
extern const struct wl_surface_listener gio_surface_listener; extern const struct wl_surface_listener gio_surface_listener;
extern const struct xdg_surface_listener gio_xdg_surface_listener; extern const struct xdg_surface_listener gio_xdg_surface_listener;
extern const struct xdg_toplevel_listener gio_xdg_toplevel_listener; extern const struct xdg_toplevel_listener gio_xdg_toplevel_listener;
extern const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener;
extern const struct xdg_wm_base_listener gio_xdg_wm_base_listener; extern const struct xdg_wm_base_listener gio_xdg_wm_base_listener;
extern const struct wl_callback_listener gio_callback_listener; extern const struct wl_callback_listener gio_callback_listener;
extern const struct wl_output_listener gio_output_listener; extern const struct wl_output_listener gio_output_listener;
@@ -149,6 +150,7 @@ type repeatState struct {
type window struct { type window struct {
w *callbacks w *callbacks
disp *wlDisplay disp *wlDisplay
seat *wlSeat
surf *C.struct_wl_surface surf *C.struct_wl_surface
wmSurf *C.struct_xdg_surface wmSurf *C.struct_xdg_surface
topLvl *C.struct_xdg_toplevel topLvl *C.struct_xdg_toplevel
@@ -188,9 +190,10 @@ type window struct {
newScale bool newScale bool
scale int scale int
// size is the unscaled window size (unlike config.Size which is scaled). // size is the unscaled window size (unlike config.Size which is scaled).
size image.Point size image.Point
config Config config Config
wsize image.Point // window config size before going fullscreen wsize image.Point // window config size before going fullscreen or maximized
inCompositor bool // window is moving or being resized
wakeups chan struct{} wakeups chan struct{}
} }
@@ -212,7 +215,7 @@ type wlOutput struct {
} }
// callbackMap maps Wayland native handles to corresponding Go // callbackMap maps Wayland native handles to corresponding Go
// references. It is necessary because the the Wayland client API // references. It is necessary because the Wayland client API
// forces the use of callbacks and storing pointers to Go values // forces the use of callbacks and storing pointers to Go values
// in C is forbidden. // in C is forbidden.
var callbackMap sync.Map var callbackMap sync.Map
@@ -369,9 +372,8 @@ func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf)) C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf))
if d.decor != nil { if d.decor != nil {
// Request server side decorations.
w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(d.decor, w.topLvl) w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(d.decor, w.topLvl)
C.zxdg_toplevel_decoration_v1_set_mode(w.decor, C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) C.zxdg_toplevel_decoration_v1_add_listener(w.decor, &C.gio_zxdg_toplevel_decoration_v1_listener, unsafe.Pointer(w.surf))
} }
w.updateOpaqueRegion() w.updateOpaqueRegion()
return w, nil return w, nil
@@ -499,6 +501,24 @@ func gio_onToplevelConfigure(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel,
w.size = image.Pt(int(width), int(height)) w.size = image.Pt(int(width), int(height))
w.updateOpaqueRegion() w.updateOpaqueRegion()
} }
w.needAck = true
}
//export gio_onToplevelDecorationConfigure
func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_toplevel_decoration_v1, mode C.uint32_t) {
w := callbackLoad(data).(*window)
decorated := w.config.Decorated
switch mode {
case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
w.config.Decorated = false
case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
w.config.Decorated = true
}
if decorated != w.config.Decorated {
w.w.Event(ConfigEvent{Config: w.config})
}
w.needAck = true
w.draw(true)
} }
//export gio_onOutputMode //export gio_onOutputMode
@@ -772,15 +792,22 @@ func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, seria
s := callbackLoad(data).(*wlSeat) s := callbackLoad(data).(*wlSeat)
s.serial = serial s.serial = serial
w := callbackLoad(unsafe.Pointer(surf)).(*window) w := callbackLoad(unsafe.Pointer(surf)).(*window)
w.seat = s
s.pointerFocus = w s.pointerFocus = w
w.setCursor(pointer, serial) w.setCursor(pointer, serial)
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)} w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
} }
//export gio_onPointerLeave //export gio_onPointerLeave
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surface *C.struct_wl_surface) { func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) {
w := callbackLoad(unsafe.Pointer(surf)).(*window)
w.seat = nil
s := callbackLoad(data).(*wlSeat) s := callbackLoad(data).(*wlSeat)
s.serial = serial s.serial = serial
if w.inCompositor {
w.inCompositor = false
w.w.Event(pointer.Event{Type: pointer.Cancel})
}
} }
//export gio_onPointerMotion //export gio_onPointerMotion
@@ -818,6 +845,8 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
case 0: case 0:
w.pointerBtns &^= btn w.pointerBtns &^= btn
typ = pointer.Release typ = pointer.Release
// Move or resize gestures no longer applies.
w.inCompositor = false
case 1: case 1:
w.pointerBtns |= btn w.pointerBtns |= btn
typ = pointer.Press typ = pointer.Press
@@ -978,6 +1007,9 @@ func (w *window) Configure(options []Option) {
C.xdg_toplevel_set_max_size(w.topLvl, C.int32_t(cnf.MaxSize.X), C.int32_t(cnf.MaxSize.Y)) C.xdg_toplevel_set_max_size(w.topLvl, C.int32_t(cnf.MaxSize.X), C.int32_t(cnf.MaxSize.Y))
} }
} }
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
if w.config != prev { if w.config != prev {
w.w.Event(ConfigEvent{Config: w.config}) w.w.Event(ConfigEvent{Config: w.config})
} }
@@ -992,6 +1024,63 @@ func (w *window) setTitle(prev, cnf Config) {
} }
} }
func (w *window) Perform(actions system.Action) {
walkActions(actions, func(action system.Action) {
switch action {
case system.ActionMinimize:
w.Configure([]Option{Minimized.Option()})
case system.ActionMaximize:
w.Configure([]Option{Maximized.Option()})
case system.ActionUnmaximize:
w.Configure([]Option{Windowed.Option()})
case system.ActionClose:
w.Close()
case system.ActionMove:
w.move()
default:
w.resize(action)
}
})
}
func (w *window) move() {
if !w.inCompositor && w.seat != nil {
w.inCompositor = true
s := w.seat
C.xdg_toplevel_move(w.topLvl, s.seat, s.serial)
}
}
func (w *window) resize(a system.Action) {
if w.inCompositor || w.seat == nil {
return
}
var edge int
switch a {
case system.ActionResizeNorth:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_TOP
case system.ActionResizeSouth:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM
case system.ActionResizeEast:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_LEFT
case system.ActionResizeWest:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_RIGHT
case system.ActionResizeNorthWest:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT
case system.ActionResizeNorthEast:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT
case system.ActionResizeSouthEast:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT
case system.ActionResizeSouthWest:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT
default:
return
}
w.inCompositor = true
s := w.seat
C.xdg_toplevel_resize(w.topLvl, s.seat, s.serial, C.uint32_t(edge))
}
func (w *window) Raise() { func (w *window) Raise() {
// NB. there is no way for a minimized window to be unminimized. // NB. there is no way for a minimized window to be unminimized.
// https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:set_minimized // https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:set_minimized
+4
View File
@@ -531,6 +531,8 @@ func (w *window) Configure(options []Option) {
metric := configForDPI(dpi) metric := configForDPI(dpi)
w.config.apply(metric, options) w.config.apply(metric, options)
windows.SetWindowText(w.hwnd, w.config.Title) windows.SetWindowText(w.hwnd, w.config.Title)
// Decorations are never disabled.
w.config.Decorated = true
switch w.config.Mode { switch w.config.Mode {
case Minimized: case Minimized:
@@ -691,6 +693,8 @@ func (w *window) Close() {
windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0) windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
} }
func (w *window) Perform(system.Action) {}
func (w *window) Raise() { func (w *window) Raise() {
windows.SetForegroundWindow(w.hwnd) windows.SetForegroundWindow(w.hwnd)
windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0, windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0,
+7
View File
@@ -164,6 +164,8 @@ func (w *x11Window) Configure(options []Option) {
prev := w.config prev := w.config
cnf := w.config cnf := w.config
cnf.apply(w.metric, options) cnf.apply(w.metric, options)
// Decorations are never disabled.
cnf.Decorated = true
switch cnf.Mode { switch cnf.Mode {
case Fullscreen: case Fullscreen:
@@ -245,6 +247,9 @@ func (w *x11Window) Configure(options []Option) {
C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y)) C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y))
} }
} }
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
if w.config != prev { if w.config != prev {
w.w.Event(ConfigEvent{Config: w.config}) w.w.Event(ConfigEvent{Config: w.config})
} }
@@ -268,6 +273,8 @@ func (w *x11Window) setTitle(prev, cnf Config) {
} }
} }
func (w *x11Window) Perform(system.Action) {}
func (w *x11Window) Raise() { func (w *x11Window) Raise() {
var xev C.XEvent var xev C.XEvent
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev)) ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
+83 -3
View File
@@ -11,14 +11,18 @@ import (
"time" "time"
"gioui.org/f32" "gioui.org/f32"
"gioui.org/font/gofont"
"gioui.org/gpu" "gioui.org/gpu"
"gioui.org/internal/ops"
"gioui.org/io/event" "gioui.org/io/event"
"gioui.org/io/pointer" "gioui.org/io/pointer"
"gioui.org/io/profile" "gioui.org/io/profile"
"gioui.org/io/router" "gioui.org/io/router"
"gioui.org/io/system" "gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op" "gioui.org/op"
"gioui.org/unit" "gioui.org/unit"
"gioui.org/widget/material"
_ "gioui.org/app/internal/log" _ "gioui.org/app/internal/log"
) )
@@ -59,8 +63,13 @@ type Window struct {
nextFrame time.Time nextFrame time.Time
delayedDraw *time.Timer delayedDraw *time.Timer
queue queue queue queue
cursor pointer.CursorName cursor pointer.CursorName
decorations struct {
op.Ops
Config
*material.Decorations
}
callbacks callbacks callbacks callbacks
@@ -578,9 +587,16 @@ func (w *Window) processEvent(d driver, e event.Event) {
w.hasNextFrame = false w.hasNextFrame = false
e2.Frame = w.update e2.Frame = w.update
e2.Queue = &w.queue e2.Queue = &w.queue
// Prepare the decorations and update the frame insets.
wrapper := &w.decorations.Ops
wrapper.Reset()
size := e2.Size // save the initial window size as the decorations will change it.
e2.FrameEvent.Size = w.decorate(d, e2.FrameEvent, wrapper)
w.out <- e2.FrameEvent w.out <- e2.FrameEvent
frame, gotFrame := w.waitFrame() frame, gotFrame := w.waitFrame()
err := w.validateAndProcess(d, e2.Size, e2.Sync, frame) ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal))
err := w.validateAndProcess(d, size, e2.Sync, wrapper)
if gotFrame { if gotFrame {
// We're done with frame, let the client continue. // We're done with frame, let the client continue.
w.frameAck <- struct{}{} w.frameAck <- struct{}{}
@@ -606,6 +622,9 @@ func (w *Window) processEvent(d driver, e event.Event) {
w.out <- e2 w.out <- e2
w.waitAck() w.waitAck()
case wakeupEvent: case wakeupEvent:
case ConfigEvent:
w.decorations.Config = e2.Config
w.out <- e
case event.Event: case event.Event:
if w.queue.q.Queue(e2) { if w.queue.q.Queue(e2) {
w.setNextFrame(time.Time{}) w.setNextFrame(time.Time{})
@@ -664,6 +683,59 @@ func (w *Window) updateCursor(d driver) {
} }
} }
// decorate the window if enabled and returns the corresponding Insets.
func (w *Window) decorate(d driver, e system.FrameEvent, o *op.Ops) image.Point {
if w.decorations.Config.Decorated || w.decorations.Config.Mode == Fullscreen {
return e.Size
}
deco := w.decorations.Decorations
if deco == nil {
theme := material.NewTheme(gofont.Collection())
allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize |
system.ActionClose | system.ActionMove |
system.ActionResizeNorth | system.ActionResizeSouth |
system.ActionResizeWest | system.ActionResizeEast |
system.ActionResizeNorthWest | system.ActionResizeSouthWest |
system.ActionResizeNorthEast | system.ActionResizeSouthEast
deco = &material.Decorations{
DecorationsStyle: material.Decorate(theme, allActions),
}
w.decorations.Decorations = deco
}
// Update the decorations based on the current window mode.
var actions system.Action
switch m := w.decorations.Config.Mode; m {
case Windowed:
actions |= system.ActionUnmaximize
case Minimized:
actions |= system.ActionMinimize
case Maximized:
actions |= system.ActionMaximize
case Fullscreen:
actions |= system.ActionFullscreen
default:
panic(fmt.Errorf("unknown WindowMode %v", m))
}
deco.Perform(actions)
// Update the window based on the actions on the decorations.
d.Perform(deco.Actions())
gtx := layout.Context{
Ops: o,
Now: e.Now,
Queue: e.Queue,
Metric: e.Metric,
Constraints: layout.Exact(e.Size),
}
rec := op.Record(o)
dims := deco.Decorate(gtx, w.decorations.Config.Title)
op.Defer(o, rec.Stop())
// Offset to place the frame content below the decorations.
size := image.Point{Y: dims.Size.Y}
op.Offset(f32.Point{Y: float32(size.Y)}).Add(o)
return e.Size.Sub(size)
}
// Raise requests that the platform bring this window to the top of all open windows. // Raise requests that the platform bring this window to the top of all open windows.
// Some platforms do not allow this except under certain circumstances, such as when // Some platforms do not allow this except under certain circumstances, such as when
// a window from the same application already has focus. If the platform does not // a window from the same application already has focus. If the platform does not
@@ -764,3 +836,11 @@ func CustomRenderer(custom bool) Option {
cnf.CustomRenderer = custom cnf.CustomRenderer = custom
} }
} }
// Decorated controls whether automatic window decorations
// are enabled.
func Decorated(enabled bool) Option {
return func(_ unit.Metric, cnf *Config) {
cnf.Decorated = enabled
}
}
+2 -4
View File
@@ -57,7 +57,7 @@ type Decorations struct {
// Decorate a window with the title and actions defined in DecorationsStyle. // Decorate a window with the title and actions defined in DecorationsStyle.
// The space used by the decorations is returned as an inset for the window // The space used by the decorations is returned as an inset for the window
// content. // content.
func (d *Decorations) Decorate(gtx layout.Context, title string) layout.Inset { func (d *Decorations) Decorate(gtx layout.Context, title string) layout.Dimensions {
rec := op.Record(gtx.Ops) rec := op.Record(gtx.Ops)
dims := d.layoutDecorations(gtx, title) dims := d.layoutDecorations(gtx, title)
decos := rec.Stop() decos := rec.Stop()
@@ -65,9 +65,7 @@ func (d *Decorations) Decorate(gtx layout.Context, title string) layout.Inset {
paint.FillShape(gtx.Ops, d.DecorationsStyle.Background, r.Op()) paint.FillShape(gtx.Ops, d.DecorationsStyle.Background, r.Op())
decos.Add(gtx.Ops) decos.Add(gtx.Ops)
d.layoutResizing(gtx) d.layoutResizing(gtx)
return layout.Inset{ return dims
Top: unit.Px(float32(dims.Size.Y)),
}
} }
func (d *Decorations) layoutResizing(gtx layout.Context) { func (d *Decorations) layoutResizing(gtx layout.Context) {