~eliasnaur/gio

7814da47a0ffdf00462fbd0095af5e3d23757ef2 — Elias Naur 4 years ago f60a5c7
layout: simplify Stack API

Similar to what a previous commit did for Flex, this change simplifies
Stack to just one Layout call:

	layout.Stack{}.Layout(gtx,
		layout.Stacked(func() {...}),
		layout.Expanded(func() {...}),
	)

Signed-off-by: Elias Naur <mail@eliasnaur.com>
3 files changed, 125 insertions(+), 118 deletions(-)

M layout/example_test.go
M layout/stack.go
M widget/material/button.go
M layout/example_test.go => layout/example_test.go +13 -14
@@ 74,21 74,20 @@ func ExampleFlex() {
func ExampleStack() {
	gtx := new(layout.Context)
	gtx.Reset(nil, image.Point{X: 100, Y: 100})
	gtx.Constraints.Width.Min = 0
	gtx.Constraints.Height.Min = 0

	stack := layout.Stack{}

	// Rigid 50x50 widget.
	child1 := stack.Rigid(gtx, func() {
		layoutWidget(gtx, 50, 50)
	})

	// Force widget to the same size as the first.
	child2 := stack.Expand(gtx, func() {
		fmt.Printf("Expand: %v\n", gtx.Constraints)
		layoutWidget(gtx, 10, 10)
	})

	stack.Layout(gtx, child1, child2)
	layout.Stack{}.Layout(gtx,
		// Force widget to the same size as the second.
		layout.Expanded(func() {
			fmt.Printf("Expand: %v\n", gtx.Constraints)
			layoutWidget(gtx, 10, 10)
		}),
		// Rigid 50x50 widget.
		layout.Stacked(func() {
			layoutWidget(gtx, 50, 50)
		}),
	)

	// Output:
	// Expand: {{50 100} {50 100}}

M layout/stack.go => layout/stack.go +58 -49
@@ 14,70 14,79 @@ type Stack struct {
	// Alignment is the direction to align children
	// smaller than the available space.
	Alignment Direction

	maxSZ image.Point
	// Use an empty StackOp for tracking whether Rigid, Flex
	// is called in the same layout scope as Layout.
	begun bool
	stack op.StackOp
}

// StackChild is the layout result of a call to End.
// StackChild represents a child for a Stack layout.
type StackChild struct {
	expanded bool
	widget   Widget

	// Scratch space.
	macro op.MacroOp
	dims  Dimensions
}

// Rigid lays out a widget with the same constraints that were
// passed to Init.
func (s *Stack) Rigid(gtx *Context, w Widget) StackChild {
	cs := gtx.Constraints
	cs.Width.Min = 0
	cs.Height.Min = 0
	var m op.MacroOp
	m.Record(gtx.Ops)
	dims := ctxLayout(gtx, cs, w)
	m.Stop()
	s.expand(gtx.Ops, dims)
	return StackChild{m, dims}
// Stacked returns a Stack child that laid out with the same maximum
// constraints as the Stack.
func Stacked(w Widget) StackChild {
	return StackChild{
		widget: w,
	}
}

// Expand lays out a widget.
func (s *Stack) Expand(gtx *Context, w Widget) StackChild {
	var m op.MacroOp
	m.Record(gtx.Ops)
	cs := Constraints{
		Width:  Constraint{Min: s.maxSZ.X, Max: gtx.Constraints.Width.Max},
		Height: Constraint{Min: s.maxSZ.Y, Max: gtx.Constraints.Height.Max},
// Expanded returns a Stack child that is forced to take up at least
// the the space as the largest Stacked.
func Expanded(w Widget) StackChild {
	return StackChild{
		expanded: true,
		widget:   w,
	}
	dims := ctxLayout(gtx, cs, w)
	m.Stop()
	s.expand(gtx.Ops, dims)
	return StackChild{m, dims}
}

func (s *Stack) expand(ops *op.Ops, dims Dimensions) {
	if !s.begun {
		s.stack.Push(ops)
		s.begun = true
	}
	if w := dims.Size.X; w > s.maxSZ.X {
		s.maxSZ.X = w
// Layout a stack of children. The position of the children are
// determined by the specified order, but Stacked children are laid out
// before Expanded children.
func (s Stack) Layout(gtx *Context, children ...StackChild) {
	var maxSZ image.Point
	// First lay out Stacked children.
	for i, w := range children {
		if w.expanded {
			continue
		}
		cs := gtx.Constraints
		cs.Width.Min = 0
		cs.Height.Min = 0
		var m op.MacroOp
		m.Record(gtx.Ops)
		dims := ctxLayout(gtx, cs, w.widget)
		m.Stop()
		if w := dims.Size.X; w > maxSZ.X {
			maxSZ.X = w
		}
		if h := dims.Size.Y; h > maxSZ.Y {
			maxSZ.Y = h
		}
		children[i].macro = m
		children[i].dims = dims
	}
	if h := dims.Size.Y; h > s.maxSZ.Y {
		s.maxSZ.Y = h
	maxSZ = gtx.Constraints.Constrain(maxSZ)
	// Then lay out Expanded children.
	for i, w := range children {
		if !w.expanded {
			continue
		}
		var m op.MacroOp
		m.Record(gtx.Ops)
		cs := Constraints{
			Width:  Constraint{Min: maxSZ.X, Max: gtx.Constraints.Width.Max},
			Height: Constraint{Min: maxSZ.Y, Max: gtx.Constraints.Height.Max},
		}
		dims := ctxLayout(gtx, cs, w.widget)
		m.Stop()
		children[i].macro = m
		children[i].dims = dims
	}
}

// Layout a list of children. The order of the children determines their laid
// out order.
func (s *Stack) Layout(gtx *Context, children ...StackChild) {
	if len(children) > 0 {
		s.stack.Pop()
	}
	maxSZ := gtx.Constraints.Constrain(s.maxSZ)
	s.maxSZ = image.Point{}
	s.begun = false
	var baseline int
	for _, ch := range children {
		sz := ch.dims.Size

M widget/material/button.go => widget/material/button.go +54 -55
@@ 60,68 60,67 @@ func (t *Theme) IconButton(icon *Icon) IconButton {
func (b Button) Layout(gtx *layout.Context, button *widget.Button) {
	col := b.Color
	bgcol := b.Background
	st := layout.Stack{Alignment: layout.Center}
	hmin := gtx.Constraints.Width.Min
	vmin := gtx.Constraints.Height.Min
	lbl := st.Rigid(gtx, func() {
		gtx.Constraints.Width.Min = hmin
		gtx.Constraints.Height.Min = vmin
		layout.Align(layout.Center).Layout(gtx, func() {
			layout.Inset{Top: unit.Dp(10), Bottom: unit.Dp(10), Left: unit.Dp(12), Right: unit.Dp(12)}.Layout(gtx, func() {
				paint.ColorOp{Color: col}.Add(gtx.Ops)
				widget.Label{}.Layout(gtx, b.shaper, b.Font, b.Text)
	layout.Stack{Alignment: layout.Center}.Layout(gtx,
		layout.Expanded(func() {
			rr := float32(gtx.Px(unit.Dp(4)))
			clip.Rect{
				Rect: f32.Rectangle{Max: f32.Point{
					X: float32(gtx.Constraints.Width.Min),
					Y: float32(gtx.Constraints.Height.Min),
				}},
				NE: rr, NW: rr, SE: rr, SW: rr,
			}.Op(gtx.Ops).Add(gtx.Ops)
			fill(gtx, bgcol)
			for _, c := range button.History() {
				drawInk(gtx, c)
			}
		}),
		layout.Stacked(func() {
			gtx.Constraints.Width.Min = hmin
			gtx.Constraints.Height.Min = vmin
			layout.Align(layout.Center).Layout(gtx, func() {
				layout.Inset{Top: unit.Dp(10), Bottom: unit.Dp(10), Left: unit.Dp(12), Right: unit.Dp(12)}.Layout(gtx, func() {
					paint.ColorOp{Color: col}.Add(gtx.Ops)
					widget.Label{}.Layout(gtx, b.shaper, b.Font, b.Text)
				})
			})
		})
		pointer.Rect(image.Rectangle{Max: gtx.Dimensions.Size}).Add(gtx.Ops)
		button.Layout(gtx)
	})
	bg := st.Expand(gtx, func() {
		rr := float32(gtx.Px(unit.Dp(4)))
		clip.Rect{
			Rect: f32.Rectangle{Max: f32.Point{
				X: float32(gtx.Constraints.Width.Min),
				Y: float32(gtx.Constraints.Height.Min),
			}},
			NE: rr, NW: rr, SE: rr, SW: rr,
		}.Op(gtx.Ops).Add(gtx.Ops)
		fill(gtx, bgcol)
		for _, c := range button.History() {
			drawInk(gtx, c)
		}
	})
	st.Layout(gtx, bg, lbl)
			pointer.Rect(image.Rectangle{Max: gtx.Dimensions.Size}).Add(gtx.Ops)
			button.Layout(gtx)
		}),
	)
}

func (b IconButton) Layout(gtx *layout.Context, button *widget.Button) {
	st := layout.Stack{}
	ico := st.Rigid(gtx, func() {
		layout.UniformInset(b.Padding).Layout(gtx, func() {
			size := gtx.Px(b.Size) - 2*gtx.Px(b.Padding)
			if b.Icon != nil {
				b.Icon.Color = b.Color
				b.Icon.Layout(gtx, unit.Px(float32(size)))
			}
			gtx.Dimensions = layout.Dimensions{
				Size: image.Point{X: size, Y: size},
	layout.Stack{}.Layout(gtx,
		layout.Expanded(func() {
			size := float32(gtx.Constraints.Width.Min)
			rr := float32(size) * .5
			clip.Rect{
				Rect: f32.Rectangle{Max: f32.Point{X: size, Y: size}},
				NE:   rr, NW: rr, SE: rr, SW: rr,
			}.Op(gtx.Ops).Add(gtx.Ops)
			fill(gtx, b.Background)
			for _, c := range button.History() {
				drawInk(gtx, c)
			}
		})
		pointer.Ellipse(image.Rectangle{Max: gtx.Dimensions.Size}).Add(gtx.Ops)
		button.Layout(gtx)
	})
	bgcol := b.Background
	bg := st.Expand(gtx, func() {
		size := float32(gtx.Constraints.Width.Min)
		rr := float32(size) * .5
		clip.Rect{
			Rect: f32.Rectangle{Max: f32.Point{X: size, Y: size}},
			NE:   rr, NW: rr, SE: rr, SW: rr,
		}.Op(gtx.Ops).Add(gtx.Ops)
		fill(gtx, bgcol)
		for _, c := range button.History() {
			drawInk(gtx, c)
		}
	})
	st.Layout(gtx, bg, ico)
		}),
		layout.Stacked(func() {
			layout.UniformInset(b.Padding).Layout(gtx, func() {
				size := gtx.Px(b.Size) - 2*gtx.Px(b.Padding)
				if b.Icon != nil {
					b.Icon.Color = b.Color
					b.Icon.Layout(gtx, unit.Px(float32(size)))
				}
				gtx.Dimensions = layout.Dimensions{
					Size: image.Point{X: size, Y: size},
				}
			})
			pointer.Ellipse(image.Rectangle{Max: gtx.Dimensions.Size}).Add(gtx.Ops)
			button.Layout(gtx)
		}),
	)
}

func toPointF(p image.Point) f32.Point {