Skip to content

UI

Labels, menus, UI controls (panels, buttons, progress bars, text fields, scroll boxes, rich text), layouts and themes.

Files: ui_label.odin, ui_menu.odin, ui_menu_draw.odin, ui_menu_input.odin, ui_style.odin, ui_controls.odin, ui_theme.odin.

UI controls: panels that lay out what’s in them, buttons, progress bars, text fields, scroll boxes and rich text, for menus, HUDs and dialogs. They’re nodes like any others (kinds Panel, Button, ProgressBar, TextField, ScrollBox, RichText), drawn in the scene; put them on a screen layer (the scene’s screenLayers) so the camera doesn’t move them.

  • Sizes and places (UISettings): a control’s size (0 on an axis: as big as what’s in it), and where it goes in its parent: by its anchor (a corner, an edge’s middle, the middle, or stretched across: Full, Top_Wide…) moved by its position (Control.offset), or by its parent’s layout when that’s a Row, Column or Grid. A top-level control’s parent is the screen (the game’s resolution, which the window scales), so anchors keep a HUD in the corners whatever the window. Under a node that isn’t a control, the anchors are relative to that node’s position.
  • Containers (Panel, ScrollBox): layout None (each child by its anchor), Row, Column or Grid (columns); padding and spacing (-1: the theme’s); align places children across the layout (Start, Center, End, Stretch); justify along it (Start, Center, End); a child with expand takes the room left along it. A ScrollBox is a column that scrolls (the wheel over it, or focus moving into a hidden child); children that don’t fit wholly are hidden.
  • Looks: a theme (ui_theme.odin: colors, text size, padding, font), from the control’s theme, its control ancestors’ or the project’s. A control with a texture draws it instead of its fill, stretched as a 9-patch by patchMargins (left, top, right, bottom pixels that don’t stretch): a framed panel, an image button.
  • Input: the mouse (hover, press, release on the control; the wheel over a scroll box), the keyboard and gamepads through the game’s ui_* actions (MENU_ACTION_*: ui_up / down / left / right move the focus to the nearest focusable control that way, ui_accept presses), else the arrow keys, Tab, Enter and Space. Buttons and text fields take focus; a focused one is outlined. A text field takes typing while it has focus.
  • Events: Button onPressed (data: its action), TextField onChanged and onSubmitted (data: the text), every control’s onFocusChanged (data: true when it got focus). A button’s action “scene:<Name>” switches scene and “quit” quits, like a Menu’s.
  • Text: a Button’s, TextField’s or RichText’s text (“@key” translates: Tr_Text). RichText takes tags: [color=#ff8000]...[/color], [size=28]...[/size]; it wraps at its width (0: only at line breaks).
  • Code: CreatePanel, CreateButton, CreateProgressBar, CreateTextField, CreateScrollBox, CreateRichText; Control_SetText, ProgressBar_SetValue, Control_Focus, Control_GetRect. Move a control with its offset (its position is set by the layout).

UI themes: how the UI controls (ui_controls.odin) look. A theme is a font and a UITheme: text sizes, colors and spacing for panels, buttons, progress bars, text fields and scroll boxes.

Themes are assets of kind “Theme” (.theme.xml files):

<theme formatVersion="13" font="assets/fonts/pixel.ttf" textSize="18" buttonColor="#3a5a8c"/>

The root element holds the font and every field of UITheme that differs from DEFAULT_UI_THEME (FormatTheme writes them, ParseTheme reads them). A control uses the theme its theme names, or its nearest control ancestor’s, or the project’s (uiTheme in project.xml), else the default. Give a whole menu or HUD a theme by setting it on its top panel.

Label :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// The text to draw, or "@key" for a translated string. `CreateLabel` copies it into the node's
// arena; if you assign a new string yourself, it must outlive the label
text: string,
// Font size in points
size: f32,
// Text color
color: sdl.Color,
// true: the position is the center of the text; false: its top-left corner
centered: bool,
// Font path, or `DEFAULT_FONT`. Not copied: use a string that outlives the label
font: string,
}

A node that draws one line of static text.

When centered is true the node’s position is the center of the text, otherwise its top-left corner. Labels are .Position_Only nodes: they follow their world position but don’t rotate or scale. Text starting with “@” is a translation key, looked up with Tr_Text each frame.

struct. Source: ui_label.odin:13

MenuCallback :: proc(menu: ^Menu, item: ^MenuItem)

Called when a menu item is pressed or its value changes. For Menu.onBack, item is nil

type. Source: ui_menu.odin:11

MenuItemKind :: enum {
// Non-interactive text, e.g. a section header. Never takes focus
Label,
Button,
Checkbox,
// Only one radio per group is checked at a time
Radio,
Slider,
// Dropdown list of options
Select,
}

What a MenuItem is, which decides how it is drawn and what input does to it

enum. Source: ui_menu.odin:14

