~eliasnaur/gio

7bf3265ccd387d643db3d270bf0f9d6cdc5bedd0 — Elias Naur 8 days ago 67a9d9e
layout,widget: transpose Constraints to use image.Points for limits

Instead of

    type Contraints struct {
	    Width, Height Constraint
    }

use

    type Constraints struct {
	    Min, Max image.Point
    }

which leads to simpler use. For example, the Min method is trivally replaced by
the field, and the RigidConstraints constructor is no longer a net win.

API Change. Rewrites:

    gofmt -r 'gtx.Constraints.Min() -> gtx.Constraints.Min'
    gofmt -r 'gtx.Constraints.Width.Min -> gtx.Constraints.Min.X'
    gofmt -r 'gtx.Constraints.Height.Min -> gtx.Constraints.Min.Y'
    gofmt -r 'gtx.Constraints.Height.Max -> gtx.Constraints.Max.Y'
    gofmt -r 'gtx.Constraints.Width.Max -> gtx.Constraints.Max.X'

Signed-off-by: Elias Naur <mail@eliasnaur.com>
M layout/context.go => layout/context.go +1 -1
@@ 45,7 45,7 @@ func ctxLayout(gtx *Context, cs Constraints, w Widget) 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 = RigidConstraints(size)
	c.Constraints = Constraints{Min: size, Max: size}
	c.Dimensions = Dimensions{}
	c.cfg = cfg
	c.queue = q

