Log inGet started

ui

Updated October 20, 2026

UI system — screens, styles, themes, widget responses, focus, custom-widget builders, per-widget state.

require("@builtin::modules.api.engine.ui") -- ui (also available as global 'ui')

The ui namespace: 216 functions.

globals/ui/blur

ui.blur()

Surrender keyboard focus from whichever widget currently holds it, in the editor's UI and the game's alike.

globals/ui/bringAreaToFront

ui.bringAreaToFront(id: string)

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.

Parameters

  • id string — Area widget id.

globals/ui/captureWindow

ui.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).

Parameters

  • screen string — Screen id containing the target Window.
  • window string — Widget id of the Window.
  • opts CaptureOpts (optional) — { width, height } (optional).

Returns CaptureResult? — { rtHandle, texturePath } or nil.

globals/ui/click

ui.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.

Parameters

  • callbackId string — Callback id the widget emits (its onClick / onChange / on* prop).
  • value any (optional) — Optional value the callback receives as data.value: a number, 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).

Returns boolean — true when a widget on a registered screen emits the id; false when none does, and the click then still reaches every ui.registerCallbackEnv env and every component and scene entrypoint that receives callbacks no screen claims.

globals/ui/copyText

ui.copyText(text: string)

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.

Parameters

  • text string — The text to copy.
ui.copyText("/zero/source/scenes/main.scene")

globals/ui/defineStyle

ui.defineStyle(name: string, style: StyleProps)

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.

Parameters

  • name string — Style name.
  • style StyleProps — Style properties table.

globals/ui/defineStyles

ui.defineStyles(styles: { [string]: StyleProps })

Define multiple named styles at once.

Parameters

  • styles { [string]: StyleProps } — Map of style name to style properties.

globals/ui/defineWidget

ui.defineWidget(name: string, builderFn: (WidgetTree, { WidgetTree }) -> WidgetTree)

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.

Parameters

  • name string — Custom widget kind name.
  • builderFn (WidgetTree, { WidgetTree }) -> WidgetTree — Builder closure (props, children) -> widgetTable.

globals/ui/diagnose

ui.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.

Parameters

  • widgetId string — The id the widget records layout under.

Returns WidgetPaint? — The widget's row, or nil before the UI has published a frame.

local row = ui.diagnose("hud-healthbar")

globals/ui/displayScale

ui.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.

Returns DisplayScale — { density?, textScale, touchTarget, pixelRatio }.

local scale = ui.displayScale()
local buttonHeight = math.max(32, scale.touchTarget)

globals/ui/dragState

ui.dragState() -> { payload: string, x: number, y: number, label: string?, ghost: boolean }?

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.

Returns { payload: string, x: number, y: number, label: string?, ghost: boolean }? — { payload, x, y, label?, ghost } during a drag, nil otherwise.

globals/ui/elementTree

ui.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.

Parameters

  • screenName string — Screen id passed to ui.registerScreen.

Returns ElementNode? — An ElementNode tree, or nil when no screen is registered under that name.

globals/ui/focus

ui.focus(widgetId: string)

Programmatically request keyboard focus on a widget. Queued as a one-shot; the next render of the matching widget calls response.request_focus().

Parameters

  • widgetId string — Widget id to focus.

globals/ui/focusedWidget

ui.focusedWidget() -> (string?, string?)

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.

Returns (string?, string?) — Focused widget id or nil, and its space.

local id, space = ui.focusedWidget()

globals/ui/getAreaPos

ui.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.

Parameters

  • id string — Area widget id.

Returns AreaPos? — { x, y } or nil.

globals/ui/getAreaSize

ui.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.

Parameters

  • id string — Area widget id.

Returns AreaSize? — { w, h } or nil.

globals/ui/getDockLayout

ui.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.

Parameters

  • id string — DockArea widget id.

Returns string? — Serialized DockState JSON string, or nil.

globals/ui/getLayoutInfo

ui.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.

Parameters

  • widgetId string (optional) — Optional widget id to query.

Returns LayoutInfo? — Layout info table or nil.

globals/ui/getPersistentDockLayout

ui.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.

Parameters

  • id string — DockArea widget id.

Returns string? — Serialized DockState JSON string without transient tabs, or nil.

globals/ui/getScreenTree

ui.getScreenTree(screenName: string) -> WidgetTree?

Return the last widget tree table passed to registerScreen / updateScreen for screenName.

Parameters

  • screenName string — Screen name to query.

Returns WidgetTree? — Widget tree or nil.

globals/ui/getTheme

ui.getTheme() -> string

Get the name of the currently active theme.

Returns string — Active theme name.

globals/ui/getToken

ui.getToken(name: string) -> string?

Look up a single design token value from the active theme.

Parameters

  • name string — Token name (without $ prefix).

Returns string? — Token value or nil.

globals/ui/getTokens

ui.getTokens() -> { [string]: string }

Get all design tokens from the active theme as a key-value map.

Returns { [string]: string } — Token map.

globals/ui/getWidgetProps

ui.getWidgetProps(typeName: string) -> { WidgetPropDescriptor }?

Get the property definitions for a widget type.

Parameters

  • typeName string — Widget type name.

Returns { WidgetPropDescriptor }? — Array of property descriptors, or nil if type not found.

globals/ui/getWidgetTypes

ui.getWidgetTypes() -> { string }

Get all available widget type names that can be used in widget trees.

Returns { string } — Array of widget type names.

globals/ui/hideScreen

ui.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.

Parameters

  • name string — Screen identifier to hide.

Returns boolean — True when a screen by this name is registered.

globals/ui/hitTest

ui.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.

Parameters

  • x number — Logical X.
  • y number — Logical Y.

Returns PaintHitTest? — { widget, screen, stack, x, y } — widget nil when the point is over no UI — or nil before the UI has published a frame.

local hit = ui.hitTest(640, 360)

globals/ui/idCollisions

ui.idCollisions() -> { [string]: { string } }

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.

Returns { [string]: { string } } — Colliding id -> the screens that recorded it.

for id, screens in ui.idCollisions() do print(id, table.concat(screens, ", ")) end

globals/ui/invisibilityReasons

ui.invisibilityReasons() -> { string }

