Skip to main content

UI

Two layers, both script-facing.

LayerWhat it isFor
widgetA component on a scene node. Retained: saved with the scene, edited in the inspector, previewed in the viewportA game HUD, a settings screen, a whole tool
the ui moduleImmediate-mode egui, redrawn every frame from a script's draw_uiA canvas that paints itself: a timeline, a weight table

The editor's shell, docks, inspector and tab rows are widget nodes in editor/scenes/main.toml. Its four painting views use the ui module.

Widgets: game HUDs as scene nodes​

[[nodes]]
name = "Score"

[nodes.widget]
kind = "label"
text = "Score 0"
anchor = "top_left"
x = 20
y = 16
font_size = 22
text_color = "#2b2b2b"
  • One widget component. kind says what it draws: label, button or panel. Every component with variants selects one with kind.
  • Widgets are scene nodes: saved with the scene, edited in the editor, updated through the component handle.

The UI workspace: examples/angrynerds' Score, Birds, Hint and Restart widgets in the tree, the HUD over the safe area, and the Restart button in the inspector

A button acts in one of two ways:

[nodes.widget]
kind = "button"
text = "Start"
on_click = "start_pressed"
pub fn start_pressed(this) {
log::info("start");
}

// or, without on_click:
pub fn update(this, dt) {
if this.node.widget.get().clicked {
log::info("start");
}
}
// scripts/restart_button.rn, on the Restart node
pub fn on_restart(this) {
this.node.get_node("../Game").call("restart");
}
  • on_click names a method on the node's own script. A button acting on another node forwards the call.
  • clicked is read-only and true for the frame of the press.
let w = this.node.widget.get();
w.text = "Score 10";
this.node.widget.set(w);
  • A handle's set builds the component from the given table over the schema's defaults. To change one property, get first.
  • Panels size to their content when width or height is 0.
  • visible hides a widget and keeps its state. ui::set_widget_layer scopes and toggles the layer.

The kinds​

GroupKinds
Textlabel, text_field, text_area, code
Controlsbutton, checkbox, switch, dropdown, slider, number_field, color_picker, progress_bar, menu
Datalist, tree, table
Boxespanel, row, column, grid, flow, scroll, tabs, fold, dialog, toast, window, stack
Otherimage, separator, draw
  • list — rows from options; text is the one picked. A row splits on U+001F into an icon, a label, a trailing note and an #rrggbb. With columns above one, rows flow into a grid of cards (ItemList in icon mode) and row_height is the card's height.
  • tree — a list whose rows nest. One leading tab is one level. The fold caret is the kind's own.
  • table — one row per line, one cell per column. titles names the columns and says how many there are; a name ending in > draws its column against the right edge. A drag on the seam in the header moves a column's width, and a row may carry an #rrggbb past its last cell.
  • multi_select — lets a list, tree or table hold more than one row. The platform's command key toggles a row and shift takes the run from the last one clicked. selection is what the widget holds either way, one row or many, so nothing reading it back branches; text is the row last clicked. on_change hears the whole set where the widget holds many, and the row where it holds one.
  • widths — a share per table column, normalised: ["3", "1"] is three quarters and a quarter, and empty divides the width evenly. A drag on a seam in the header writes the shares back, so the scene keeps them.
  • header — off, a table draws no strip of column names. The columns are still titles', and a table that names none keeps its first row.
  • sort — the column the rows are drawn in the order of; reverse turns it round, and sortable lets a click on a header ask for either and writes both back. A cell that starts with a number sorts as one, so 12 KB follows 3 KB.
  • reorderable — a drag moves a row of a list or a tree. The kind draws where the row would land and calls on_move with the row moved, the row it landed on, and before, after or into. The rows themselves are the script's to reorder.
  • code — an editor with gutter, syntax colours and caret. The same editor as the ui module's code_editor.
  • draw — a rect a script fills. The node owns the placement, the script owns the contents.
  • menu — a button that drops its child rows. A row that is itself a menu is a submenu. placement says where it opens.
  • toast — a message that stacks at its anchor and leaves when its duration is up. A toast binding action puts one up with no script.

Only the rows on screen are built, in all three. A long list costs its viewport rather than its length. A secondary click picks the row it lands on, so a context menu acts on what it opened over; a row already picked keeps the set it is in.

A list with two rows picked, the same entries as a folding tree, and again as a table with named columns

A screen of nodes: a card grid, a control row and a wrapping chip flow

Layout​

A container hands its children rects.

  • grow — the share of leftover space this box takes along the container's direction. Zero takes only its own size.
  • gap — space between children. padding is space inside the container's edge.
  • align — where children sit across the container's direction.
  • justify — how they spread along it: start, center, end, between, around or evenly.
  • width — the box, with height, min_width and min_height. Zero hugs the content.
  • handle — the grab width of the seams between children. A drag writes the new size onto the neighbour that states one.

Layout is taffy, the layout engine Dioxus and Blitz use. It runs over the whole tree before drawing. ui::widget_rect reads a rect back in the same frame.