M layout/example_test.go => layout/example_test.go +7 -9
@@ 12,8 12,7 @@ func ExampleInset() {
	gtx := new(layout.Context)
	gtx.Reset(nil, nil, image.Point{X: 100, Y: 100})
	// Loose constraints with no minimal size.
	gtx.Constraints.Width.Min = 0
	gtx.Constraints.Height.Min = 0
	gtx.Constraints.Min = image.Point{}

	// Inset all edges by 10.
	inset := layout.UniformInset(unit.Dp(10))


@@ 55,26 54,25 @@ func ExampleFlex() {
	layout.Flex{}.Layout(gtx,
		// Rigid 10x10 widget.
		layout.Rigid(func() {
			fmt.Printf("Rigid: %v\n", gtx.Constraints.Width)
			fmt.Printf("Rigid: %v\n", gtx.Constraints)
			layoutWidget(gtx, 10, 10)
		}),
		// Child with 50% space allowance.
		layout.Flexed(0.5, func() {
			fmt.Printf("50%%: %v\n", gtx.Constraints.Width)
			fmt.Printf("50%%: %v\n", gtx.Constraints)
			layoutWidget(gtx, 10, 10)
		}),
	)

	// Output:
	// Rigid: {0 100}
	// 50%: {45 45}
	// Rigid: {(0,100) (100,100)}
	// 50%: {(45,100) (45,100)}
}

func ExampleStack() {
	gtx := new(layout.Context)
	gtx.Reset(nil, nil, image.Point{X: 100, Y: 100})
	gtx.Constraints.Width.Min = 0
	gtx.Constraints.Height.Min = 0
	gtx.Constraints.Min = image.Point{}

	layout.Stack{}.Layout(gtx,
		// Force widget to the same size as the second.


@@ 89,7 87,7 @@ func ExampleStack() {
	)

	// Output:
	// Expand: {{50 100} {50 100}}
	// Expand: {(50,50) (100,100)}
}

func ExampleList() {

M layout/flex.go => layout/flex.go +22 -21
@@ 83,12 83,13 @@ func (f Flex) Layout(gtx *Context, children ...FlexChild) {
			continue
		}
		cs := gtx.Constraints
		mainc := axisMainConstraint(f.Axis, cs)
		mainMax := mainc.Max - size
		_, mainMax := axisMainConstraint(f.Axis, cs)
		mainMax -= size
		if mainMax < 0 {
			mainMax = 0
		}
		cs = axisConstraints(f.Axis, Constraint{Max: mainMax}, axisCrossConstraint(f.Axis, cs))
		crossMin, crossMax := axisCrossConstraint(f.Axis, cs)
		cs = axisConstraints(f.Axis, 0, mainMax, crossMin, crossMax)
		var m op.MacroOp
		m.Record(gtx.Ops)
		dims := ctxLayout(gtx, cs, child.widget)


@@ 107,21 108,21 @@ func (f Flex) Layout(gtx *Context, children ...FlexChild) {
			continue
		}
		cs := gtx.Constraints
		mainc := axisMainConstraint(f.Axis, cs)
		_, mainMax := axisMainConstraint(f.Axis, cs)
		var flexSize int
		if mainc.Max > size {
			flexSize = mainc.Max - rigidSize
		if mainMax > size {
			flexSize = mainMax - rigidSize
			// Apply weight and add any leftover fraction from a
			// previous Flexed.
			childSize := float32(flexSize)*child.weight + fraction
			flexSize = int(childSize + .5)
			fraction = childSize - float32(flexSize)
			if max := mainc.Max - size; flexSize > max {
			if max := mainMax - size; flexSize > max {
				flexSize = max
			}
		}
		submainc := Constraint{Min: flexSize, Max: flexSize}
		cs = axisConstraints(f.Axis, submainc, axisCrossConstraint(f.Axis, cs))
		crossMin, crossMax := axisCrossConstraint(f.Axis, cs)
		cs = axisConstraints(f.Axis, flexSize, flexSize, crossMin, crossMax)
		var m op.MacroOp
		m.Record(gtx.Ops)
		dims := ctxLayout(gtx, cs, child.widget)


@@ 142,10 143,10 @@ func (f Flex) Layout(gtx *Context, children ...FlexChild) {
		}
	}
	cs := gtx.Constraints
	mainc := axisMainConstraint(f.Axis, cs)
	mainMin, _ := axisMainConstraint(f.Axis, cs)
	var space int
	if mainc.Min > size {
		space = mainc.Min - size
	if mainMin > size {
		space = mainMin - size
	}
	var mainSize int
	switch f.Spacing {


@@ 227,27 228,27 @@ func axisCross(a Axis, sz image.Point) int {
	}
}

func axisMainConstraint(a Axis, cs Constraints) Constraint {
func axisMainConstraint(a Axis, cs Constraints) (int, int) {
	if a == Horizontal {
		return cs.Width
		return cs.Min.X, cs.Max.X
	} else {
		return cs.Height
		return cs.Min.Y, cs.Max.Y
	}
}

func axisCrossConstraint(a Axis, cs Constraints) Constraint {
func axisCrossConstraint(a Axis, cs Constraints) (int, int) {
	if a == Horizontal {
		return cs.Height
		return cs.Min.Y, cs.Max.Y
	} else {
		return cs.Width
		return cs.Min.X, cs.Max.X
	}
}

func axisConstraints(a Axis, mainc, crossc Constraint) Constraints {
func axisConstraints(a Axis, mainMin, mainMax, crossMin, crossMax int) Constraints {
	if a == Horizontal {
		return Constraints{Width: mainc, Height: crossc}
		return Constraints{Min: image.Pt(mainMin, crossMin), Max: image.Pt(mainMax, crossMax)}
	} else {
		return Constraints{Width: crossc, Height: mainc}
		return Constraints{Min: image.Pt(crossMin, mainMin), Max: image.Pt(crossMax, mainMax)}
	}
}


M layout/layout.go => layout/layout.go +30 -51
@@ 9,17 9,9 @@ import (
	"gioui.org/unit"
)

// Constraints represent a set of acceptable ranges for
// a widget's width and height.
// Constraints represent the minimum and maximum size of a widget.
type Constraints struct {
	Width  Constraint
	Height Constraint
}

// Constraint is a range of acceptable sizes in a single
// dimension.
type Constraint struct {
	Min, Max int
	Min, Max image.Point
}

// Dimensions are the resolved size and baseline for a widget.


@@ 66,33 58,21 @@ const (
	Vertical
)

// Constrain a value to the range [Min; Max].
func (c Constraint) Constrain(v int) int {
	if v < c.Min {
		return c.Min
	} else if v > c.Max {
		return c.Max
	}
	return v
}

// Constrain a size to the Width and Height ranges.
// Constrain a size so each dimension is in the range [min;max].
func (c Constraints) Constrain(size image.Point) image.Point {
	return image.Point{X: c.Width.Constrain(size.X), Y: c.Height.Constrain(size.Y)}
}

// Min returns the smallest dimensions that satisfy the constraints.
func (c Constraints) Min() image.Point {
	return image.Point{X: c.Width.Min, Y: c.Height.Min}
}

// RigidConstraints returns the constraints that can only be
// satisfied by the given dimensions.
func RigidConstraints(size image.Point) Constraints {
	return Constraints{
		Width:  Constraint{Min: size.X, Max: size.X},
		Height: Constraint{Min: size.Y, Max: size.Y},
	if min := c.Min.X; size.X < min {
		size.X = min
	}
	if min := c.Min.Y; size.Y < min {
		size.Y = min
	}
	if max := c.Max.X; size.X > max {
		size.X = max
	}
	if max := c.Max.Y; size.Y > max {
		size.Y = max
	}
	return size
}

// Inset adds space around a widget.


@@ 107,23 87,23 @@ func (in Inset) Layout(gtx *Context, w Widget) {
	bottom := gtx.Px(in.Bottom)
	left := gtx.Px(in.Left)
	mcs := gtx.Constraints
	mcs.Width.Max -= left + right
	if mcs.Width.Max < 0 {
	mcs.Max.X -= left + right
	if mcs.Max.X < 0 {
		left = 0
		right = 0
		mcs.Width.Max = 0
		mcs.Max.X = 0
	}
	if mcs.Width.Min > mcs.Width.Max {
		mcs.Width.Min = mcs.Width.Max
	if mcs.Min.X > mcs.Max.X {
		mcs.Min.X = mcs.Max.X
	}
	mcs.Height.Max -= top + bottom
	if mcs.Height.Max < 0 {
	mcs.Max.Y -= top + bottom
	if mcs.Max.Y < 0 {
		bottom = 0
		top = 0
		mcs.Height.Max = 0
		mcs.Max.Y = 0
	}
	if mcs.Height.Min > mcs.Height.Max {
		mcs.Height.Min = mcs.Height.Max
	if mcs.Min.Y > mcs.Max.Y {
		mcs.Min.Y = mcs.Max.Y
	}
	var stack op.StackOp
	stack.Push(gtx.Ops)


@@ 148,16 128,15 @@ func (a Direction) Layout(gtx *Context, w Widget) {
	macro.Record(gtx.Ops)
	cs := gtx.Constraints
	mcs := cs
	mcs.Width.Min = 0
	mcs.Height.Min = 0
	mcs.Min = image.Point{}
	dims := ctxLayout(gtx, mcs, w)
	macro.Stop()
	sz := dims.Size
	if sz.X < cs.Width.Min {
		sz.X = cs.Width.Min
	if sz.X < cs.Min.X {
		sz.X = cs.Min.X
	}
	if sz.Y < cs.Height.Min {
		sz.Y = cs.Height.Min
	if sz.Y < cs.Min.Y {
		sz.Y = cs.Min.Y
	}
	var p image.Point
	switch Direction(a) {

M layout/list.go => layout/list.go +16 -9
@@ 103,7 103,8 @@ func (l *List) init(gtx *Context, len int) {
// Layout the List.
func (l *List) Layout(gtx *Context, len int, w ListElement) {
	for l.init(gtx, len); l.more(); l.next() {
		cs := axisConstraints(l.Axis, Constraint{Max: inf}, axisCrossConstraint(l.Axis, l.ctx.Constraints))
		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)


@@ 160,7 161,7 @@ func (l *List) more() bool {
}

func (l *List) nextDir() iterationDir {
	vsize := axisMainConstraint(l.Axis, l.ctx.Constraints).Max
	_, vsize := axisMainConstraint(l.Axis, l.ctx.Constraints)
	last := l.Position.First + len(l.children)
	// Clamp offset.
	if l.maxSize-l.Position.Offset < vsize && last == l.len {


@@ 204,7 205,7 @@ func (l *List) layout() Dimensions {
	if l.more() {
		panic("unfinished child")
	}
	mainc := axisMainConstraint(l.Axis, l.ctx.Constraints)
	mainMin, mainMax := axisMainConstraint(l.Axis, l.ctx.Constraints)
	children := l.children
	// Skip invisible children
	for len(children) > 0 {


@@ 225,7 226,7 @@ func (l *List) layout() Dimensions {
			maxCross = c
		}
		size += axisMain(l.Axis, sz)
		if size >= mainc.Max {
		if size >= mainMax {
			children = children[:i+1]
			break
		}


@@ 233,7 234,7 @@ func (l *List) layout() Dimensions {
	ops := l.ctx.Ops
	pos := -l.Position.Offset
	// ScrollToEnd lists are end aligned.
	if space := mainc.Max - size; l.ScrollToEnd && space > 0 {
	if space := mainMax - size; l.ScrollToEnd && space > 0 {
		pos += space
	}
	for _, child := range children {


@@ 247,8 248,8 @@ func (l *List) layout() Dimensions {
		}
		childSize := axisMain(l.Axis, sz)
		max := childSize + pos
		if max > mainc.Max {
			max = mainc.Max
		if max > mainMax {
			max = mainMax
		}
		min := pos
		if min < 0 {


@@ 267,12 268,18 @@ func (l *List) layout() Dimensions {
		pos += childSize
	}
	atStart := l.Position.First == 0 && l.Position.Offset <= 0
	atEnd := l.Position.First+len(children) == l.len && mainc.Max >= pos
	atEnd := l.Position.First+len(children) == l.len && mainMax >= pos
	if atStart && l.scrollDelta < 0 || atEnd && l.scrollDelta > 0 {
		l.scroll.Stop()
	}
	l.Position.BeforeEnd = !atEnd
	dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
	if pos < mainMin {
		pos = mainMin
	}
	if pos > mainMax {
		pos = mainMax
	}
	dims := axisPoint(l.Axis, pos, maxCross)
	l.macro.Stop()
	pointer.Rect(image.Rectangle{Max: dims}).Add(ops)
	l.scroll.Add(ops)

M layout/stack.go => layout/stack.go +2 -4
@@ 54,8 54,7 @@ func (s Stack) Layout(gtx *Context, children ...StackChild) {
			continue
		}
		cs := gtx.Constraints
		cs.Width.Min = 0
		cs.Height.Min = 0
		cs.Min = image.Pt(0, 0)
		var m op.MacroOp
		m.Record(gtx.Ops)
		dims := ctxLayout(gtx, cs, w.widget)


@@ 77,8 76,7 @@ func (s Stack) Layout(gtx *Context, children ...StackChild) {
		var m op.MacroOp
		m.Record(gtx.Ops)
		cs := Constraints{
			Width:  Constraint{Min: maxSZ.X, Max: gtx.Constraints.Width.Max},
			Height: Constraint{Min: maxSZ.Y, Max: gtx.Constraints.Height.Max},
			Min: maxSZ, Max: gtx.Constraints.Max,
		}
		dims := ctxLayout(gtx, cs, w.widget)
		m.Stop()

M layout/stack_test.go => layout/stack_test.go +1 -2
@@ 10,8 10,7 @@ import (
func TestStack(t *testing.T) {
	var gtx Context
	gtx.Reset(nil, nil, image.Point{X: 100, Y: 100})
	gtx.Constraints.Width.Min = 0
	gtx.Constraints.Height.Min = 0
	gtx.Constraints.Min = image.Point{}
	exp := image.Point{X: 60, Y: 70}
	Stack{Alignment: Center}.Layout(&gtx,
		Expanded(func() {

M widget/button.go => widget/button.go +1 -1
@@ 54,7 54,7 @@ func (b *Clickable) Layout(gtx *layout.Context) {
	b.Update(gtx)
	var st op.StackOp
	st.Push(gtx.Ops)
	pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min()}).Add(gtx.Ops)
	pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
	b.click.Add(gtx.Ops)
	st.Pop()
	for len(b.history) > 0 {

M widget/editor.go => widget/editor.go +1 -1
@@ 244,7 244,7 @@ func (e *Editor) Layout(gtx *layout.Context, sh text.Shaper, font text.Font, siz
		e.font = font
		e.textSize = textSize
	}
	maxWidth := gtx.Constraints.Width.Max
	maxWidth := gtx.Constraints.Max.X
	if e.SingleLine {
		maxWidth = inf
	}

M widget/image.go => widget/image.go +1 -1
@@ 32,7 32,7 @@ func (im Image) Layout(gtx *layout.Context) {
	wf, hf := float32(size.X), float32(size.Y)
	w, h := gtx.Px(unit.Dp(wf*scale)), gtx.Px(unit.Dp(hf*scale))
	cs := gtx.Constraints
	d := image.Point{X: cs.Width.Constrain(w), Y: cs.Height.Constrain(h)}
	d := cs.Constrain(image.Pt(w, h))
	var s op.StackOp
	s.Push(gtx.Ops)
	clip.Rect{Rect: f32.Rectangle{Max: toPointF(d)}}.Op(gtx.Ops).Add(gtx.Ops)

M widget/label.go => widget/label.go +1 -1
@@ 87,7 87,7 @@ func (l *lineIterator) Next() (int, int, []text.Glyph, f32.Point, bool) {
func (l Label) Layout(gtx *layout.Context, s text.Shaper, font text.Font, size unit.Value, txt string) {
	cs := gtx.Constraints
	textSize := fixed.I(gtx.Px(size))
	lines := s.LayoutString(font, textSize, cs.Width.Max, txt)
	lines := s.LayoutString(font, textSize, cs.Max.X, txt)
	if max := l.MaxLines; max > 0 && len(lines) > max {
		lines = lines[:max]
	}

M widget/material/button.go => widget/material/button.go +8 -10
@@ 87,8 87,8 @@ func Clickable(gtx *layout.Context, button *widget.Clickable, w layout.Widget) {
		layout.Expanded(func() {
			clip.Rect{
				Rect: f32.Rectangle{Max: f32.Point{
					X: float32(gtx.Constraints.Width.Min),
					Y: float32(gtx.Constraints.Height.Min),
					X: float32(gtx.Constraints.Min.X),
					Y: float32(gtx.Constraints.Min.Y),
				}},
			}.Op(gtx.Ops).Add(gtx.Ops)
			for _, c := range button.History() {


@@ 111,15 111,14 @@ func (b ButtonStyle) Layout(gtx *layout.Context, button *widget.Clickable) {
}

func (b ButtonLayoutStyle) Layout(gtx *layout.Context, button *widget.Clickable, w layout.Widget) {
	hmin := gtx.Constraints.Width.Min
	vmin := gtx.Constraints.Height.Min
	min := gtx.Constraints.Min
	layout.Stack{Alignment: layout.Center}.Layout(gtx,
		layout.Expanded(func() {
			rr := float32(gtx.Px(b.CornerRadius))
			clip.Rect{
				Rect: f32.Rectangle{Max: f32.Point{
					X: float32(gtx.Constraints.Width.Min),
					Y: float32(gtx.Constraints.Height.Min),
					X: float32(gtx.Constraints.Min.X),
					Y: float32(gtx.Constraints.Min.Y),
				}},
				NE: rr, NW: rr, SE: rr, SW: rr,
			}.Op(gtx.Ops).Add(gtx.Ops)


@@ 129,8 128,7 @@ func (b ButtonLayoutStyle) Layout(gtx *layout.Context, button *widget.Clickable,
			}
		}),
		layout.Stacked(func() {
			gtx.Constraints.Width.Min = hmin
			gtx.Constraints.Height.Min = vmin
			gtx.Constraints.Min = min
			layout.Center.Layout(gtx, func() {
				b.Inset.Layout(gtx, func() {
					w()


@@ 146,7 144,7 @@ func (b ButtonLayoutStyle) Layout(gtx *layout.Context, button *widget.Clickable,
func (b IconButtonStyle) Layout(gtx *layout.Context, button *widget.Clickable) {
	layout.Stack{Alignment: layout.Center}.Layout(gtx,
		layout.Expanded(func() {
			size := gtx.Constraints.Width.Min
			size := gtx.Constraints.Min.X
			sizef := float32(size)
			rr := sizef * .5
			clip.Rect{


@@ 171,7 169,7 @@ func (b IconButtonStyle) Layout(gtx *layout.Context, button *widget.Clickable) {
			})
		}),
		layout.Expanded(func() {
			pointer.Ellipse(image.Rectangle{Max: gtx.Constraints.Min()}).Add(gtx.Ops)
			pointer.Ellipse(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
			button.Layout(gtx)
		}),
	)

M widget/material/checkable.go => widget/material/checkable.go +2 -4
@@ 34,8 34,7 @@ func (c *checkable) layout(gtx *layout.Context, checked bool) {
		icon = c.uncheckedStateIcon
	}

	hmin := gtx.Constraints.Width.Min
	vmin := gtx.Constraints.Height.Min
	min := gtx.Constraints.Min
	layout.Flex{Alignment: layout.Middle}.Layout(gtx,
		layout.Rigid(func() {
			layout.Center.Layout(gtx, func() {


@@ 51,8 50,7 @@ func (c *checkable) layout(gtx *layout.Context, checked bool) {
		}),

		layout.Rigid(func() {
			gtx.Constraints.Width.Min = hmin
			gtx.Constraints.Height.Min = vmin
			gtx.Constraints.Min = min
			layout.W.Layout(gtx, func() {
				layout.UniformInset(unit.Dp(2)).Layout(gtx, func() {
					paint.ColorOp{Color: c.Color}.Add(gtx.Ops)

M widget/material/editor.go => widget/material/editor.go +4 -4
@@ 45,11 45,11 @@ func (e EditorStyle) Layout(gtx *layout.Context, editor *widget.Editor) {
	tl := widget.Label{Alignment: editor.Alignment}
	tl.Layout(gtx, e.shaper, e.Font, e.TextSize, e.Hint)
	macro.Stop()
	if w := gtx.Dimensions.Size.X; gtx.Constraints.Width.Min < w {
		gtx.Constraints.Width.Min = w
	if w := gtx.Dimensions.Size.X; gtx.Constraints.Min.X < w {
		gtx.Constraints.Min.X = w
	}
	if h := gtx.Dimensions.Size.Y; gtx.Constraints.Height.Min < h {
		gtx.Constraints.Height.Min = h
	if h := gtx.Dimensions.Size.Y; gtx.Constraints.Min.Y < h {
		gtx.Constraints.Min.Y = h
	}
	editor.Layout(gtx, e.shaper, e.Font, e.TextSize)
	if editor.Len() > 0 {

M widget/material/progressbar.go => widget/material/progressbar.go +1 -1
@@ 50,7 50,7 @@ func (b ProgressBarStyle) Layout(gtx *layout.Context, progress int) {
		progress = 0
	}

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

	layout.Stack{Alignment: layout.W}.Layout(gtx,
		layout.Stacked(func() {

M widget/material/theme.go => widget/material/theme.go +1 -2
@@ 3,7 3,6 @@
package material

import (
	"image"
	"image/color"

	"gioui.org/f32"


@@ 66,7 65,7 @@ func argb(c uint32) color.RGBA {

func fill(gtx *layout.Context, col color.RGBA) {
	cs := gtx.Constraints
	d := image.Point{X: cs.Width.Min, Y: cs.Height.Min}
	d := cs.Min
	dr := f32.Rectangle{
		Max: f32.Point{X: float32(d.X), Y: float32(d.Y)},
	}