Skip to content

UI Builder

The UI builder provides a code-first way to create UI hierarchies. Instead of manually spawning entities and inserting components, use factory methods that handle all the wiring automatically.

Factory Methods

Each method spawns a fully configured UI entity and returns its Entity ID:

import { UI } from 'esengine';
const btn = UI.button(world, { text: 'Play', parent: rootEntity });
const slider = UI.slider(world, { value: 0.5, parent: rootEntity });
const label = UI.label(world, { text: 'Score: 0', parent: rootEntity });

Available Factories

MethodDescription
UI.label(world, options)Text label
UI.panel(world, options)Container with background color
UI.button(world, options)Clickable button with text
UI.slider(world, options)Value slider with fill and handle
UI.toggle(world, options)Checkbox/toggle switch
UI.progressBar(world, options)Non-interactive progress display
UI.textInput(world, options)Editable text field
UI.dropdown(world, options)Dropdown selector
UI.scrollView(world, options)Scrollable container
UI.flexRow(world, options)Horizontal flex layout container
UI.flexColumn(world, options)Vertical flex layout container

Widget Options

All options are optional unless noted. Defaults come from the active theme.

ButtonOptions

UI.button(world, {
text: 'Click Me', // default: 'Button'
fontSize: 16, // default: theme.fontSize.md
size: { x: 150, y: 40 }, // default: { x: 120, y: 36 }
color: { r: 0.2, g: 0.5, b: 0.9, a: 1 },
textColor: { r: 1, g: 1, b: 1, a: 1 },
transition: null, // ColorTransition or null
parent: rootEntity,
events: uiEvents, // required for onClick/onHover to work
onClick: (entity) => console.log('Clicked!'),
});

SliderOptions

UI.slider(world, {
value: 0.5,
minValue: 0,
maxValue: 1,
wholeNumbers: false,
size: { x: 200, y: 16 },
trackColor: { r: 0.15, g: 0.15, b: 0.15, a: 1 },
fillColor: { r: 0.25, g: 0.56, b: 0.96, a: 1 },
handleSize: { x: 24, y: 24 },
handleColor: { r: 1, g: 1, b: 1, a: 1 },
parent: rootEntity,
events: uiEvents, // required for onChange to work
onChange: (value, entity) => console.log('Value:', value),
});

ToggleOptions

UI.toggle(world, {
isOn: true,
size: { x: 24, y: 24 },
onColor: { r: 0.2, g: 0.6, b: 1, a: 1 },
offColor: { r: 0.4, g: 0.4, b: 0.4, a: 1 },
label: 'Enable sound', // optional text label
group: toggleGroupEntity, // for radio-button behavior
parent: rootEntity,
events: uiEvents,
onChange: (isOn, entity) => console.log('Toggle:', isOn),
});

TextInputOptions

UI.textInput(world, {
placeholder: 'Enter name...',
value: '',
size: { x: 200, y: 36 },
fontSize: 16,
maxLength: 0, // 0 = unlimited
multiline: false,
password: false,
parent: rootEntity,
events: uiEvents,
onChange: (value, entity) => console.log('Text:', value),
onSubmit: (value, entity) => console.log('Submit:', value),
});
UI.dropdown(world, {
options: ['Easy', 'Normal', 'Hard'], // required
selectedIndex: 1,
size: { x: 160, y: 32 },
parent: rootEntity,
events: uiEvents,
onChange: (index, entity) => console.log('Selected:', index),
});

FlexOptions

UI.flexRow(world, {
gap: 8,
padding: { left: 12, top: 12, right: 12, bottom: 12 },
wrap: false,
justifyContent: 'center', // 'start', 'center', 'end', 'spaceBetween', 'spaceAround', 'spaceEvenly'
alignItems: 'stretch', // 'stretch', 'start', 'center', 'end'
parent: rootEntity,
});

Declarative Tree Builder

For complex UIs, use UI.build() to create an entire hierarchy from a nested definition:

import { UI } from 'esengine';
let nameInput: Entity;
const form = UI.build(world, {
type: 'panel',
options: { size: { x: 400, y: 300 } },
children: [
{
type: 'label',
options: { text: 'Player Setup', fontSize: 20 },
},
{
type: 'textInput',
options: { placeholder: 'Enter name' },
ref: (entity) => { nameInput = entity; },
},
{
type: 'flexRow',
options: { gap: 8 },
children: [
{
type: 'button',
options: { text: 'Cancel' },
},
{
type: 'button',
options: { text: 'Start', onClick: () => startGame() },
},
],
},
],
});

Node Types

TypeChildrenDescription
elementYesRaw UI entity with manual component configuration
labelNoText label
buttonNoButton widget
sliderNoSlider widget
toggleNoToggle widget
textInputNoText input
dropdownNoDropdown selector
progressBarNoProgress bar
panelYesContainer with background
flexRowYesHorizontal flex container
flexColumnYesVertical flex container
scrollViewYesScrollable container (children auto-parent to content)

Entity Refs

Use ref on any node to capture the created entity:

let sliderRef: Entity;
UI.build(world, {
type: 'slider',
options: { value: 0.5 },
ref: (entity) => { sliderRef = entity; },
});

Theming

All widgets use the active UITheme for default colors, sizes, and fonts. The built-in DARK_THEME is used by default.

Custom Theme

Override the theme by inserting UIThemeRes before initialization:

import { UIThemeRes, DARK_THEME } from 'esengine';
const myTheme = {
...DARK_THEME,
primary: { r: 0.9, g: 0.3, b: 0.1, a: 1 },
button: {
...DARK_THEME.button,
color: { r: 0.9, g: 0.3, b: 0.1, a: 1 },
},
};
cmds.insertResource(UIThemeRes, myTheme);

UITheme Properties

GroupProperties
Colorsprimary, secondary, background, surface, error, text, textSecondary, border
TypographyfontFamily, fontSize (xs, sm, md, lg, xl)
Spacingspacing (xs, sm, md, lg, xl)
Buttonheight, color, textColor, transition
SlidertrackHeight, trackColor, fillColor, handleSize, handleColor
Togglesize, onColor, offColor, checkColor
Inputheight, backgroundColor, textColor, placeholderColor, fontSize, padding
Dropdownheight, backgroundColor, itemHeight
PanelbackgroundColor, padding

Cleanup

Remove a UI entity and all its children:

UI.destroy(world, formEntity);

Next Steps