Every verdict ui.diagnose can report, as a closed list.

Returns { string } — The reason names.

globals/ui/lastRegistration

ui.lastRegistration() -> { name: string, layer: number? }?

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.

Returns { name: string, layer: number? }? — { name, layer } for the last registration, or nil if none yet.

globals/ui/lastValidation

ui.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 returns a { [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").

Parameters

  • screenName string (optional) — Optional screen name.

Returns any — Validation entry, full map, or nil.

globals/ui/listFonts

ui.listFonts() -> { FontFamilyInfo }

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.

Returns { FontFamilyInfo } — Array of { family, aliases, faces, more, system }, by family.

for _, f in ui.listFonts() do print(f.family) end

globals/ui/listScreens

ui.listScreens() -> { ScreenSummary }

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.

Returns { ScreenSummary } — Array of screen summaries.

globals/ui/listThemes

ui.listThemes() -> { string }

List all registered theme names.

Returns { string } — Array of theme names.

globals/ui/lockOrientation

ui.lockOrientation(lock: OrientationLock)

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.

Parameters

  • lock OrientationLock — "any", "landscape" or "portrait".
ui.lockOrientation("portrait")
ui.lockOrientation("any")

globals/ui/missingGlyphs

ui.missingGlyphs() -> { string }

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.

Returns { string } — Sorted array of single-character strings; empty when every character drawn had a glyph.

if #ui.missingGlyphs() > 0 then print(table.concat(ui.missingGlyphs(), " ")) end

globals/ui/observe

ui.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.

Parameters

  • screenName string (optional) — Narrow the rows to one screen. Omit for every screen.

Returns PaintObservation? — The reading; nil before the UI has published a frame, and nil for a screenName no registered screen answers to.

local reading = ui.observe("hud")

globals/ui/onOrientationChange

ui.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.

Parameters

  • callback (reading: OrientationReading) -> () — Receives the reading, as ui.orientation() returns it.

Returns () -> () — A function that removes this listener.

local off = ui.onOrientationChange(function(o) print(o.current, o.rotation) end)

globals/ui/orientation

ui.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.

Returns OrientationReading — { current, lock, enforced, reason?, rotation?, revision }.

if ui.orientation().current == "portrait" then print("lay the HUD out for a tall screen") end

globals/ui/paintOrder

ui.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.

Parameters

  • a string — First widget id.
  • b string — Second widget id.

Returns number? — -1, 0, 1, or nil when the reading holds no row for one of them.

local order = ui.paintOrder("panel-a", "panel-b")

globals/ui/pixelRatio

ui.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.

Returns number — Physical pixels per logical point (1.0 when unscaled).

globals/ui/pointerWidget

ui.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.

Returns PointerRead? — { x, y, overUi, widget, screen }, or nil before the UI has published a frame.

globals/ui/registerBackgroundShader

ui.registerBackgroundShader(shaderHandle: any?, width: number?, height: number?)

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.

Parameters

  • shaderHandle any (optional) — The screen .shader's asset handle, from asset.resolve.
  • width number (optional) — Render target width (default 1280).
  • height number (optional) — Render target height (default 720).

globals/ui/registerCallbackEnv

ui.registerCallbackEnv(key: string, env: { [string]: any })

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.

Parameters

  • key string — Stable identifier for this registration (re-register replaces).
  • env { [string]: any } — Environment table whose onCallback receives the broadcasts.

globals/ui/registerScreen

ui.registerScreen(name: string, widgetTree: WidgetTree, layer: number?)

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.

Parameters

  • name string — Unique screen identifier.
  • widgetTree WidgetTree — Root widget table.
  • layer number (optional) — Z-order layer (optional).
ui.registerScreen("hud", tree)

globals/ui/registerTheme

ui.registerTheme(name: string, theme: ThemeDefinition)

Register a theme from a flat Luau table. Most callers should use Z.theme.register(name, table) which runs the cascade for them.

Parameters

  • name string — Theme name to register.
  • theme ThemeDefinition — Flat-resolved theme table.

globals/ui/removeScreen

ui.removeScreen(name: string) -> boolean

Alias for ui.unregisterScreen.

Parameters

  • name string — Screen identifier to remove.

Returns boolean — True when a screen by this name was registered.

globals/ui/resetAreaSize

ui.resetAreaSize(id: string)

Clear a resizable area's remembered size (from a grip drag or ui.setAreaSize) so its declared — or content — size takes over again.

Parameters

  • id string — Area widget id.

globals/ui/response

ui.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.

Parameters

  • widgetId string — The widget id (NOT the onClick / onChange callback id).

Returns WidgetResponse? — WidgetResponse or nil.

globals/ui/safeArea

ui.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.

Returns SafeArea — { top, right, bottom, left, x, y, width, height, cutouts, revision } in logical UI points.

local safe = ui.safeArea()
ui.setAreaPos("hud", safe.x + 8, safe.y + 8)

globals/ui/screen

ui.screen(name: string) -> { [string]: any }?

Get a screen proxy with methods like setResolution and rasterize.

Parameters

  • name string — Screen name.

Returns { [string]: any }? — Screen proxy table, or nil.

globals/ui/screenSize

ui.screenSize() -> { width: number, height: number }

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 for the task that asks, which is how a layout written against this is checked at a second shape without rebooting the engine, read in the call that set the size before the surface goes back. 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.

Returns { width: number, height: number } — { width, height } in logical UI points.

globals/ui/scroll

ui.scroll(deltaX: number, deltaY: number)

Simulate a mouse-wheel scroll event on the UI.

Parameters

  • deltaX number — Horizontal scroll delta.
  • deltaY number — Vertical scroll delta.

globals/ui/setAreaPos

ui.setAreaPos(id: string, x: number, y: number)

Programmatically move a movable area widget to (x, y). Applied for one frame; subsequent frames let drag tracking take over.

Parameters

  • id string — Area widget id.
  • x number — Target pivot x (screen coords).
  • y number — Target pivot y (screen coords).

globals/ui/setAreaSize

ui.setAreaSize(id: string, w: number, h: number)

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.

Parameters

  • id string — Area widget id.
  • w number — Target width (screen coords).
  • h number — Target height (screen coords).

globals/ui/setDockWindowRect

ui.setDockWindowRect(dockId: string, panelId: string, x: number, y: number, width: number, height: number)

Place the floating window of a dockArea panel at (x, y) with size (width, height). Applies once the panel occupies a window — a request made before then waits for it.

Parameters

  • dockId string — DockArea widget id.
  • panelId string — Id of the panel held by the window to place.
  • x number — Window left edge (screen coords).
  • y number — Window top edge (screen coords).
  • width number — Window width (screen coords).
  • height number — Window height (screen coords).

globals/ui/setScreenRenderLayer

ui.setScreenRenderLayer(name: string, mask: number)

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.

Parameters

  • name string — Screen identifier.
  • mask number — Render-layer membership bitmask — the OR of bit32.lshift(1, __renderLayers.bit(name)) over the layers the screen is on.
ui.setScreenRenderLayer("hud", bit32.lshift(1, __renderLayers.bit("ui")))

globals/ui/setScreenSpace

ui.setScreenSpace(name: string, space: string)

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.

Parameters

  • name string — Screen identifier.
  • space string — "camera" | "window".
ui.setScreenSpace("agent-banner", "window")

globals/ui/setScrollPosition

ui.setScrollPosition(widgetId: string, offsetY: number)

Set the scroll offset of a scrollArea widget.

Parameters

  • widgetId string — Scroll area widget id.
  • offsetY number — Vertical scroll offset in pixels.

globals/ui/setShaderUniforms

ui.setShaderUniforms(name: string, uniforms: { [string]: number })

Set uniform values on a registered background shader.

Parameters

  • name string — Shader name identifier.
  • uniforms { [string]: number } — Map of uniform name to number value.

globals/ui/setTheme

ui.setTheme(name: string)

Switch the active global theme by name.

Parameters

  • name string — Theme name to activate.

globals/ui/showScreen

ui.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.

Parameters

  • name string — Screen identifier to show.

Returns boolean — True when a screen by this name is registered.

globals/ui/simulateDisplayScale

ui.simulateDisplayScale(scale: DisplayScaleSpec?)

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.

Parameters

  • scale DisplayScaleSpec (optional) — { density?, textScale? }; nil returns to the platform's scale.
ui.simulateDisplayScale({ density = 2.625, textScale = 1.3 })
ui.simulateDisplayScale()

globals/ui/simulateSafeArea

ui.simulateSafeArea(area: SafeAreaSpec?)

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.

Parameters

  • area SafeAreaSpec (optional) — { top?, right?, bottom?, left?, cutouts? } in logical points; nil returns to the platform's safe area.
ui.simulateSafeArea({ top = 40, bottom = 30, cutouts = { { x = 600, y = 0, width = 80, height = 40 } } })
ui.simulateSafeArea()

globals/ui/unregisterCallbackEnv

ui.unregisterCallbackEnv(key: string)

Remove an environment registered with ui.registerCallbackEnv. Its onCallback stops receiving broadcasts. No-op if key isn't registered.

Parameters

  • key string — The key passed to ui.registerCallbackEnv.

globals/ui/unregisterScreen

ui.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.

Parameters

  • name string — Screen identifier to unregister.

Returns boolean — True when a screen by this name was registered.

globals/ui/unregisterWidget

ui.unregisterWidget(name: string)

Drop a registered custom widget kind. Subsequent references produce an unknown-widget-type diagnostic.

Parameters

  • name string — Custom widget kind name.

globals/ui/updateScreen

ui.updateScreen(name: string, widgetTree: WidgetTree)

Replace the widget tree of an already-registered screen.

Parameters

  • name string — Screen identifier to update.
  • widgetTree WidgetTree — New root widget table.

globals/ui/useStyles

ui.useStyles(themeName: string)

Apply a registered style file's classes additively without changing the active theme.

Parameters

  • themeName string — Name of the registered style / theme asset.

globals/ui/widgetState

ui.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.

Parameters

  • widgetId string — Widget id whose state to read.
  • key string — State key.
  • default any (optional) — Value to return when nothing has been written.

Returns any — Stored value, default, or nil.

globals/ui/widgetStateClear

ui.widgetStateClear(widgetId: string, key: string)

Remove a per-widget state entry.

Parameters

  • widgetId string — Widget id whose state to clear.
  • key string — State key.

globals/ui/widgetStateSet

ui.widgetStateSet(widgetId: string, key: string, value: any?)

Write per-widget cross-frame state. Replaces any existing value under (widgetId, key). Tables are stored by reference.

Parameters

  • widgetId string — Widget id to scope the state under.
  • key string — State key.
  • value any (optional) — Value to store (must be non-nil).

modules/ui/blur

blur()

Surrender keyboard focus from whichever widget currently holds it, in the editor's UI and the game's alike.

modules/ui/bringAreaToFront

bringAreaToFront(id: string)

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.

Parameters

  • id string — Area widget id.

modules/ui/captureWindow

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).

Parameters

  • screen string — Screen id containing the target Window.
  • window string — Widget id of the Window.
  • opts CaptureOpts? (optional) — { width, height } (optional).

modules/ui/click

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.

Parameters

  • callbackId string — Callback id the widget emits (its onClick / onChange / on* prop).
  • value any? (optional) — Optional value the callback receives as data.value: a number, 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).

