From 2993ba18383a758e7aba509b57cf74289b0bccc5 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 18 Jul 2022 22:44:28 +0200 Subject: [PATCH] app: [Wayland] account for fallback decoration height in window sizes Pass through a fallback window decoration height to the Wayland backend, so that it can account for it when determining surface size. Fixes: https://todo.sr.ht/~eliasnaur/gio/435 Signed-off-by: Elias Naur --- app/os.go | 3 +++ app/os_wayland.go | 42 ++++++++++++++++++++++++--------- app/window.go | 59 ++++++++++++++++++++++++++++++----------------- 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/app/os.go b/app/os.go index 8979ee33..b5f18bcc 100644 --- a/app/os.go +++ b/app/os.go @@ -43,6 +43,9 @@ type Config struct { CustomRenderer bool // Decorated reports whether window decorations are provided automatically. Decorated bool + // decoHeight is the height of the fallback decoration for platforms such + // as Wayland that may need fallback client-side decorations. + decoHeight unit.Dp } // ConfigEvent is sent whenever the configuration of a Window changes. diff --git a/app/os_wayland.go b/app/os_wayland.go index 2b54f53f..c54fd3a0 100644 --- a/app/os_wayland.go +++ b/app/os_wayland.go @@ -576,6 +576,12 @@ func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_ w.config.Decorated = true } if decorated != w.config.Decorated { + w.setWindowConstraints() + if w.config.Decorated { + w.size.Y -= int(w.config.decoHeight) + } else { + w.size.Y += int(w.config.decoHeight) + } w.w.Event(ConfigEvent{Config: w.config}) w.redraw = true } @@ -1022,6 +1028,7 @@ func (w *window) Configure(options []Option) { prev := w.config cnf := w.config cnf.apply(cfg, options) + w.config.decoHeight = cnf.decoHeight switch cnf.Mode { case Fullscreen: @@ -1064,22 +1071,35 @@ func (w *window) Configure(options []Option) { w.setTitle(prev, cnf) if prev.Size != cnf.Size { w.config.Size = cnf.Size - w.size = cnf.Size.Div(w.scale) - } - if prev.MinSize != cnf.MinSize { - w.config.MinSize = cnf.MinSize - scaled := cnf.MinSize.Div(w.scale) - C.xdg_toplevel_set_min_size(w.topLvl, C.int32_t(scaled.X), C.int32_t(scaled.Y)) - } - if prev.MaxSize != cnf.MaxSize { - w.config.MaxSize = cnf.MaxSize - scaled := cnf.MaxSize.Div(w.scale) - C.xdg_toplevel_set_max_size(w.topLvl, C.int32_t(scaled.X), C.int32_t(scaled.Y)) + w.config.Size.Y += int(w.decoHeight()) * w.scale + w.size = w.config.Size.Div(w.scale) } + w.config.MinSize = cnf.MinSize + w.config.MaxSize = cnf.MaxSize + w.setWindowConstraints() } w.w.Event(ConfigEvent{Config: w.config}) } +func (w *window) setWindowConstraints() { + decoHeight := w.decoHeight() + if scaled := w.config.MinSize.Div(w.scale); scaled != (image.Point{}) { + C.xdg_toplevel_set_min_size(w.topLvl, C.int32_t(scaled.X), C.int32_t(scaled.Y+decoHeight)) + } + if scaled := w.config.MaxSize.Div(w.scale); scaled != (image.Point{}) { + C.xdg_toplevel_set_max_size(w.topLvl, C.int32_t(scaled.X), C.int32_t(scaled.Y+decoHeight)) + } +} + +// decoHeight returns the adjustment for client-side decorations, if applicable. +// The unit is in surface-local coordinates. +func (w *window) decoHeight() int { + if !w.config.Decorated { + return int(w.config.decoHeight) + } + return 0 +} + func (w *window) setTitle(prev, cnf Config) { if prev.Title != cnf.Title { w.config.Title = cnf.Title diff --git a/app/window.go b/app/window.go index d1562369..06b82de8 100644 --- a/app/window.go +++ b/app/window.go @@ -85,9 +85,10 @@ type Window struct { // capability. enabled bool Config + height unit.Dp + currentHeight int *material.Theme *widget.Decorations - size image.Point // decorations size } callbacks callbacks @@ -137,10 +138,24 @@ type queue struct { // Calling NewWindow more than once is not supported on // iOS, Android, WebAssembly. func NewWindow(options ...Option) *Window { + // Measure decoration height. + deco := new(widget.Decorations) + theme := material.NewTheme(gofont.Collection()) + decoStyle := material.Decorations(theme, deco, 0, "") + gtx := layout.Context{ + Ops: new(op.Ops), + // Measure in Dp. + Metric: unit.Metric{}, + } + // Allow plenty of space. + gtx.Constraints.Max.Y = 200 + dims := decoStyle.Layout(gtx) + decoHeight := unit.Dp(dims.Size.Y) defaultOptions := []Option{ Size(800, 600), Title("Gio"), Decorated(true), + decoHeightOpt(decoHeight), } options = append(defaultOptions, options...) var cnf Config @@ -162,6 +177,8 @@ func NewWindow(options ...Option) *Window { actions: make(chan system.Action, 1), nocontext: cnf.CustomRenderer, } + w.decorations.Theme = theme + w.decorations.Decorations = deco w.decorations.enabled = cnf.Decorated w.imeState.compose = key.Range{Start: -1, End: -1} w.semantic.ids = make(map[router.SemanticID]router.SemanticNode) @@ -170,6 +187,12 @@ func NewWindow(options ...Option) *Window { return w } +func decoHeightOpt(h unit.Dp) Option { + return func(m unit.Metric, c *Config) { + c.decoHeight = h + } +} + // Events returns the channel where events are delivered. func (w *Window) Events() <-chan event.Event { return w.out @@ -474,6 +497,11 @@ func (c *callbacks) Event(e event.Event) bool { opt(c.w.metric, &cnf) } c.w.decorations.enabled = cnf.Decorated + decoHeight := c.w.decorations.height + if !c.w.decorations.enabled { + decoHeight = 0 + } + opts = append(opts, decoHeightOpt(decoHeight)) c.d.Configure(opts) default: } @@ -879,9 +907,7 @@ func (w *Window) processEvent(d driver, e event.Event) bool { case ConfigEvent: w.decorations.Config = e2.Config if !w.fallbackDecorate() { - // Decorations are no longer applied. - w.decorations.Decorations = nil - w.decorations.size = image.Point{} + w.decorations.height = 0 } e2.Config = w.effectiveConfig() w.out <- e2 @@ -975,19 +1001,10 @@ func (w *Window) decorate(d driver, e system.FrameEvent, o *op.Ops) (size, offse if !w.fallbackDecorate() { return e.Size, image.Pt(0, 0) } - theme := w.decorations.Theme - if theme == nil { - theme = material.NewTheme(gofont.Collection()) - w.decorations.Theme = theme - } deco := w.decorations.Decorations - if deco == nil { - deco = new(widget.Decorations) - w.decorations.Decorations = deco - } allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize | system.ActionClose | system.ActionMove - style := material.Decorations(theme, deco, allActions, w.decorations.Config.Title) + style := material.Decorations(w.decorations.Theme, deco, allActions, w.decorations.Config.Title) // Update the decorations based on the current window mode. var actions system.Action switch m := w.decorations.Config.Mode; m { @@ -1010,22 +1027,22 @@ func (w *Window) decorate(d driver, e system.FrameEvent, o *op.Ops) (size, offse Metric: e.Metric, Constraints: layout.Exact(e.Size), } - dims := style.Layout(gtx) + style.Layout(gtx) // Update the window based on the actions on the decorations. w.Perform(deco.Actions()) // Offset to place the frame content below the decorations. - decoSize := image.Point{Y: dims.Size.Y} - appSize := e.Size.Sub(decoSize) - if w.decorations.size != decoSize { - w.decorations.size = decoSize + decoHeight := gtx.Dp(w.decorations.Config.decoHeight) + if w.decorations.currentHeight != decoHeight { + w.decorations.currentHeight = decoHeight w.out <- ConfigEvent{Config: w.effectiveConfig()} } - return appSize, image.Pt(0, decoSize.Y) + e.Size.Y -= w.decorations.currentHeight + return e.Size, image.Pt(0, decoHeight) } func (w *Window) effectiveConfig() Config { cnf := w.decorations.Config - cnf.Size = cnf.Size.Sub(w.decorations.size) + cnf.Size.Y -= w.decorations.currentHeight cnf.Decorated = w.decorations.enabled || cnf.Decorated return cnf }