ui/layout: introduce Context

Context keeps the current Constraints and Dimensions so the layout
function scopes don't have to.

With

	ctx := new(layout.Context)

a label with margins and alignment goes from

	return al.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
		in := layout.Inset{...}
		return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
			return text.Label{...}.Layout(ops, cs)
		})
	})

to

	al.Layout(ops, ctx, func() {
		in := layout.Inset{...}
		in.Layout(c, ops, ctx, func() {
		       text.Label{...}.Layout(ops, ctx)
		})
	})

It was a difficult trade-off between the verbose functional approach
and the shorter but more complex Context.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-09-24 19:06:07 +02:00
parent 64add13d28
commit ce9bcee62b
9 changed files with 126 additions and 89 deletions
+15 -13
View File
@@ -5,28 +5,30 @@ Package layout implements layouts common to GUI programs.
Constraints and dimensions Constraints and dimensions
Constraints and dimensions form the interface between Constraints and dimensions form the interface between layouts and
layouts and interface child elements. Every layout operation interface child elements. This package operates on Widgets, functions
start with a set of constraints for acceptable widths and heights that compute Dimensions from a a set of constraints for acceptable
of a child. The operation ends with the child computing and returning widths and heights. Both the constraints and dimensions are maintained
its size and baseline (if any). in an implicit Context to keep the Widget declaration short.
For example, to add space above a widget: For example, to add space above a widget:
var cs layout.Constraints = ... ctx := new(layout.Context)
ctx.Constraints = ...
// Configure a top inset. // Configure a top inset.
inset := layout.Inset{Top: ui.Dp(8), ...} inset := layout.Inset{Top: ui.Dp(8), ...}
// Use the inset to lay out a widget. // Use the inset to lay out a widget.
inset.Layout(..., cs, func(cs layout.Constraints) layout.Dimensions { inset.Layout(..., ctx, func() {
// Lay out widget and determine its size given the constraints. // Lay out widget and determine its size given the constraints.
dimensions := widget.Layout(..., cs) ...
return dimensions dims := layout.Dimensions{...}
ctx.Dimensions = dims
}) })
Note that the example does not generate any garbage even though the Note that the example does not generate any garbage even though the
Inset is transient. Layouts that don't accept user input are designed Inset is transient. Layouts that don't accept user input are designed
to escape to the heap during their use. to not escape to the heap during their use.
Layout operations are recursive: a child in a layout operation can Layout operations are recursive: a child in a layout operation can
itself be another layout. That way, complex user interfaces can itself be another layout. That way, complex user interfaces can
@@ -35,10 +37,10 @@ be created from a few generic layouts.
This example both aligns and insets a child: This example both aligns and insets a child:
inset := layout.Inset{...} inset := layout.Inset{...}
inset.Layout(..., cs, func(cs layout.Constraints) layout.Dimensions { inset.Layout(..., ctx, func() {
align := layout.Align{...} align := layout.Align{...}
return align.Layout(..., cs, func(cs layout.Constraints) layout.Dimensions { align.Layout(..., ctx, func() {
return widget.Layout(..., cs) widget.Layout(..., ctx)
}) })
}) })
+18 -13
View File
@@ -20,9 +20,9 @@ type Flex struct {
// Alignment is the alignment in the cross axis. // Alignment is the alignment in the cross axis.
Alignment Alignment Alignment Alignment
ctx *Context
macro ui.MacroOp macro ui.MacroOp
ops *ui.Ops ops *ui.Ops
cs Constraints
mode flexMode mode flexMode
size int size int
rigidSize int rigidSize int
@@ -69,13 +69,13 @@ const (
) )
// Init must be called before Rigid or Flexible. // Init must be called before Rigid or Flexible.
func (f *Flex) Init(ops *ui.Ops, cs Constraints) *Flex { func (f *Flex) Init(ops *ui.Ops, ctx *Context) *Flex {
if f.mode > modeBegun { if f.mode > modeBegun {
panic("must End the current child before calling Init again") panic("must End the current child before calling Init again")
} }
f.mode = modeBegun f.mode = modeBegun
f.ops = ops f.ops = ops
f.cs = cs f.ctx = ctx
f.size = 0 f.size = 0
f.rigidSize = 0 f.rigidSize = 0
f.maxCross = 0 f.maxCross = 0
@@ -98,20 +98,23 @@ func (f *Flex) begin(mode flexMode) {
// from 0 to the remaining space. // from 0 to the remaining space.
func (f *Flex) Rigid(w Widget) FlexChild { func (f *Flex) Rigid(w Widget) FlexChild {
f.begin(modeRigid) f.begin(modeRigid)
mainc := axisMainConstraint(f.Axis, f.cs) cs := f.ctx.Constraints
mainc := axisMainConstraint(f.Axis, cs)
mainMax := mainc.Max - f.size mainMax := mainc.Max - f.size
if mainMax < 0 { if mainMax < 0 {
mainMax = 0 mainMax = 0
} }
cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, axisCrossConstraint(f.Axis, f.cs)) cs = axisConstraints(f.Axis, Constraint{Max: mainMax}, axisCrossConstraint(f.Axis, cs))
return f.end(w(cs)) dims := f.ctx.Layout(cs, w)
return f.end(dims)
} }
// Flexible is like Rigid, where the main axis size is also constrained to a // Flexible is like Rigid, where the main axis size is also constrained to a
// fraction of the space not taken up by Rigid children. // fraction of the space not taken up by Rigid children.
func (f *Flex) Flexible(weight float32, w Widget) FlexChild { func (f *Flex) Flexible(weight float32, w Widget) FlexChild {
f.begin(modeFlex) f.begin(modeFlex)
mainc := axisMainConstraint(f.Axis, f.cs) cs := f.ctx.Constraints
mainc := axisMainConstraint(f.Axis, cs)
var flexSize int var flexSize int
if mainc.Max > f.size { if mainc.Max > f.size {
flexSize = mainc.Max - f.rigidSize flexSize = mainc.Max - f.rigidSize
@@ -125,8 +128,9 @@ func (f *Flex) Flexible(weight float32, w Widget) FlexChild {
} }
} }
submainc := Constraint{Max: flexSize} submainc := Constraint{Max: flexSize}
cs := axisConstraints(f.Axis, submainc, axisCrossConstraint(f.Axis, f.cs)) cs = axisConstraints(f.Axis, submainc, axisCrossConstraint(f.Axis, cs))
return f.end(w(cs)) dims := f.ctx.Layout(cs, w)
return f.end(dims)
} }
// End a child by specifying its dimensions. Pass the returned layout result // End a child by specifying its dimensions. Pass the returned layout result
@@ -153,9 +157,10 @@ func (f *Flex) end(dims Dimensions) FlexChild {
// Layout a list of children. The order of the children determines their laid // Layout a list of children. The order of the children determines their laid
// out order. // out order.
func (f *Flex) Layout(children ...FlexChild) Dimensions { func (f *Flex) Layout(children ...FlexChild) {
mainc := axisMainConstraint(f.Axis, f.cs) cs := f.ctx.Constraints
crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross) mainc := axisMainConstraint(f.Axis, cs)
crossSize := axisCrossConstraint(f.Axis, cs).Constrain(f.maxCross)
var space int var space int
if mainc.Min > f.size { if mainc.Min > f.size {
space = mainc.Min - f.size space = mainc.Min - f.size
@@ -220,7 +225,7 @@ func (f *Flex) Layout(children ...FlexChild) Dimensions {
if baseline == 0 { if baseline == 0 {
baseline = sz.Y baseline = sz.Y
} }
return Dimensions{Size: sz, Baseline: baseline} f.ctx.Dimensions = Dimensions{Size: sz, Baseline: baseline}
} }
func axisPoint(a Axis, main, cross int) image.Point { func axisPoint(a Axis, main, cross int) image.Point {
+31 -12
View File
@@ -37,9 +37,16 @@ type Alignment uint8
// space. // space.
type Direction uint8 type Direction uint8
// Widget is a function that computes a set of dimensions that // Widget is a function scope for drawing, processing input and
// satisfies the given constraints. // computing dimensions for a user interface element.
type Widget func(cs Constraints) Dimensions type Widget func()
// Context tracks the current constraints and dimensions during
// layout.
type Context struct {
Constraints Constraints
Dimensions Dimensions
}
const ( const (
Start Alignment = iota Start Alignment = iota
@@ -65,6 +72,17 @@ const (
Vertical Vertical
) )
// Layout a widget with a set of constraints and return its
// dimensions. The previous constraints are restored after layout.
func (s *Context) Layout(cs Constraints, w Widget) Dimensions {
saved := s.Constraints
s.Constraints = cs
s.Dimensions = Dimensions{}
w()
s.Constraints = saved
return s.Dimensions
}
// Constrain a value to the range [Min; Max]. // Constrain a value to the range [Min; Max].
func (c Constraint) Constrain(v int) int { func (c Constraint) Constrain(v int) int {
if v < c.Min { if v < c.Min {
@@ -100,12 +118,12 @@ type Align struct {
} }
// Layout a widget. // Layout a widget.
func (in *Inset) Layout(c ui.Config, ops *ui.Ops, cs Constraints, w Widget) Dimensions { func (in Inset) Layout(c ui.Config, ops *ui.Ops, ctx *Context, w Widget) {
top := c.Px(in.Top) top := c.Px(in.Top)
right := c.Px(in.Right) right := c.Px(in.Right)
bottom := c.Px(in.Bottom) bottom := c.Px(in.Bottom)
left := c.Px(in.Left) left := c.Px(in.Left)
mcs := cs mcs := ctx.Constraints
mcs.Width.Min -= left + right mcs.Width.Min -= left + right
mcs.Width.Max -= left + right mcs.Width.Max -= left + right
if mcs.Width.Min < 0 { if mcs.Width.Min < 0 {
@@ -125,10 +143,10 @@ func (in *Inset) Layout(c ui.Config, ops *ui.Ops, cs Constraints, w Widget) Dime
var stack ui.StackOp var stack ui.StackOp
stack.Push(ops) stack.Push(ops)
ui.TransformOp{}.Offset(toPointF(image.Point{X: left, Y: top})).Add(ops) ui.TransformOp{}.Offset(toPointF(image.Point{X: left, Y: top})).Add(ops)
dims := w(mcs) dims := ctx.Layout(mcs, w)
stack.Pop() stack.Pop()
return Dimensions{ ctx.Dimensions = Dimensions{
Size: cs.Constrain(dims.Size.Add(image.Point{X: right + left, Y: top + bottom})), Size: ctx.Constraints.Constrain(dims.Size.Add(image.Point{X: right + left, Y: top + bottom})),
Baseline: dims.Baseline + top, Baseline: dims.Baseline + top,
} }
} }
@@ -140,13 +158,14 @@ func UniformInset(v ui.Value) Inset {
} }
// Layout a widget. // Layout a widget.
func (a *Align) Layout(ops *ui.Ops, cs Constraints, w Widget) Dimensions { func (a Align) Layout(ops *ui.Ops, st *Context, w Widget) {
var macro ui.MacroOp var macro ui.MacroOp
macro.Record(ops)
cs := st.Constraints
mcs := cs mcs := cs
mcs.Width.Min = 0 mcs.Width.Min = 0
mcs.Height.Min = 0 mcs.Height.Min = 0
macro.Record(ops) dims := st.Layout(mcs, w)
dims := w(mcs)
macro.Stop() macro.Stop()
sz := dims.Size sz := dims.Size
if sz.X < cs.Width.Min { if sz.X < cs.Width.Min {
@@ -173,7 +192,7 @@ func (a *Align) Layout(ops *ui.Ops, cs Constraints, w Widget) Dimensions {
ui.TransformOp{}.Offset(toPointF(p)).Add(ops) ui.TransformOp{}.Offset(toPointF(p)).Add(ops)
macro.Add(ops) macro.Add(ops)
stack.Pop() stack.Pop()
return Dimensions{ st.Dimensions = Dimensions{
Size: sz, Size: sz,
Baseline: dims.Baseline, Baseline: dims.Baseline,
} }
+36 -34
View File
@@ -19,22 +19,21 @@ var cfg = new(config)
func ExampleInset() { func ExampleInset() {
ops := new(ui.Ops) ops := new(ui.Ops)
ctx := new(layout.Context)
// Loose constraints with no minimal size. // Loose constraints with no minimal size.
var cs layout.Constraints ctx.Constraints.Width.Max = 100
cs.Width.Max = 100 ctx.Constraints.Height.Max = 100
cs.Height.Max = 100
// Inset all edges by 10. // Inset all edges by 10.
inset := layout.UniformInset(ui.Dp(10)) inset := layout.UniformInset(ui.Dp(10))
dims := inset.Layout(cfg, ops, cs, func(cs layout.Constraints) layout.Dimensions { inset.Layout(cfg, ops, ctx, func() {
// Lay out a 50x50 sized widget. // Lay out a 50x50 sized widget.
dims := layoutWidget(50, 50, cs) layoutWidget(ctx, 50, 50)
fmt.Println(dims.Size) fmt.Println(ctx.Dimensions.Size)
return dims
}) })
fmt.Println(dims.Size) fmt.Println(ctx.Dimensions.Size)
// Output: // Output:
// (50,50) // (50,50)
@@ -43,19 +42,19 @@ func ExampleInset() {
func ExampleAlign() { func ExampleAlign() {
ops := new(ui.Ops) ops := new(ui.Ops)
ctx := new(layout.Context)
// Rigid constraints with both minimum and maximum set. // Rigid constraints with both minimum and maximum set.
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100}) ctx.Constraints = layout.RigidConstraints(image.Point{X: 100, Y: 100})
align := layout.Align{Alignment: layout.Center} align := layout.Align{Alignment: layout.Center}
dims := align.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions { align.Layout(ops, ctx, func() {
// Lay out a 50x50 sized widget. // Lay out a 50x50 sized widget.
dims := layoutWidget(50, 50, cs) layoutWidget(ctx, 50, 50)
fmt.Println(dims.Size) fmt.Println(ctx.Dimensions.Size)
return dims
}) })
fmt.Println(dims.Size) fmt.Println(ctx.Dimensions.Size)
// Output: // Output:
// (50,50) // (50,50)
@@ -64,22 +63,23 @@ func ExampleAlign() {
func ExampleFlex() { func ExampleFlex() {
ops := new(ui.Ops) ops := new(ui.Ops)
ctx := new(layout.Context)
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100}) ctx.Constraints = layout.RigidConstraints(image.Point{X: 100, Y: 100})
flex := layout.Flex{} flex := layout.Flex{}
flex.Init(ops, cs) flex.Init(ops, ctx)
// Rigid 10x10 widget. // Rigid 10x10 widget.
child1 := flex.Rigid(func(cs layout.Constraints) layout.Dimensions { child1 := flex.Rigid(func() {
fmt.Printf("Rigid: %v\n", cs.Width) fmt.Printf("Rigid: %v\n", ctx.Constraints.Width)
return layoutWidget(10, 10, cs) layoutWidget(ctx, 10, 10)
}) })
// Child with 50% space allowance. // Child with 50% space allowance.
child2 := flex.Flexible(0.5, func(cs layout.Constraints) layout.Dimensions { child2 := flex.Flexible(0.5, func() {
fmt.Printf("50%%: %v\n", cs.Width) fmt.Printf("50%%: %v\n", ctx.Constraints.Width)
return layoutWidget(10, 10, cs) layoutWidget(ctx, 10, 10)
}) })
flex.Layout(child1, child2) flex.Layout(child1, child2)
@@ -91,21 +91,22 @@ func ExampleFlex() {
func ExampleStack() { func ExampleStack() {
ops := new(ui.Ops) ops := new(ui.Ops)
ctx := new(layout.Context)
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100}) ctx.Constraints = layout.RigidConstraints(image.Point{X: 100, Y: 100})
stack := layout.Stack{} stack := layout.Stack{}
stack.Init(ops, cs) stack.Init(ops, ctx)
// Rigid 50x50 widget. // Rigid 50x50 widget.
child1 := stack.Rigid(func(cs layout.Constraints) layout.Dimensions { child1 := stack.Rigid(func() {
return layoutWidget(50, 50, cs) layoutWidget(ctx, 50, 50)
}) })
// Force widget to the same size as the first. // Force widget to the same size as the first.
child2 := stack.Expand(func(cs layout.Constraints) layout.Dimensions { child2 := stack.Expand(func() {
fmt.Printf("Expand: %v\n", cs) fmt.Printf("Expand: %v\n", ctx.Constraints)
return layoutWidget(10, 10, cs) layoutWidget(ctx, 10, 10)
}) })
stack.Layout(child1, child2) stack.Layout(child1, child2)
@@ -116,17 +117,18 @@ func ExampleStack() {
func ExampleList() { func ExampleList() {
ops := new(ui.Ops) ops := new(ui.Ops)
ctx := new(layout.Context)
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100}) ctx.Constraints = layout.RigidConstraints(image.Point{X: 100, Y: 100})
// The list is 1e6 elements, but only 5 fit the constraints. // The list is 1e6 elements, but only 5 fit the constraints.
const listLen = 1e6 const listLen = 1e6
var list layout.List var list layout.List
count := 0 count := 0
list.Layout(cfg, q, ops, cs, listLen, func(cs layout.Constraints, i int) layout.Dimensions { list.Layout(cfg, q, ops, ctx, listLen, func(i int) {
count++ count++
return layoutWidget(20, 20, cs) layoutWidget(ctx, 20, 20)
}) })
fmt.Println(count) fmt.Println(count)
@@ -135,8 +137,8 @@ func ExampleList() {
// 5 // 5
} }
func layoutWidget(width, height int, cs layout.Constraints) layout.Dimensions { func layoutWidget(ctx *layout.Context, width, height int) {
return layout.Dimensions{ ctx.Dimensions = layout.Dimensions{
Size: image.Point{ Size: image.Point{
X: width, X: width,
Y: height, Y: height,
+8 -4
View File
@@ -58,7 +58,7 @@ type List struct {
// ListElement is a function that computes the dimensions of // ListElement is a function that computes the dimensions of
// a list element. // a list element.
type ListElement func(cs Constraints, index int) Dimensions type ListElement func(index int)
type iterationDir uint8 type iterationDir uint8
@@ -96,12 +96,16 @@ func (l *List) init(cfg ui.Config, q input.Queue, ops *ui.Ops, cs Constraints, l
} }
// Layout the List and return its dimensions. // Layout the List and return its dimensions.
func (l *List) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs Constraints, len int, w ListElement) Dimensions { func (l *List) Layout(c ui.Config, q input.Queue, ops *ui.Ops, ctx *Context, len int, w ListElement) {
cs := ctx.Constraints
for l.init(c, q, ops, cs, len); l.more(); l.next() { for l.init(c, q, ops, cs, len); l.more(); l.next() {
cs := axisConstraints(l.Axis, Constraint{Max: inf}, axisCrossConstraint(l.Axis, l.cs)) cs := axisConstraints(l.Axis, Constraint{Max: inf}, axisCrossConstraint(l.Axis, l.cs))
l.end(w(cs, l.index())) i := l.index()
l.end(ctx.Layout(cs, func() {
w(i)
}))
} }
return l.layout() ctx.Dimensions = l.layout()
} }
func (l *List) scrollToEnd() bool { func (l *List) scrollToEnd() bool {
+9 -7
View File
@@ -18,7 +18,7 @@ type Stack struct {
macro ui.MacroOp macro ui.MacroOp
ops *ui.Ops ops *ui.Ops
constrained bool constrained bool
cs Constraints ctx *Context
maxSZ image.Point maxSZ image.Point
baseline int baseline int
} }
@@ -30,9 +30,9 @@ type StackChild struct {
} }
// Init a stack before calling Rigid or Expand. // Init a stack before calling Rigid or Expand.
func (s *Stack) Init(ops *ui.Ops, cs Constraints) *Stack { func (s *Stack) Init(ops *ui.Ops, ctx *Context) *Stack {
s.ops = ops s.ops = ops
s.cs = cs s.ctx = ctx
s.constrained = true s.constrained = true
s.maxSZ = image.Point{} s.maxSZ = image.Point{}
s.baseline = 0 s.baseline = 0
@@ -50,7 +50,8 @@ func (s *Stack) begin() {
// passed to Init. // passed to Init.
func (s *Stack) Rigid(w Widget) StackChild { func (s *Stack) Rigid(w Widget) StackChild {
s.begin() s.begin()
return s.end(w(s.cs)) dims := s.ctx.Layout(s.ctx.Constraints, w)
return s.end(dims)
} }
// Expand lays out a widget with constraints that exactly match // Expand lays out a widget with constraints that exactly match
@@ -61,7 +62,8 @@ func (s *Stack) Expand(w Widget) StackChild {
Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X}, Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X},
Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y}, Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y},
} }
return s.end(w(cs)) dims := s.ctx.Layout(cs, w)
return s.end(dims)
} }
// End a child by specifying its dimensions. // End a child by specifying its dimensions.
@@ -83,7 +85,7 @@ func (s *Stack) end(dims Dimensions) StackChild {
// Layout a list of children. The order of the children determines their laid // Layout a list of children. The order of the children determines their laid
// out order. // out order.
func (s *Stack) Layout(children ...StackChild) Dimensions { func (s *Stack) Layout(children ...StackChild) {
for _, ch := range children { for _, ch := range children {
sz := ch.dims.Size sz := ch.dims.Size
var p image.Point var p image.Point
@@ -109,7 +111,7 @@ func (s *Stack) Layout(children ...StackChild) Dimensions {
if b == 0 { if b == 0 {
b = s.maxSZ.Y b = s.maxSZ.Y
} }
return Dimensions{ s.ctx.Dimensions = Dimensions{
Size: s.maxSZ, Size: s.maxSZ,
Baseline: b, Baseline: b,
} }
+3 -2
View File
@@ -165,7 +165,8 @@ func (e *Editor) Focus() {
e.requestFocus = true e.requestFocus = true
} }
func (e *Editor) Layout(cfg ui.Config, queue input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions { func (e *Editor) Layout(cfg ui.Config, queue input.Queue, ops *ui.Ops, ctx *layout.Context) {
cs := ctx.Constraints
for _, ok := e.Next(cfg, queue); ok; _, ok = e.Next(cfg, queue) { for _, ok := e.Next(cfg, queue); ok; _, ok = e.Next(cfg, queue) {
} }
twoDp := cfg.Px(ui.Dp(2)) twoDp := cfg.Px(ui.Dp(2))
@@ -270,7 +271,7 @@ func (e *Editor) Layout(cfg ui.Config, queue input.Queue, ops *ui.Ops, cs layout
pointer.RectAreaOp{Rect: r}.Add(ops) pointer.RectAreaOp{Rect: r}.Add(ops)
e.scroller.Add(ops) e.scroller.Add(ops)
e.clicker.Add(ops) e.clicker.Add(ops)
return layout.Dimensions{Size: e.viewSize, Baseline: baseline} ctx.Dimensions = layout.Dimensions{Size: e.viewSize, Baseline: baseline}
} }
// Text returns the contents of the editor. // Text returns the contents of the editor.
+3 -2
View File
@@ -92,7 +92,8 @@ func (l *lineIterator) Next() (String, f32.Point, bool) {
return String{}, f32.Point{}, false return String{}, f32.Point{}, false
} }
func (l Label) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimensions { func (l Label) Layout(ops *ui.Ops, ctx *layout.Context) {
cs := ctx.Constraints
textLayout := l.Face.Layout(l.Text, LayoutOptions{MaxWidth: cs.Width.Max}) textLayout := l.Face.Layout(l.Text, LayoutOptions{MaxWidth: cs.Width.Max})
lines := textLayout.Lines lines := textLayout.Lines
if max := l.MaxLines; max > 0 && len(lines) > max { if max := l.MaxLines; max > 0 && len(lines) > max {
@@ -127,7 +128,7 @@ func (l Label) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
paint.PaintOp{Rect: lclip}.Add(ops) paint.PaintOp{Rect: lclip}.Add(ops)
stack.Pop() stack.Pop()
} }
return dims ctx.Dimensions = dims
} }
func toRectF(r image.Rectangle) f32.Rectangle { func toRectF(r image.Rectangle) f32.Rectangle {
+3 -2
View File
@@ -25,7 +25,7 @@ type Image struct {
Scale float32 Scale float32
} }
func (im Image) Layout(c ui.Config, ops *ui.Ops, cs layout.Constraints) layout.Dimensions { func (im Image) Layout(c ui.Config, ops *ui.Ops, ctx *layout.Context) {
size := im.Src.Bounds() size := im.Src.Bounds()
wf, hf := float32(size.Dx()), float32(size.Dy()) wf, hf := float32(size.Dx()), float32(size.Dy())
var w, h int var w, h int
@@ -35,6 +35,7 @@ func (im Image) Layout(c ui.Config, ops *ui.Ops, cs layout.Constraints) layout.D
} else { } else {
w, h = int(wf*im.Scale+.5), int(hf*im.Scale+.5) w, h = int(wf*im.Scale+.5), int(hf*im.Scale+.5)
} }
cs := ctx.Constraints
d := image.Point{X: cs.Width.Constrain(w), Y: cs.Height.Constrain(h)} d := image.Point{X: cs.Width.Constrain(w), Y: cs.Height.Constrain(h)}
aspect := float32(w) / float32(h) aspect := float32(w) / float32(h)
dw, dh := float32(d.X), float32(d.Y) dw, dh := float32(d.X), float32(d.Y)
@@ -49,5 +50,5 @@ func (im Image) Layout(c ui.Config, ops *ui.Ops, cs layout.Constraints) layout.D
} }
paint.ImageOp{Src: im.Src, Rect: im.Rect}.Add(ops) paint.ImageOp{Src: im.Src, Rect: im.Rect}.Add(ops)
paint.PaintOp{Rect: dr}.Add(ops) paint.PaintOp{Rect: dr}.Add(ops)
return layout.Dimensions{Size: d, Baseline: d.Y} ctx.Dimensions = layout.Dimensions{Size: d, Baseline: d.Y}
} }