modules/ui/copyText

copyText(text: string)

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.

Parameters

  • text string — The text to copy.
ui.copyText("/zero/source/scenes/main.scene")

modules/ui/defineStyle

defineStyle(name: string, style: StyleProps)

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.

Parameters

  • name string — Style name.
  • style StyleProps — Style properties table.

modules/ui/defineStyles

defineStyles(styles: { [string]: StyleProps })

Define multiple named styles at once.

Parameters

  • styles { [string]: StyleProps } — Map of style name to style properties.

modules/ui/defineWidget

defineWidget(name: string, builderFn: (WidgetTree, { WidgetTree }) -> WidgetTree)

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.

Parameters

  • name string — Custom widget kind name.
  • builderFn (WidgetTree, { WidgetTree }) -> WidgetTree — Builder closure (props, children) -> widgetTable.

modules/ui/diagnose

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.

Parameters

  • widgetId string — The id the widget records layout under.
local row = ui.diagnose("hud-healthbar")

modules/ui/displayScale

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.

local scale = ui.displayScale()
local buttonHeight = math.max(32, scale.touchTarget)

modules/ui/dragState

dragState(): { payload: string, x: number, y: number, label: string?, ghost: boolean }?

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.

modules/ui/elementTree

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.

Parameters

  • screenName string — Screen id passed to ui.registerScreen.

