~eliasnaur/gio

2993ba18383a758e7aba509b57cf74289b0bccc5 — Elias Naur 10 months ago 63d2353
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>
3 files changed, 72 insertions(+), 32 deletions(-)

M app/os.go
M app/os_wayland.go
M app/window.go
M app/os.go => app/os.go +3 -0
@@ 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.

M app/os_wayland.go => app/os_wayland.go +31 -11
@@ 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

M app/window.go => app/window.go +38 -21
@@ 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
}