layout: add Background

It's relatively common to create a widget and then add a background to
it. Using layout.Stack causes bunch of heap allocs, which we would like
to avoid whenever we can.

This adds layout.Background which is roughly the same as:

    layout.Stack{Alignment: layout.C}.Layout(gtx,
    	layout.Expanded(background),
    	layout.Stacked(widget)
    )

goos: windows
goarch: amd64
pkg: gioui.org/layout
cpu: AMD Ryzen Threadripper 2950X 16-Core Processor
     │    Stack     │             Background              │
     │    sec/op    │   sec/op     vs base                │
*-32   203.80n ± 1%   83.36n ± 3%  -59.09% (p=0.000 n=10)

     │   Stack    │             Background             │
     │    B/op    │   B/op     vs base                 │
*-32   48.00 ± 0%   0.00 ± 0%  -100.00% (p=0.000 n=10)

     │   Stack    │             Background              │
     │ allocs/op  │ allocs/op   vs base                 │
*-32   2.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
This commit is contained in:
Egon Elbre
2023-11-15 16:08:06 +02:00
committed by Elias Naur
parent 8097df9930
commit f39245df99
6 changed files with 147 additions and 28 deletions
+24
View File
@@ -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),
+33
View File
@@ -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,
}
}
+66
View File
@@ -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}
}