modules/ui/focus

focus(widgetId: string)

Programmatically request keyboard focus on a widget. Queued as a one-shot; the next render of the matching widget calls response.request_focus().

Parameters

  • widgetId string — Widget id to focus.

modules/ui/focusedWidget

focusedWidget(): (string?, string?)

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.

local id, space = ui.focusedWidget()

modules/ui/getAreaPos

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.

Parameters

  • id string — Area widget id.

modules/ui/getAreaSize

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.

Parameters

  • id string — Area widget id.

modules/ui/getDockLayout

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.

Parameters

  • id string — DockArea widget id.

modules/ui/getLayoutInfo

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.

Parameters

  • widgetId string? (optional) — Optional widget id to query.

modules/ui/getPersistentDockLayout

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.

Parameters

  • id string — DockArea widget id.

modules/ui/getScreenTree

getScreenTree(screenName: string): WidgetTree?

Return the last widget tree table passed to registerScreen / updateScreen for screenName.

Parameters

  • screenName string — Screen name to query.

modules/ui/getTheme

getTheme(): string

Get the name of the currently active theme.

modules/ui/getToken

getToken(name: string): string?

Look up a single design token value from the active theme.

Parameters

  • name string — Token name (without $ prefix).

modules/ui/getTokens

getTokens(): { [string]: string }

Get all design tokens from the active theme as a key-value map.

modules/ui/getWidgetProps

getWidgetProps(typeName: string): { WidgetPropDescriptor }?

Get the property definitions for a widget type.

Parameters

  • typeName string — Widget type name.

modules/ui/getWidgetTypes

getWidgetTypes(): { string }

Get all available widget type names that can be used in widget trees.

modules/ui/hideScreen

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.

Parameters

  • name string — Screen identifier to hide.

modules/ui/hitTest

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.

Parameters

  • x number — Logical X.
  • y number — Logical Y.
local hit = ui.hitTest(640, 360)

modules/ui/idCollisions

idCollisions(): { [string]: { string } }

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.

for id, screens in ui.idCollisions() do print(id, table.concat(screens, ", ")) end

modules/ui/invisibilityReasons

invisibilityReasons(): { string }

Every verdict ui.diagnose can report, as a closed list.

modules/ui/lastRegistration

lastRegistration(): { name: string, layer: number? }?

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.

