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 form the interface between
layouts and interface child elements. Every layout operation
start with a set of constraints for acceptable widths and heights
of a child. The operation ends with the child computing and returning
its size and baseline (if any).
Constraints and dimensions form the interface between layouts and
interface child elements. This package operates on Widgets, functions
that compute Dimensions from a a set of constraints for acceptable
widths and heights. Both the constraints and dimensions are maintained
in an implicit Context to keep the Widget declaration short.
For example, to add space above a widget:
var cs layout.Constraints = ...
ctx := new(layout.Context)
ctx.Constraints = ...
// Configure a top inset.
inset := layout.Inset{Top: ui.Dp(8), ...}
// 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.
dimensions := widget.Layout(..., cs)
return dimensions
...
dims := layout.Dimensions{...}
ctx.Dimensions = dims
})
Note that the example does not generate any garbage even though the
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
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:
inset := layout.Inset{...}
inset.Layout(..., cs, func(cs layout.Constraints) layout.Dimensions {
inset.Layout(..., ctx, func() {
align := layout.Align{...}
return align.Layout(..., cs, func(cs layout.Constraints) layout.Dimensions {
return widget.Layout(..., cs)
align.Layout(..., ctx, func() {
widget.Layout(..., ctx)
})
})