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>
This commit is contained in:
Elias Naur
2020-05-23 19:55:02 +02:00
parent af10307f4a
commit 3af01a3f43
25 changed files with 226 additions and 208 deletions
+29 -32
View File
@@ -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
+12 -11
View File
@@ -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
}
+3 -2
View File
@@ -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
}
+2 -2
View File
@@ -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
+6 -5
View File
@@ -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
}
+2 -2
View File
@@ -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)
}
+9 -9
View File
@@ -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)
}),
)
}
+3 -2
View File
@@ -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
}
+2 -2
View File
@@ -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},
}
}
+2 -2
View File
@@ -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}
}