modules/ui/lastValidation

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 returns a { [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").

Parameters

  • screenName string? (optional) — Optional screen name.

modules/ui/listFonts

listFonts(): { FontFamilyInfo }

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.

for _, f in ui.listFonts() do print(f.family) end

modules/ui/listScreens

listScreens(): { ScreenSummary }

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.

modules/ui/listThemes

listThemes(): { string }

List all registered theme names.

modules/ui/lockOrientation

lockOrientation(lock: OrientationLock)

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.

Parameters

  • lock OrientationLock — "any", "landscape" or "portrait".
ui.lockOrientation("portrait")
ui.lockOrientation("any")

modules/ui/missingGlyphs

missingGlyphs(): { string }

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.

if #ui.missingGlyphs() > 0 then print(table.concat(ui.missingGlyphs(), " ")) end

modules/ui/observe

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.

Parameters

  • screenName string? (optional) — Narrow the rows to one screen. Omit for every screen.
local reading = ui.observe("hud")

modules/ui/onOrientationChange

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.

Parameters

  • callback (reading: OrientationReading) -> () — Receives the reading, as ui.orientation() returns it.

Returns () — A function that removes this listener.

local off = ui.onOrientationChange(function(o) print(o.current, o.rotation) end)

modules/ui/orientation

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.

if ui.orientation().current == "portrait" then print("lay the HUD out for a tall screen") end

modules/ui/paintOrder

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.

Parameters

  • a string — First widget id.
  • b string — Second widget id.
local order = ui.paintOrder("panel-a", "panel-b")

modules/ui/pixelRatio

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.

modules/ui/pointerWidget

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.

modules/ui/registerBackgroundShader

registerBackgroundShader(shaderHandle: any, width: number?, height: number?)

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.

Parameters

  • shaderHandle any (optional) — The screen .shader's asset handle, from asset.resolve.
  • width number? (optional) — Render target width (default 1280).
  • height number? (optional) — Render target height (default 720).

modules/ui/registerCallbackEnv

registerCallbackEnv(key: string, env: { [string]: any })

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.

Parameters

  • key string — Stable identifier for this registration (re-register replaces).
  • env { [string]: any } — Environment table whose onCallback receives the broadcasts.

modules/ui/registerScreen

registerScreen(name: string, widgetTree: WidgetTree, layer: number?)

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.

Parameters

  • name string — Unique screen identifier.
  • widgetTree WidgetTree — Root widget table.
  • layer number? (optional) — Z-order layer (optional).
ui.registerScreen("hud", tree)

modules/ui/registerTheme

registerTheme(name: string, theme: ThemeDefinition)

Register a theme from a flat Luau table. Most callers should use Z.theme.register(name, table) which runs the cascade for them.

Parameters

  • name string — Theme name to register.
  • theme ThemeDefinition — Flat-resolved theme table.

modules/ui/removeScreen

removeScreen(name: string): boolean

Alias for ui.unregisterScreen.

Parameters

  • name string — Screen identifier to remove.

modules/ui/resetAreaSize

resetAreaSize(id: string)

Clear a resizable area's remembered size (from a grip drag or ui.setAreaSize) so its declared — or content — size takes over again.

Parameters

  • id string — Area widget id.

modules/ui/response

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.

Parameters

  • widgetId string — The widget id (NOT the onClick / onChange callback id).

modules/ui/safeArea

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.

local safe = ui.safeArea()
ui.setAreaPos("hud", safe.x + 8, safe.y + 8)

modules/ui/screen

screen(name: string): { [string]: any }?

Get a screen proxy with methods like setResolution and rasterize.

Parameters

  • name string — Screen name.

modules/ui/screenSize

screenSize(): { width: number, height: number }

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 for the task that asks, which is how a layout written against this is checked at a second shape without rebooting the engine, read in the call that set the size before the surface goes back. 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.

modules/ui/scroll

scroll(deltaX: number, deltaY: number)

Simulate a mouse-wheel scroll event on the UI.

Parameters

  • deltaX number — Horizontal scroll delta.
  • deltaY number — Vertical scroll delta.

modules/ui/setAreaPos

setAreaPos(id: string, x: number, y: number)

Programmatically move a movable area widget to (x, y). Applied for one frame; subsequent frames let drag tracking take over.

Parameters

  • id string — Area widget id.
  • x number — Target pivot x (screen coords).
  • y number — Target pivot y (screen coords).

modules/ui/setAreaSize

setAreaSize(id: string, w: number, h: number)

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.

Parameters

  • id string — Area widget id.
  • w number — Target width (screen coords).
  • h number — Target height (screen coords).

modules/ui/setDockWindowRect

setDockWindowRect(

Place the floating window of a dockArea panel at (x, y) with size (width, height). Applies once the panel occupies a window — a request made before then waits for it.

modules/ui/setScreenRenderLayer

setScreenRenderLayer(name: string, mask: number)

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.

Parameters

  • name string — Screen identifier.
  • mask number — Render-layer membership bitmask — the OR of bit32.lshift(1, __renderLayers.bit(name)) over the layers the screen is on.
ui.setScreenRenderLayer("hud", bit32.lshift(1, __renderLayers.bit("ui")))

modules/ui/setScreenSpace

setScreenSpace(name: string, space: string)

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.

Parameters

  • name string — Screen identifier.
  • space string — "camera" | "window".
ui.setScreenSpace("agent-banner", "window")

modules/ui/setScrollPosition

setScrollPosition(widgetId: string, offsetY: number)

Set the scroll offset of a scrollArea widget.

Parameters

  • widgetId string — Scroll area widget id.
  • offsetY number — Vertical scroll offset in pixels.

modules/ui/setShaderUniforms

setShaderUniforms(name: string, uniforms: { [string]: number })

Set uniform values on a registered background shader.

Parameters

  • name string — Shader name identifier.
  • uniforms { [string]: number } — Map of uniform name to number value.

modules/ui/setTheme

setTheme(name: string)

Switch the active global theme by name.

Parameters

  • name string — Theme name to activate.

modules/ui/showScreen

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.

Parameters

  • name string — Screen identifier to show.

modules/ui/simulateDisplayScale

simulateDisplayScale(scale: DisplayScaleSpec?)

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.

Parameters

  • scale DisplayScaleSpec? (optional) — { density?, textScale? }; nil returns to the platform's scale.
ui.simulateDisplayScale({ density = 2.625, textScale = 1.3 })
ui.simulateDisplayScale()

modules/ui/simulateSafeArea

simulateSafeArea(area: SafeAreaSpec?)

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.

Parameters

  • area SafeAreaSpec? (optional) — { top?, right?, bottom?, left?, cutouts? } in logical points; nil returns to the platform's safe area.
ui.simulateSafeArea({ top = 40, bottom = 30, cutouts = { { x = 600, y = 0, width = 80, height = 40 } } })
ui.simulateSafeArea()

modules/ui/unregisterCallbackEnv

unregisterCallbackEnv(key: string)

Remove an environment registered with ui.registerCallbackEnv. Its onCallback stops receiving broadcasts. No-op if key isn't registered.

Parameters

  • key string — The key passed to ui.registerCallbackEnv.

modules/ui/unregisterScreen

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.

Parameters

  • name string — Screen identifier to unregister.

modules/ui/unregisterWidget

unregisterWidget(name: string)

Drop a registered custom widget kind. Subsequent references produce an unknown-widget-type diagnostic.

Parameters

  • name string — Custom widget kind name.

modules/ui/updateScreen

updateScreen(name: string, widgetTree: WidgetTree)

Replace the widget tree of an already-registered screen.

Parameters

  • name string — Screen identifier to update.
  • widgetTree WidgetTree — New root widget table.

modules/ui/useStyles

useStyles(themeName: string)

Apply a registered style file's classes additively without changing the active theme.

Parameters

  • themeName string — Name of the registered style / theme asset.

modules/ui/widgetState

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.

Parameters

  • widgetId string — Widget id whose state to read.
  • key string — State key.
  • default any? (optional) — Value to return when nothing has been written.

modules/ui/widgetStateClear

widgetStateClear(widgetId: string, key: string)

Remove a per-widget state entry.

Parameters

  • widgetId string — Widget id whose state to clear.
  • key string — State key.

modules/ui/widgetStateSet

widgetStateSet(widgetId: string, key: string, value: any)

Write per-widget cross-frame state. Replaces any existing value under (widgetId, key). Tables are stored by reference.

Parameters

  • widgetId string — Widget id to scope the state under.
  • key string — State key.
  • value any (optional) — Value to store (must be non-nil).

typed/builtin//modules/api/engine/ui/ui/blur

ui.blur()

Surrender keyboard focus from whichever widget currently holds it, in the editor's UI and the game's alike.

typed/builtin//modules/api/engine/ui/ui/bringAreaToFront

ui.bringAreaToFront(id: string)

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.

Parameters

  • id string — Area widget id.

typed/builtin//modules/api/engine/ui/ui/captureWindow

ui.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).

Parameters

  • screen string — Screen id containing the target Window.
  • window string — Widget id of the Window.
  • opts CaptureOpts (optional) — { width, height } (optional).

Returns CaptureResult? — { rtHandle, texturePath } or nil.

typed/builtin//modules/api/engine/ui/ui/click

ui.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.

Parameters

  • callbackId string — Callback id the widget emits (its onClick / onChange / on* prop).
  • value any (optional) — Optional value the callback receives as data.value: a number, 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).

Returns boolean — true when a widget on a registered screen emits the id; false when none does, and the click then still reaches every ui.registerCallbackEnv env and every component and scene entrypoint that receives callbacks no screen claims.

typed/builtin//modules/api/engine/ui/ui/copyText

ui.copyText(text: string)

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.

Parameters

  • text string — The text to copy.
ui.copyText("/zero/source/scenes/main.scene")

typed/builtin//modules/api/engine/ui/ui/defineStyle

ui.defineStyle(name: string, style: StyleProps)

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.

Parameters

  • name string — Style name.
  • style StyleProps — Style properties table.

typed/builtin//modules/api/engine/ui/ui/defineStyles

ui.defineStyles(styles: { [string]: StyleProps })

Define multiple named styles at once.

Parameters

  • styles { [string]: StyleProps } — Map of style name to style properties.

typed/builtin//modules/api/engine/ui/ui/defineWidget

ui.defineWidget(name: string, builderFn: (WidgetTree, { WidgetTree }) -> WidgetTree)

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.

Parameters

  • name string — Custom widget kind name.
  • builderFn (WidgetTree, { WidgetTree }) -> WidgetTree — Builder closure (props, children) -> widgetTable.

typed/builtin//modules/api/engine/ui/ui/diagnose

ui.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.

Parameters

  • widgetId string — The id the widget records layout under.

Returns WidgetPaint? — The widget's row, or nil before the UI has published a frame.

local row = ui.diagnose("hud-healthbar")

typed/builtin//modules/api/engine/ui/ui/displayScale

ui.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.

Returns DisplayScale — { density?, textScale, touchTarget, pixelRatio }.

local scale = ui.displayScale()
local buttonHeight = math.max(32, scale.touchTarget)

typed/builtin//modules/api/engine/ui/ui/dragState

ui.dragState() -> { payload: string, x: number, y: number, label: string?, ghost: boolean }?

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.

Returns { payload: string, x: number, y: number, label: string?, ghost: boolean }? — { payload, x, y, label?, ghost } during a drag, nil otherwise.

typed/builtin//modules/api/engine/ui/ui/elementTree

ui.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.

Parameters

  • screenName string — Screen id passed to ui.registerScreen.

Returns ElementNode? — An ElementNode tree, or nil when no screen is registered under that name.

typed/builtin//modules/api/engine/ui/ui/focus

ui.focus(widgetId: string)

Programmatically request keyboard focus on a widget. Queued as a one-shot; the next render of the matching widget calls response.request_focus().

Parameters

  • widgetId string — Widget id to focus.

typed/builtin//modules/api/engine/ui/ui/focusedWidget

ui.focusedWidget() -> (string?, string?)

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.

Returns (string?, string?) — Focused widget id or nil, and its space.

local id, space = ui.focusedWidget()

typed/builtin//modules/api/engine/ui/ui/getAreaPos

ui.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.

Parameters

  • id string — Area widget id.

Returns AreaPos? — { x, y } or nil.

typed/builtin//modules/api/engine/ui/ui/getAreaSize

ui.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.

Parameters

  • id string — Area widget id.

Returns AreaSize? — { w, h } or nil.

typed/builtin//modules/api/engine/ui/ui/getDockLayout

ui.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.

Parameters

  • id string — DockArea widget id.

Returns string? — Serialized DockState JSON string, or nil.

typed/builtin//modules/api/engine/ui/ui/getLayoutInfo

ui.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.

Parameters

  • widgetId string (optional) — Optional widget id to query.

Returns LayoutInfo? — Layout info table or nil.

typed/builtin//modules/api/engine/ui/ui/getPersistentDockLayout

ui.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.

Parameters

  • id string — DockArea widget id.

Returns string? — Serialized DockState JSON string without transient tabs, or nil.

typed/builtin//modules/api/engine/ui/ui/getScreenTree

ui.getScreenTree(screenName: string) -> WidgetTree?

Return the last widget tree table passed to registerScreen / updateScreen for screenName.

Parameters

  • screenName string — Screen name to query.

Returns WidgetTree? — Widget tree or nil.

typed/builtin//modules/api/engine/ui/ui/getTheme

ui.getTheme() -> string

Get the name of the currently active theme.

Returns string — Active theme name.

typed/builtin//modules/api/engine/ui/ui/getToken

ui.getToken(name: string) -> string?

Look up a single design token value from the active theme.

Parameters

  • name string — Token name (without $ prefix).

Returns string? — Token value or nil.

typed/builtin//modules/api/engine/ui/ui/getTokens

ui.getTokens() -> { [string]: string }

Get all design tokens from the active theme as a key-value map.

Returns { [string]: string } — Token map.

typed/builtin//modules/api/engine/ui/ui/getWidgetProps

ui.getWidgetProps(typeName: string) -> { WidgetPropDescriptor }?

Get the property definitions for a widget type.

Parameters

  • typeName string — Widget type name.

Returns { WidgetPropDescriptor }? — Array of property descriptors, or nil if type not found.

typed/builtin//modules/api/engine/ui/ui/getWidgetTypes

ui.getWidgetTypes() -> { string }

Get all available widget type names that can be used in widget trees.

Returns { string } — Array of widget type names.

typed/builtin//modules/api/engine/ui/ui/hideScreen

ui.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.

Parameters

  • name string — Screen identifier to hide.

Returns boolean — True when a screen by this name is registered.

typed/builtin//modules/api/engine/ui/ui/hitTest

ui.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.

Parameters

  • x number — Logical X.
  • y number — Logical Y.

Returns PaintHitTest? — { widget, screen, stack, x, y } — widget nil when the point is over no UI — or nil before the UI has published a frame.

local hit = ui.hitTest(640, 360)

typed/builtin//modules/api/engine/ui/ui/idCollisions

ui.idCollisions() -> { [string]: { string } }

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.

Returns { [string]: { string } } — Colliding id -> the screens that recorded it.

for id, screens in ui.idCollisions() do print(id, table.concat(screens, ", ")) end

typed/builtin//modules/api/engine/ui/ui/invisibilityReasons

ui.invisibilityReasons() -> { string }

Every verdict ui.diagnose can report, as a closed list.

Returns { string } — The reason names.

typed/builtin//modules/api/engine/ui/ui/lastRegistration

ui.lastRegistration() -> { name: string, layer: number? }?

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.

Returns { name: string, layer: number? }? — { name, layer } for the last registration, or nil if none yet.

typed/builtin//modules/api/engine/ui/ui/lastValidation

ui.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 returns a { [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").

Parameters

  • screenName string (optional) — Optional screen name.

Returns any — Validation entry, full map, or nil.

typed/builtin//modules/api/engine/ui/ui/listFonts

ui.listFonts() -> { FontFamilyInfo }

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.

Returns { FontFamilyInfo } — Array of { family, aliases, faces, more, system }, by family.

for _, f in ui.listFonts() do print(f.family) end

typed/builtin//modules/api/engine/ui/ui/listScreens

ui.listScreens() -> { ScreenSummary }

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.

Returns { ScreenSummary } — Array of screen summaries.

typed/builtin//modules/api/engine/ui/ui/listThemes

ui.listThemes() -> { string }

List all registered theme names.

Returns { string } — Array of theme names.

typed/builtin//modules/api/engine/ui/ui/lockOrientation

ui.lockOrientation(lock: OrientationLock)

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.

Parameters

  • lock OrientationLock — "any", "landscape" or "portrait".
ui.lockOrientation("portrait")
ui.lockOrientation("any")

typed/builtin//modules/api/engine/ui/ui/missingGlyphs

ui.missingGlyphs() -> { string }

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.

Returns { string } — Sorted array of single-character strings; empty when every character drawn had a glyph.

if #ui.missingGlyphs() > 0 then print(table.concat(ui.missingGlyphs(), " ")) end

typed/builtin//modules/api/engine/ui/ui/observe

ui.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.

Parameters

  • screenName string (optional) — Narrow the rows to one screen. Omit for every screen.

Returns PaintObservation? — The reading; nil before the UI has published a frame, and nil for a screenName no registered screen answers to.

local reading = ui.observe("hud")

typed/builtin//modules/api/engine/ui/ui/onOrientationChange

ui.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.

Parameters

  • callback (reading: OrientationReading) -> () — Receives the reading, as ui.orientation() returns it.

Returns () -> () — A function that removes this listener.

local off = ui.onOrientationChange(function(o) print(o.current, o.rotation) end)

typed/builtin//modules/api/engine/ui/ui/orientation

ui.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.

Returns OrientationReading — { current, lock, enforced, reason?, rotation?, revision }.

if ui.orientation().current == "portrait" then print("lay the HUD out for a tall screen") end

typed/builtin//modules/api/engine/ui/ui/paintOrder

ui.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.

Parameters

  • a string — First widget id.
  • b string — Second widget id.

Returns number? — -1, 0, 1, or nil when the reading holds no row for one of them.

local order = ui.paintOrder("panel-a", "panel-b")

typed/builtin//modules/api/engine/ui/ui/pixelRatio

ui.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.

Returns number — Physical pixels per logical point (1.0 when unscaled).

typed/builtin//modules/api/engine/ui/ui/pointerWidget

ui.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.

Returns PointerRead? — { x, y, overUi, widget, screen }, or nil before the UI has published a frame.

typed/builtin//modules/api/engine/ui/ui/registerBackgroundShader

ui.registerBackgroundShader(shaderHandle: any?, width: number?, height: number?)

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.

Parameters

  • shaderHandle any (optional) — The screen .shader's asset handle, from asset.resolve.
  • width number (optional) — Render target width (default 1280).
  • height number (optional) — Render target height (default 720).

typed/builtin//modules/api/engine/ui/ui/registerCallbackEnv

ui.registerCallbackEnv(key: string, env: { [string]: any })

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.

Parameters

  • key string — Stable identifier for this registration (re-register replaces).
  • env { [string]: any } — Environment table whose onCallback receives the broadcasts.

typed/builtin//modules/api/engine/ui/ui/registerScreen

ui.registerScreen(name: string, widgetTree: WidgetTree, layer: number?)

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.

Parameters

  • name string — Unique screen identifier.
  • widgetTree WidgetTree — Root widget table.
  • layer number (optional) — Z-order layer (optional).
ui.registerScreen("hud", tree)

typed/builtin//modules/api/engine/ui/ui/registerTheme

ui.registerTheme(name: string, theme: ThemeDefinition)

Register a theme from a flat Luau table. Most callers should use Z.theme.register(name, table) which runs the cascade for them.

Parameters

  • name string — Theme name to register.
  • theme ThemeDefinition — Flat-resolved theme table.

typed/builtin//modules/api/engine/ui/ui/removeScreen

ui.removeScreen(name: string) -> boolean

Alias for ui.unregisterScreen.

Parameters

  • name string — Screen identifier to remove.

Returns boolean — True when a screen by this name was registered.

typed/builtin//modules/api/engine/ui/ui/resetAreaSize

ui.resetAreaSize(id: string)

Clear a resizable area's remembered size (from a grip drag or ui.setAreaSize) so its declared — or content — size takes over again.

Parameters

  • id string — Area widget id.

typed/builtin//modules/api/engine/ui/ui/response

ui.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.

Parameters

  • widgetId string — The widget id (NOT the onClick / onChange callback id).

Returns WidgetResponse? — WidgetResponse or nil.

typed/builtin//modules/api/engine/ui/ui/safeArea

ui.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.

Returns SafeArea — { top, right, bottom, left, x, y, width, height, cutouts, revision } in logical UI points.

local safe = ui.safeArea()
ui.setAreaPos("hud", safe.x + 8, safe.y + 8)

typed/builtin//modules/api/engine/ui/ui/screen

ui.screen(name: string) -> { [string]: any }?

Get a screen proxy with methods like setResolution and rasterize.

Parameters

  • name string — Screen name.

Returns { [string]: any }? — Screen proxy table, or nil.

typed/builtin//modules/api/engine/ui/ui/screenSize

ui.screenSize() -> { width: number, height: number }

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 for the task that asks, which is how a layout written against this is checked at a second shape without rebooting the engine, read in the call that set the size before the surface goes back. 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.

Returns { width: number, height: number } — { width, height } in logical UI points.

typed/builtin//modules/api/engine/ui/ui/scroll

ui.scroll(deltaX: number, deltaY: number)

Simulate a mouse-wheel scroll event on the UI.

Parameters

  • deltaX number — Horizontal scroll delta.
  • deltaY number — Vertical scroll delta.

typed/builtin//modules/api/engine/ui/ui/setAreaPos

ui.setAreaPos(id: string, x: number, y: number)

Programmatically move a movable area widget to (x, y). Applied for one frame; subsequent frames let drag tracking take over.

Parameters

  • id string — Area widget id.
  • x number — Target pivot x (screen coords).
  • y number — Target pivot y (screen coords).

typed/builtin//modules/api/engine/ui/ui/setAreaSize

ui.setAreaSize(id: string, w: number, h: number)

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.

Parameters

  • id string — Area widget id.
  • w number — Target width (screen coords).
  • h number — Target height (screen coords).

typed/builtin//modules/api/engine/ui/ui/setDockWindowRect

ui.setDockWindowRect(dockId: string, panelId: string, x: number, y: number, width: number, height: number)

Place the floating window of a dockArea panel at (x, y) with size (width, height). Applies once the panel occupies a window — a request made before then waits for it.

Parameters

  • dockId string — DockArea widget id.
  • panelId string — Id of the panel held by the window to place.
  • x number — Window left edge (screen coords).
  • y number — Window top edge (screen coords).
  • width number — Window width (screen coords).
  • height number — Window height (screen coords).

typed/builtin//modules/api/engine/ui/ui/setScreenRenderLayer

ui.setScreenRenderLayer(name: string, mask: number)

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.

Parameters

  • name string — Screen identifier.
  • mask number — Render-layer membership bitmask — the OR of bit32.lshift(1, __renderLayers.bit(name)) over the layers the screen is on.
ui.setScreenRenderLayer("hud", bit32.lshift(1, __renderLayers.bit("ui")))

typed/builtin//modules/api/engine/ui/ui/setScreenSpace

ui.setScreenSpace(name: string, space: string)

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.

Parameters

  • name string — Screen identifier.
  • space string — "camera" | "window".
ui.setScreenSpace("agent-banner", "window")

typed/builtin//modules/api/engine/ui/ui/setScrollPosition

ui.setScrollPosition(widgetId: string, offsetY: number)

Set the scroll offset of a scrollArea widget.

Parameters

  • widgetId string — Scroll area widget id.
  • offsetY number — Vertical scroll offset in pixels.

typed/builtin//modules/api/engine/ui/ui/setShaderUniforms

ui.setShaderUniforms(name: string, uniforms: { [string]: number })

Set uniform values on a registered background shader.

Parameters

  • name string — Shader name identifier.
  • uniforms { [string]: number } — Map of uniform name to number value.

typed/builtin//modules/api/engine/ui/ui/setTheme

ui.setTheme(name: string)

Switch the active global theme by name.

Parameters

  • name string — Theme name to activate.

typed/builtin//modules/api/engine/ui/ui/showScreen

ui.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.

Parameters

  • name string — Screen identifier to show.

Returns boolean — True when a screen by this name is registered.

typed/builtin//modules/api/engine/ui/ui/simulateDisplayScale

ui.simulateDisplayScale(scale: DisplayScaleSpec?)

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.

Parameters

  • scale DisplayScaleSpec (optional) — { density?, textScale? }; nil returns to the platform's scale.
ui.simulateDisplayScale({ density = 2.625, textScale = 1.3 })
ui.simulateDisplayScale()

typed/builtin//modules/api/engine/ui/ui/simulateSafeArea

ui.simulateSafeArea(area: SafeAreaSpec?)

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.

Parameters

  • area SafeAreaSpec (optional) — { top?, right?, bottom?, left?, cutouts? } in logical points; nil returns to the platform's safe area.
ui.simulateSafeArea({ top = 40, bottom = 30, cutouts = { { x = 600, y = 0, width = 80, height = 40 } } })
ui.simulateSafeArea()

typed/builtin//modules/api/engine/ui/ui/unregisterCallbackEnv

ui.unregisterCallbackEnv(key: string)

Remove an environment registered with ui.registerCallbackEnv. Its onCallback stops receiving broadcasts. No-op if key isn't registered.

Parameters

  • key string — The key passed to ui.registerCallbackEnv.

typed/builtin//modules/api/engine/ui/ui/unregisterScreen

ui.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.

Parameters

  • name string — Screen identifier to unregister.

Returns boolean — True when a screen by this name was registered.

typed/builtin//modules/api/engine/ui/ui/unregisterWidget

ui.unregisterWidget(name: string)

Drop a registered custom widget kind. Subsequent references produce an unknown-widget-type diagnostic.

Parameters

  • name string — Custom widget kind name.

typed/builtin//modules/api/engine/ui/ui/updateScreen

ui.updateScreen(name: string, widgetTree: WidgetTree)

Replace the widget tree of an already-registered screen.

Parameters

  • name string — Screen identifier to update.
  • widgetTree WidgetTree — New root widget table.

typed/builtin//modules/api/engine/ui/ui/useStyles

ui.useStyles(themeName: string)

Apply a registered style file's classes additively without changing the active theme.

Parameters

  • themeName string — Name of the registered style / theme asset.

typed/builtin//modules/api/engine/ui/ui/widgetState

ui.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.

Parameters

  • widgetId string — Widget id whose state to read.
  • key string — State key.
  • default any (optional) — Value to return when nothing has been written.

Returns any — Stored value, default, or nil.

typed/builtin//modules/api/engine/ui/ui/widgetStateClear

ui.widgetStateClear(widgetId: string, key: string)

Remove a per-widget state entry.

Parameters

  • widgetId string — Widget id whose state to clear.
  • key string — State key.

typed/builtin//modules/api/engine/ui/ui/widgetStateSet

ui.widgetStateSet(widgetId: string, key: string, value: any?)

Write per-widget cross-frame state. Replaces any existing value under (widgetId, key). Tables are stored by reference.

Parameters

  • widgetId string — Widget id to scope the state under.
  • key string — State key.
  • value any (optional) — Value to store (must be non-nil).
  • api
  • reference