~eliasnaur/gio

3af01a3f431244214a439c7bb392d0166e724a00 — Elias Naur 6 months ago af10307
layout: change Widget to take explicit Context and return explicit Dimensions

Change the definition of Widget from the implicit

        type Widget func()

to the explicit functional

        type Widget func(gtx layout.Context) layout.Dimensions

The advantages are numerous:

- Clearer connection between the incoming context and the output dimensions.
- Returning the Dimensions are impossible to omit.
- Contexts passed by value, so its fields can be exported
and freely mutated by the program.

The only disadvantage is the longer function literals and the many "returns".
What tipped the scales in favour of the explicit Widget variant is that type
aliases can dramatically shorten the literals:

	type (
		C = layout.Context
		D = layout.Dimensions
	)

	widget := func(gtx C) D {
		...
	}

Note that the aliases are not part of the Gio API and it is up to each user
whether they want to use them.

Finally the Go proposal for lightweight function literals,
https://github.com/golang/go/issues/21498, may remove the disadvantage
completely in future.

Context becomes a plain struct with only public fields, and its Reset is
replaced by a NewContext convenience constructor.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
M layout/context.go => layout/context.go +28 -37
@@ 20,63 20,54 @@ type Context struct {
	// Constraints track the constraints for the active widget or
	// layout.
	Constraints Constraints
	// Dimensions track the result of the most recent layout
	// operation.
	Dimensions Dimensions

	cfg   system.Config
	queue event.Queue
	Config system.Config
	Queue  event.Queue
	*op.Ops
}

// layout a widget with a set of constraints and return its
// dimensions. The widget dimensions are constrained and the previous
// constraints are restored after layout.
func ctxLayout(gtx *Context, cs Constraints, w Widget) Dimensions {
	saved := gtx.Constraints
	gtx.Constraints = cs
	gtx.Dimensions = Dimensions{}
	w()
	gtx.Dimensions.Size = cs.Constrain(gtx.Dimensions.Size)
	gtx.Constraints = saved
	return gtx.Dimensions
}

