diff --git a/layout/example_test.go b/layout/example_test.go index 86174292..72fa0c10 100644 --- a/layout/example_test.go +++ b/layout/example_test.go @@ -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}} diff --git a/layout/stack.go b/layout/stack.go index 5dd87032..90bfaa84 100644 --- a/layout/stack.go +++ b/layout/stack.go @@ -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} -} - -// 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}, - } - 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 - } - if h := dims.Size.Y; h > s.maxSZ.Y { - s.maxSZ.Y = h +// 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, } } -// 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() +// 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, } - maxSZ := gtx.Constraints.Constrain(s.maxSZ) - s.maxSZ = image.Point{} - s.begun = false +} + +// 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 + } + 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 + } + var baseline int for _, ch := range children { sz := ch.dims.Size diff --git a/widget/material/button.go b/widget/material/button.go index 17c9810b..b1a440d5 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -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))) + 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) } - 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) - }) - 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 {