ui.um offers an immediate GUI library suitable both for simple game menus and more complex applications. See the tutorial for example usage.

struct BoxStyle

type BoxStyle* = struct {
	img: image.Image
	outer, inner: rect.Rect
	scale: th.fu
	color: uint32

BoxStyle describes how a box within the GUI is styled. In this case box can be anything, ranging from a button to a container. By default the box is drawn using the image.Image.drawNinepatch method. However if the image is invalid, a rectangle with color color is drawn.

interface Font

type Font* = interface {
	draw(text: str, pos: th.Vf2, color: uint32, scale: th.fu = 1.0)
	measure(test: str): th.Vf2

This interface is used by all elements that draw text. A font.Font implements this interface.

struct PixelFont

type PixelFont* = struct { }

This struct implement the Font interface using the canvas.um pixel font.

struct Style

type Style* = struct {
	// current font
	ft: Font
	// font scale
	ftScale: th.fu
	// text color
	ftColor: uint32

	// Positive box - i. e. unpressed button
	posBox: BoxStyle
	// Negative box - i. e. pressed button, text box
	negBox: BoxStyle
	// Used to draw containers
	containerBox: BoxStyle

Style is used as a global state for styling the GUI.

interface Container

type Container* = interface {
	// This adds a rectangle to the container, and returns the rectangle
	// which was actually added (the container can modify the rectangle).
	// See individual containers for further documentation.
	pushRect(r: rect.Rect): rect.Rect
	getDims(): rect.Rect

Containers are used to layout elements or other containers.

struct Gui

type Gui* = struct {
	// user context passed to layout functions
	ctx: any

	// the index of the current selection. TODO implement properly
	selection: int
	// true, if the layout is being evaluated, not drawn
	isEval: bool
	// contains more unexported fields

This is the main struct of any UI. Styles and containers are in a stack.

fn mk

fn mk*(r: rect.Rect, s: Style): Gui

Creates a new gui spanning r, with style s.

type LayoutFn

type LayoutFn* = fn(gui: ^Gui)

The layout function calls different element or container methods to create the user interface itself. It is called in the eval and draw.

fn BoxStyle.draw

fn (this: ^BoxStyle) draw*(r: rect.Rect) {

Draws a rectangle using a BoxStyle

fn Gui.pushStyle

fn (this: ^Gui) pushStyle*(s: Style) {

Pushes a style onto the style stack.

fn Gui.popStyle

fn (this: ^Gui) popStyle*() {

Pops a style from the style stack.

fn Gui.getStyle

fn (this: ^Gui) getStyle*(): ^Style {

Returns a pointer to the style atop the style stack.

fn Gui.getContainer

fn (this: ^Gui) getContainer*(): Container {

Returns the container atop the container stack.

fn Gui.pushRect

fn (this: ^Gui) pushRect*(r: rect.Rect): rect.Rect {

Shortcut to this.getContainer().pushRect(r)

fn Gui.getDims

fn (this: ^Gui) getDims*(): rect.Rect {

Shortcut to this.getContainer().getDims(r)

fn Gui.dupStyle

fn (this: ^Gui) dupStyle*() {

Duplicates the current style.

fn Gui.pushContainer

fn (this: ^Gui) pushContainer*(c: Container) {

Pushes a container onto the container stack.

fn Gui.popContainer

fn (this: ^Gui) popContainer*() {

Pops a container from the container stack.

fn Gui.eval

fn (this: ^Gui) eval*(layout: LayoutFn) {

Runs the evaluation phase on layout.

fn Gui.draw

fn (this: ^Gui) draw*(layout: LayoutFn) {

Runs the draw phase on layout.

enum BoxGrow

type BoxGrow* = uint
const (
	// Increments by an amount set by the user.
	BoxGrowDimension*   = BoxGrow(0)
	// Divides the container into `n` equal parts.
	BoxGrowSubdivision* = BoxGrow(1)

The different types of "growing" the box can do.

enum BoxDirection

type BoxDirection* = uint
const (
	BoxDirectionDown*  = BoxDirection(0)
	BoxDirectionRight* = BoxDirection(1)
	BoxDirectionUp*    = BoxDirection(2)
	BoxDirectionLeft*  = BoxDirection(3)

Direction in which the box will grow.

struct BoxConfig

type BoxConfig* = struct {
	// dimension to grow by if `BoxGrowDimension` is used
	dimension: th.fu
	// number of subdivisions if `BoxGrowSubdivisions` is used
	subdivisions: uint
	// the grow type
	growType: BoxGrow
	// the grow direction
	dir: BoxDirection
	// rect passed to the current container
	rect: rect.Rect
	// padding inserted after each element
	padding: th.fu

Configuration of the Box container.

struct Box

type Box* = struct {
	grow: th.fu
	dm: rect.Rect
	cfg: BoxConfig

Box is the main layout. It puts the elements next to each other, according to the config.

If the dimensions of the rect passed to pushRect are non zero, they will be kept. Position is always forced.

fn Gui.box

fn (gui: ^Gui) box*(cfg: BoxConfig = {
	dimension: 30,
	growType: BoxGrowDimension,
	dir: BoxDirectionDown }) {

Adds the Box container to the gui.

struct StackConfig

type StackConfig* = struct {
	rect: rect.Rect
	padding: th.fu

Configuration for the Stack container.

struct Stack

type Stack* = struct {
	dm: rect.Rect
	cfg: StackConfig

The stack container puts elements on top of each other. If a property of the rect passed to pushRect is zero, it will be changed to an equivalent property of the containers' dimensions (minus the padding), else it will stay the same. This means stack can be used either to put multiple elements/containers on top of each other, or for absolutely positioned elements.

fn Gui.stack

fn (gui: ^Gui) stack*(cfg: StackConfig = {}) {

Adds the Stack container to the gui.

struct ScrollAreaConfig

type ScrollAreaConfig* = struct {
	rect: rect.Rect
	// scroll speed. Default is 1
	speed: real32
	// if true, scrolling will be horizontal
	horizontal: bool

Configuration for the scroll area.

struct ScrollArea

type ScrollArea* = struct {
	dm: rect.Rect
	cfg: ScrollAreaConfig
	scroll: ^real32
	maxScroll: real32

Scroll area is a container which allows the user to scroll. It acts as a stack container, but all the elements are shifted by the scroll.

fn Gui.scrollArea

fn (gui: ^Gui) scrollArea*(scroll: ^real32, maxScroll: real32, cfg: ScrollAreaConfig = {}) {

Pushes a scroll area. scroll is both input and output value. Both scroll and maxScroll are in pixels.

struct ButtonConfig

type ButtonConfig* = struct {
	rect: rect.Rect

Configuration for the button.

fn Gui.button

fn (gui: ^Gui) button*(cfg: ButtonConfig = {}): bool {

Adds a button to the gui. The button acts like a Stack container, but it is drawn using the pos/nexBox styles and handles clicks. If the button is pressed and the gui is in the eval phase, the return value will be true.

struct LabelConfig

type LabelConfig* = struct {
	// centers the label along the X axis, enables `stretchX`
	centerX: bool
	// centers the label along the Y axis, enables `stretchY`
	centerY: bool
	// if false, the rect passed to `pushRect` will have the width of
	// the text, else it will be 0
	stretchX: bool
	// if false, the rect passed to `pushRect` will have the height of
	// the text, else it will be 0
	stretchY: bool
	// forces the rectangle the label will use
	rect: rect.Rect

fn Gui.label

fn (gui: ^Gui) label*(text: str, cfg: LabelConfig = {

Draws a label using the current font style.

fn Gui.qbutton

fn (gui: ^Gui) qbutton*(text: str, cfg: ButtonConfig = {}): bool {

Adds a button with a label to gui.

struct TextBoxConfig

type TextBoxConfig* = struct {
	// force the rect of the text box
	rect: rect.Rect

struct TextBox

type TextBox* = struct {
	// the content of the textbox
	buffer: str
	// index of the cursor
	cursor: int

fn TextBox.clear

fn (this: ^TextBox) clear*() {
	this.buffer = ""
	this.cursor = 0

Clears the textbox


	r := gui.pushRect(cfg.rect)

	hover := gui.hover(r)

	if gui.isEval {
		if input.isJustPressed(input.mouse1) && hover {
			gui.selection = gui.idx

		if input.isJustPressed(input.mouse1) && !hover &&
			gui.selection == gui.idx {
				gui.selection = 0
		if input.isJustPressed(input.key_escape) && gui.selection == gui.idx {
			gui.selection = 0

		if gui.selection != gui.idx {

		if input.isJustPressed(input.key_left) ||
			input.isPressedRepeat(input.key_left) {
			if tb.cursor > 0 { tb.cursor-- }

		if input.isJustPressed(input.key_right) ||
			input.isPressedRepeat(input.key_right) {
			if tb.cursor < len(tb.buffer) { tb.cursor++ }

		if input.isJustPressed(input.key_backspace) ||
			input.isPressedRepeat(input.key_backspace) {

			if tb.cursor > 0 {
				tb.buffer = slice(tb.buffer, 0, tb.cursor - 1) +
					slice(tb.buffer, tb.cursor)

		v := true

		if input.isPressed(input.key_ctrl) && input.isJustPressedc('u') {
			v = false

		s := input.getStr()
		for i in s {
			if int(s[i]) < int(' ') || int(s[i]) == int('z') + 1 {
				v = false
		if len(s) > 0 && v {
			tb.buffer = slice(tb.buffer, 0, tb.cursor) + s +
				slice(tb.buffer, tb.cursor)
			tb.cursor += len(s)


	style := gui.getStyle()


	dm := style.ft.measure(tb.buffer).mulf(style.ftScale)

	p := th.Vf2{}
	p.y = r.y + r.h/2 - dm.y/2
	c := th.Vf2{r.x, p.y}

	cdmx := style.ft.measure(slice(tb.buffer, 0, tb.cursor)).x * style.ftScale
	if cdmx < r.w - 2 {
		p.x = r.x + 1
		c.x = p.x + cdmx
	} else {
		c.x = r.x + r.w - 1
		p.x = c.x - cdmx
	aW := style.ft.measure("A").x * style.ftScale

	style.ft.draw(tb.buffer, p, style.ftColor, style.ftScale)
	if gui.selection == gui.idx {
		canvas.drawRect(style.ftColor, rect.mk(c.x, c.y, aW / 4, dm.y))


	if cfg.centerX { cfg.stretchX = true }
	if cfg.centerY { cfg.stretchY = true }

	r := cfg.rect
	dm := i.getDims().mul(cfg.scale)
	if r.w == 0 { r.w = dm.x }
	if r.h == 0 { r.h = dm.y }

	if cfg.stretchX { r.w = 0 }
	if cfg.stretchY { r.h = 0 }

	r = gui.pushRect(r)

	if gui.isEval { return }

	x := r.x
	y := r.y
	if cfg.centerX {
		x += (r.w - dm.x) / 2

	if cfg.centerY {
		y += (r.h - dm.y) / 2

		p: { x, y },
		s: cfg.scale }, cfg.color)