// Reset the context. The constraints' minimum and maximum values are
// set to the size.
func (c *Context) Reset(q event.Queue, cfg system.Config, size image.Point) {
	c.Constraints = Constraints{Min: size, Max: size}
	c.Dimensions = Dimensions{}
	c.cfg = cfg
	c.queue = q
	if c.Ops == nil {
		c.Ops = new(op.Ops)
// NewContext is a shorthand for
//
//   Context{
//     Ops: ops,
//     Queue: q,
//     Config: cfg,
//     Constraints: Exact(size),
//   }
//
// NewContext calls ops.Reset.
func NewContext(ops *op.Ops, q event.Queue, cfg system.Config, size image.Point) Context {
	ops.Reset()
	return Context{
		Ops:         ops,
		Queue:       q,
		Config:      cfg,
		Constraints: Exact(size),
	}
	c.Ops.Reset()
}

// Now returns the configuration time or the zero time.
func (c *Context) Now() time.Time {
	if c.cfg == nil {
func (c Context) Now() time.Time {
	if c.Config == nil {
		return time.Time{}
	}
	return c.cfg.Now()
	return c.Config.Now()
}

// Px maps the value to pixels. If no configuration is set,
// Px returns the rounded value of v.
func (c *Context) Px(v unit.Value) int {
	if c.cfg == nil {
func (c Context) Px(v unit.Value) int {
	if c.Config == nil {
		return int(math.Round(float64(v.V)))
	}
	return c.cfg.Px(v)
	return c.Config.Px(v)
}

// Events returns the events available for the key. If no
// queue is configured, Events returns nil.
func (c *Context) Events(k event.Tag) []event.Event {
	if c.queue == nil {
func (c Context) Events(k event.Tag) []event.Event {
	if c.Queue == nil {
		return nil
	}
	return c.queue.Events(k)
	return c.Queue.Events(k)
}

M layout/doc.go => layout/doc.go +7 -8
@@ 13,17 13,16 @@ in an implicit Context to keep the Widget declaration short.

For example, to add space above a widget:

	gtx := new(layout.Context)
	gtx.Reset(...)
	var gtx layout.Context

	// Configure a top inset.
	inset := layout.Inset{Top: unit.Dp(8), ...}
	// Use the inset to lay out a widget.
	inset.Layout(gtx, func() {
		// Lay out widget and determine its size given the constraints.
		// Lay out widget and determine its size given the constraints
		// in gtx.Constraints.
		...
		dims := layout.Dimensions{...}
		gtx.Dimensions = dims
		return layout.Dimensions{...}
	})

Note that the example does not generate any garbage even though the


@@ 37,10 36,10 @@ be created from a few generic layouts.
This example both aligns and insets a child:

	inset := layout.Inset{...}
	inset.Layout(gtx, func() {
	inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		align := layout.Align(...)
		align.Layout(gtx, func() {
			widget.Layout(gtx, ...)
		return align.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
			return widget.Layout(gtx, ...)
		})
	})


M layout/example_test.go => layout/example_test.go +51 -34
@@ 5,24 5,29 @@ import (
	"image"

	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/unit"
)

func ExampleInset() {
	gtx := new(layout.Context)
	gtx.Reset(nil, nil, image.Point{X: 100, Y: 100})
	// Loose constraints with no minimal size.
	gtx.Constraints.Min = image.Point{}
	gtx := layout.Context{
		Ops: new(op.Ops),
		// Loose constraints with no minimal size.
		Constraints: layout.Constraints{
			Max: image.Point{X: 100, Y: 100},
		},
	}

	// Inset all edges by 10.
	inset := layout.UniformInset(unit.Dp(10))
	inset.Layout(gtx, func() {
	dims := inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		// Lay out a 50x50 sized widget.
		layoutWidget(gtx, 50, 50)
		fmt.Println(gtx.Dimensions.Size)
		dims := layoutWidget(gtx, 50, 50)
		fmt.Println(dims.Size)
		return dims
	})

	fmt.Println(gtx.Dimensions.Size)
	fmt.Println(dims.Size)

	// Output:
	// (50,50)


@@ 30,17 35,20 @@ func ExampleInset() {
}

func ExampleDirection() {
	gtx := new(layout.Context)
	// Rigid constraints with both minimum and maximum set.
	gtx.Reset(nil, nil, image.Point{X: 100, Y: 100})
	gtx := layout.Context{
		Ops: new(op.Ops),
		// Rigid constraints with both minimum and maximum set.
		Constraints: layout.Exact(image.Point{X: 100, Y: 100}),
	}

	layout.Center.Layout(gtx, func() {
	dims := layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		// Lay out a 50x50 sized widget.
		layoutWidget(gtx, 50, 50)
		fmt.Println(gtx.Dimensions.Size)
		dims := layoutWidget(gtx, 50, 50)
		fmt.Println(dims.Size)
		return dims
	})

	fmt.Println(gtx.Dimensions.Size)
	fmt.Println(dims.Size)

	// Output:
	// (50,50)


@@ 48,19 56,22 @@ func ExampleDirection() {
}

func ExampleFlex() {
	gtx := new(layout.Context)
	gtx.Reset(nil, nil, image.Point{X: 100, Y: 100})
	gtx := layout.Context{
		Ops: new(op.Ops),
		// Rigid constraints with both minimum and maximum set.
		Constraints: layout.Exact(image.Point{X: 100, Y: 100}),
	}

	layout.Flex{}.Layout(gtx,
		// Rigid 10x10 widget.
		layout.Rigid(func() {
		layout.Rigid(func(gtx layout.Context) layout.Dimensions {
			fmt.Printf("Rigid: %v\n", gtx.Constraints)
			layoutWidget(gtx, 10, 10)
			return layoutWidget(gtx, 10, 10)
		}),
		// Child with 50% space allowance.
		layout.Flexed(0.5, func() {
		layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions {
			fmt.Printf("50%%: %v\n", gtx.Constraints)
			layoutWidget(gtx, 10, 10)
			return layoutWidget(gtx, 10, 10)
		}),
	)



@@ 70,19 81,22 @@ func ExampleFlex() {
}

func ExampleStack() {
	gtx := new(layout.Context)
	gtx.Reset(nil, nil, image.Point{X: 100, Y: 100})
	gtx.Constraints.Min = image.Point{}
	gtx := layout.Context{
		Ops: new(op.Ops),
		Constraints: layout.Constraints{
			Max: image.Point{X: 100, Y: 100},
		},
	}

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



@@ 91,17 105,20 @@ func ExampleStack() {
}

func ExampleList() {
	gtx := new(layout.Context)
	gtx.Reset(nil, nil, image.Point{X: 100, Y: 100})
	gtx := layout.Context{
		Ops: new(op.Ops),
		// Rigid constraints with both minimum and maximum set.
		Constraints: layout.Exact(image.Point{X: 100, Y: 100}),
	}

	// The list is 1e6 elements, but only 5 fit the constraints.
	const listLen = 1e6

	var list layout.List
	count := 0
	list.Layout(gtx, listLen, func(i int) {
	list.Layout(gtx, listLen, func(gtx layout.Context, i int) layout.Dimensions {
		count++
		layoutWidget(gtx, 20, 20)
		return layoutWidget(gtx, 20, 20)
	})

	fmt.Println(count)


@@ 110,8 127,8 @@ func ExampleList() {
	// 5
}

func layoutWidget(ctx *layout.Context, width, height int) {
	ctx.Dimensions = layout.Dimensions{
func layoutWidget(ctx layout.Context, width, height int) layout.Dimensions {
	return layout.Dimensions{
		Size: image.Point{
			X: width,
			Y: height,

M layout/flex.go => layout/flex.go +8 -4
@@ 74,7 74,7 @@ func Flexed(weight float32, widget Widget) FlexChild {
// Layout a list of children. The position of the children are
// determined by the specified order, but Rigid children are laid out
// before Flexed children.
func (f Flex) Layout(gtx *Context, children ...FlexChild) {
func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
	size := 0
	// Lay out Rigid children.
	for i, child := range children {


@@ 91,7 91,9 @@ func (f Flex) Layout(gtx *Context, children ...FlexChild) {
		cs = axisConstraints(f.Axis, 0, mainMax, crossMin, crossMax)
		var m op.MacroOp
		m.Record(gtx.Ops)
		dims := ctxLayout(gtx, cs, child.widget)
		gtx := gtx
		gtx.Constraints = cs
		dims := child.widget(gtx)
		m.Stop()
		sz := axisMain(f.Axis, dims.Size)
		size += sz


@@ 124,7 126,9 @@ func (f Flex) Layout(gtx *Context, children ...FlexChild) {
		cs = axisConstraints(f.Axis, flexSize, flexSize, crossMin, crossMax)
		var m op.MacroOp
		m.Record(gtx.Ops)
		dims := ctxLayout(gtx, cs, child.widget)
		gtx := gtx
		gtx.Constraints = cs
		dims := child.widget(gtx)
		m.Stop()
		sz := axisMain(f.Axis, dims.Size)
		size += sz


@@ 200,7 204,7 @@ func (f Flex) Layout(gtx *Context, children ...FlexChild) {
		mainSize += space / (len(children) * 2)
	}
	sz := axisPoint(f.Axis, mainSize, maxCross)
	gtx.Dimensions = Dimensions{Size: sz, Baseline: sz.Y - maxBaseline}
	return Dimensions{Size: sz, Baseline: sz.Y - maxBaseline}
}

func axisPoint(a Axis, main, cross int) image.Point {

M layout/layout.go => layout/layout.go +9 -9
@@ 40,7 40,7 @@ type Direction uint8

// Widget is a function scope for drawing, processing events and
// computing dimensions for a user interface element.
type Widget func()
type Widget func(gtx Context) Dimensions

const (
	Start Alignment = iota


@@ 111,7 111,7 @@ type Inset struct {
}

// Layout a widget.
func (in Inset) Layout(gtx *Context, w Widget) {
func (in Inset) Layout(gtx Context, w Widget) Dimensions {
	top := gtx.Px(in.Top)
	right := gtx.Px(in.Right)
	bottom := gtx.Px(in.Bottom)


@@ 138,9 138,10 @@ func (in Inset) Layout(gtx *Context, w Widget) {
	var stack op.StackOp
	stack.Push(gtx.Ops)
	op.TransformOp{}.Offset(FPt(image.Point{X: left, Y: top})).Add(gtx.Ops)
	dims := ctxLayout(gtx, mcs, w)
	gtx.Constraints = mcs
	dims := w(gtx)
	stack.Pop()
	gtx.Dimensions = Dimensions{
	return Dimensions{
		Size:     dims.Size.Add(image.Point{X: right + left, Y: top + bottom}),
		Baseline: dims.Baseline + bottom,
	}


@@ 153,13 154,12 @@ func UniformInset(v unit.Value) Inset {
}

// Layout a widget according to the direction.
func (a Direction) Layout(gtx *Context, w Widget) {
func (a Direction) Layout(gtx Context, w Widget) Dimensions {
	var macro op.MacroOp
	macro.Record(gtx.Ops)
	cs := gtx.Constraints
	mcs := cs
	mcs.Min = image.Point{}
	dims := ctxLayout(gtx, mcs, w)
	gtx.Constraints.Min = image.Point{}
	dims := w(gtx)
	macro.Stop()
	sz := dims.Size
	if sz.X < cs.Min.X {


@@ 186,7 186,7 @@ func (a Direction) Layout(gtx *Context, w Widget) {
	op.TransformOp{}.Offset(FPt(p)).Add(gtx.Ops)
	macro.Add()
	stack.Pop()
	gtx.Dimensions = Dimensions{
	return Dimensions{
		Size:     sz,
		Baseline: dims.Baseline + sz.Y - dims.Size.Y - p.Y,
	}

M layout/list.go => layout/list.go +7 -8
@@ 29,7 29,7 @@ type List struct {
	// Alignment is the cross axis alignment of list elements.
	Alignment Alignment

	ctx         *Context
	ctx         Context
	macro       op.MacroOp
	child       op.MacroOp
	scroll      gesture.Scroll


@@ 51,7 51,7 @@ type List struct {

// ListElement is a function that computes the dimensions of
// a list element.
type ListElement func(index int)
type ListElement func(gtx Context, index int) Dimensions

type iterationDir uint8



@@ 82,7 82,7 @@ const (
const inf = 1e6

// init prepares the list for iterating through its children with next.
func (l *List) init(gtx *Context, len int) {
func (l *List) init(gtx Context, len int) {
	if l.more() {
		panic("unfinished child")
	}


@@ 100,16 100,15 @@ func (l *List) init(gtx *Context, len int) {
}

// Layout the List.
func (l *List) Layout(gtx *Context, len int, w ListElement) {
func (l *List) Layout(gtx Context, len int, w ListElement) Dimensions {
	for l.init(gtx, len); l.more(); l.next() {
		crossMin, crossMax := axisCrossConstraint(l.Axis, l.ctx.Constraints)
		cs := axisConstraints(l.Axis, 0, inf, crossMin, crossMax)
		i := l.index()
		l.end(ctxLayout(gtx, cs, func() {
			w(i)
		}))
		gtx.Constraints = cs
		l.end(w(gtx, i))
	}
	gtx.Dimensions = l.layout()
	return l.layout()
}

func (l *List) scrollToEnd() bool {

M layout/stack.go => layout/stack.go +8 -7
@@ 47,18 47,18 @@ func Expanded(w Widget) StackChild {
// 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) {
func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
	var maxSZ image.Point
	// First lay out Stacked children.
	for i, w := range children {
		if w.expanded {
			continue
		}
		cs := gtx.Constraints
		cs.Min = image.Pt(0, 0)
		var m op.MacroOp
		m.Record(gtx.Ops)
		dims := ctxLayout(gtx, cs, w.widget)
		gtx := gtx
		gtx.Constraints.Min = image.Pt(0, 0)
		dims := w.widget(gtx)
		m.Stop()
		if w := dims.Size.X; w > maxSZ.X {
			maxSZ.X = w


@@ 76,10 76,11 @@ func (s Stack) Layout(gtx *Context, children ...StackChild) {
		}
		var m op.MacroOp
		m.Record(gtx.Ops)
		cs := Constraints{
		gtx := gtx
		gtx.Constraints = Constraints{
			Min: maxSZ, Max: gtx.Constraints.Max,
		}
		dims := ctxLayout(gtx, cs, w.widget)
		dims := w.widget(gtx)
		m.Stop()
		if w := dims.Size.X; w > maxSZ.X {
			maxSZ.X = w


@@ 119,7 120,7 @@ func (s Stack) Layout(gtx *Context, children ...StackChild) {
			}
		}
	}
	gtx.Dimensions = Dimensions{
	return Dimensions{
		Size:     maxSZ,
		Baseline: baseline,
	}

M layout/stack_test.go => layout/stack_test.go +14 -9
@@ 5,22 5,27 @@ package layout
import (
	"image"
	"testing"

	"gioui.org/op"
)

func TestStack(t *testing.T) {
	var gtx Context
	gtx.Reset(nil, nil, image.Point{X: 100, Y: 100})
	gtx.Constraints.Min = image.Point{}
	gtx := Context{
		Ops: new(op.Ops),
		Constraints: Constraints{
			Max: image.Pt(100, 100),
		},
	}
	exp := image.Point{X: 60, Y: 70}
	Stack{Alignment: Center}.Layout(&gtx,
		Expanded(func() {
			gtx.Dimensions.Size = exp
	dims := Stack{Alignment: Center}.Layout(gtx,
		Expanded(func(gtx Context) Dimensions {
			return Dimensions{Size: exp}
		}),
		Stacked(func() {
			gtx.Dimensions.Size = image.Point{X: 50, Y: 50}
		Stacked(func(gtx Context) Dimensions {
			return Dimensions{Size: image.Point{X: 50, Y: 50}}
		}),
	)
	if got := gtx.Dimensions.Size; got != exp {
	if got := dims.Size; got != exp {
		t.Errorf("Stack ignored Expanded size, got %v expected %v", got, exp)
	}
}

M widget/bool.go => widget/bool.go +2 -2
@@ 16,7 16,7 @@ type Bool struct {

// Update the checked state according to incoming events,
// and reports whether Value changed.
func (b *Bool) Update(gtx *layout.Context) bool {
func (b *Bool) Update(gtx layout.Context) bool {
	was := b.Value
	for _, e := range b.gesture.Events(gtx) {
		switch e.Type {


@@ 31,6 31,6 @@ func (b *Bool) Update(gtx *layout.Context) bool {
	return b.Value != was
}

func (b *Bool) Layout(gtx *layout.Context) {
func (b *Bool) Layout(gtx layout.Context) {
	b.gesture.Add(gtx.Ops)
}

M widget/button.go => widget/button.go +4 -3
@@ 30,7 30,7 @@ type Click struct {
// Clicked calls Update and reports whether the button was
// clicked since the last call. Multiple clicks result in Clicked
// returning true once per click.
func (b *Clickable) Clicked(gtx *layout.Context) bool {
func (b *Clickable) Clicked(gtx layout.Context) bool {
	b.Update(gtx)
	if b.clicks > 0 {
		b.clicks--


@@ 49,7 49,7 @@ func (b *Clickable) History() []Click {
	return b.history
}

func (b *Clickable) Layout(gtx *layout.Context) {
func (b *Clickable) Layout(gtx layout.Context) layout.Dimensions {
	// Flush clicks from before the previous frame.
	b.Update(gtx)
	var st op.StackOp


@@ 65,11 65,12 @@ func (b *Clickable) Layout(gtx *layout.Context) {
		n := copy(b.history, b.history[1:])
		b.history = b.history[:n]
	}
	return layout.Dimensions{Size: gtx.Constraints.Min}
}

// Update the button state by processing events. The underlying
// gesture events are returned for use beyond what Clicked offers.
func (b *Clickable) Update(gtx *layout.Context) []gesture.ClickEvent {
func (b *Clickable) Update(gtx layout.Context) []gesture.ClickEvent {
	evts := b.click.Events(gtx)
	for _, e := range evts {
		switch e.Type {

M widget/editor.go => widget/editor.go +10 -10
@@ 89,7 89,7 @@ const (
)

// Events returns available editor events.
func (e *Editor) Events(gtx *layout.Context) []EditorEvent {
func (e *Editor) Events(gtx layout.Context) []EditorEvent {
	e.processEvents(gtx)
	events := e.events
	e.events = nil


@@ 97,7 97,7 @@ func (e *Editor) Events(gtx *layout.Context) []EditorEvent {
	return events
}

func (e *Editor) processEvents(gtx *layout.Context) {
func (e *Editor) processEvents(gtx layout.Context) {
	if e.shaper == nil {
		// Can't process events without a shaper.
		return


@@ 114,7 114,7 @@ func (e *Editor) makeValid() {
	}
}

func (e *Editor) processPointer(gtx *layout.Context) {
func (e *Editor) processPointer(gtx layout.Context) {
	sbounds := e.scrollBounds()
	var smin, smax int
	var axis gesture.Axis


@@ 154,7 154,7 @@ func (e *Editor) processPointer(gtx *layout.Context) {
	}
}

func (e *Editor) processKey(gtx *layout.Context) {
func (e *Editor) processKey(gtx layout.Context) {
	if e.rr.Changed() {
		e.events = append(e.events, ChangeEvent{})
	}


@@ 233,7 233,7 @@ func (e *Editor) Focused() bool {
}

// Layout lays out the editor.
func (e *Editor) Layout(gtx *layout.Context, sh text.Shaper, font text.Font, size unit.Value) {
func (e *Editor) Layout(gtx layout.Context, sh text.Shaper, font text.Font, size unit.Value) layout.Dimensions {
	// Flush events from before the previous frame.
	copy(e.events, e.events[e.prevEvents:])
	e.events = e.events[:len(e.events)-e.prevEvents]


@@ 258,10 258,10 @@ func (e *Editor) Layout(gtx *layout.Context, sh text.Shaper, font text.Font, siz
	}

	e.processEvents(gtx)
	e.layout(gtx)
	return e.layout(gtx)
}

func (e *Editor) layout(gtx *layout.Context) {
func (e *Editor) layout(gtx layout.Context) layout.Dimensions {
	e.makeValid()

	e.viewSize = gtx.Constraints.Constrain(e.dims.Size)


@@ 321,10 321,10 @@ func (e *Editor) layout(gtx *layout.Context) {
		e.caretOn = e.focused && (!blinking || dt%timePerBlink < timePerBlink/2)
	}

	gtx.Dimensions = layout.Dimensions{Size: e.viewSize, Baseline: e.dims.Baseline}
	return layout.Dimensions{Size: e.viewSize, Baseline: e.dims.Baseline}
}

func (e *Editor) PaintText(gtx *layout.Context) {
func (e *Editor) PaintText(gtx layout.Context) {
	clip := textPadding(e.lines)
	clip.Max = clip.Max.Add(e.viewSize)
	for _, shape := range e.shapes {


@@ 337,7 337,7 @@ func (e *Editor) PaintText(gtx *layout.Context) {
	}
}

func (e *Editor) PaintCaret(gtx *layout.Context) {
func (e *Editor) PaintCaret(gtx layout.Context) {
	if !e.caretOn {
		return
	}

M widget/enum.go => widget/enum.go +2 -2
@@ 23,7 23,7 @@ func index(vs []string, t string) int {

// Update the Value according to incoming events, and
// reports whether Value changed.
func (e *Enum) Update(gtx *layout.Context) bool {
func (e *Enum) Update(gtx layout.Context) bool {
	was := e.Value
	for i := range e.clicks {
		for _, ev := range e.clicks[i].Events(gtx) {


@@ 37,7 37,7 @@ func (e *Enum) Update(gtx *layout.Context) bool {
}

// Layout adds the event handler for key.
func (e *Enum) Layout(gtx *layout.Context, key string) {
func (e *Enum) Layout(gtx layout.Context, key string) {
	if index(e.values, key) == -1 {
		e.values = append(e.values, key)
		e.clicks = append(e.clicks, gesture.Click{})

M widget/icon.go => widget/icon.go +2 -2
@@ 32,7 32,7 @@ func NewIcon(data []byte) (*Icon, error) {
	return &Icon{src: data, Color: color.RGBA{A: 0xff}}, nil
}

func (ic *Icon) Layout(gtx *layout.Context, sz unit.Value) {
func (ic *Icon) Layout(gtx layout.Context, sz unit.Value) layout.Dimensions {
	ico := ic.image(gtx.Px(sz))
	ico.Add(gtx.Ops)
	paint.PaintOp{


@@ 40,7 40,7 @@ func (ic *Icon) Layout(gtx *layout.Context, sz unit.Value) {
			Max: layout.FPt(ico.Size()),
		},
	}.Add(gtx.Ops)
	gtx.Dimensions = layout.Dimensions{
	return layout.Dimensions{
		Size: ico.Size(),
	}
}

M widget/image.go => widget/image.go +2 -2
@@ 23,7 23,7 @@ type Image struct {
	Scale float32
}

func (im Image) Layout(gtx *layout.Context) {
func (im Image) Layout(gtx layout.Context) layout.Dimensions {
	scale := im.Scale
	if scale == 0 {
		scale = 160.0 / 72.0


@@ 39,5 39,5 @@ func (im Image) Layout(gtx *layout.Context) {
	im.Src.Add(gtx.Ops)
	paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: float32(w), Y: float32(h)}}}.Add(gtx.Ops)
	s.Pop()
	gtx.Dimensions = layout.Dimensions{Size: d}
	return layout.Dimensions{Size: d}
}

M widget/label.go => widget/label.go +2 -2
@@ 84,7 84,7 @@ func (l *lineIterator) Next() (int, int, []text.Glyph, f32.Point, bool) {
	return 0, 0, nil, f32.Point{}, false
}

func (l Label) Layout(gtx *layout.Context, s text.Shaper, font text.Font, size unit.Value, txt string) {
func (l Label) Layout(gtx layout.Context, s text.Shaper, font text.Font, size unit.Value, txt string) layout.Dimensions {
	cs := gtx.Constraints
	textSize := fixed.I(gtx.Px(size))
	lines := s.LayoutString(font, textSize, cs.Max.X, txt)


@@ 115,7 115,7 @@ func (l Label) Layout(gtx *layout.Context, s text.Shaper, font text.Font, size u
		paint.PaintOp{Rect: lclip}.Add(gtx.Ops)
		stack.Pop()
	}
	gtx.Dimensions = dims
	return dims
}

func textPadding(lines []text.Line) (padding image.Rectangle) {

M widget/material/button.go => widget/material/button.go +29 -32
@@ 80,12 80,10 @@ func IconButton(th *Theme, icon *widget.Icon) IconButtonStyle {

// Clickable lays out a rectangular clickable widget without further
// decoration.
func Clickable(gtx *layout.Context, button *widget.Clickable, w layout.Widget) {
	layout.Stack{}.Layout(gtx,
		layout.Expanded(func() {
			button.Layout(gtx)
		}),
		layout.Expanded(func() {
func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) layout.Dimensions {
	return layout.Stack{}.Layout(gtx,
		layout.Expanded(button.Layout),
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			clip.Rect{
				Rect: f32.Rectangle{Max: f32.Point{
					X: float32(gtx.Constraints.Min.X),


@@ 95,26 93,27 @@ func Clickable(gtx *layout.Context, button *widget.Clickable, w layout.Widget) {
			for _, c := range button.History() {
				drawInk(gtx, c)
			}
			return layout.Dimensions{Size: gtx.Constraints.Min}
		}),
		layout.Stacked(w),
	)
}

func (b ButtonStyle) Layout(gtx *layout.Context, button *widget.Clickable) {
	ButtonLayoutStyle{
func (b ButtonStyle) Layout(gtx layout.Context, button *widget.Clickable) layout.Dimensions {
	return ButtonLayoutStyle{
		Background:   b.Background,
		CornerRadius: b.CornerRadius,
		Inset:        b.Inset,
	}.Layout(gtx, button, func() {
	}.Layout(gtx, button, func(gtx layout.Context) layout.Dimensions {
		paint.ColorOp{Color: b.Color}.Add(gtx.Ops)
		widget.Label{Alignment: text.Middle}.Layout(gtx, b.shaper, b.Font, b.TextSize, b.Text)
		return widget.Label{Alignment: text.Middle}.Layout(gtx, b.shaper, b.Font, b.TextSize, b.Text)
	})
}

func (b ButtonLayoutStyle) Layout(gtx *layout.Context, button *widget.Clickable, w layout.Widget) {
func (b ButtonLayoutStyle) Layout(gtx layout.Context, button *widget.Clickable, w layout.Widget) layout.Dimensions {
	min := gtx.Constraints.Min
	layout.Stack{Alignment: layout.Center}.Layout(gtx,
		layout.Expanded(func() {
	return layout.Stack{Alignment: layout.Center}.Layout(gtx,
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			rr := float32(gtx.Px(b.CornerRadius))
			clip.Rect{
				Rect: f32.Rectangle{Max: f32.Point{


@@ 123,28 122,25 @@ func (b ButtonLayoutStyle) Layout(gtx *layout.Context, button *widget.Clickable,
				}},
				NE: rr, NW: rr, SE: rr, SW: rr,
			}.Op(gtx.Ops).Add(gtx.Ops)
			fill(gtx, b.Background)
			dims := fill(gtx, b.Background)
			for _, c := range button.History() {
				drawInk(gtx, c)
			}
			return dims
		}),
		layout.Stacked(func() {
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			gtx.Constraints.Min = min
			layout.Center.Layout(gtx, func() {
				b.Inset.Layout(gtx, func() {
					w()
				})
			return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
				return b.Inset.Layout(gtx, w)
			})
		}),
		layout.Expanded(func() {
			button.Layout(gtx)
		}),
		layout.Expanded(button.Layout),
	)
}

func (b IconButtonStyle) Layout(gtx *layout.Context, button *widget.Clickable) {
	layout.Stack{Alignment: layout.Center}.Layout(gtx,
		layout.Expanded(func() {
func (b IconButtonStyle) Layout(gtx layout.Context, button *widget.Clickable) layout.Dimensions {
	return layout.Stack{Alignment: layout.Center}.Layout(gtx,
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			size := gtx.Constraints.Min.X
			sizef := float32(size)
			rr := sizef * .5


@@ 152,31 148,32 @@ func (b IconButtonStyle) Layout(gtx *layout.Context, button *widget.Clickable) {
				Rect: f32.Rectangle{Max: f32.Point{X: sizef, Y: sizef}},
				NE:   rr, NW: rr, SE: rr, SW: rr,
			}.Op(gtx.Ops).Add(gtx.Ops)
			fill(gtx, b.Background)
			dims := fill(gtx, b.Background)
			for _, c := range button.History() {
				drawInk(gtx, c)
			}
			return dims
		}),
		layout.Stacked(func() {
			b.Inset.Layout(gtx, func() {
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
				size := gtx.Px(b.Size)
				if b.Icon != nil {
					b.Icon.Color = b.Color
					b.Icon.Layout(gtx, unit.Px(float32(size)))
				}
				gtx.Dimensions = layout.Dimensions{
				return layout.Dimensions{
					Size: image.Point{X: size, Y: size},
				}
			})
		}),
		layout.Expanded(func() {
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			pointer.Ellipse(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
			button.Layout(gtx)
			return button.Layout(gtx)
		}),
	)
}

func drawInk(gtx *layout.Context, c widget.Click) {
func drawInk(gtx layout.Context, c widget.Click) {
	d := gtx.Now().Sub(c.Time)
	t := float32(d.Seconds())
	const duration = 0.5

M widget/material/checkable.go => widget/material/checkable.go +12 -11
@@ 26,7 26,7 @@ type checkable struct {
	uncheckedStateIcon *widget.Icon
}

func (c *checkable) layout(gtx *layout.Context, checked bool) {
func (c *checkable) layout(gtx layout.Context, checked bool) layout.Dimensions {
	var icon *widget.Icon
	if checked {
		icon = c.checkedStateIcon


@@ 35,29 35,30 @@ func (c *checkable) layout(gtx *layout.Context, checked bool) {
	}

	min := gtx.Constraints.Min
	layout.Flex{Alignment: layout.Middle}.Layout(gtx,
		layout.Rigid(func() {
			layout.Center.Layout(gtx, func() {
				layout.UniformInset(unit.Dp(2)).Layout(gtx, func() {
	dims := layout.Flex{Alignment: layout.Middle}.Layout(gtx,
		layout.Rigid(func(gtx layout.Context) layout.Dimensions {
			return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
				return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					size := gtx.Px(c.Size)
					icon.Color = c.IconColor
					icon.Layout(gtx, unit.Px(float32(size)))
					gtx.Dimensions = layout.Dimensions{
					return layout.Dimensions{
						Size: image.Point{X: size, Y: size},
					}
				})
			})
		}),

		layout.Rigid(func() {
		layout.Rigid(func(gtx layout.Context) layout.Dimensions {
			gtx.Constraints.Min = min
			layout.W.Layout(gtx, func() {
				layout.UniformInset(unit.Dp(2)).Layout(gtx, func() {
			return layout.W.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
				return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					paint.ColorOp{Color: c.Color}.Add(gtx.Ops)
					widget.Label{}.Layout(gtx, c.shaper, c.Font, c.TextSize, c.Label)
					return widget.Label{}.Layout(gtx, c.shaper, c.Font, c.TextSize, c.Label)
				})
			})
		}),
	)
	pointer.Rect(image.Rectangle{Max: gtx.Dimensions.Size}).Add(gtx.Ops)
	pointer.Rect(image.Rectangle{Max: dims.Size}).Add(gtx.Ops)
	return dims
}

M widget/material/checkbox.go => widget/material/checkbox.go +3 -2
@@ 28,8 28,9 @@ func CheckBox(th *Theme, label string) CheckBoxStyle {
}

// Layout updates the checkBox and displays it.
func (c CheckBoxStyle) Layout(gtx *layout.Context, checkBox *widget.Bool) {
func (c CheckBoxStyle) Layout(gtx layout.Context, checkBox *widget.Bool) layout.Dimensions {
	checkBox.Update(gtx)
	c.layout(gtx, checkBox.Value)
	dims := c.layout(gtx, checkBox.Value)
	checkBox.Layout(gtx)
	return dims
}

M widget/material/doc.go => widget/material/doc.go +2 -2
@@ 11,7 11,7 @@
//
// This snippet defines a button that prints a message when clicked:
//
//     var gtx *layout.Context
//     var gtx layout.Context
//     button := new(widget.Clickable)
//
//     for button.Clicked(gtx) {


@@ 43,7 43,7 @@
//
//     btn := material.Button(theme, "Click me!")
//     btn.Font.Style = text.Italic
//     btn.Layout(gtx)
//     btn.Layout(gtx, button)
//
// Widget variants: A widget can have several distinct representations even
// though the underlying state is the same. A widget.Clickable can be drawn as a

M widget/material/editor.go => widget/material/editor.go +6 -5
@@ 36,22 36,22 @@ func Editor(th *Theme, hint string) EditorStyle {
	}
}

func (e EditorStyle) Layout(gtx *layout.Context, editor *widget.Editor) {
func (e EditorStyle) Layout(gtx layout.Context, editor *widget.Editor) layout.Dimensions {
	var stack op.StackOp
	stack.Push(gtx.Ops)
	var macro op.MacroOp
	macro.Record(gtx.Ops)
	paint.ColorOp{Color: e.HintColor}.Add(gtx.Ops)
	tl := widget.Label{Alignment: editor.Alignment}
	tl.Layout(gtx, e.shaper, e.Font, e.TextSize, e.Hint)
	dims := tl.Layout(gtx, e.shaper, e.Font, e.TextSize, e.Hint)
	macro.Stop()
	if w := gtx.Dimensions.Size.X; gtx.Constraints.Min.X < w {
	if w := dims.Size.X; gtx.Constraints.Min.X < w {
		gtx.Constraints.Min.X = w
	}
	if h := gtx.Dimensions.Size.Y; gtx.Constraints.Min.Y < h {
	if h := dims.Size.Y; gtx.Constraints.Min.Y < h {
		gtx.Constraints.Min.Y = h
	}
	editor.Layout(gtx, e.shaper, e.Font, e.TextSize)
	dims = editor.Layout(gtx, e.shaper, e.Font, e.TextSize)
	if editor.Len() > 0 {
		paint.ColorOp{Color: e.Color}.Add(gtx.Ops)
		editor.PaintText(gtx)


@@ 61,4 61,5 @@ func (e EditorStyle) Layout(gtx *layout.Context, editor *widget.Editor) {
	paint.ColorOp{Color: e.Color}.Add(gtx.Ops)
	editor.PaintCaret(gtx)
	stack.Pop()
	return dims
}

M widget/material/label.go => widget/material/label.go +2 -2
@@ 72,8 72,8 @@ func Label(th *Theme, size unit.Value, txt string) LabelStyle {
	}
}

func (l LabelStyle) Layout(gtx *layout.Context) {
func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
	paint.ColorOp{Color: l.Color}.Add(gtx.Ops)
	tl := widget.Label{Alignment: l.Alignment, MaxLines: l.MaxLines}
	tl.Layout(gtx, l.shaper, l.Font, l.TextSize, l.Text)
	return tl.Layout(gtx, l.shaper, l.Font, l.TextSize, l.Text)
}

M widget/material/progressbar.go => widget/material/progressbar.go +9 -9
@@ 23,8 23,8 @@ func ProgressBar(th *Theme) ProgressBarStyle {
	}
}

func (b ProgressBarStyle) Layout(gtx *layout.Context, progress int) {
	shader := func(width float32, color color.RGBA) {
func (p ProgressBarStyle) Layout(gtx layout.Context, progress int) layout.Dimensions {
	shader := func(width float32, color color.RGBA) layout.Dimensions {
		maxHeight := unit.Dp(4)
		rr := float32(gtx.Px(unit.Dp(2)))



@@ 41,7 41,7 @@ func (b ProgressBarStyle) Layout(gtx *layout.Context, progress int) {
		paint.ColorOp{Color: color}.Add(gtx.Ops)
		paint.PaintOp{Rect: dr}.Add(gtx.Ops)

		gtx.Dimensions = layout.Dimensions{Size: d}
		return layout.Dimensions{Size: d}
	}

	if progress > 100 {


@@ 52,16 52,16 @@ func (b ProgressBarStyle) Layout(gtx *layout.Context, progress int) {

	progressBarWidth := float32(gtx.Constraints.Max.X)

	layout.Stack{Alignment: layout.W}.Layout(gtx,
		layout.Stacked(func() {
	return layout.Stack{Alignment: layout.W}.Layout(gtx,
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			// Use a transparent equivalent of progress color.
			bgCol := mulAlpha(b.Color, 150)
			bgCol := mulAlpha(p.Color, 150)

			shader(progressBarWidth, bgCol)
			return shader(progressBarWidth, bgCol)
		}),
		layout.Stacked(func() {
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			fillWidth := (progressBarWidth / 100) * float32(progress)
			shader(fillWidth, b.Color)
			return shader(fillWidth, p.Color)
		}),
	)
}

M widget/material/radiobutton.go => widget/material/radiobutton.go +3 -2
@@ 33,8 33,9 @@ func RadioButton(th *Theme, key, label string) RadioButtonStyle {
}

// Layout updates enum and displays the radio button.
func (r RadioButtonStyle) Layout(gtx *layout.Context, enum *widget.Enum) {
func (r RadioButtonStyle) Layout(gtx layout.Context, enum *widget.Enum) layout.Dimensions {
	enum.Update(gtx)
	r.layout(gtx, enum.Value == r.Key)
	dims := r.layout(gtx, enum.Value == r.Key)
	enum.Layout(gtx, r.Key)
	return dims
}

M widget/material/switch.go => widget/material/switch.go +2 -2
@@ 27,7 27,7 @@ func Switch(th *Theme) SwitchStyle {
}

// Layout updates the checkBox and displays it.
func (s SwitchStyle) Layout(gtx *layout.Context, swtch *widget.Bool) {
func (s SwitchStyle) Layout(gtx layout.Context, swtch *widget.Bool) layout.Dimensions {
	swtch.Update(gtx)

	trackWidth := gtx.Px(unit.Dp(36))


@@ 112,7 112,7 @@ func (s SwitchStyle) Layout(gtx *layout.Context, swtch *widget.Bool) {
	swtch.Layout(gtx)
	stack.Pop()

	gtx.Dimensions = layout.Dimensions{
	return layout.Dimensions{
		Size: image.Point{X: trackWidth, Y: trackHeight},
	}
}

M widget/material/theme.go => widget/material/theme.go +2 -2
@@ 63,7 63,7 @@ func argb(c uint32) color.RGBA {
	return color.RGBA{A: uint8(c >> 24), R: uint8(c >> 16), G: uint8(c >> 8), B: uint8(c)}
}

func fill(gtx *layout.Context, col color.RGBA) {
func fill(gtx layout.Context, col color.RGBA) layout.Dimensions {
	cs := gtx.Constraints
	d := cs.Min
	dr := f32.Rectangle{


@@ 71,5 71,5 @@ func fill(gtx *layout.Context, col color.RGBA) {
	}
	paint.ColorOp{Color: col}.Add(gtx.Ops)
	paint.PaintOp{Rect: dr}.Add(gtx.Ops)
	gtx.Dimensions = layout.Dimensions{Size: d}
	return layout.Dimensions{Size: d}
}