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 <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2022-07-18 22:44:28 +02:00
parent 63d2353864
commit 2993ba1838
3 changed files with 72 additions and 32 deletions
+3
View File
@@ -43,6 +43,9 @@ type Config struct {
CustomRenderer bool CustomRenderer bool
// Decorated reports whether window decorations are provided automatically. // Decorated reports whether window decorations are provided automatically.
Decorated bool 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. // ConfigEvent is sent whenever the configuration of a Window changes.
+31 -11
View File
@@ -576,6 +576,12 @@ func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_
w.config.Decorated = true w.config.Decorated = true
} }
if decorated != w.config.Decorated { 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.w.Event(ConfigEvent{Config: w.config})
w.redraw = true w.redraw = true
} }
@@ -1022,6 +1028,7 @@ func (w *window) Configure(options []Option) {
prev := w.config prev := w.config
cnf := w.config cnf := w.config
cnf.apply(cfg, options) cnf.apply(cfg, options)
w.config.decoHeight = cnf.decoHeight
switch cnf.Mode { switch cnf.Mode {
case Fullscreen: case Fullscreen:
@@ -1064,22 +1071,35 @@ func (w *window) Configure(options []Option) {
w.setTitle(prev, cnf) w.setTitle(prev, cnf)
if prev.Size != cnf.Size { if prev.Size != cnf.Size {
w.config.Size = cnf.Size w.config.Size = cnf.Size
w.size = cnf.Size.Div(w.scale) w.config.Size.Y += int(w.decoHeight()) * w.scale
} w.size = w.config.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.MinSize = cnf.MinSize
w.config.MaxSize = cnf.MaxSize
w.setWindowConstraints()
} }
w.w.Event(ConfigEvent{Config: w.config}) 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) { func (w *window) setTitle(prev, cnf Config) {
if prev.Title != cnf.Title { if prev.Title != cnf.Title {
w.config.Title = cnf.Title w.config.Title = cnf.Title
+38 -21
View File
@@ -85,9 +85,10 @@ type Window struct {
// capability. // capability.
enabled bool enabled bool
Config Config
height unit.Dp
currentHeight int
*material.Theme *material.Theme
*widget.Decorations *widget.Decorations
size image.Point // decorations size
} }
callbacks callbacks callbacks callbacks
@@ -137,10 +138,24 @@ type queue struct {
// Calling NewWindow more than once is not supported on // Calling NewWindow more than once is not supported on
// iOS, Android, WebAssembly. // iOS, Android, WebAssembly.
func NewWindow(options ...Option) *Window { 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{ defaultOptions := []Option{
Size(800, 600), Size(800, 600),
Title("Gio"), Title("Gio"),
Decorated(true), Decorated(true),
decoHeightOpt(decoHeight),
} }
options = append(defaultOptions, options...) options = append(defaultOptions, options...)
var cnf Config var cnf Config
@@ -162,6 +177,8 @@ func NewWindow(options ...Option) *Window {
actions: make(chan system.Action, 1), actions: make(chan system.Action, 1),
nocontext: cnf.CustomRenderer, nocontext: cnf.CustomRenderer,
} }
w.decorations.Theme = theme
w.decorations.Decorations = deco
w.decorations.enabled = cnf.Decorated w.decorations.enabled = cnf.Decorated
w.imeState.compose = key.Range{Start: -1, End: -1} w.imeState.compose = key.Range{Start: -1, End: -1}
w.semantic.ids = make(map[router.SemanticID]router.SemanticNode) w.semantic.ids = make(map[router.SemanticID]router.SemanticNode)
@@ -170,6 +187,12 @@ func NewWindow(options ...Option) *Window {
return w 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. // Events returns the channel where events are delivered.
func (w *Window) Events() <-chan event.Event { func (w *Window) Events() <-chan event.Event {
return w.out return w.out
@@ -474,6 +497,11 @@ func (c *callbacks) Event(e event.Event) bool {
opt(c.w.metric, &cnf) opt(c.w.metric, &cnf)
} }
c.w.decorations.enabled = cnf.Decorated 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) c.d.Configure(opts)
default: default:
} }
@@ -879,9 +907,7 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
case ConfigEvent: case ConfigEvent:
w.decorations.Config = e2.Config w.decorations.Config = e2.Config
if !w.fallbackDecorate() { if !w.fallbackDecorate() {
// Decorations are no longer applied. w.decorations.height = 0
w.decorations.Decorations = nil
w.decorations.size = image.Point{}
} }
e2.Config = w.effectiveConfig() e2.Config = w.effectiveConfig()
w.out <- e2 w.out <- e2
@@ -975,19 +1001,10 @@ func (w *Window) decorate(d driver, e system.FrameEvent, o *op.Ops) (size, offse
if !w.fallbackDecorate() { if !w.fallbackDecorate() {
return e.Size, image.Pt(0, 0) 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 deco := w.decorations.Decorations
if deco == nil {
deco = new(widget.Decorations)
w.decorations.Decorations = deco
}
allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize | allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize |
system.ActionClose | system.ActionMove 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. // Update the decorations based on the current window mode.
var actions system.Action var actions system.Action
switch m := w.decorations.Config.Mode; m { 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, Metric: e.Metric,
Constraints: layout.Exact(e.Size), Constraints: layout.Exact(e.Size),
} }
dims := style.Layout(gtx) style.Layout(gtx)
// Update the window based on the actions on the decorations. // Update the window based on the actions on the decorations.
w.Perform(deco.Actions()) w.Perform(deco.Actions())
// Offset to place the frame content below the decorations. // Offset to place the frame content below the decorations.
decoSize := image.Point{Y: dims.Size.Y} decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
appSize := e.Size.Sub(decoSize) if w.decorations.currentHeight != decoHeight {
if w.decorations.size != decoSize { w.decorations.currentHeight = decoHeight
w.decorations.size = decoSize
w.out <- ConfigEvent{Config: w.effectiveConfig()} 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 { func (w *Window) effectiveConfig() Config {
cnf := w.decorations.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 cnf.Decorated = w.decorations.enabled || cnf.Decorated
return cnf return cnf
} }