diff --git a/layout/context.go b/layout/context.go index 1e7b4633..33e0e691 100644 --- a/layout/context.go +++ b/layout/context.go @@ -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 diff --git a/layout/example_test.go b/layout/example_test.go index ac5b424d..a2710d28 100644 --- a/layout/example_test.go +++ b/layout/example_test.go @@ -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() { diff --git a/layout/flex.go b/layout/flex.go index f1b241f4..0d9ca013 100644 --- a/layout/flex.go +++ b/layout/flex.go @@ -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)} } } diff --git a/layout/layout.go b/layout/layout.go index d1ab7ac9..2a6a6732 100644 --- a/layout/layout.go +++ b/layout/layout.go @@ -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) { diff --git a/layout/list.go b/layout/list.go index a9e2a9e1..0023fba9 100644 --- a/layout/list.go +++ b/layout/list.go @@ -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) diff --git a/layout/stack.go b/layout/stack.go index 67c962d5..1b0af623 100644 --- a/layout/stack.go +++ b/layout/stack.go @@ -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() diff --git a/layout/stack_test.go b/layout/stack_test.go index 78fd3703..1fc619f5 100644 --- a/layout/stack_test.go +++ b/layout/stack_test.go @@ -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(>x, Expanded(func() { diff --git a/widget/button.go b/widget/button.go index a5eb1508..c43e5732 100644 --- a/widget/button.go +++ b/widget/button.go @@ -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 { diff --git a/widget/editor.go b/widget/editor.go index 11f8b3ea..12f80ddf 100644 --- a/widget/editor.go +++ b/widget/editor.go @@ -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 } diff --git a/widget/image.go b/widget/image.go index b8392476..a546ba2d 100644 --- a/widget/image.go +++ b/widget/image.go @@ -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) diff --git a/widget/label.go b/widget/label.go index d42538d5..09f9fff4 100644 --- a/widget/label.go +++ b/widget/label.go @@ -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] } diff --git a/widget/material/button.go b/widget/material/button.go index 334e4e61..483ac4cf 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -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) }), ) diff --git a/widget/material/checkable.go b/widget/material/checkable.go index dc1199a8..495b6da5 100644 --- a/widget/material/checkable.go +++ b/widget/material/checkable.go @@ -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) diff --git a/widget/material/editor.go b/widget/material/editor.go index d150f364..c2bb7e1f 100644 --- a/widget/material/editor.go +++ b/widget/material/editor.go @@ -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 { diff --git a/widget/material/progressbar.go b/widget/material/progressbar.go index 12c9f758..1293ab8c 100644 --- a/widget/material/progressbar.go +++ b/widget/material/progressbar.go @@ -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() { diff --git a/widget/material/theme.go b/widget/material/theme.go index 581ea1af..27d5237c 100644 --- a/widget/material/theme.go +++ b/widget/material/theme.go @@ -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)}, }