Themes and roles​

A widget_theme asset says how each kind looks. A widget takes the theme of the nearest ancestor that names one.

type = "widget_theme"

[colors]
background = "#0e1116"
primary = "#6fa4d8"

[button]
fill = "bg_control"
corner_radius = "radius"
padding_x = 10

[button.hover]
fill = "primary_bg"

[roles.primary]
fill = "primary_fill"
text_color = "text_on_primary"
font_weight = 700
  • [colors] states the source colours: background, foreground, primary, secondary, success, warning and danger. Every other token is derived from them, and stating one overrides its rule.
  • dark follows from the background. Each ink is walked toward the foreground until it reads at WCAG AA on the three surfaces text lands on.
  • [sizes] states font_size, radius, control_height and stroke_width, and the rest derive from those four. A number in any table may name a size instead.
  • [<kind>] is that kind's look. A kind the file leaves out keeps the built-in one.
  • [roles.<name>] is the same table under a name. A widget takes it with role = "primary", over its kind's.
  • hover, active, focus, disabled and checked tables say how it looks in each state. checked dresses a checkbox, a switch or a toggle button that is on.
  • A widget's own fill, stroke, corner_radius, text_color, font_size and font_family win over the theme.
  • [<kind>] image with slice makes the kind's background a nine-patch, on a row view as on a panel.
TokenWhat it paints
bg_app, bg_panelthe ground behind everything, and a sheet on it
bg_control, bg_control_hovera control at rest, and under the pointer
border_defaultevery outline
text_default, text_muted, text_subtlethree levels of text
primary_fill, primary_fill_hovera solid fill, and the same under the pointer
primary_text, primary_bgink in the colour, and a tint of it behind a control that is on
text_on_primaryink on primary_fill
font_size_small, font_size_large, font_size_titlefrom font_size
radius_small, radius_largefrom radius
control_height_small, control_height_large, control_height_touchfrom control_height

secondary, success, warning and danger take the same five colour tokens as primary.

A list, tree or table paints more than one plate, so the parts it draws are [colors] of their own. Each takes the built-in look where the theme names none, and a colour with no alpha draws nothing.

TokenWhat it paints
row_selected, row_selected_texta picked row, and the ink on it
row_hover, row_activewhat a row takes under the pointer, and while it is held
row_stripea table's every other row
table_headerits header's plate
table_rulethe lines down its columns, and the one under its header
tree_guidethe lines down a tree's indent

These are the rows, not the box: [list.hover] still dresses the whole view under the pointer, the way it does any other kind. So table_rule = "#00000000" is a table with no lines, and row_stripe the same is one with no striping. padding_x on the kind is the air either side of a cell, and gap is the space between a list's cards.

Also on every widget: tooltip, icon (a glyph from the theme's icon family, drawn before the caption), enabled and interactive. Both are on unless turned off: enabled = false greys the widget out and swallows its clicks, and interactive = false lets the pointer through to the scene.

image is the picture an image widget or a button draws, sheet the picture a list cuts its card faces from, and language what a code widget highlights.

Three more say what a widget answers to. context names a menu a right click or a long press opens at the pointer. shortcut is a chord that clicks the widget from anywhere, as cmd+shift+s or f5. selectable lets a drag over a label select its text for the platform's copy key.

The ui module: immediate mode​

pub fn draw_ui(this) {
ui::left_panel("outline", #{ width: 260 }, || {
ui::label("Nodes", #{ font_size: "font_size_large" });
if ui::button("Add node", #{ role: "primary" }) {
// ...
}
});
}
  • draw_ui runs once per frame inside the egui pass.
  • Panel and container calls take a closure for their children, as Rust egui code does.
  • A widget that changes returns a tuple: let (text, changed) = ui::text_field(...).
  • The set: panels, labels, buttons, switches, sliders, number fields, dropdowns, text fields, dialogs, scroll areas, menus, shortcuts, and ui::code_editor, an editable Rune-highlighted buffer.
  • For canvases: a timeline's lanes, a weight table, a painting tool. A draw node places one in a tree of widgets.
  • ui::set_theme takes the document a widget_theme file holds, so its roles dress both kinds of UI. A call's own options win over its role.
  • ui::complete_theme answers the document with every token derived, and ui::contrast measures two colours the way the theme does.
  • Fonts load from <project>/fonts/*.ttf, with heading, ui, mono and icon always available.
  • All dimensions are design pixels. ui::set_scale(f) multiplies every metric and the query functions divide back. HiDPI is separate and automatic.

The editor's four painting views use this module inside draw nodes. See The editor.

Example​

examples/interface is a whole screen built from widgets: a card grid, an outline, a table, a row of every control, and a theme with roles and hover states. One script fills the three views; the layout is in the scene.

Three more scenes sit beside it. scenes/rows.toml is the three row views over one set of entries. scenes/menus.toml is a menu bar, a context menu and a toast; scenes/text.toml is a label with links and hints, a selectable line, and numbers with arrows and units. Open any of them with --scene.