Files
gio-patched/ui/layout/stack.go
T
Elias Naur 29639565cd ui/layout: replace implicit begin/end scopes with explicit function scopes
Before this change, layout objects followed a pattern where a
begin method would set up the layout and return a tweaked set
of constraints, and a end method would take the widget dimensions
and return the tweaked dimensions.

As has been pointed out, this process is error prone, because the
scope of the layout objects are not clear and because it is easy
to swap two begins or two ends.

It turns out that it is possible to implement layout with function
scopes in garbage free way. A typical layout changes from

        al := layout.Align{Alignment: layout.NE}
	cs = al.Begin(ops, cs)
	in := layout.Inset{Top: ui.Dp(16)}
	cs = in.Begin(c, ops, cs)
	txt := fmt.Sprintf("m: %d %s", mallocs, u.profile.Timings)
	dims := text.Label{Material: theme.text, Face: u.face(fonts.mono, 10), Text: txt}.Layout(ops, cs)
	dims = in.End(dims)
	return al.End(dims)

to

        al := layout.Align{Alignment: layout.NE}
	return al.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
	       in := layout.Inset{Top: ui.Dp(16)}
	       return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
		       txt := fmt.Sprintf("m: %d %s", mallocs, u.profile.Timings)
		       return text.Label{Material: theme.text, Face: u.face(fonts.mono, 10), Text: txt}.Layout(ops, cs)
	       })
	})

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2019-09-18 19:31:36 +02:00

123 lines
2.5 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui"
)
// Stack lays out child elements on top of each other,
// according to an alignment direction.
type Stack struct {
// Alignment is the direction to align children
// smaller than the available space.
Alignment Direction
macro ui.MacroOp
ops *ui.Ops
constrained bool
cs Constraints
begun bool
maxSZ image.Point
baseline int
}
// StackChild is the layout result of a call to End.
type StackChild struct {
macro ui.MacroOp
dims Dimensions
}
// Init a stack before calling Rigid or Expand.
func (s *Stack) Init(ops *ui.Ops, cs Constraints) *Stack {
s.ops = ops
s.cs = cs
s.constrained = true
s.maxSZ = image.Point{}
s.baseline = 0
return s
}
func (s *Stack) begin() {
if !s.constrained {
panic("must Init before adding a child")
}
if s.begun {
panic("must End before adding a child")
}
s.begun = true
s.macro.Record(s.ops)
}
// Rigid lays out a widget with the same constraints that were
// passed to Init.
func (s *Stack) Rigid(w Widget) StackChild {
s.begin()
return s.end(w(s.cs))
}
// Expand lays out a widget with constraints that exactly match
// the biggest child previously added.
func (s *Stack) Expand(w Widget) StackChild {
s.begin()
cs := Constraints{
Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X},
Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y},
}
return s.end(w(cs))
}
// End a child by specifying its dimensions.
func (s *Stack) end(dims Dimensions) StackChild {
s.macro.Stop()
s.begun = false
if w := dims.Size.X; w > s.maxSZ.X {
s.maxSZ.X = w
}
if h := dims.Size.Y; h > s.maxSZ.Y {
s.maxSZ.Y = h
}
if s.baseline == 0 {
if b := dims.Baseline; b != dims.Size.Y {
s.baseline = b
}
}
return StackChild{s.macro, dims}
}
// Layout a list of children. The order of the children determines their laid
// out order.
func (s *Stack) Layout(children ...StackChild) Dimensions {
for _, ch := range children {
sz := ch.dims.Size
var p image.Point
switch s.Alignment {
case N, S, Center:
p.X = (s.maxSZ.X - sz.X) / 2
case NE, SE, E:
p.X = s.maxSZ.X - sz.X
}
switch s.Alignment {
case W, Center, E:
p.Y = (s.maxSZ.Y - sz.Y) / 2
case SW, S, SE:
p.Y = s.maxSZ.Y - sz.Y
}
var stack ui.StackOp
stack.Push(s.ops)
ui.TransformOp{}.Offset(toPointF(p)).Add(s.ops)
ch.macro.Add(s.ops)
stack.Pop()
}
b := s.baseline
if b == 0 {
b = s.maxSZ.Y
}
return Dimensions{
Size: s.maxSZ,
Baseline: b,
}
}