diff --git a/layout/example_test.go b/layout/example_test.go index e2f4a2f7..b475235a 100644 --- a/layout/example_test.go +++ b/layout/example_test.go @@ -103,6 +103,30 @@ func ExampleStack() { // Expand: {(50,50) (100,100)} } +func ExampleBackground() { + gtx := layout.Context{ + Ops: new(op.Ops), + Constraints: layout.Constraints{ + Max: image.Point{X: 100, Y: 100}, + }, + } + + layout.Background{}.Layout(gtx, + // Force widget to the same size as the second. + func(gtx layout.Context) layout.Dimensions { + fmt.Printf("Expand: %v\n", gtx.Constraints) + return layoutWidget(gtx, 10, 10) + }, + // Rigid 50x50 widget. + func(gtx layout.Context) layout.Dimensions { + return layoutWidget(gtx, 50, 50) + }, + ) + + // Output: + // Expand: {(50,50) (100,100)} +} + func ExampleList() { gtx := layout.Context{ Ops: new(op.Ops), diff --git a/layout/stack.go b/layout/stack.go index 72f81708..6eef9f6d 100644 --- a/layout/stack.go +++ b/layout/stack.go @@ -118,3 +118,36 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions { Baseline: baseline, } } + +// Background lays out single child widget on top of a background, +// centering, if necessary. +type Background struct{} + +// Layout a widget and then add a background to it. +func (Background) Layout(gtx Context, background, widget Widget) Dimensions { + macro := op.Record(gtx.Ops) + wdims := widget(gtx) + baseline := wdims.Baseline + call := macro.Stop() + + cgtx := gtx + cgtx.Constraints.Min = gtx.Constraints.Constrain(wdims.Size) + bdims := background(cgtx) + + if bdims.Size != wdims.Size { + p := image.Point{ + X: (bdims.Size.X - wdims.Size.X) / 2, + Y: (bdims.Size.Y - wdims.Size.Y) / 2, + } + baseline += (bdims.Size.Y - wdims.Size.Y) / 2 + trans := op.Offset(p).Push(gtx.Ops) + defer trans.Pop() + } + + call.Add(gtx.Ops) + + return Dimensions{ + Size: bdims.Size, + Baseline: baseline, + } +} diff --git a/layout/stack_test.go b/layout/stack_test.go new file mode 100644 index 00000000..b7182894 --- /dev/null +++ b/layout/stack_test.go @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package layout + +import ( + "image" + "testing" + + "gioui.org/op" +) + +func BenchmarkStack(b *testing.B) { + gtx := Context{ + Ops: new(op.Ops), + Constraints: Constraints{ + Max: image.Point{X: 100, Y: 100}, + }, + } + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + gtx.Ops.Reset() + + Stack{}.Layout(gtx, + Expanded(emptyWidget{ + Size: image.Point{X: 60, Y: 60}, + }.Layout), + Stacked(emptyWidget{ + Size: image.Point{X: 30, Y: 30}, + }.Layout), + ) + } +} + +func BenchmarkBackground(b *testing.B) { + gtx := Context{ + Ops: new(op.Ops), + Constraints: Constraints{ + Max: image.Point{X: 100, Y: 100}, + }, + } + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + gtx.Ops.Reset() + + Background{}.Layout(gtx, + emptyWidget{ + Size: image.Point{X: 60, Y: 60}, + }.Layout, + emptyWidget{ + Size: image.Point{X: 30, Y: 30}, + }.Layout, + ) + } +} + +type emptyWidget struct { + Size image.Point +} + +func (w emptyWidget) Layout(gtx Context) Dimensions { + return Dimensions{Size: w.Size} +} diff --git a/widget/material/button.go b/widget/material/button.go index 15cad855..4edab341 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -93,9 +93,8 @@ func IconButton(th *Theme, button *widget.Clickable, icon *widget.Icon, descript func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) layout.Dimensions { return button.Layout(gtx, func(gtx layout.Context) layout.Dimensions { semantic.Button.Add(gtx.Ops) - constraints := gtx.Constraints - return layout.Stack{}.Layout(gtx, - layout.Expanded(func(gtx layout.Context) layout.Dimensions { + return layout.Background{}.Layout(gtx, + func(gtx layout.Context) layout.Dimensions { defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop() if button.Hovered() || button.Focused() { paint.Fill(gtx.Ops, f32color.Hovered(color.NRGBA{})) @@ -104,11 +103,8 @@ func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) la drawInk(gtx, c) } return layout.Dimensions{Size: gtx.Constraints.Min} - }), - layout.Stacked(func(gtx layout.Context) layout.Dimensions { - gtx.Constraints = constraints - return w(gtx) - }), + }, + w, ) }) } @@ -131,8 +127,8 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di min := gtx.Constraints.Min return b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions { semantic.Button.Add(gtx.Ops) - return layout.Stack{Alignment: layout.Center}.Layout(gtx, - layout.Expanded(func(gtx layout.Context) layout.Dimensions { + return layout.Background{}.Layout(gtx, + func(gtx layout.Context) layout.Dimensions { rr := gtx.Dp(b.CornerRadius) defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop() background := b.Background @@ -147,11 +143,11 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di drawInk(gtx, c) } return layout.Dimensions{Size: gtx.Constraints.Min} - }), - layout.Stacked(func(gtx layout.Context) layout.Dimensions { + }, + func(gtx layout.Context) layout.Dimensions { gtx.Constraints.Min = min return layout.Center.Layout(gtx, w) - }), + }, ) }) } @@ -163,8 +159,8 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions { if d := b.Description; d != "" { semantic.DescriptionOp(b.Description).Add(gtx.Ops) } - return layout.Stack{Alignment: layout.Center}.Layout(gtx, - layout.Expanded(func(gtx layout.Context) layout.Dimensions { + return layout.Background{}.Layout(gtx, + func(gtx layout.Context) layout.Dimensions { rr := (gtx.Constraints.Min.X + gtx.Constraints.Min.Y) / 4 defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop() background := b.Background @@ -179,8 +175,8 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions { drawInk(gtx, c) } return layout.Dimensions{Size: gtx.Constraints.Min} - }), - layout.Stacked(func(gtx layout.Context) layout.Dimensions { + }, + func(gtx layout.Context) layout.Dimensions { return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { size := gtx.Dp(b.Size) if b.Icon != nil { @@ -191,7 +187,7 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions { Size: image.Point{X: size, Y: size}, } }) - }), + }, ) }) c := m.Stop() diff --git a/widget/material/decorations.go b/widget/material/decorations.go index d327dbbc..2c15a104 100644 --- a/widget/material/decorations.go +++ b/widget/material/decorations.go @@ -88,18 +88,18 @@ func (d DecorationsStyle) layoutDecorations(gtx layout.Context) layout.Dimension cl := d.Decorations.Clickable(a) dims := cl.Layout(gtx, func(gtx layout.Context) layout.Dimensions { semantic.Button.Add(gtx.Ops) - return layout.Stack{Alignment: layout.Center}.Layout(gtx, - layout.Expanded(func(gtx layout.Context) layout.Dimensions { + return layout.Background{}.Layout(gtx, + func(gtx layout.Context) layout.Dimensions { defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop() for _, c := range cl.History() { drawInk(gtx, c) } return layout.Dimensions{Size: gtx.Constraints.Min} - }), - layout.Stacked(func(gtx layout.Context) layout.Dimensions { + }, + func(gtx layout.Context) layout.Dimensions { paint.ColorOp{Color: d.Foreground}.Add(gtx.Ops) return inset.Layout(gtx, w) - }), + }, ) }) size.X += dims.Size.X diff --git a/widget/material/list.go b/widget/material/list.go index 6974b422..baccdf57 100644 --- a/widget/material/list.go +++ b/widget/material/list.go @@ -169,8 +169,8 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta // the minimum to zero. outerConstraints := gtx.Constraints - return layout.Stack{}.Layout(gtx, - layout.Expanded(func(gtx layout.Context) layout.Dimensions { + return layout.Background{}.Layout(gtx, + func(gtx layout.Context) layout.Dimensions { // Lay out the draggable track underneath the scroll indicator. area := image.Rectangle{ Max: gtx.Constraints.Min, @@ -187,8 +187,8 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta paint.FillShape(gtx.Ops, s.Track.Color, clip.Rect(area).Op()) return layout.Dimensions{} - }), - layout.Stacked(func(gtx layout.Context) layout.Dimensions { + }, + func(gtx layout.Context) layout.Dimensions { gtx.Constraints = outerConstraints return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { // Use axis-independent constraints. @@ -231,7 +231,7 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta return layout.Dimensions{Size: axis.Convert(gtx.Constraints.Min)} }) - }), + }, ) }