MenuItem :: struct {
// What the item is
kind: MenuItemKind,
// Text drawn on the row, or "@key" for a translated string. Copied into the menu node's arena
label: string,
// Disabled items are drawn with `MenuStyle.textDisabledColor` and can't be focused or used. true when added
enabled: bool,
// Called when the button is pressed or the value changes (nil for none)
onChange: MenuCallback,
// Free for game code, e.g. a pointer to the setting this item controls
userData: rawptr,
// Style override for this item (set with `MenuItem_SetStyle`); nil uses the menu's style
style: Maybe(MenuStyle),
// Checkbox, Radio: whether it is checked
checked: bool,
// Radio: the group number; radios sharing one are mutually exclusive
group: int,
// Slider: current value, range and step (0 for a continuous slider)
value, minValue, maxValue, step: f32,
// Select: the option strings (or "@key" translation keys), copied into the menu node's arena
options: []string,
// Select: index of the selected option in `options`
selectedOption: int,
}

One row of a Menu. Made by the Menu_Add* procedures, which return a pointer to it; the pointer stays valid for the menu’s lifetime. Fields below a kind’s comment only apply to that kind.

struct. Source: ui_menu.odin:30

Menu :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// Style of the menu and of items without their own (`MenuItem_SetStyle`)
style: MenuStyle,
// The items; only the first `itemCount` are in use
items: [MAX_MENU_ITEMS]MenuItem,
// Number of items added
itemCount: int,
// Index of the focused item, -1 if none. The first interactive item added gets the focus
focused: int,
// Called (with a nil item) on Escape / ui_cancel when no dropdown is open; nil for none
onBack: MenuCallback,
// Interaction state
// Slider being dragged, -1 if none
dragging: int,
// Dropdown of the focused select item is open
popupOpen: bool,
// Highlighted option in the open dropdown
popupHover: int,
// Direction action held down (ui_up...), repeating
held: MenuCommand,
// Seconds to its next repeat
repeatTimer: f32,
// The focus its update last reported (onFocusChanged), and whether it has reported one
reportedFocus: int,
focusReported: bool,
// Events (data: the item's index, `items[index]`)
// A button was pressed (Enter / ui_accept, or a click)
onButtonPressed: EventSubject `event:"A button was pressed (data: its index)" data:"index := data.(int)"`,
// A checkbox, radio, select or slider changed value
onValueChanged: EventSubject `event:"A checkbox, radio, select or slider changed (data: its index)" data:"index := data.(int)"`,
// The focus moved to another item (noticed in its update)
onFocusChanged: EventSubject `event:"The focus moved to another item (data: its index)" data:"index := data.(int)"`,
// Escape / ui_cancel with no dropdown open (as `onBack`)
onBackPressed: EventSubject `event:"Escape / ui_cancel was pressed (no dropdown open)"`,
}

A node that draws a vertical list of interactive UI items, with built-in keyboard, gamepad and mouse navigation.

The node’s position is the top-center of the first row; the rest stack below it. Menus are .Position_Only nodes: they follow their world position but don’t rotate or scale. A menu only takes input while it is visible in the tree (Node_IsVisibleInTree).

Keyboard and gamepad: through the game’s ui_* input actions when it has MENU_ACTION_ACCEPT (ui_up / ui_down move focus, ui_left / ui_right adjust sliders, radios and select boxes, ui_accept activates, ui_cancel closes an open dropdown or runs onBack; held directions repeat), else the keys W/S or Up/Down, A/D or Left/Right, Enter/Space, Escape. Menu_Command drives it from code. Mouse: hover to focus, click to activate, click and drag sliders.

Example

menu := e.CreateMenu(scene, {640, 200})
e.Menu_AddLabel(menu, "Settings")
e.Menu_AddSlider(menu, "Volume", 0.8, 0, 1, 0.05, proc(menu: ^e.Menu, item: ^e.MenuItem) {
log.infof("volume %.2f", item.value)
})
e.Menu_AddButton(menu, "Play", proc(menu: ^e.Menu, item: ^e.MenuItem) {
log.info("play")
})
menu.onBack = proc(menu: ^e.Menu, item: ^e.MenuItem) { log.info("back") }

struct. Source: ui_menu.odin:81

MenuCommand :: enum u8 {
// Does nothing
None,
Up,
Down,
Left,
Right,
Accept,
Cancel,
}

What a key press or an action asks a menu to do (see Menu_Command)

enum. Source: ui_menu_input.odin:33

MenuStyle :: struct {
// Font path, or `DEFAULT_FONT`
font: string,
// Font size of item text, in points (slider values and select options use a smaller size)
textSize: f32,
// Width and height of every row, in pixels
itemSize: [2]f32,
// Vertical gap between rows, in pixels (only the menu's own style is used for this)
spacing: f32,
// Inner horizontal padding of a row, in pixels
padding: f32,
// Corner radius of rows, in pixels
cornerRadius: f32,
// Border width of rows, in pixels. 0 disables borders
borderWidth: f32,
// Width of the control (slider track, select box) on the right side of a row, as a fraction of the row (0 to 1)
controlWidth: f32,
// Item text
textColor: sdl.Color,
// Text of the focused item
textFocusedColor: sdl.Color,
// Text of disabled items
textDisabledColor: sdl.Color,
// Text of label items (section headers)
labelColor: sdl.Color,
// Row background
backgroundColor: sdl.Color,
// Background of the focused row
backgroundFocusedColor: sdl.Color,
// Row border
borderColor: sdl.Color,
// Border of the focused row, and of an open dropdown
borderFocusedColor: sdl.Color,
// Checkbox / radio box, slider track, select box
controlColor: sdl.Color,
// Controls of the focused row
controlFocusedColor: sdl.Color,
// Check mark, radio dot, slider fill and knob
accentColor: sdl.Color,
// Background of a select item's dropdown list
popupColor: sdl.Color,
// Highlighted option in the dropdown list
popupHighlightColor: sdl.Color,
}

