ui
Public Luau surface over the `__ui` Internal FFI namespace — the egui-backed UI system's full Luau-facing surface.
ui Module
Public Luau surface over the __ui Internal FFI namespace — the
egui-backed UI system's full Luau-facing surface.
Purpose
Wrap the raw __ui.* FFI namespace in a typed Luau table that gets
auto-injected as _G.ui via the prelude. Covers screens, styles,
themes, widget introspection, input simulation, scroll / area /
layout, per-widget responses, focus, validation, custom-widget
builders, and per-widget cross-frame state.
Higher-level Luau libraries (zui, Z.tags, Z.theme, ...) build
composable surfaces on top of these primitives.
Usage
-- Register a screen
ui.registerScreen("hud", {
type = "panel",
children = {
{ type = "label", props = { text = "Score: 0" }, id = "score" },
},
})
-- Read responses + drive focus
local r = ui.response("score")
if r and r.clicked then ui.focus("score-input") end
-- Themes + styles
ui.setTheme("dark")
ui.defineStyle("button.primary", { bg = "#3b82f6", padding = 8 })
-- Custom widget kinds
ui.defineWidget("badge", function(props, children)
return { type = "label", props = { text = "[" .. props.text .. "]" } }
end)
-- Per-widget state (cross-frame, screen-scoped)
ui.widgetStateSet("filterBar", "active", true)
print(ui.widgetState("filterBar", "active"))
Exports
- Screens:
registerScreen,updateScreen,showScreen,hideScreen,unregisterScreen/removeScreen,listScreens,getScreenTree,screen,captureWindow. - Styles + themes:
defineStyle,defineStyles,useStyles,setTheme,getTheme,listThemes,registerTheme,getToken,getTokens. - Widget introspection:
getWidgetTypes,getWidgetProps. - Input simulation:
click,scroll. - Background shaders:
registerBackgroundShader,setShaderUniforms. - Scroll / area / layout:
setScrollPosition,getAreaPos,setAreaPos,getLayoutInfo. - Dock arrangement:
getDockLayout,getPersistentDockLayout,setDockWindowRect. - Responses + focus:
response,focus,blur,focusedWidget. - Validation:
lastValidation. - Custom widgets + state:
defineWidget,unregisterWidget,widgetState,widgetStateSet,widgetStateClear.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2global ui global-types module ui UI system — screens, styles, themes, widget responses, focus, custom-widget builders, per-widget state. Public Luau surface over the `__ui` Internal FFI namespace. require modules/api/engine/ui
resolveTextureSrc(value: any) → any
An `image` widget's `src` and any widget's `style.backgroundImage` name a texture ASSET (by name, path, guid, or `@lib::id`). The renderer only finds a texture once it is materialised (Disk→CPU→GPU) and keyed under `asset:<guid>`; a bare name otherwise falls through to the HTTP image cache and 404s. Resolve + materialise here, at `registerScreen` / `updateScreen` — the single seam EVERY screen tree passes through (raw tables AND builder output) — so the dependency is enforced by the system, not left for the author to wire by hand. A future generic "field → asset" mechanism replaces this one seam.
| arg | type | description |
|---|---|---|
| value | any |
resolveStyleTextures(style: any) → void
The texture references a style table names, resolved and materialised in place. A `backgroundImage` reaches the painter from whichever layer of the cascade declares it, so it is materialised on the same terms from whichever layer states it: a widget's own `style`, a named style stated through `defineStyle` / `defineStyles`, and each style a theme's `styles` map carries all pass through here.
| arg | type | description |
|---|---|---|
| style | any |
resolveTreeTextures(node: any, depth: number) → void
| arg | type | description |
|---|---|---|
| node | any | |
| depth | number |
registerScreen(name: string, widgetTree: WidgetTree, layer: number?) → void
Register a named UI screen with a widget tree. Optional `layer` controls z-ordering (higher = on top), in bands: below 0 behind everything, 0-99 ordinary app depth, 100-999 always-on-top chrome, 1000+ menu and popup depth. A screen in a higher band covers one in a lower band whatever their roots are; inside a band a floating `area` or `window` root sits over ordinary content, and a `modal` root sits over the whole stack. Tag-based grouping lives in `Z.tags` (`Z.tags.set(name, { "editor" })` after register). A screen belongs to the component instance whose own code registers it (its hooks and methods, and any closure they create, such as a listener it subscribes): that instance's `onCallback` receives its callbacks, and the screen is unregistered when the instance ends. Code that is not an instance's own (a scene entrypoint, a module, a listener either subscribed) registers a screen no instance owns, which stays until it is unregistered, whichever component's hook ran that code. `topics/ui` has the rule.
| arg | type | description |
|---|---|---|
| name | string | Unique screen identifier. |
| widgetTree | WidgetTree | Root widget table. |
| layer | number? | Z-order layer (optional). |
examples
ui.registerScreen("hud", tree)lastRegistration( ) →
The name and layer passed to the most recent `ui.registerScreen` call, recorded synchronously at call time. A host that mounts a nested app reads this immediately after the mount to learn which screen the nested code registered, without intercepting the `ui` table.
updateScreen(name: string, widgetTree: WidgetTree) → void
Replace the widget tree of an already-registered screen.
| arg | type | description |
|---|---|---|
| name | string | Screen identifier to update. |
| widgetTree | WidgetTree | New root widget table. |
showScreen(name: string) → boolean
Make a registered screen visible, and report whether a screen by that name is registered. The engine applies the show later in the frame; `listScreens` reflects it from the next call onwards.
| arg | type | description |
|---|---|---|
| name | string | Screen identifier to show. |
hideScreen(name: string) → boolean
Hide a registered screen, and report whether a screen by that name is registered. The engine applies the hide later in the frame; `listScreens` reflects it from the next call onwards.
| arg | type | description |
|---|---|---|
| name | string | Screen identifier to hide. |
setScreenRenderLayer(name: string, mask: number) → void
Set a screen's render-layer membership bitmask. A screen draws into a camera or capture only when this mask intersects the camera's include mask, the same rule geometry follows. Content UI carries the `ui` bit; the editor places its chrome on `EditorUI` so agent captures can drop it, and a screen carrying that bit is part of the window rather than the camera's picture. Stating a mask here fixes the membership: a screen left to itself takes the one the surface it is placed on implies, `EditorUI` for the window and `ui` for the camera's picture. `__renderLayers.bit(name)` answers with a layer's bit INDEX, so a mask sets the bit at that index and `__renderLayers.names(mask)` reads it back as the names it holds. `bit32.lshift(1, __renderLayers.bit(name))` over the layers the screen is on.
| arg | type | description |
|---|---|---|
| name | string | Screen identifier. |
| mask | number | Render-layer membership bitmask — the OR of |
examples
ui.setScreenRenderLayer("hud", bit32.lshift(1, __renderLayers.bit("ui")))setScreenSpace(name: string, space: string) → void
Set which surface a screen is part of. `"camera"` is the picture the viewport camera produces: the screen lays out over the camera's target and is drawn into it, so it sits inside the Scene panel presenting the scene and appears in every capture of the camera — the default for content UI. `"window"` is the window itself, whatever presents the scene — where the editor's chrome lives, and the default for a screen on the `EditorUI` layer. The two are one statement made from either end, so a screen placed on the window takes the `EditorUI` membership unless `setScreenRenderLayer` states another: the window goes on drawing it while a capture that drops the authoring layers, which is what an agent takes by default, leaves it out. Where the scene presents on the window, the two surfaces coincide. A name that is neither raises.
| arg | type | description |
|---|---|---|
| name | string | Screen identifier. |
| space | string | `"camera"` | `"window"`. |
examples
ui.setScreenSpace("agent-banner", "window")unregisterScreen(name: string) → boolean
Remove a screen from the registry entirely. Unlike `hideScreen`, this deletes the entry so it no longer appears in `listScreens` or render iteration.
| arg | type | description |
|---|---|---|
| name | string | Screen identifier to unregister. |
removeScreen(name: string) → boolean
Alias for `ui.unregisterScreen`.
| arg | type | description |
|---|---|---|
| name | string | Screen identifier to remove. |
registerCallbackEnv(key: string, env: { [string]: any }) → void
Register an environment table to receive widget-callback broadcasts: its global `onCallback(id, value)` fires for any widget callback not owned by a specific component instance — the same broadcast a component's `onCallback` receives. Keyed by `key`; re-registering the same key replaces the previous env. A component instance is folded into the callback dispatch automatically, so reach for this from a non-component context that hosts a UI surface (a scene entrypoint registering its own screen). Pair with `ui.unregisterCallbackEnv(key)` so the ref is released.
| arg | type | description |
|---|---|---|
| key | string | Stable identifier for this registration (re-register replaces). |
| env | { [string]: any } | Environment table whose `onCallback` receives the broadcasts. |
unregisterCallbackEnv(key: string) → void
Remove an environment registered with `ui.registerCallbackEnv`. Its `onCallback` stops receiving broadcasts. No-op if `key` isn't registered.
| arg | type | description |
|---|---|---|
| key | string | The key passed to `ui.registerCallbackEnv`. |
listScreens( ) →
List every registered screen with its current visibility, z-order `layer`, `renderLayer` (the render-layer membership mask it draws under: the mask `setScreenRenderLayer` stated, else the one the surface `setScreenSpace` named implies, else the `ui` bit), `editorChrome` (whether the mask `setScreenRenderLayer` stated carries the `EditorUI` bit, which makes the screen the editor's own chrome; a screen placed on the window without stating it is not chrome), whether the screen has a populated root widget tree, and the space it draws in (`"window"` for the editor's UI, `"camera"` for the game's), including the register / show / hide / unregister calls the running script has already made. Sorted by layer ascending, then name.
idCollisions( ) →
Element ids more than one screen recorded, each mapped to the screens that recorded it. An id addresses one element: two screens sharing one leave `ui.getLayoutInfo` and the `gui` element tools answering for whichever painted last, and the `gui` tools refuse such an id. Empty while every id is unique.
examples
for id, screens in ui.idCollisions() do print(id, table.concat(screens, ", ")) end
defineStyle(name: string, style: StyleProps) → void
Define a named style. Style keys follow `<widgetType>.<className>` (e.g. `"label.h1"`, `"button.primary"`) or bare `<className>` to apply across widget types. Widgets reference styles via the `classes` (or `class`) prop.
| arg | type | description |
|---|---|---|
| name | string | Style name. |
| style | StyleProps | Style properties table. |
defineStyles(styles: { [string]: StyleProps }) → void
Define multiple named styles at once.
| arg | type | description |
|---|---|---|
| styles | { [string]: StyleProps } | Map of style name to style properties. |
useStyles(themeName: string) → void
Apply a registered style file's classes additively without changing the active theme.
| arg | type | description |
|---|---|---|
| themeName | string | Name of the registered style / theme asset. |
getWidgetTypes( ) →
Get all available widget type names that can be used in widget trees.
getWidgetProps(typeName: string) →
Get the property definitions for a widget type.
| arg | type | description |
|---|---|---|
| typeName | string | Widget type name. |
setTheme(name: string) → void
Switch the active global theme by name.
| arg | type | description |
|---|---|---|
| name | string | Theme name to activate. |
getTheme( ) → string
Get the name of the currently active theme.
listThemes( ) →
List all registered theme names.
listFonts( ) →
Every font family a `style.fontFamily` can select. Read from the registry the UI text renderer resolves a family token through, so a family this returns is one a label renders in. `family` and every name in `aliases` are accepted as a `fontFamily`, case-insensitively; `aliases` carries the web-font names, CSS generic families and face names that select the same group. `faces` names the concrete face in each weight/style slot, so a `fontWeight = 700` against a family with no `bold` face gets a synthesised heavy. `more` lists the other faces registered in the family's slots, each drawing the codepoints the slot's face lacks: the installed chunks of an on-demand face (`font.onDemand()` lists them all). `system = true` marks a family taken from the host OS — present on this machine, absent on one without it, and absent on WASM — so a UI that must look the same everywhere picks a family with `system = false`. A `fontFamily` naming nothing in this list is reported as an `unknown-font-family` warning through `ui.lastValidation(screen)` once the screen paints, and the text renders in the default proportional face.
examples
for _, f in ui.listFonts() do print(f.family) end
missingGlyphs( ) →
The characters the UI is showing without a glyph: each appears in text a UI surface (the window, the game view, a camera's UI overlay, a screen drawn into a texture) drew in what it last painted, and no face of the family it was drawn in has a glyph for it, so it shows as the replacement box. Every family falls back glyph by glyph to every face the UI holds, so a character here is one no installed face covers. A character a declared on-demand face covers (`font.onDemand()`) requests that face the frame it is met, and leaves this list once the face is installed and the next frame draws it. character drawn had a glyph.
examples
if #ui.missingGlyphs() > 0 then print(table.concat(ui.missingGlyphs(), " ")) end
registerTheme(name: string, theme: ThemeDefinition) → void
Register a theme from a flat Luau table. Most callers should use `Z.theme.register(name, table)` which runs the cascade for them.
| arg | type | description |
|---|---|---|
| name | string | Theme name to register. |
| theme | ThemeDefinition | Flat-resolved theme table. |
getToken(name: string) → string
Look up a single design token value from the active theme.
| arg | type | description |
|---|---|---|
| name | string | Token name (without `$` prefix). |
getTokens( ) →
Get all design tokens from the active theme as a key-value map.
click(callbackId: string, value: any?) → boolean
Simulate a widget click / interaction by its callback id. The call carries no screen, so an id that names widgets on several screens reaches every component that declared it, once each. The callback id is the one a widget emits: the string its `onClick` / `onChange` / other `on*` prop names, or its element `id` where it leaves that prop unnamed (a button clicked without an `onClick`, a slider changed without an `onChange`). Given the element id of a widget whose interactions emit other ids, it raises an error naming those ids, and clicks nothing. `on*` prop). a string, a boolean, or a table with the fields a structured interaction delivers (a code editor's `{ token, line, col }`, a canvas drag's deltas). none does, and the click then still reaches every `ui.registerCallbackEnv` env and every component and scene entrypoint that receives callbacks no screen claims.
| arg | type | description |
|---|---|---|
| callbackId | string | Callback id the widget emits (its `onClick` / `onChange` / |
| value | any? | Optional value the callback receives as `data.value`: a number, |
scroll(deltaX: number, deltaY: number) → void
Simulate a mouse-wheel scroll event on the UI.
| arg | type | description |
|---|---|---|
| deltaX | number | Horizontal scroll delta. |
| deltaY | number | Vertical scroll delta. |
registerBackgroundShader(shaderHandle: any, width: number?, height: number?) → void
Register a screen-domain `.shader` as a UI background, drawn via the `backgroundShader` style. Takes the shader's asset handle from `asset.resolve`. A shader whose compile failed (`shaderHandle:compileStatus()` reads `failed`), or whose program the compile gate refuses, is not registered: the call raises an error naming the shader and the compiler's message, the one `compileStatus().error` reads. A shader whose `compileStatus()` still reads `pending` raises too: register it once it reads `compiled`.
| arg | type | description |
|---|---|---|
| shaderHandle | any | The screen `.shader`'s asset handle, from `asset.resolve`. |
| width | number? | Render target width (default 1280). |
| height | number? | Render target height (default 720). |
setShaderUniforms(name: string, uniforms: { [string]: number }) → void
Set uniform values on a registered background shader.
| arg | type | description |
|---|---|---|
| name | string | Shader name identifier. |
| uniforms | { [string]: number } | Map of uniform name to number value. |
setScrollPosition(widgetId: string, offsetY: number) → void
Set the scroll offset of a scrollArea widget.
| arg | type | description |
|---|---|---|
| widgetId | string | Scroll area widget id. |
| offsetY | number | Vertical scroll offset in pixels. |
getAreaPos(id: string) → AreaPos
Read the current pivot position of an `area` widget, including any user drag deltas. Returns `{ x, y }` or nil if the area didn't render this frame.
| arg | type | description |
|---|---|---|
| id | string | Area widget id. |
getAreaSize(id: string) → AreaSize
Read the measured size of an `area` widget, including any user resize-grip drags if the area is `resizable`. Returns `{ w, h }` or nil if the area didn't render this frame.
| arg | type | description |
|---|---|---|
| id | string | Area widget id. |
getDockLayout(id: string) → string
Read the current serialized layout (split/tab arrangement) of a `dockArea` widget as a JSON string. Returns nil if the dockArea didn't render this frame. Persist the string and pass it back via the dockArea's `layout` prop to restore the arrangement.
| arg | type | description |
|---|---|---|
| id | string | DockArea widget id. |
getPersistentDockLayout(id: string) → string
Read a `dockArea`'s arrangement as a JSON string with every tab whose `dockPanel` is `transient` left out — the form to save and restore across sessions. Returns nil if the dockArea didn't render this frame.
| arg | type | description |
|---|---|---|
| id | string | DockArea widget id. |
setAreaPos(id: string, x: number, y: number) → void
Programmatically move a movable `area` widget to `(x, y)`. Applied for one frame; subsequent frames let drag tracking take over.
| arg | type | description |
|---|---|---|
| id | string | Area widget id. |
| x | number | Target pivot x (screen coords). |
| y | number | Target pivot y (screen coords). |
setAreaSize(id: string, w: number, h: number) → void
Programmatically set a `resizable` `area`'s size (the user-size override) — for maximize / restore / tile. Persists until the area's declared width/height changes or `ui.resetAreaSize(id)` clears it.
| arg | type | description |
|---|---|---|
| id | string | Area widget id. |
| w | number | Target width (screen coords). |
| h | number | Target height (screen coords). |
resetAreaSize(id: string) → void
Clear a `resizable` `area`'s remembered size (from a grip drag or `ui.setAreaSize`) so its declared — or content — size takes over again.
| arg | type | description |
|---|---|---|
| id | string | Area widget id. |
bringAreaToFront(id: string) → void
Raise a movable `area` to the top of the window stacking order — the programmatic equivalent of clicking it. Areas sharing a stacking band order by interaction, so this is the call that brings one forward from code: use it when a taskbar button, focus change, or app launch should raise a window. Moving a screen to a higher `layer` band raises it over the bands below.
| arg | type | description |
|---|---|---|
| id | string | Area widget id. |
getLayoutInfo(widgetId: string?) → LayoutInfo
Get layout info (position, size, content bounds) for UI containers. If `widgetId` is given, returns info for that widget only; otherwise returns all.
| arg | type | description |
|---|---|---|
| widgetId | string? | Optional widget id to query. |
screenSize( ) →
The UI coordinate space as `{ width, height }` (logical points). This is the space `area` `pos`, anchors, and `getLayoutInfo` rects use — and it is NOT the pixel size of a `capture` screenshot, which may be downscaled. Use this for absolute `area` positioning (e.g. pinning a menu above a bottom taskbar) instead of guessing the size from a capture image. It follows the size the engine draws at, and `renderer.setViewportSize` changes that size at runtime — which is how a layout written against this is checked at a second shape without rebooting the engine. The pixel size behind these points is `renderer.surfaceSize()` — the whole drawing surface a screen is laid out over, which an editor layout makes larger than the rect `renderer.viewportSize()` draws the scene into — and `ui.pixelRatio()` is the factor between the two spaces.
pixelRatio( ) → number
Physical pixels per logical point — the factor between the logical space `ui.screenSize()` / `getLayoutInfo` rects live in and the physical space `input.mousePosition`, the camera viewport rect and `input.simulateMouse*` coordinates live in. Multiply a layout coordinate by this to aim a simulated pointer at a widget.
safeArea( ) → SafeArea
The part of the screen no system bar, display cutout or on-screen keyboard covers, in the logical points `ui.screenSize()` reports. `top` / `right` / `bottom` / `left` are how far in from each edge the covered band reaches; `x` / `y` / `width` / `height` are the rect they leave; `cutouts` lists each display cutout (a camera hole or a notch) as `{ x, y, width, height }`. Screens lay out inside this rect, so anchors, top-level `%` sizes and a window's default position already keep clear of it; read it to place something by absolute `pos`, or to run a background under the bars while its controls stay inside. `revision` advances every time the area changes (a rotation, the system bars shown or hidden, the keyboard opening, a window resize): compare it with the one you last laid out against. All zero on a desktop window; `ui.simulateSafeArea` lays the UI out as a phone would on any engine.
examples
local safe = ui.safeArea()
ui.setAreaPos("hud", safe.x + 8, safe.y + 8)simulateSafeArea(area: SafeAreaSpec?) → void
Lay the UI out as if the system covered the screen by `area` (logical points) in place of what the platform states: each inset defaults to 0 and `cutouts` is a list of `{ x, y, width, height }`. Previews a phone's status bar, camera cutout and navigation bar on a desktop or headless engine, and makes a layout test independent of the device it runs on. `ui.safeArea()` reports it from the next frame. Call it with no argument (or `nil`) to return to the platform's own safe area. A negative or non-number inset is refused by name.
| arg | type | description |
|---|---|---|
| area | SafeAreaSpec? | `{ top?, right?, bottom?, left?, cutouts? }` in logical points; nil returns to the platform's safe area. |
examples
ui.simulateSafeArea({ top = 40, bottom = 30, cutouts = { { x = 600, y = 0, width = 80, height = 40 } } })ui.simulateSafeArea()
displayScale( ) → DisplayScale
The physical scale the UI is sized at. `density` is the physical pixels one density-independent point covers on a handheld display (a phone, a tablet, a page in a touch browser): a point of the UI covers at least that many pixels whatever the screen's width or orientation, so text and buttons keep their physical size. It is nil on a desktop window, whose UI is sized by its width alone. `textScale` is the user's text size setting, which multiplies every font size the UI typesets (labels, buttons, inputs, editor chrome; canvas text is drawn at the size it names). `touchTarget` is the smallest target, in points, a touch reaches an interactive widget within: 48 on a handheld display, 0 on a desktop. `pixelRatio` is `ui.pixelRatio()` of the same frame. `ui.simulateDisplayScale` previews a phone's on any engine.
examples
local scale = ui.displayScale()
local buttonHeight = math.max(32, scale.touchTarget)
simulateDisplayScale(scale: DisplayScaleSpec?) → void
Size the UI as a display stating `scale` would, in place of what the platform states. A `density` sizes it as a handheld display of that many physical pixels per point (omit it for a desktop display sized by its width), and `textScale` multiplies every font size the UI typesets (1 when omitted). Previews a phone's UI on a desktop or headless engine, and makes a layout test independent of the device it runs on. `ui.displayScale()`, `ui.screenSize()` and `ui.pixelRatio()` report it from the next frame. Call it with no argument (or `nil`) to return to the platform's own scale. A value that is not a number above 0 and at most 8 is refused by name.
| arg | type | description |
|---|---|---|
| scale | DisplayScaleSpec? | `{ density?, textScale? }`; nil returns to the platform's scale. |
examples
ui.simulateDisplayScale({ density = 2.625, textScale = 1.3 })ui.simulateDisplayScale()
orientation( ) → OrientationReading
The orientation the display presents the app in, and the lock it is held to. `current` is `"landscape"` (wider than tall) or `"portrait"`: the shape of the window the app presents in. `lock` is what `ui.lockOrientation` last asked, `"any"` when the display follows the device. `enforced` is whether the platform holds the display to it: Android always does, a browser does once it grants the Screen Orientation lock, and a desktop window never does, presenting in the shape it is given; `reason` names why when it does not. `rotation` is the display's rotation from its natural orientation in degrees (0, 90, 180, 270) where the platform states one, and nil on a desktop window. `revision` advances every time any of these changes; `ui.onOrientationChange` calls back on each change.
examples
if ui.orientation().current == "portrait" then layoutForTallScreen() end
lockOrientation(lock: OrientationLock) → void
Hold the display to the orientations `lock` allows: `"landscape"` (both landscape directions, so turning the device over turns the app with it), `"portrait"` (both portrait directions) or `"any"` (the display follows the device). A world states the orientation it plays in as `world.orientation`, which the engine locks to in play mode and releases in edit mode, so the editor follows the device; call this to change it while the world runs. Android turns the app at once, a browser grants the lock only to a page in fullscreen in most cases (the page asks again each time it enters fullscreen), and a desktop window presents in the shape it is given. `ui.orientation()` reports the lock and whether the platform holds it from the next frame. Any other value is refused by name.
| arg | type | description |
|---|---|---|
| lock | OrientationLock | `"any"`, `"landscape"` or `"portrait"`. |
examples
ui.lockOrientation("portrait")ui.lockOrientation("any")onOrientationChange(callback: (reading: OrientationReading) →
Call `callback` with the new reading every time `ui.orientation()` changes: the display turns between landscape and portrait or to another rotation, the lock changes, or the platform starts or stops holding it. Each listener is watched by a task of the context that registered it, so a listener a component registered ends when that component is removed, and every other listener goes on hearing changes.
| arg | type | description |
|---|---|---|
| callback | (reading: OrientationReading | Receives the reading, as `ui.orientation()` returns it. |
examples
local off = ui.onOrientationChange(function(o) print(o.current, o.rotation) end)
response(widgetId: string) → WidgetResponse
Per-widget interaction snapshot for the most recent frame. Returns `{ clicked, hovered, focused, changed, value }` where `clicked` / `changed` mark transitions and `hovered` / `focused` mark current state.
| arg | type | description |
|---|---|---|
| widgetId | string | The widget id (NOT the onClick / onChange callback id). |
focus(widgetId: string) → void
Programmatically request keyboard focus on a widget. Queued as a one-shot; the next render of the matching widget calls `response.request_focus()`.
| arg | type | description |
|---|---|---|
| widgetId | string | Widget id to focus. |
blur( ) → void
Surrender keyboard focus from whichever widget currently holds it, in the editor's UI and the game's alike.
copyText(text: string) → void
Put `text` on the host clipboard — the operating system's clipboard on a native build, the browser's in a web build. What a "Copy path" or "Copy reference" action calls.
| arg | type | description |
|---|---|---|
| text | string | The text to copy. |
examples
ui.copyText("/zero/source/scenes/main.scene")focusedWidget( ) →
Return the widget id of whichever widget currently holds keyboard focus, or nil, and the space it draws in: `"window"` for the editor's UI around the game view, `"camera"` for the game's own UI in the game view. Snapshotted post-render each frame.
examples
local id, space = ui.focusedWidget()
dragState( ) →
The in-flight drag-and-drop payload while a `dragPayload` widget is being dragged, else nil. `x`/`y` are the pointer's position in the logical space `ui.getLayoutInfo` rects live in, so the reading resolves directly against widget rects. `label` is the text the drag ghost beside the pointer reads (`dragLabel`, else the source's `text`; nil for an outline ghost), and `ghost` whether the engine draws one (`dragGhost`) — a capture never shows the ghost, so this is how a drag's name-at-cursor is checked. Poll during a drag to drive live feedback; the drop itself still lands through the target's `onDrop`. Snapshotted each frame.
getScreenTree(screenName: string) → WidgetTree
Return the last widget tree table passed to `registerScreen` / `updateScreen` for `screenName`.
| arg | type | description |
|---|---|---|
| screenName | string | Screen name to query. |
lastValidation(screenName: string?) → any
Validation diagnostics produced at the most recent `registerScreen` / `updateScreen`, plus what the render stage found while painting — `unknown-font-family` reports a `style.fontFamily` that named no registered font family, once per family per screen, `wrap-label-no-width` reports a label with `props.wrap` whose box came out narrower than its own longest word, once per label per screen, and `svg-not-as-declared` reports an `svg` widget whose document declared something the picture does not hold — a `<text>` whose `font-family` selected no registered family, a `<text>` the picture holds no glyph of, or a shape whose `url(#id)` paint resolved to nothing — once per finding per widget per screen. With no args `{ [screen] = entry }` map; with a name returns that screen's entry or nil. Validation gated by world setting `ui.validation` = `"off" | "warn" | "strict"` (default `"warn"`).
| arg | type | description |
|---|---|---|
| screenName | string? | Optional screen name. |
screen(name: string) →
Get a screen proxy with methods like `setResolution` and `rasterize`.
| arg | type | description |
|---|---|---|
| name | string | Screen name. |
captureWindow(screen: string, window: string, opts: CaptureOpts?) → CaptureResult
Render a single Window widget to its own offscreen texture and write the result as PNG at `/runtime/render_surfaces/<rtHandle>.png`. The screen does NOT need to be visible. Returns `{ rtHandle, texturePath }` or nil on invalid inputs (width/height clamped to `[1, 8192]`, defaults 600x400).
| arg | type | description |
|---|---|---|
| screen | string | Screen id containing the target Window. |
| window | string | Widget id of the Window. |
| opts | CaptureOpts? | `{ width, height }` (optional). |
elementTree(screenName: string) → ElementNode
Introspect a screen's rendered widget hierarchy with each element's layout rect. Every node the renderer draws appears, nested exactly as the widgets nest, under the id it records layout against: the `id` set on the node when the author gave it one, otherwise `<screen>/<type>@<path>`. `bounds` is that element's rect — the same table `ui.getLayoutInfo(id)` returns — and appears once the element has been measured. Kinds registered through `ui.defineWidget` appear expanded into the primitives they build. Feeds the `gui.captureElement` tool: list the tree, pick the ids to frame, capture their region. that name.
| arg | type | description |
|---|---|---|
| screenName | string | Screen id passed to `ui.registerScreen`. |
defineWidget(name: string, builderFn: (WidgetTree, { WidgetTree }) → void
Register a custom widget kind. When a tree contains `{ type = name, props = ..., children = ... }`, the decoder calls `builderFn(props, children)` at register / update time and substitutes the returned widget table in place. Errors surface through `ui.lastValidation()` with codes `widget-builder-error` / `widget-builder-bad-return` / `decode-recursion-depth-exceeded`.
| arg | type | description |
|---|---|---|
| name | string | Custom widget kind name. |
| builderFn | (WidgetTree, { WidgetTree } | Builder closure `(props, children) -> widgetTable`. |
unregisterWidget(name: string) → void
Drop a registered custom widget kind. Subsequent references produce an `unknown-widget-type` diagnostic.
| arg | type | description |
|---|---|---|
| name | string | Custom widget kind name. |
observe(screenName: string?) → PaintObservation
What the last UI frame painted. Returns `{ generation, viewport, pointer, pointerOverUi, pointerWidget, widgets }` with one `widgets` row per widget any registered screen holds — its layout box, the clip chain it painted under, the part of that box which reached the frame (`visible`), the order it painted in (`paintIndex`), and its `reason` from the closed set `ui.invisibilityReasons()` lists. Every rect, the reading's `viewport` (the window) and `pointer` are in logical points on the window; a row's `viewport` is the region the pass that drew it paints into (the whole window, or the rect a Scene panel presents the camera's picture in), and `offViewport` is judged against it. `generation` advances once per re-rendered frame, so two calls reporting the same number describe the same frame. `screenName` no registered screen answers to.
| arg | type | description |
|---|---|---|
| screenName | string? | Narrow the rows to one screen. Omit for every screen. |
examples
"hud"
diagnose(widgetId: string) → WidgetPaint
Why one widget did or did not reach the last frame. Returns that widget's row from `ui.observe()` — the same fields, resolved against the same reading. An id no registered screen carries reads `noSuchWidget`, which is how a misspelling separates from a widget whose screen is hidden and from one the frame laid out no box for.
| arg | type | description |
|---|---|---|
| widgetId | string | The id the widget records layout under. |
examples
"hud-healthbar"
hitTest(x: number, y: number) → PaintHitTest
Which widget a pointer at `(x, y)` reaches, and the stack beneath it. Coordinates are logical points on the window, the space `getLayoutInfo` and `gui.bounds` rects are in wherever a widget's screen draws (a screen in the Scene view's picture included). A widget that takes no pointer, a canvas declaring `pointerEvents = "none"`, is passed over: the answer is the widget beneath it. over no UI — or nil before the UI has published a frame.
| arg | type | description |
|---|---|---|
| x | number | Logical X. |
| y | number | Logical Y. |
examples
640, 360
pointerWidget( ) → PointerRead
Whether the UI is consuming the pointer, and which widget holds it — the pointer counterpart of `ui.focusedWidget()`. `x`, `y` are in logical points on the window, the space `getLayoutInfo` rects are in. published a frame.
paintOrder(a: string, b: string) → number
Which of two widgets paints later: `-1` when `a` paints before `b`, `1` when after, `0` when level. This is what separates two widgets whose rects are identical.
| arg | type | description |
|---|---|---|
| a | string | First widget id. |
| b | string | Second widget id. |
examples
"panel-a", "panel-b"
invisibilityReasons( ) →
Every verdict `ui.diagnose` can report, as a closed list.
widgetState(widgetId: string, key: string, default: any?) → any
Read per-widget cross-frame state. Returns the value previously written via `widgetStateSet`, or `default` (or nil). State is keyed by widget id and persists across re-renders within a screen's lifetime; cleared automatically when the owning screen is unregistered.
| arg | type | description |
|---|---|---|
| widgetId | string | Widget id whose state to read. |
| key | string | State key. |
| default | any? | Value to return when nothing has been written. |
widgetStateSet(widgetId: string, key: string, value: any) → void
Write per-widget cross-frame state. Replaces any existing value under `(widgetId, key)`. Tables are stored by reference.
| arg | type | description |
|---|---|---|
| widgetId | string | Widget id to scope the state under. |
| key | string | State key. |
| value | any | Value to store (must be non-nil). |
widgetStateClear(widgetId: string, key: string) → void
Remove a per-widget state entry.
| arg | type | description |
|---|---|---|
| widgetId | string | Widget id whose state to clear. |
| key | string | State key. |
WidgetTree = { [string]: any } | { any }ScreenSummary = {FontFamilyInfo = {StyleProps = { [string]: any }WidgetPropDescriptor = {WidgetResponse = {LayoutInfo = {SafeAreaRect = { x: number, y: number, width: number, height: number }SafeArea = {SafeAreaSpec = {DisplayScale = {DisplayScaleSpec = {Orientation = "landscape" | "portrait"OrientationLock = "any" | "landscape" | "portrait"OrientationReading = {ValidationIssue = {ValidationEntry = {ThemeDefinition = {CaptureOpts = { width: number?, height: number? }CaptureResult = { rtHandle: string, texturePath: string }ElementNode = {AreaPos = { x: number, y: number }AreaSize = { w: number, h: number }UiRect = Observe.RectWidgetPaint = Observe.WidgetPaintPaintObservation = Observe.ObservationPaintHitTest = Observe.HitTestPointerRead = Observe.PointerReadSub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
Showing up to 3 levels deep. Open a part to see what it holds further down.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
agent_score is exposed.+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
Scoped to this part · feeds back into the world's score.