Skip to content
Right-to-left layout

Right-to-left layout

This page covers the API that controls layout direction: forcing it, scoping it to part of a window, and supporting it in your own layouts and text styles.

If you are translating an app into Arabic, Hebrew, Persian or Urdu, start with Right-to-left languages in the Localisation section. It covers what Bellui mirrors and what it does not, fonts, and the text-direction limits of v1.0.0.

By default the direction comes from the app’s locale. A locale whose script is written right to left, such as Arabic or Hebrew, gives a right-to-left layout; everything else is left to right. Horizontal containers then place their first item on the right, leading-aligned text sits on the right, and directional icons are drawn mirrored.

Force a direction

bellui.SetLayoutDirection overrides the direction for the whole app, whatever the locale:

direction := widget.NewRadioGroup([]string{"Auto", "Left to right", "Right to left"}, func(s string) {
	switch s {
	case "Left to right":
		bellui.SetLayoutDirection(bellui.DirectionLTR)
	case "Right to left":
		bellui.SetLayoutDirection(bellui.DirectionRTL)
	default:
		bellui.SetLayoutDirection(bellui.DirectionAuto) // follow the locale
	}
})
direction.SetSelected("Auto")
  • bellui.DirectionLTR and bellui.DirectionRTL force a direction.
  • bellui.DirectionAuto, the default, follows the locale again.

When the direction changes while the app runs, every window is laid out and painted again, as for a theme change. Call it on the UI goroutine, for example from a widget callback. bellui.CurrentLayoutDirection returns the setting, which may be DirectionAuto. lang.IsRTLDirection returns the direction actually in effect, as a boolean.

Forcing RTL in an English app is a quick way to test that your layout mirrors well before you have any translations.

Keep part of the window in one direction

Some content must not mirror: source code, a phone number field, a media timeline. Wrap it in container.NewDirectionScope:

// This row always flows left to right, whatever the app direction is.
code := container.NewDirectionScope(
	container.NewHBox(widget.NewLabel("x := 1"), widget.NewLabel("// comment")),
	bellui.DirectionLTR,
)

The scope sets the direction for everything inside it: layout, alignment, focus order and the base direction of text. Scopes can be nested, and the innermost one wins. DirectionAuto makes a scope inherit the direction around it. To change a scope’s direction later, call its SetDirection method.

In Bellui v1.0.0 a scope can be lost when the whole window is laid out again. After Settings().SetThemeVariant switches between light and dark, the scoped content follows the app’s direction: a left-to-right scope in an Arabic app mirrors like the rest of the window. Check scoped content after a theme change before you rely on it.

Custom layouts

The standard layouts mirror themselves. If you write your own core.Layout, position the children left to right as usual, then, when lang.IsRTLDirection() is true, call layout.MirrorChildren with the children and the container’s width. It reflects each visible child horizontally and leaves vertical positions and sizes alone.

Text direction

The direction of a piece of text is set by Direction in its bellui.TextStyle. Left at its default, it follows the layout direction. Set it to bellui.TextDirectionLTR or TextDirectionRTL for text whose direction differs from the interface, such as a Hebrew quotation in an English document.

The direction only sets the base order of runs and neutral characters such as punctuation. Arabic letter joining comes from the text itself and does not depend on it. Language in the same style overrides the language used for shaping; left empty, it follows the locale. For the limits of mixed right-to-left and left-to-right text in v1.0.0, see Mixed Arabic and Latin text.

The full program:

// Right-to-left layout: forcing the direction for the whole app, and keeping
// one part of the window left-to-right.
package main

import (
	"github.com/belluikit/bellui"
	"github.com/belluikit/bellui/app"
	"github.com/belluikit/bellui/container"
	"github.com/belluikit/bellui/theme"
	"github.com/belluikit/bellui/widget"
)

func main() {
	a := app.New()
	w := a.NewWindow("Direction")

	row := container.NewHBox(
		widget.NewButtonWithIcon("Back", theme.NavigateBackIcon(), func() {}),
		widget.NewLabel("Leading"),
		widget.NewLabel("Trailing"),
	)

	direction := widget.NewRadioGroup([]string{"Auto", "Left to right", "Right to left"}, func(s string) {
		switch s {
		case "Left to right":
			bellui.SetLayoutDirection(bellui.DirectionLTR)
		case "Right to left":
			bellui.SetLayoutDirection(bellui.DirectionRTL)
		default:
			bellui.SetLayoutDirection(bellui.DirectionAuto) // follow the locale
		}
	})
	direction.SetSelected("Auto")

	// This row always flows left to right, whatever the app direction is.
	code := container.NewDirectionScope(
		container.NewHBox(widget.NewLabel("x := 1"), widget.NewLabel("// comment")),
		bellui.DirectionLTR,
	)

	w.SetContent(container.NewVBox(direction, row, widget.NewEntry(), code))
	bellui.ShowAndRun(w)
}