Appearance of a Menu and its items: sizes in pixels, colors, font.

Copy DEFAULT_MENU_STYLE and change what you need. A style is set for a whole menu (CreateMenu), or overridden per item with MenuItem_SetStyle. Row spacing always comes from the menu’s style, so per-item overrides keep the column intact.

struct. Source: ui_style.odin:12

ControlKind :: enum {
Panel,
Button,
ProgressBar,
TextField,
ScrollBox,
RichText,
}

What a control is (one type per kind: Panel, Button…)

enum. Source: ui_controls.odin:51

UIAnchor :: enum {
Top_Left,
Top,
Top_Right,
Left,
Center,
Right,
Bottom_Left,
Bottom,
Bottom_Right,
// Fills the parent (less `margin` on each side)
Full,
// The parent's width, at its top (less `margin` left and right)
Top_Wide,
// The parent's width, at its bottom
Bottom_Wide,
// The parent's height, at its left (less `margin` top and bottom)
Left_Tall,
// The parent's height, at its right
Right_Tall,
}

Where a control goes in its parent when the parent doesn’t lay it out

enum. Source: ui_controls.odin:61

UILayout :: enum {
// Each by its anchor and position
None,
// Side by side, left to right
Row,
// One under another
Column,
// In rows of `columns` cells
Grid,
}

How a container places its control children

enum. Source: ui_controls.odin:84

UIAlign :: enum {
Start,
Center,
End,
// As wide (in a column) or tall (in a row) as the container
Stretch,
}

Where children go across a layout (align) or along it (justify: Stretch is Start)

enum. Source: ui_controls.odin:96

ProgressDirection :: enum {
Right,
Left,
Up,
Down,
}

Which way a progress bar fills

enum. Source: ui_controls.odin:105

UITextAlign :: enum {
Auto,
Left,
Center,
Right,
}

Where text goes in a control (Auto: centered in buttons, at the left elsewhere)

enum. Source: ui_controls.odin:113

UISettings :: struct {
// Width and height (0 on an axis: as big as what's in it)
size: [2]f32,
// Where it goes in a parent that doesn't lay it out
anchor: UIAnchor,
// Stretching anchors: the space left on each side
margin: f32,
// Containers: how children are placed
layout: UILayout,
// Containers: children across the layout
align: UIAlign,
// Containers: children along the layout
justify: UIAlign,
// Grids: cells in a row
columns: int,
// Space inside it (-1: the theme's)
padding: f32,
// Space between children (-1: the theme's)
spacing: f32,
// In a Row or Column: takes the room left along it
expand: bool,
// Buttons and text fields: can't be used
disabled: bool,
// Progress bars: the value and its range
value, minValue, maxValue: f32,
// Progress bars: which way it fills
direction: ProgressDirection,
// Progress bars: the value as a percentage on it
showValue: bool,
// Progress bars: show the loading screen's progress (`Game_LoadingProgress`) instead of `value`
showLoading: bool,
// Text fields: the most characters (0: any)
maxLength: int,
// With a texture: its edges that don't stretch, in pixels (left, top, right, bottom)
patchMargins: [4]f32,
// Text size in points (0: the theme's)
textSize: f32,
// Where text goes
textAlign: UITextAlign,
// A theme asset (`.theme.xml`) for it and the controls in it ("": its parent's)
theme: string,
// Text fields: shown while it's empty
placeholder: string,
// Buttons: "scene:<Name>" switches scene, "quit" quits ("" none; `onPressed` fires anyway)
action: string,
}

A control’s settings in a scene file (SceneNodeData.ui). The strings are owned: free with UISettings_Destroy (the parser’s and UISettings_Clone’s).

struct. Source: ui_controls.odin:124

Control :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// Which control it is
kind: ControlKind,
// Its settings (strings in the node's arena)
settings: UISettings,
// Its text (node arena; `Control_SetText`)
text: string,
// Its image (nil: the theme's fill)
texture: ^sdl.Texture,
// Where it is from its anchor (its scene file position); a layout ignores it. Set this, not
// `position`, to move a control
offset: [2]f32,
// Its size after the last layout
size: [2]f32,
// The mouse is over it
hovered: bool,
// The mouse pressed it and hasn't been released
pressed: bool,
// A scroll box's parent hid it: it doesn't fit wholly
clipped: bool,
// The layout pass it was last laid out in
layoutStamp: u64,
// Its focus changed (data: true when it got focus)
onFocusChanged: EventSubject `event:"It got or lost the keyboard's or gamepad's focus (data: true when it got it)" data:"focused := data.(bool)"`,
}

The part every control type starts with: its settings, its laid-out size, its state. Control types: Panel, Button, ProgressBar, TextField, ScrollBox, RichText.

