Containers and layouts
Each section below shows one container, taken from a single program that
draws them all. In the examples, box(name) is a small helper that returns a
tinted rectangle with a label on it.
| Constructor | Children are | Use it for |
|---|---|---|
NewVBox, NewHBox | In a column or a row, at their minimum height (or width) | Lists of controls, toolbars |
NewBorder | Around the edges, with the rest in the middle | The outer frame of most screens |
NewGridWithColumns, NewGridWithRows | In equal cells | Button pads, dashboards |
NewGridWrap | At one fixed size, wrapping onto new rows | Thumbnails, tiles |
NewCenter | At their minimum size, centred | A single control in a large space |
NewStack | On top of each other, all full size | Backgrounds, overlays |
NewPadded | Full size, inset by the theme padding | Breathing room around content |
New(layout.NewFormLayout(), ...) | In label and value columns | Forms |
VBox and HBox
container.NewVBox places children top to bottom. Each child is as wide as
the container and as tall as its own minimum height.
// Top to bottom. Each child is full width and only as tall as it needs.
return container.NewVBox(box("One"), box("Two"), box("Three"))container.NewHBox does the same left to right: each child is as tall as the
container and as wide as its minimum width.
A layout.NewSpacer()
in a box takes up the free space. Two spacers share it equally. Use one to
push items to the far end, or two to centre something:
// Left to right. The spacer takes the free space, pushing "Right" to the end.
return container.NewHBox(box("Left"), layout.NewSpacer(), box("Right"))Boxes never stretch a child along their main direction. If you want a child to
fill the remaining space, use a Border instead. Between children, boxes put
the theme’s padding; layout.NewCustomPaddedVBoxLayout(p) and
NewCustomPaddedHBoxLayout(p) use your own value.
Border
container.NewBorder(top, bottom, left, right, objects...) puts the first
four objects along the edges and stretches the rest over the middle. Pass
nil for an edge you do not need.
// Edges first (top, bottom, left, right; nil leaves one empty),
// then the content that fills the middle.
return container.NewBorder(box("Top"), box("Bottom"), box("L"), nil, box("Middle"))The top and bottom objects get their minimum height and the full width. The left and right objects get their minimum width and the height between top and bottom. Everything after the fourth argument fills the middle; if you pass several, they are stacked on top of each other.
A Border is the usual frame for a screen: a toolbar on top, a status line at the bottom, and a scrolling list in the middle that gets all the remaining space.
Grid
container.NewGridWithColumns(n, ...) lays children out in n columns,
adding rows as needed. Every cell is the same size, and each child fills its
cell.
// Equal-sized cells, two per row. Rows are added as needed.
return container.NewGridWithColumns(2, box("1"), box("2"), box("3"), box("4"))container.NewGridWithRows(n, ...) fixes the number of rows instead.
container.NewAdaptiveGrid(n, ...) switches between the two with the device
orientation; see Responsive layouts.
GridWrap
container.NewGridWrap(size, ...) gives every child the same fixed size and
places them in rows, wrapping to a new row when the next one does not fit.
// Every child gets the same fixed size; they flow onto new rows
// when the width runs out.
var tiles []core.CanvasObject
for i := 1; i <= 6; i++ {
tiles = append(tiles, box(fmt.Sprint(i)))
}
return container.NewGridWrap(geo.NewSize(50, 40), tiles...)When the children have different natural widths, such as a set of tags, use
layout.NewRowWrapLayout() instead; see
Responsive layouts.
Center
container.NewCenter keeps each child at its minimum size, in the middle of
the space.
// The child keeps its minimum size and sits in the middle.
return container.NewCenter(widget.NewButton("Centred", func() {}))Stack
container.NewStack makes every child the full size of the container and
draws them in order, so later children are on top. Use it to put a
background behind something, or a badge over it.
// Children on top of each other, all at full size. Later ones are on top.
bg := canvas.NewVerticalGradient(
color.NRGBA{R: 0x3b, G: 0x6e, B: 0xc8, A: 0xff},
color.NRGBA{R: 0xe8, G: 0x8a, B: 0x2c, A: 0xff})
return container.NewStack(bg, container.NewCenter(widget.NewLabel("On top")))Padded
container.NewPadded makes each child the full size of the container minus
the theme’s padding on all four sides.
// The child fills the space minus the theme's padding on every side.
return container.NewPadded(box("Padded"))For different padding on each side, use
container.New(layout.NewCustomPaddedLayout(top, bottom, left, right), ...).
Form
The form layout has two columns. Children are taken in pairs: the first of each pair is the label, the second its value. The label column is as wide as the widest label.
// Two columns: labels on the left, content on the right.
return container.New(layout.NewFormLayout(),
widget.NewLabel("Name"), widget.NewEntry(),
widget.NewLabel("Email"), widget.NewEntry(),
)For a form with a submit button and validation, use the
widget.Form
widget instead.
Other layouts
The layout package
has a few more, used with container.New:
NewFlexHBoxLayout(flex)andNewFlexVBoxLayout(flex): a row or column where each child has a grow factor. Children with factor 0 keep their minimum size; the rest share the free space in proportion. TheMinWidthandMaxWidthfields (orMinHeightandMaxHeight) clamp individual children.NewShrinkLayout(): reports a minimum size of zero and fills the space. Wrap content in it when a parent such as a split must be able to shrink below the content’s natural minimum size.NewResponsiveLayoutandNewRowWrapLayout: see Responsive layouts.
Changing a container’s children
A container’s children are in its Objects field. Change them with methods,
so the layout runs again:
Add(obj)appends a child. It is safe to call from any goroutine.Remove(obj)removes one child, andRemoveAll()removes them all. Call these on the UI thread.
If you assign to Objects directly, call Refresh() afterwards.
Hide() on a child removes it from the layout: the layouts above skip
hidden children.
The whole program
// Every standard container in one window, each in its own titled cell.
package main
import (
"fmt"
"image/color"
"github.com/belluikit/bellui"
"github.com/belluikit/bellui/app"
"github.com/belluikit/bellui/canvas"
"github.com/belluikit/bellui/container"
"github.com/belluikit/bellui/core"
"github.com/belluikit/bellui/geo"
"github.com/belluikit/bellui/layout"
"github.com/belluikit/bellui/widget"
)
// box is a coloured rectangle with a label, used as sample content.
func box(name string) core.CanvasObject {
bg := canvas.NewRectangle(color.NRGBA{R: 0x3b, G: 0x6e, B: 0xc8, A: 0x60})
return container.NewStack(bg, widget.NewLabel(name))
}
func vbox() core.CanvasObject {
// Top to bottom. Each child is full width and only as tall as it needs.
return container.NewVBox(box("One"), box("Two"), box("Three"))
}
func hbox() core.CanvasObject {
// Left to right. The spacer takes the free space, pushing "Right" to the end.
return container.NewHBox(box("Left"), layout.NewSpacer(), box("Right"))
}
func border() core.CanvasObject {
// Edges first (top, bottom, left, right; nil leaves one empty),
// then the content that fills the middle.
return container.NewBorder(box("Top"), box("Bottom"), box("L"), nil, box("Middle"))
}
func grid() core.CanvasObject {
// Equal-sized cells, two per row. Rows are added as needed.
return container.NewGridWithColumns(2, box("1"), box("2"), box("3"), box("4"))
}
func gridWrap() core.CanvasObject {
// Every child gets the same fixed size; they flow onto new rows
// when the width runs out.
var tiles []core.CanvasObject
for i := 1; i <= 6; i++ {
tiles = append(tiles, box(fmt.Sprint(i)))
}
return container.NewGridWrap(geo.NewSize(50, 40), tiles...)
}
func center() core.CanvasObject {
// The child keeps its minimum size and sits in the middle.
return container.NewCenter(widget.NewButton("Centred", func() {}))
}
func stack() core.CanvasObject {
// Children on top of each other, all at full size. Later ones are on top.
bg := canvas.NewVerticalGradient(
color.NRGBA{R: 0x3b, G: 0x6e, B: 0xc8, A: 0xff},
color.NRGBA{R: 0xe8, G: 0x8a, B: 0x2c, A: 0xff})
return container.NewStack(bg, container.NewCenter(widget.NewLabel("On top")))
}
func padded() core.CanvasObject {
// The child fills the space minus the theme's padding on every side.
return container.NewPadded(box("Padded"))
}
func form() core.CanvasObject {
// Two columns: labels on the left, content on the right.
return container.New(layout.NewFormLayout(),
widget.NewLabel("Name"), widget.NewEntry(),
widget.NewLabel("Email"), widget.NewEntry(),
)
}
func main() {
a := app.NewWithID("com.example.containers")
w := a.NewWindow("Containers")
cell := func(title string, content core.CanvasObject) core.CanvasObject {
heading := widget.NewLabelWithStyle(title, bellui.TextAlignLeading, bellui.TextStyle{Bold: true})
frame := canvas.NewRectangle(color.Transparent)
frame.StrokeColor = color.NRGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80}
frame.StrokeWidth = 1
return container.NewBorder(heading, nil, nil, nil,
container.NewStack(frame, container.NewPadded(content)))
}
w.SetContent(container.NewGridWithColumns(3,
cell("NewVBox", vbox()),
cell("NewHBox", hbox()),
cell("NewBorder", border()),
cell("NewGridWithColumns", grid()),
cell("NewGridWrap", gridWrap()),
cell("NewCenter", center()),
cell("NewStack", stack()),
cell("NewPadded", padded()),
cell("FormLayout", form()),
))
w.Resize(geo.NewSize(900, 650))
bellui.ShowAndRun(w)
}