Cross OS desktop GUI with GO and Fyne - Layouts

May 14, 2019

Fyne Layout Managers API

Like any great widget toolkit Fyne use layout managers to adjust and rework positions, sizes and the overall look-and-feel of the container.

Layout managers have the ability to lay out graphical control elements (widgets) by their relative positions without using distance units.

Before going into more detail, let’s recall some concepts:

fyne.CanvasObject

This interface is implemented by any object that can be added to a canvas.

fyne.Container

A container holds a collection of fyne.CanvasObject.

fyne.Layout

A layout manages the size and position of a collection of fyne.CanvasObject objects and define the minimum size required to fit all of them.

Fyne groups one or more fyne.CanvasObject objects in a fyne.Container and lay out its child objects using a fyne.Layout.

Below a description of the standard layouts.

BorderLayout

It places a specific canvas object at the top, bottom, left, and right edges of a container; any other objects will fill the central space.

   BorderLayout

func buildContent() *fyne.Container {
    lbTop := widget.NewLabel("@top")
    lbTop.Alignment = fyne.TextAlignCenter

    lbBottom := widget.NewLabel("@bottom")
    lbBottom.Alignment = fyne.TextAlignCenter

    lbLeft := widget.NewLabel("@left")
    lbLeft.Alignment = fyne.TextAlignCenter

    lbRight := widget.NewLabel("@right")
    lbRight.Alignment = fyne.TextAlignCenter

    lbCenter := widget.NewLabelWithStyle("@center",
        fyne.TextAlignCenter,
        fyne.TextStyle{Bold: true},
    )

    return fyne.NewContainerWithLayout(
        layout.NewBorderLayout(lbTop, lbBottom, lbLeft, lbRight),
        lbTop, lbBottom, lbLeft, lbRight,
        lbCenter,
    )
}

BoxLayout

It arrange items in a stack, each at their minimum height (vertical) or width (horizontal); the other dimension will expand to the container edge.

   BorderLayout    BorderLayout

func buildContent(vertical bool) *fyne.Container {
	btItems := make([]*widget.Button, 0)
	for i := 0; i < 4; i++ {
		label := fmt.Sprintf("Button %d", i+1)
		bt := widget.NewButton(label, func() {})

		btItems = append(btItems, bt)
	}

	var ret *fyne.Container
	if vertical {
		ret = fyne.NewContainerWithLayout(layout.NewVBoxLayout())
	} else {
		ret = fyne.NewContainerWithLayout(layout.NewHBoxLayout())
	}

	for _, bt := range btItems {
		ret.AddObject(bt)
	}
	return ret
}

FixedGridLayout

This layout specifies the size of every cell and then arranges them in rows within the available space.

   BorderLayout

func buildContent() *fyne.Container {
	rows := 10
	cols := 9
	cellSize := fyne.NewSize(40, 40)
	
	btItems := make([]*widget.Button, 0)

	tot := rows * cols
	for i := 0; i < tot; i++ {
		label := fmt.Sprintf("%02d", i+1)
		bt := widget.NewButton(label, func() {})
		btItems = append(btItems, bt)
	}

	ret := fyne.NewContainerWithLayout(layout.NewFixedGridLayout(cellSize))

	for _, bt := range btItems {
		ret.AddObject(bt)
	}
	return ret
}

GridLayout

This layout has a specified number of columns.

   BorderLayout

func buildContent() *fyne.Container {
	btItems := make([]*widget.Button, 0)

	tot := 7 
	for i := 0; i < tot; i++ {
		label := fmt.Sprintf("%02d", i+1)
		bt := widget.NewButton(label, func() {})
		btItems = append(btItems, bt)
	}

	cols := 3
	ret := fyne.NewContainerWithLayout(layout.NewGridLayout(cols))
	for _, bt := range btItems {
		ret.AddObject(bt)
	}
	return ret
}

MaxLayout

Every item is set to the same size to fill the available space.

   BorderLayout

func buildContent() *fyne.Container {
	bt := widget.NewButton("Click Me!", func() {})

	ret := fyne.NewContainerWithLayout(layout.NewMaxLayout())
	ret.AddObject(bt)

	return ret
}

FormLayout

A two column grid where each row has a label and a widget.

   BorderLayout

func buildContent() *fyne.Container {
	lbUser := widget.NewLabel("Username")
	tfUser := widget.NewEntry()

	lbPass := widget.NewLabel("Password")
	tfPass := widget.NewPasswordEntry()

	ret := fyne.NewContainerWithLayout(layout.NewFormLayout())
	ret.AddObject(lbUser)
	ret.AddObject(tfUser)
	ret.AddObject(lbPass)
	ret.AddObject(tfPass)

	return ret
}

For today that’s all. Thank you for your time! Next Time: widgets. Stay Tuned!