struct. Source: ui_controls.odin:178

Panel :: struct {
using control: Control,
}

A box that lays out the controls in it, with the theme’s fill (or its texture)

struct. Source: ui_controls.odin:207

Button :: struct {
using control: Control,
// Pressed: clicked, or accepted while focused
onPressed: EventSubject `event:"It was pressed: clicked, or accepted with the keyboard or a gamepad (data: its action)" data:"action := data.(string)"`,
}

A button with text, an image, or both

struct. Source: ui_controls.odin:212

ProgressBar :: struct {
using control: Control,
}

A bar showing a value in its range: health, loading

struct. Source: ui_controls.odin:219

TextField :: struct {
using control: Control,
// The text, edited in place (node arena)
buffer: [dynamic]u8,
// The caret's byte offset
caret: int,
// Seconds the caret has blinked
blink: f32,
// Its text changed
onChanged: EventSubject `event:"Its text changed (data: the text)" data:"text := data.(string)"`,
// Enter was pressed in it
onSubmitted: EventSubject `event:"Enter was pressed in it (data: the text)" data:"text := data.(string)"`,
}

A box to type a line of text in

struct. Source: ui_controls.odin:224

ScrollBox :: struct {
using control: Control,
// How far it's scrolled, in pixels
scroll: f32,
// The height of what's in it
contentHeight: f32,
}

A column of controls that scrolls

struct. Source: ui_controls.odin:239

RichText :: struct {
using control: Control,
}

