From 3af01a3f431244214a439c7bb392d0166e724a00 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sat, 23 May 2020 19:55:02 +0200 Subject: [PATCH] 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 --- layout/context.go | 65 +++++++++++--------------- layout/doc.go | 15 +++--- layout/example_test.go | 85 ++++++++++++++++++++-------------- layout/flex.go | 12 +++-- layout/layout.go | 18 +++---- layout/list.go | 15 +++--- layout/stack.go | 15 +++--- layout/stack_test.go | 23 +++++---- widget/bool.go | 4 +- widget/button.go | 7 +-- widget/editor.go | 20 ++++---- widget/enum.go | 4 +- widget/icon.go | 4 +- widget/image.go | 4 +- widget/label.go | 4 +- widget/material/button.go | 61 ++++++++++++------------ widget/material/checkable.go | 23 ++++----- widget/material/checkbox.go | 5 +- widget/material/doc.go | 4 +- widget/material/editor.go | 11 +++-- widget/material/label.go | 4 +- widget/material/progressbar.go | 18 +++---- widget/material/radiobutton.go | 5 +- widget/material/switch.go | 4 +- widget/material/theme.go | 4 +- 25 files changed, 226 insertions(+), 208 deletions(-) diff --git a/layout/context.go b/layout/context.go index 33e0e691..dc35e2ff 100644 --- a/layout/context.go +++ b/layout/context.go @@ -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) } diff --git a/layout/doc.go b/layout/doc.go index 4fe8b4a7..d318765b 100644 --- a/layout/doc.go +++ b/layout/doc.go @@ -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, ...) }) }) diff --git a/layout/example_test.go b/layout/example_test.go index a2710d28..a7ad1c21 100644 --- a/layout/example_test.go +++ b/layout/example_test.go @@ -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, diff --git a/layout/flex.go b/layout/flex.go index 39f5b70c..a31077e0 100644 --- a/layout/flex.go +++ b/layout/flex.go @@ -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 { diff --git a/layout/layout.go b/layout/layout.go index 6094a213..a92df55b 100644 --- a/layout/layout.go +++ b/layout/layout.go @@ -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, } diff --git a/layout/list.go b/layout/list.go index cd09d79e..a0022b21 100644 --- a/layout/list.go +++ b/layout/list.go @@ -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 { diff --git a/layout/stack.go b/layout/stack.go index a083b7fc..447547b2 100644 --- a/layout/stack.go +++ b/layout/stack.go @@ -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, } diff --git a/layout/stack_test.go b/layout/stack_test.go index 1fc619f5..f46fbfcd 100644 --- a/layout/stack_test.go +++ b/layout/stack_test.go @@ -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(>x, - 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) } } diff --git a/widget/bool.go b/widget/bool.go index ec459ac3..fde8c323 100644 --- a/widget/bool.go +++ b/widget/bool.go @@ -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) } diff --git a/widget/button.go b/widget/button.go index c43e5732..2f4b73f9 100644 --- a/widget/button.go +++ b/widget/button.go @@ -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 { diff --git a/widget/editor.go b/widget/editor.go index fbe2f4c7..ebab3919 100644 --- a/widget/editor.go +++ b/widget/editor.go @@ -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 } diff --git a/widget/enum.go b/widget/enum.go index 6068740d..7b38931d 100644 --- a/widget/enum.go +++ b/widget/enum.go @@ -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{}) diff --git a/widget/icon.go b/widget/icon.go index da703bd0..ea3a4094 100644 --- a/widget/icon.go +++ b/widget/icon.go @@ -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(), } } diff --git a/widget/image.go b/widget/image.go index 1c226162..15a3ca83 100644 --- a/widget/image.go +++ b/widget/image.go @@ -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} } diff --git a/widget/label.go b/widget/label.go index f424b092..cab5aeec 100644 --- a/widget/label.go +++ b/widget/label.go @@ -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) { diff --git a/widget/material/button.go b/widget/material/button.go index 2cf2e536..610e6571 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -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 diff --git a/widget/material/checkable.go b/widget/material/checkable.go index 495b6da5..24febe2a 100644 --- a/widget/material/checkable.go +++ b/widget/material/checkable.go @@ -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 } diff --git a/widget/material/checkbox.go b/widget/material/checkbox.go index 552f06cf..0d23f825 100644 --- a/widget/material/checkbox.go +++ b/widget/material/checkbox.go @@ -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 } diff --git a/widget/material/doc.go b/widget/material/doc.go index 0431886d..d7679d58 100644 --- a/widget/material/doc.go +++ b/widget/material/doc.go @@ -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 diff --git a/widget/material/editor.go b/widget/material/editor.go index c2bb7e1f..6887a258 100644 --- a/widget/material/editor.go +++ b/widget/material/editor.go @@ -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 } diff --git a/widget/material/label.go b/widget/material/label.go index 96830be5..c475c31d 100644 --- a/widget/material/label.go +++ b/widget/material/label.go @@ -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) } diff --git a/widget/material/progressbar.go b/widget/material/progressbar.go index 33e8c974..391f80e9 100644 --- a/widget/material/progressbar.go +++ b/widget/material/progressbar.go @@ -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) }), ) } diff --git a/widget/material/radiobutton.go b/widget/material/radiobutton.go index b44d7a9e..1a9d2006 100644 --- a/widget/material/radiobutton.go +++ b/widget/material/radiobutton.go @@ -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 } diff --git a/widget/material/switch.go b/widget/material/switch.go index 7b1fd6c8..302c449a 100644 --- a/widget/material/switch.go +++ b/widget/material/switch.go @@ -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}, } } diff --git a/widget/material/theme.go b/widget/material/theme.go index 27d5237c..160dfbd4 100644 --- a/widget/material/theme.go +++ b/widget/material/theme.go @@ -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} }