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
| Method | Description |
|---|---|
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),});DropdownOptions
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
| Type | Children | Description |
|---|---|---|
element | Yes | Raw UI entity with manual component configuration |
label | No | Text label |
button | No | Button widget |
slider | No | Slider widget |
toggle | No | Toggle widget |
textInput | No | Text input |
dropdown | No | Dropdown selector |
progressBar | No | Progress bar |
panel | Yes | Container with background |
flexRow | Yes | Horizontal flex container |
flexColumn | Yes | Vertical flex container |
scrollView | Yes | Scrollable 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
| Group | Properties |
|---|---|
| Colors | primary, secondary, background, surface, error, text, textSecondary, border |
| Typography | fontFamily, fontSize (xs, sm, md, lg, xl) |
| Spacing | spacing (xs, sm, md, lg, xl) |
| Button | height, color, textColor, transition |
| Slider | trackHeight, trackColor, fillColor, handleSize, handleColor |
| Toggle | size, onColor, offColor, checkColor |
| Input | height, backgroundColor, textColor, placeholderColor, fontSize, padding |
| Dropdown | height, backgroundColor, itemHeight |
| Panel | backgroundColor, padding |
Cleanup
Remove a UI entity and all its children:
UI.destroy(world, formEntity);Next Steps
- UI Layout — anchor-based layout with UIRect
- UI Widgets — component-level widget reference
- UI Interaction — events and callbacks