Package gioui.org/layout
provides support for common layout operations such as spacing, lists and stacks of overlapping widgets.
In the layout examples we’ll use this ColorBox
widget to visualize layouts:
// Test colors.
var (
background = color.NRGBA{R: 0xC0, G: 0xC0, B: 0xC0, A: 0xFF}
red = color.NRGBA{R: 0xC0, G: 0x40, B: 0x40, A: 0xFF}
green = color.NRGBA{R: 0x40, G: 0xC0, B: 0x40, A: 0xFF}
blue = color.NRGBA{R: 0x40, G: 0x40, B: 0xC0, A: 0xFF}
)
// ColorBox creates a widget with the specified dimensions and color.
func ColorBox(gtx layout.Context, size image.Point, color color.NRGBA) layout.Dimensions {
defer clip.Rect{Max: size}.Push(gtx.Ops).Pop()
paint.ColorOp{Color: color}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
return layout.Dimensions{Size: size}
}
Inset
layout.Inset
adds space around a widget.
func inset(gtx layout.Context) layout.Dimensions {
// Draw rectangles inside of each other, with 30dp padding.
return layout.UniformInset(unit.Dp(30)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, gtx.Constraints.Max, red)
})
}
Stack
layout.Stack
lays out overlapping child elements according to the alignment direction. The child of a stack layout can be:
Stacked
- which doesn’t have minimum constraints and the maximum constraints passed to Stack.Layout.Expanded
- which uses the largest Stacked item as the minimum constraint and maximum is the maximum constraints passed to Stack.Layout.
For example, this draws green and blue rectangles on top of a red background:
func stacked(gtx layout.Context) layout.Dimensions {
return layout.Stack{}.Layout(gtx,
// Force widget to the same size as the second.
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
// This will have a minimum constraint of 100x100.
return ColorBox(gtx, gtx.Constraints.Min, red)
}),
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, image.Pt(100, 30), green)
}),
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, image.Pt(30, 100), blue)
}),
)
}
Background
Because layouting a background for a widget is very frequent there is a more performant implementation for that scenario, which roughly corresponds to:
layout.Stack{Alignment: layout.C}.Layout(gtx,
layout.Expanded(background),
layout.Stacked(widget)
)
func layoutBackground(gtx layout.Context) layout.Dimensions {
return layout.Background{}.Layout(gtx,
func(gtx layout.Context) layout.Dimensions {
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
paint.Fill(gtx.Ops, background)
return layout.Dimensions{Size: gtx.Constraints.Min}
}, func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, image.Pt(30, 100), blue)
})
}
List
layout.List
can display a potentially large list of items. Since List
also handles scrolling it must be persisted across layouts, otherwise the scrolling position is lost. List handles large numbers of items by only laying out the visible elements. Each frame, the provided closure is invoked only for indicies visible at the current scroll position (and possibly a small number of items above and below the scroll position).
var list = layout.List{}
func listing(gtx layout.Context) layout.Dimensions {
return list.Layout(gtx, 100, func(gtx layout.Context, i int) layout.Dimensions {
col := color.NRGBA{R: byte(i * 20), G: 0x20, B: 0x20, A: 0xFF}
return ColorBox(gtx, image.Pt(20, 100), col)
})
}
Flex
layout.Flex
lays out children according to their weights or rigid constraints. First the rigid elements are used to determine the remaining space and then the remaining space is divided among flexed children according to weights.
The children can be:
Rigid
- are laid out with as much space left over from other rigid children.Flexed
- children are sized according to their weights and the space left over from rigid children.
func flexed(gtx layout.Context) layout.Dimensions {
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, image.Pt(100, 100), red)
}),
layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, gtx.Constraints.Min, blue)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, image.Pt(100, 100), red)
}),
layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, gtx.Constraints.Min, green)
}),
)
}
Spacer
layout.Spacer
can be used together with layout.List
or layout.Flex
to add empty space between items.
func spacer(gtx layout.Context) layout.Dimensions {
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, image.Pt(100, 100), red)
}),
layout.Rigid(layout.Spacer{Width: 20}.Layout),
layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, gtx.Constraints.Min, blue)
}),
layout.Rigid(layout.Spacer{Width: 20}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, image.Pt(100, 100), red)
}),
layout.Rigid(layout.Spacer{Width: 20}.Layout),
layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions {
return ColorBox(gtx, gtx.Constraints.Min, green)
}),
)
}
Custom
Sometimes the builtin layouts are not sufficient. To create a custom layout for widgets there are special functions and structures to manipulate layout.Context. In general, layout code performs the following steps for each sub-widget:
- Use
op.Save
. - Set
layout.Context.Constraints
. - Set
op.TransformOp
. - Call
widget.Layout(gtx, ...)
. - Use dimensions returned by widget.
- Use
StateOp.Load
.
For complicated layouts you would also need to use macros. As an example take a look at layout.Flex. Which roughly implements:
- Record widgets in macros.
- Calculate sizes for non-rigid widgets.
- Draw widgets based on the calculated sizes by replaying their macros.