Text with colors and sizes ([color=#ff8000]...[/color], [size=28]...[/size]), wrapped

struct. Source: ui_controls.odin:248

SceneUI :: struct {
// The focused control (zero: none)
focus: EntityId,
// Bumped by each update and draw: controls lay out once per pass
stamp: u64,
// The update pass whose focus moves were done (one move per frame, whichever control sees it)
navigated: u64,
// The update pass in which a press hit a control (one per frame)
pressed: u64,
}

Per scene: the focus, and the layout passes (Scene.ui)

struct. Source: ui_controls.odin:550

RichRun :: struct {
text: string,
at: [2]f32,
size: f32,
// Zero: the theme's text color
color: sdl.Color,
}

A word or space of rich text, placed

struct. Source: ui_controls.odin:1195

RichLines :: struct {
runs: []RichRun,
size: [2]f32,
}

Rich text laid out: its runs and the size they take

struct. Source: ui_controls.odin:1204

UITheme :: struct {
// Text in controls, in points
textSize: f32 `label:"Text size" group:"Text" min:"6" max:"96"`,
// Text
textColor: sdl.Color `label:"Text color"`,
// Text of disabled controls
disabledTextColor: sdl.Color `label:"Disabled text"`,
// A panel's fill
panelColor: sdl.Color `label:"Panel color" group:"Panels"`,
// Borders of panels, buttons and fields
borderColor: sdl.Color `label:"Border color"`,
// Border width (0: none)
borderWidth: f32 `label:"Border width" min:"0" max:"8"`,
// Rounded corners' radius
cornerRadius: f32 `label:"Corner radius" min:"0" max:"32"`,
// Space inside panels, buttons and fields, between their edge and what's in them
padding: f32 `label:"Padding" min:"0" max:"64"`,
// Space between a container's children
spacing: f32 `label:"Spacing" min:"0" max:"64"`,
// A button's fill
buttonColor: sdl.Color `label:"Button color" group:"Buttons"`,
// Under the mouse
buttonHoverColor: sdl.Color `label:"Hovered"`,
// Held down
buttonPressedColor: sdl.Color `label:"Pressed"`,
// Disabled
buttonDisabledColor: sdl.Color `label:"Disabled"`,
// A button's text
buttonTextColor: sdl.Color `label:"Button text"`,
// The outline of the control with the keyboard's or gamepad's focus
focusColor: sdl.Color `label:"Focus outline"`,
// A text field's fill
fieldColor: sdl.Color `label:"Field color" group:"Text fields"`,
// Its placeholder text (shown while it's empty)
placeholderColor: sdl.Color `label:"Placeholder"`,
// The text caret
caretColor: sdl.Color `label:"Caret"`,
// A progress bar's empty part
progressBackColor: sdl.Color `label:"Bar background" group:"Progress bars"`,
// Its filled part
progressFillColor: sdl.Color `label:"Bar fill"`,
// A scroll box's bar
scrollbarColor: sdl.Color `label:"Scrollbar" group:"Scroll boxes"`,
}

How UI controls look: sizes in pixels, colors. Tags describe the fields for editors (UITheme_Fields): label, group (starts a group), min / max.

struct. Source: ui_theme.odin:30

ThemeData :: struct {
// Font path, relative to the project ("" or `DEFAULT_FONT`: the built-in font)
font: string,
// The values
theme: UITheme,
// The font's full path, found when first used (the game's memory)
fontPath: string,
}

A theme as read from its file: its font and its values. Kept by the game (Game_LoadTheme).

struct. Source: ui_theme.odin:108

UIThemeField :: struct {
// The field's name in the struct and the file
name: string,
// What editors show
label: string,
// The group it starts ("": it continues the one above)
group: string,
// Its Odin type (f32 or sdl.Color)
type: typeid,
// Its range, for numbers
min, max: f32,
}

A theme field for editors (UITheme_Fields)

struct. Source: ui_theme.odin:118

CreateLabel :: proc(scene: ^Scene, text: string, pos: [2]f32, size: f32, color: sdl.Color, centered := true, font := DEFAULT_FONT) -> ^Label

Creates a Label node and adds it to the scene (at the start of the next update).

The text is copied into the label node’s arena, so a temporary string is fine.

Inputs

  • scene: The scene that owns the label
  • text: The text to draw, or “@key” for a translated string (see Tr_Text)
  • pos: Position in pixels, relative to the label’s parent (the scene root until you reparent it)
  • size: Font size in points
  • color: Text color
  • centered: true to center the text on pos, false to put its top-left corner there
  • font: Font path, or DEFAULT_FONT

Returns

  • The new label, owned by the scene

Example

title := e.CreateLabel(scene, "@menu.title", {640, 120}, 48, {255, 255, 255, 255})
title.color = {255, 220, 120, 255}

procedure. Source: ui_label.odin:50

CreateMenu :: proc(scene: ^Scene, pos: [2]f32, style := DEFAULT_MENU_STYLE) -> ^Menu

Creates an empty Menu node and adds it to the scene (at the start of the next update). Add items with the Menu_Add* procedures.

Inputs

  • scene: The scene that owns the menu
  • pos: Position of the top-center of the first row, in pixels, relative to the menu’s parent
  • style: Look of the menu and its items

Returns

  • The new menu, owned by the scene

Example

menu := e.CreateMenu(scene, {640, 200})
e.Menu_AddButton(menu, "Quit", proc(menu: ^e.Menu, item: ^e.MenuItem) { log.info("quit") })

procedure. Source: ui_menu.odin:139

Menu_AddLabel :: proc(menu: ^Menu, text: string) -> ^MenuItem

Adds a non-interactive text row, e.g. a section header. It never takes focus.

Inputs

  • menu: The menu
  • text: The text, or “@key” for a translated string. Copied into the menu node’s arena

Returns

  • The new item

procedure. Source: ui_menu.odin:166

Menu_AddButton :: proc(menu: ^Menu, label: string, onPress: MenuCallback) -> ^MenuItem

Adds a button row. Pressing it (Enter / Space / ui_accept, or a click) calls onPress, then the menu ignores the rest of that frame’s input, since buttons often switch scenes.

Inputs

  • menu: The menu
  • label: The button text, or “@key” for a translated string. Copied into the menu node’s arena
  • onPress: Called when the button is pressed

Returns

  • The new item

Example

// Given: menu: ^e.Menu
e.Menu_AddButton(menu, "Play", proc(menu: ^e.Menu, item: ^e.MenuItem) {
log.info("play pressed")
})

procedure. Source: ui_menu.odin:188

Menu_AddCheckbox :: proc(menu: ^Menu, label: string, checked: bool, onChange: MenuCallback = nil) -> ^MenuItem

Adds a checkbox row. Pressing it toggles checked, then calls onChange.

Inputs

  • menu: The menu
  • label: The row text, or “@key” for a translated string. Copied into the menu node’s arena
  • checked: The starting state
  • onChange: Called after it is toggled (nil for none)

Returns

  • The new item

Example

// Given: menu: ^e.Menu
e.Menu_AddCheckbox(menu, "Fullscreen", false, proc(menu: ^e.Menu, item: ^e.MenuItem) {
log.infof("fullscreen: %v", item.checked)
})

procedure. Source: ui_menu.odin:210

Menu_AddRadio :: proc(menu: ^Menu, label: string, group: int, checked: bool, onChange: MenuCallback = nil) -> ^MenuItem

Adds a radio row. Radios sharing a group number form one set of mutually exclusive choices: checking one unchecks the others, then calls its onChange (only the newly checked radio’s). Left / Right on a focused radio moves to the previous / next radio of its group and checks it.

Inputs

  • menu: The menu
  • label: The row text, or “@key” for a translated string. Copied into the menu node’s arena
  • group: The group number
  • checked: The starting state (check at most one per group)
  • onChange: Called when this radio becomes checked (nil for none)

Returns

  • The new item

Example

// Given: menu: ^e.Menu
e.Menu_AddRadio(menu, "Easy", 0, true)
e.Menu_AddRadio(menu, "Hard", 0, false)
difficulty := e.Menu_GetRadioIndex(menu, 0) // 0: Easy
_ = difficulty

procedure. Source: ui_menu.odin:236

Menu_AddSlider :: proc(menu: ^Menu, label: string, value, minValue, maxValue: f32, step: f32 = 0, onChange: MenuCallback = nil) -> ^MenuItem

Adds a slider row. Left / Right move it by one step, a click on the track jumps to that value and dragging moves it. Values are clamped to the range and snapped to the step.

Inputs

  • menu: The menu
  • label: The row text, or “@key” for a translated string. Copied into the menu node’s arena
  • value: The starting value (clamped and snapped)
  • minValue: The lowest value
  • maxValue: The highest value
  • step: The increment; 0 makes the slider continuous (the keyboard then moves it in 5% of the range)
  • onChange: Called when the value changes (nil for none)

Returns

  • The new item

Example

// Given: menu: ^e.Menu
e.Menu_AddSlider(menu, "Volume", 0.8, 0, 1, 0.05, proc(menu: ^e.Menu, item: ^e.MenuItem) {
log.infof("volume %.2f", item.value)
})

procedure. Source: ui_menu.odin:262

Menu_AddSelect :: proc(menu: ^Menu, label: string, options: []string, selected: int = 0, onChange: MenuCallback = nil) -> ^MenuItem

Adds a select row: a box showing the selected option, with a dropdown list. Pressing it opens the dropdown (Up / Down pick, Accept selects, Cancel closes); Left / Right cycle the options without opening it.

Inputs

  • menu: The menu
  • label: The row text, or “@key” for a translated string. Copied into the menu node’s arena
  • options: The option strings (or “@key” translation keys). Copied into the menu node’s arena, so a temporary slice literal is fine
  • selected: Index of the starting option (clamped to the options)
  • onChange: Called when the selected option changes (nil for none)

Returns

  • The new item

Example

// Given: menu: ^e.Menu
e.Menu_AddSelect(menu, "Window", {"Windowed", "Fullscreen"}, 0, proc(menu: ^e.Menu, item: ^e.MenuItem) {
log.infof("window mode: %s", e.Menu_SelectedOption(item))
})

procedure. Source: ui_menu.odin:289

MenuItem_SetStyle :: proc(item: ^MenuItem, style: MenuStyle)

Gives one item its own style instead of the menu’s. Row spacing still comes from the menu’s style.

Inputs

  • item: The item
  • style: The style to use for it (copied)

procedure. Source: ui_menu.odin:305

Menu_GetRadioIndex :: proc(menu: ^Menu, group: int) -> int

Finds which radio of a group is checked.

Inputs

  • menu: The menu
  • group: The radio group number

Returns

  • The index of the checked radio within its group (0 for the group’s first radio, in menu order), or -1 if none is checked

procedure. Source: ui_menu.odin:319

Menu_SelectedOption :: proc(item: ^MenuItem) -> string

Gets the selected option of a select item.

Inputs

  • item: A select item

Returns

  • The selected option string as given to Menu_AddSelect (not translated: use Tr_Text for “@key” options), or “” if the item isn’t a select or has no options

procedure. Source: ui_menu.odin:338

Menu_Command :: proc(menu: ^Menu, command: MenuCommand, repeat := false) -> bool

Does what a navigation command asks, as a key or an action would: move the focus, adjust the focused item, press it, or go back. For games that drive menus themselves (another input, a tutorial’s script). While a select’s dropdown is open, Up / Down move its highlight, Accept picks the option and Cancel closes it; Left / Right do nothing.

Inputs

  • menu: The menu
  • command: What to do
  • repeat: It comes from a held key or direction repeating (pressing and going back don’t repeat)

Returns

  • true when a button was pressed or onBack ran (the menu may be going away: stop using it)

Example

// Given: menu: ^e.Menu
if e.Menu_Command(menu, .Accept) do return
e.Menu_Command(menu, .Down)

procedure. Source: ui_menu_input.odin:96

CreatePanel :: proc(scene: ^Scene, layout := UILayout.None, size := [2]f32{}) -> ^Panel

Creates a Panel and adds it to the scene (at the start of the next update).

Inputs

  • scene: The scene
  • layout: How it places the controls in it
  • size: Its size (0 on an axis: as big as what’s in it)

Returns

  • The panel

Example

panel := e.CreatePanel(scene, .Column)
panel.settings.anchor = .Center
e.Node_AddChild(panel, e.CreateButton(scene, "Play", "scene:Level1"))
e.Node_AddChild(panel, e.CreateButton(scene, "Quit", "quit"))

procedure. Source: ui_controls.odin:272

CreateButton :: proc(scene: ^Scene, text: string, action := "") -> ^Button

Creates a Button and adds it to the scene (at the start of the next update).

Inputs

  • scene: The scene
  • text: Its text (“@key” translates)
  • action: “scene:<Name>”, “quit” or “” (listen to onPressed instead)

Returns

  • The button

Example

play := e.CreateButton(scene, "Play", "scene:Level1")
play.settings.anchor = .Bottom
play.offset = {0, -40}

procedure. Source: ui_controls.odin:295

CreateProgressBar :: proc(scene: ^Scene, value: f32 = 0, maxValue: f32 = 1) -> ^ProgressBar

Creates a ProgressBar and adds it to the scene (at the start of the next update).

Inputs

  • scene: The scene
  • value: Its value
  • maxValue: The full bar’s value (its minimum is 0)

Returns

  • The bar

Example

health := e.CreateProgressBar(scene, 80, 100)
health.settings.anchor = .Top_Left
health.offset = {16, 16}

procedure. Source: ui_controls.odin:319

CreateTextField :: proc(scene: ^Scene, text := "", placeholder := "") -> ^TextField

Creates a TextField and adds it to the scene (at the start of the next update).

Inputs

  • scene: The scene
  • text: Its text at first
  • placeholder: Shown while it’s empty

Returns

  • The field

Example

name := e.CreateTextField(scene, "", "Your name")
name.settings.maxLength = 12

procedure. Source: ui_controls.odin:340

CreateScrollBox :: proc(scene: ^Scene, size := [2]f32{300, 200}) -> ^ScrollBox

Creates a ScrollBox and adds it to the scene (at the start of the next update).

Inputs

  • scene: The scene
  • size: Its size (what’s in it scrolls inside)

Returns

  • The scroll box

procedure. Source: ui_controls.odin:361

CreateRichText :: proc(scene: ^Scene, text: string, width: f32 = 0) -> ^RichText

Creates a RichText and adds it to the scene (at the start of the next update).

Inputs

  • scene: The scene
  • text: Its text, with tags ([color=#ff8000]gold[/color], [size=28]big[/size])
  • width: The width it wraps at (0: only at line breaks)

Returns

  • The text

Example

note := e.CreateRichText(scene, "You found [color=#ffd040]the key[/color]!", 300)

procedure. Source: ui_controls.odin:382

Control_SetText :: proc(control: ^Control, text: string)

Sets a control’s text (a text field’s too: its caret goes to the end).

Inputs

  • control: The control
  • text: The text (copied)

procedure. Source: ui_controls.odin:396

ProgressBar_SetValue :: proc(bar: ^ProgressBar, value: f32)

Sets a progress bar’s value (clamped to its range).

Inputs

  • bar: The bar
  • value: The value

procedure. Source: ui_controls.odin:416

Control_Focus :: proc(scene: ^Scene, control: ^Control)

Gives a control the keyboard’s and gamepads’ focus (nil takes it from whatever has it). Only buttons and text fields take it.

Inputs

  • scene: The scene
  • control: The control, or nil

procedure. Source: ui_controls.odin:428

Control_Focused :: proc(scene: ^Scene) -> ^Control

The control with the focus.

Inputs

  • scene: The scene

Returns

  • The control, or nil

procedure. Source: ui_controls.odin:456

Control_GetRect :: proc(control: ^Control) -> sdl.FRect

A control’s rectangle on the screen (game coordinates), as last laid out.

Inputs

  • control: The control

Returns

  • Its rectangle

procedure. Source: ui_controls.odin:469

Node_IsControl :: proc(node: ^Node) -> bool

Whether a node is a UI control (Control starts its type).

Inputs

  • node: The node

Returns

  • true for Panel, Button, ProgressBar, TextField, ScrollBox and RichText nodes

procedure. Source: ui_controls.odin:484

Control_Theme :: proc(control: ^Control) -> (UITheme, string)

The theme a control is drawn with: its own theme, else its nearest control ancestor’s, else the project’s (Game.uiTheme), else DEFAULT_UI_THEME.

Inputs

  • control: The control

Returns

procedure. Source: ui_controls.odin:502

UISettings_Clone :: proc(settings: UISettings, allocator := context.allocator) -> UISettings

A copy of control settings, its strings cloned.

Inputs

  • settings: The settings
  • allocator: For the strings

Returns

procedure. Source: ui_controls.odin:526

UISettings_Destroy :: proc(settings: ^UISettings)

Frees control settings’ strings (the parser’s, UISettings_Clone’s).

Inputs

  • settings: The settings

procedure. Source: ui_controls.odin:540

RichText_Layout :: proc(rm: ^ResourceManager, text, font: string, size, width: f32) -> RichLines

Lays out rich text: its tags ([color=#rrggbb], [/color], [size=N], [/size]) read, words wrapped at a width.

Inputs

  • rm: The resource manager (for measuring)
  • text: The text
  • font: The font
  • size: Its text size where no [size] says otherwise
  • width: Where lines wrap (0: only at line breaks)

Returns

  • The runs (temp allocated) and their size

procedure. Source: ui_controls.odin:1223

UITheme_Fields :: proc() -> []UIThemeField

The theme’s fields for editors, in order, with their tags.

Returns

  • The fields (temp allocated)

procedure. Source: ui_theme.odin:137

UITheme_Get :: proc(theme: UITheme, name: string) -> (string, bool)

A theme field’s value as text (“20”, “#2c3e68ff”).

Inputs

  • theme: The theme
  • name: The field

Returns

  • The text (temp allocated), and false when there’s no such field

procedure. Source: ui_theme.odin:162

UITheme_Set :: proc(theme: ^UITheme, name, text: string) -> bool

Sets a theme field from text.

Inputs

  • theme: The theme
  • name: The field
  • text: Its value (“20”, “#ff8000”)

Returns

  • false when there’s no such field or the text isn’t a value of it (the field is unchanged)

procedure. Source: ui_theme.odin:184

ParseTheme :: proc(text: string, allocator := context.allocator) -> (ThemeData, ProjectError)

Reads a theme file’s text.

Inputs

  • text: The .theme.xml file’s text
  • allocator: For the font’s path

Returns

  • The theme (fields the file lacks are the defaults), and an error

procedure. Source: ui_theme.odin:212

FormatTheme :: proc(data: ThemeData, allocator := context.allocator) -> string

Writes a theme file: its font and the fields that differ from the default.

Inputs

  • data: The theme
  • allocator: For the text

Returns

  • The file’s text

procedure. Source: ui_theme.odin:242

Game_LoadTheme :: proc(game: ^Game, path: string) -> (^ThemeData, bool)

A theme asset, read once per game (tools put their unsaved themes in game.themes first).

Inputs

  • game: The game
  • path: The .theme.xml file, relative to the project

Returns

  • The theme, and false when it couldn’t be read (the default theme then)

procedure. Source: ui_theme.odin:280

MAX_MENU_ITEMS :: 32

Most items a Menu can hold (Menu_Add* asserts past it)

constant. Source: ui_menu.odin:8

MENU_ACTION_UP :: "ui_up"

The input action that moves a menu’s focus up.

Menus navigate with the MENU_ACTION_* input actions when the game has MENU_ACTION_ACCEPT (Godot’s names; the editor adds them to new projects, bound to arrows / WASD, the d-pad and the left stick to move, Enter / Space and the gamepad’s South button to press, Escape and East to go back). A game without them gets the keys only.

constant. Source: ui_menu_input.odin:15

MENU_ACTION_DOWN :: "ui_down"

The input action that moves a menu’s focus down (see MENU_ACTION_UP)

constant. Source: ui_menu_input.odin:17

MENU_ACTION_LEFT :: "ui_left"

The input action that decreases a slider, steps radios back and cycles select options back (see MENU_ACTION_UP)

constant. Source: ui_menu_input.odin:19

MENU_ACTION_RIGHT :: "ui_right"

The input action that increases a slider, steps radios forward and cycles select options forward (see MENU_ACTION_UP)

constant. Source: ui_menu_input.odin:21

MENU_ACTION_ACCEPT :: "ui_accept"

The input action that presses the focused item. Its presence makes menus use the actions instead of the keys

constant. Source: ui_menu_input.odin:23

MENU_ACTION_CANCEL :: "ui_cancel"

The input action that closes an open dropdown or runs Menu.onBack (see MENU_ACTION_UP)

constant. Source: ui_menu_input.odin:25

MENU_REPEAT_DELAY :: 0.4

Seconds a menu direction action is held before it starts repeating (every MENU_REPEAT_INTERVAL)

constant. Source: ui_menu_input.odin:28

MENU_REPEAT_INTERVAL :: 0.08

Seconds between repeats of a held menu direction action, after MENU_REPEAT_DELAY

constant. Source: ui_menu_input.odin:30

DEFAULT_MENU_STYLE :: MenuStyle{
font = DEFAULT_FONT,
textSize = 26,
itemSize = {360, 56},
spacing = 14,
padding = 18,
cornerRadius = 10,
borderWidth = 1,
controlWidth = 0.45,
textColor = {180, 190, 215, 255},
textFocusedColor = {20, 24, 40, 255},
textDisabledColor = {90, 98, 120, 255},
labelColor = {235, 240, 255, 255},
backgroundColor = {35, 42, 65, 255},
backgroundFocusedColor = {120, 200, 255, 255},
borderColor = {70, 80, 115, 255},
borderFocusedColor = {200, 235, 255, 255},
controlColor = {20, 24, 40, 255},
controlFocusedColor = {235, 245, 255, 255},
accentColor = {255, 190, 70, 255},
popupColor = {28, 34, 54, 255},
popupHighlightColor = {120, 200, 255, 255},
}

The engine’s default menu style: 360 x 56 pixel rows on a dark blue theme, with the default font.

Example

style := e.DEFAULT_MENU_STYLE
style.itemSize = {480, 64}
style.accentColor = {120, 255, 140, 255}
menu := e.CreateMenu(scene, {640, 200}, style)

constant. Source: ui_style.odin:72

DEFAULT_UI_SETTINGS :: UISettings{padding = -1, spacing = -1, maxValue = 1, columns = 2}

A control’s settings when the scene file sets none

constant. Source: ui_controls.odin:172

UI_SCROLL_STEP :: 40

Pixels a wheel step scrolls a scroll box

constant. Source: ui_controls.odin:253

DEFAULT_UI_THEME :: UITheme{
textSize = 20,
textColor = {235, 240, 255, 255},
disabledTextColor = {120, 128, 150, 255},
panelColor = {22, 28, 46, 230},
borderColor = {70, 86, 130, 255},
borderWidth = 1,
cornerRadius = 8,
padding = 10,
spacing = 8,
buttonColor = {44, 62, 104, 255},
buttonHoverColor = {62, 86, 140, 255},
buttonPressedColor = {32, 44, 76, 255},
buttonDisabledColor = {36, 40, 56, 255},
buttonTextColor = {245, 248, 255, 255},
focusColor = {255, 210, 90, 255},
fieldColor = {14, 18, 30, 255},
placeholderColor = {110, 120, 150, 255},
caretColor = {255, 210, 90, 255},
progressBackColor = {14, 18, 30, 255},
progressFillColor = {90, 200, 120, 255},
scrollbarColor = {90, 104, 150, 200},
}

The engine’s UI theme: light text on dark blue panels, like DEFAULT_MENU_STYLE

constant. Source: ui_theme.odin:81