Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·part ofmodule shell.module·originates fromworld 07158574-5…

tabApp

Layer 5 shell — visibility-driven tabbed app. Wraps `Z.app` with a per-tab refresh scheduler that gates ALL data fetches on `(window_visible AND tab_active AND refresh_due)`, so the panel costs zero engine work when hidden / closed / minimized and only the active tab's `build()`…

byzero-proxy @ DESKTOP-DB3UJOJ·posted 2mo ago
What it does

tabApp

Layer 5 shell — visibility-driven tabbed app. Wraps Z.app with a per-tab refresh scheduler that gates ALL data fetches on (window_visible AND tab_active AND refresh_due), so the panel costs zero engine work when hidden / closed / minimized and only the active tab's build() is invoked when open. Lifted from system_tools.module (the original consumer) so future live-data tools compose against this surface instead of re-implementing the scheduler.

Exports

  • M.create(opts: TabAppOpts) -> TabAppHandle — build and mount a tab app. Required: name, tabs, tabOrder.
  • M(opts) — the module table is callable; equivalent to M.create(opts).

Types:

  • TabSpec = { label?, refresh?, build?, onMount?, onCallback?, tick? }
  • ChromeOpts = { title?, layout?, minimizable?, closable?, minWidth?, minHeight?, maxWidth? }
  • ExtraScreenSpec = { build?, refresh?, layer?, tags?, onCallback? }
  • AppOpts = { layer?, tags?, ctx? }
  • TabAppOpts = { name, tabs, tabOrder, tabAliases?, statusClock?, onStatusTick?, chrome?, extraScreens?, appOpts?, initialState?, callbackPrefix?, windowId?, closedWidth?, closedHeight?, minimizedWidth?, tabGap?, tabPadding? }
  • TabAppHandle = { app, state, update, onCallback, destroy, setVisible, screenName }

Usage

local TabApp = require("@builtin::modules.zui.shell.tabApp")

local handle = TabApp {
    name         = "system_tools",
    tabs         = TABS,
    tabOrder     = { "entities", "logs" },
    statusClock  = 0.1,
    onStatusTick = function(state) ... end,
}

-- Per-frame
handle.update(dt)

Notes

  • The scheduler keeps a single shared schedClock plus per-timer lastFire timestamps. Stepping lastFire by the refresh interval (rather than to now) keeps timers phase-locked to their mount time forever — timers with matching intervals fire on the same frame.
  • extraScreens may use either a function(state) shorthand or a { build, refresh, layer, tags, onCallback } table. The handle's onCallback dispatches into App handlers first, then per-tab onCallback, then extraScreen onCallback.
  • The default chrome ships three shells (closed / minimized / open). Pass chrome.layout(state, opts) -> widget to override entirely.
  • setVisible flips windowVisible AND calls ui.showScreen / ui.hideScreen for the bound screen name.
  • destroy unmounts the App and resets the shared DataSource cache.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

ZuiShellTabApp Module Layer 5 shell — visibility-driven tabbed app. Wraps `Z.app` with a per-tab refresh scheduler that gates ALL data fetches on (window_visible AND tab_active AND refresh_due), so the panel costs zero engine work when hidden / closed / minimized and only the active tab's `build()` is invoked when open. Lifted from `system_tools.module/init.luau` (the original consumer). Future live-data tools (LSP UI, asset editors, agent panels) compose against this surface instead of re-implementing the scheduler. Tab contract (each entry in `tabs`): { label, refresh, build, onMount?, onCallback?, tick? } Default chrome ships a window frame with closed / minimized / open shells, a tab strip, a body scroll area, and a status bar fed by `onStatusTick`. Pass `chrome.layout(state, opts) -> widget` to override. Public API: local handle = Z.shell.tabApp({ name = "system_tools", tabs = TABS, tabOrder = { "entities", "logs", ... }, tabAliases = { luau_vm = "runtime" }, -- optional statusClock = 0.1, -- 0 disables onStatusTick = function(state) ... end, -- optional chrome = { title = "...", layout? = fn, minimizable, closable }, extraScreens = { details = { build, refresh, layer, tags } }, appOpts = { layer = ..., tags = {"editor"} }, initialState = { include_agent = false, ... }, }) handle.app, handle.state, handle.update(dt), handle.onCallback, handle.destroy(), handle.setVisible(bool), handle.screenName See `system_tools.module/init.luau` for the canonical worked example. Consumers: local TabApp = require("modules.deprecated.zui.shell.tabApp") local handle = TabApp.create { name = "...", tabs = ..., tabOrder = ... } -- or equivalently: local handle = TabApp { name = "...", tabs = ..., tabOrder = ... }

ids(opts: ?) → void

Internal: callback ids fired by the default chrome (override `chrome.layout` to replace). Pattern-extracted by the click handler: `<name>-tab-<key>`.

argtypedescription
opts?

titleBarButtons(state: ?, ID: ?, opts: ?) → void

argtypedescription
state?
ID?
opts?

tabStrip(state: ?, opts: ?) → void

argtypedescription
state?
opts?

statusBar(state: ?, opts: ?) → void

argtypedescription
state?
opts?

buildClosedShell(state: ?, opts: ?) → void

argtypedescription
state?
opts?

buildMinimizedShell(state: ?, opts: ?) → void

argtypedescription
state?
opts?

buildOpenShell(state: ?, opts: ?) → void

argtypedescription
state?
opts?

defaultChromeLayout(state: ?, opts: ?) → void

argtypedescription
state?
opts?

reportError(name: ?, ctx: ?, err: ?) → void

argtypedescription
name?
ctx?
err?

safeCall(name: ?, ctx: ?, fn: ?, ...: ?) → void

argtypedescription
name?
ctx?
fn?
...?

escapeForPattern(s: ?) → void

argtypedescription
s?

create(opts: TabAppOpts) → TabAppHandle

Create a visibility-driven tabbed-app handle. Wraps `Z.app` with a per-tab refresh scheduler — the panel costs zero engine work when hidden, closed, or minimized; only the active tab's `build()` is invoked when open. are required; everything else has sensible defaults. `destroy`, `setVisible`, and `screenName` fields. name = "system_tools", tabs = TABS, tabOrder = { "entities", "logs" }, statusClock = 0.1, onStatusTick = onTick }

argtypedescription
optsTabAppOptsThe tab-app configuration. `name`, `tabs`, and `tabOrder`

examples

local handle = TabApp.create {

ensureTabMounted(key: ?, state: ?) → void

argtypedescription
key?
state?

build( ) → void

build( ) → void

closeAll( ) → void

update(dt: ?) → void

argtypedescription
dt?

onCallback(callbackId: ?, data: ?) → void

argtypedescription
callbackId?
data?

destroy( ) → void

setVisible(v: ?) → void

argtypedescription
v?
⌬ Types
TabSpec = {ChromeOpts = {ExtraScreenSpec = {AppOpts = {TabAppOpts = {TabAppHandle = {

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

475items
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# dataSource TTL + gated cached data fetcher. Used by tools that pull live data from the engine at a slower cadence than the UI rebuilds. A closed gate returns the last cached value WITHOUT calling the fetch function; within TTL, the cached value is returned directly. Errors from the fetcher are swallowed by default (last value preserved) and surfaced through the optional `onError` hook. The module table itself is callable as a shorthand for `.create`. ## Exports - `M.create(fetchFn: () -> any, opts: Opts?) -> Source` — build a new source. Also reachable as `M(fetchFn, opts)`. - `M.invalidate(key: string)` — force a re-fetch on the next read for a registered key. - `M.reset()` — wipe the registry; release retained values. - `M._seedForTest(key: string, value: any) -> Source?` — test hook: install a value without invoking the fetcher. - `M._peekForTest(key: string) -> Source?` — test hook: look up a source by key. Per-instance methods (on `Source`): - `source:read() -> value` — read with TTL + gate semantics. - `source()` — callable shorthand, equivalent to `source:read()`. - `source:invalidate()` — clear cached value and timestamp. - `source.version: number` — monotonic counter bumped on every successful fetch. Types: - `Opts = { ttl: number?, gate: (() -> boolean)?, key: string?, onError: ((any) -> ())? }` - `Source` — instance carrying `fetchFn`, `ttl`, `gate`, `onError`, `key`, `version`, and the internal `_value` / `_t` cache slots. ## Usage ```luau local DataSource = require("@builtin::modules.zui.dataSource") local entities = DataSource(function() return wld.list() end, { ttl = 0.2, key = "entities:list" }) local list = entities() -- callable shorthand local same = entities:read() -- explicit form DataSource.invalidate("entities:list") -- force re-fetch ``` ## Notes - TTL `0` means "always re-fetch"; the cache slot still holds the last value so a closed gate or a fetch error returns it. - A closed gate (`gate()` returns false) skips the fetch entirely and returns the cached value as-is — useful for visibility gating. - Fetcher errors are swallowed (cached value preserved) and routed through `onError(err)` when set. - Auto-allocated keys are namespaced under `"zui:dataSource:<n>"`. Explicit keys are preferred so `invalidate` and the test hooks have a stable handle.
▲ 0↑ born
▣
module · born here
❒asset
# widget Widget namespace — re-exports every widget under `zui.widget.<name>`. Library users can either pull the whole namespace (`local W = require("modules.zui.widget")`, then `W.button(...)`) or grab a single widget directly (`local btn = require("modules.zui.widget.button")`). Each widget lives in its own `.module/` folder so authors adding a new widget edit exactly one file. The shared primitive every widget builds on is `zui.widget.node` — pulled into a user-defined widget the same way it's pulled in here. ## Exports This module is a namespace — every field is an assignment from the matching sibling module. No typed functions live here directly; the typed surface lives on each sibling. - `M.node` — core widget-table constructor (`modules.zui.widget.node`). - Content widgets: `label`, `button`, `icon`, `iconBtn`, `iconButton`, `chip`, `badge`, `card`, `colorSwatch`, `dialogueBox`, `kbd`, `input`, `codeEditor`, `slider`, `dragValue`, `checkbox`, `toggle`, `dropdown`, `datePicker`, `radioGroup`, `selectableList`, `image`, `viewport`, `progressBar`, `richText`. - DAW widgets: `fader`, `knob`, `meter`. - Game-HUD widgets: `healthBar`, `hotbar`, `minimap`. - Layout containers: `hbox`, `vbox`, `grid`, `split`, `sides`, `panel`, `section`, `scroll`, `collapsible`, `window`, `modal`, `popup`, `contextMenu`, `scene`, `area`, `anchor`, `topPanel`, `bottomPanel`, `leftPanel`, `rightPanel`, `centralPanel`. - Leafs: `spacer`, `flex`, `sep`. - Plot / Graph: `plot`, `graph`. - Drag-and-drop list: `dndList`. - Canvas: `canvas`. - Composite widgets: `tabs`, `tree`, `filterRow`, `statRow`, `statusLabel`. - Sub-namespaces: `lsp`, `fs`. ## Usage ```luau local W = require("@builtin::modules.zui.widget") return W.vbox{ W.label("Name:"), W.input(""), W.hbox{ W.button("Save", { onClick = "save" }), W.button("Cancel") }, } -- Or pull single widgets directly: local btn = require("@builtin::modules.zui.widget.button") return btn("Save", { onClick = "save" }) ``` ## Notes - The exposed `M.node` is the canonical primitive for user-defined widgets — `require("modules.zui.widget.node")` returns the same value. - Sub-namespaces (`lsp`, `fs`) collect tightly-coupled widget sets that only make sense together — isolating them keeps the top-level surface clean. - Plot and Graph are pure-Luau builders over canvas; no `egui_plot` dependency. - DndList composes per-row canvases + a transparent overlay canvas for drag-source detection and hover insertion indicators. - All re-exports are static — adding a new widget means adding a new `M.<name> = require(...)` line here (and creating the sibling module).
▲ 0↑ born
▣
module · born here
❒asset
# theme Theme tokens for zui (Layer 2). Reads through `ui.getToken(name)` first so a loaded engine theme propagates automatically; falls back to in-module defaults that cover every named color used by the demos. Also owns the Luau-side `$variable` cascade — `register` / `load` resolve references end-to-end (cycle-detected) and push flat values to `ui.registerTheme`. ## Exports - `M.with(overrides: TokenMap?) -> ThemeView` — read-only view with overlays on top of engine/defaults. - `M.defaults() -> TokenMap` — raw default token map (same reference each call). - `M.tokenNames() -> { string }` — sorted list of shipped token names. - `M.resolve(theme: any) -> (ResolvedTheme?, string?)` — flatten a theme table (`$var` → literal). Returns `(nil, errMsg)` on failure. - `M.resolveTokens(theme) -> (TokenMap?, string?)` — re-export from cascade. - `M.resolveStyles(theme, tokens) -> (StyleMap?, string?)` — re-export from cascade. - `M.register(name: string, theme: any) -> (boolean, string?)` — resolve and push to `ui.registerTheme`. - `M.load(name: string) -> (boolean, string?)` — require `@builtin::themes.<name>` and register it. - `M.activate(name: string) -> boolean` — thin wrapper over `ui.setTheme(name)`. - `M.default: ThemeView` — module-level token view with no overrides. Types: - `TokenMap = { [string]: any }` - `StyleMap = { [string]: { [string]: any } }` - `Theme = { name: string?, tokens: TokenMap?, styles: StyleMap? }` - `ResolvedTheme = { name: string, tokens: TokenMap, styles: StyleMap }` - `ThemeView` — read-only metatable proxy; writes throw. ## Usage ```luau local Theme = require("@builtin::modules.zui.theme") Theme.load("dark") -- require + register the built-in dark theme Theme.activate("dark") -- ui.setTheme("dark") local view = Theme.with({ accent = "#ff0" }) print(view.accent) -- "#ff0" print(view.bg) -- engine token or default ``` ## Notes - `register` overrides `theme.name` with the caller-supplied name so `listThemes()` / `setTheme(name)` find it under the requested key (mirrors the pre-Phase-4 #942 fix). - The cascade is cycle-detected — broken inputs surface as structured `(false, errMsg)` returns instead of silently producing garbage colors. - `M.default` is built at module-load time, after `M` is fully populated, so its function-fallback `__index` resolves correctly. - `ThemeView` writes raise — use `Z.themeWith({...})` to get an overlay view rather than mutating the existing one.
▲ 0↑ born
▣
module · born here
❒asset
# app Layer 3 lifecycle wrapper for `Z.app`. Collapses the register-or-update dance, owns multiple named screens, ticks reactive state, and routes callbacks via an embedded `Router`. Per `docs/specs/ui-v3-architecture.md` Layer 3. The instance is a callable `{ buildFn }` plus a `State` reactive store and a `Router` callback dispatcher; mount → tick → unmount drives `ui.registerScreen` / `ui.updateScreen` / `ui.unregisterScreen` under the hood. ## Exports - `App.create(name: string, builderFn: BuilderFn) -> AppInstance` — construct an App. - `App.current() -> AppInstance?` — the App whose builder is currently rendering (used by widget builders that auto-stash state). Per-instance methods (on `AppInstance`): - `:mount(ctx: any) -> AppInstance` — initial register; idempotent across re-mounts. - `:tick(dt: number?)` — re-render dirty screens from the host's `update(dt)`. - `:unmount()` — unregister every owned screen and drop the token claims. - `:markDirty()` / `:isDirty() -> boolean` — dirty bit control. - `:on(idOrPattern, handler) -> AppInstance` — router subscription (chainable). - `:cb(id, handler) -> id` — router callback registration; returns the id. - `:dispatch(callbackId, data?)` — router dispatch. - `:set(key, value) -> AppInstance` / `:update(updates) -> AppInstance` — state shortcuts. - `:_screensForTest()` / `:_routerForTest()` — test introspection. - Internals (`_nextAnonId`, `_runBuilder`, `_register`, `_renderOne`, `_isRegistered`, `_isFirstRegister`, `_forgetScreen`, `_showAll`) are exposed for use by `Z.app`'s helpers but should not be called externally. Types: - `AppInstance` — opaque App table carrying `name`, `state`, the embedded router, and registered-screen bookkeeping. - `BuilderFn = (state: any, ctx: any) -> { [screenName] = buildFn | dynamic }` - `RenderOpts = { layer: number?, tags: { string }? }` ## Usage ```luau local Z = require("@builtin::modules.zui") local app = Z.app("inventory", function(state, ctx) state:default("count", 0) return { main = function() return Z.btn("count=" .. state:get("count"), "inc") end, } end) app:on("inc", function() app:set("count", app.state:get("count") + 1) end) app:mount(self) -- in update(dt): app:tick(dt) -- on destroy: app:unmount() ``` ## Notes - A per-process token-based stale-screen guard keeps a second App with the same screen name from doubling-up `ui.updateScreen` pushes. The newest mounted App wins; older instances skip their tick silently. - Closure-as-callback widgets (`Z.btn("Save", fn)`) get stable per-screen ids via `_nextAnonId` so re-ticks update the existing router entry instead of accumulating handlers. - `Z.dynamic(list, fn)` values in the builder's return table are exploded into per-item screens named `<base>-<item.id>`; vanished items are unregistered automatically on the next tick. - `{ build, layer, tags }` is the supported sugar for screens that need `ui.registerScreen`'s optional layer or `Z.tags.set` entries. Tags are written immediately after register so the editor toggles and `Z.tags.findByTag` see them on the same frame. - A build that returns `nil` means "this screen has nothing to draw this frame": the App hides the screen and shows it again on the first tick the build produces a tree. That is the whole of the App's claim on visibility — a screen hidden by anyone else (`ui.hideScreen`, `Z.screens`, the editor's F1 toggle via `Z.tags.hideByTag("editor")`) stays hidden while the App keeps pushing tree updates on its refresh clock, and becomes visible again when that caller shows it. - Errors thrown by a builder propagate through `pcall` and re-raise with `error(..., 0)` so the screen name is preserved in the traceback.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# zui > **Deprecated for authoring content.** `zui` predates the engine's CSS-parity > `ui.*` surface and writes unlike CSS, producing flatter results. Author screens > as raw widget trees (`{ type, style, props, children }`) styled with `ui.*`, and > read `@builtin::examples.ui.*` for complete worked screens. `zui` remains in use > internally by the editor; it will be rebuilt on the CSS-parity core. Convenience UI library on top of the engine's `ui.*` Rust bindings. Ships Layer 1 (per-widget builders), Layer 2 (theme tokens), Layer 3 (app lifecycle), router with unhandled-warning, pre-built shells, and debug overlay. The engine's Rust UI surface is the substrate — `zui` is one library on top of it. Any third-party Lua UI library can build on the same primitives; the engine has no concept of "the zui library", only widget types and a stable contract. See [`docs/specs/ui-v3-architecture.md`](../../../../docs/specs/ui-v3-architecture.md). ## Exports Layer 1 (widget builders) — short aliases on `Z.*`: - `Z.lbl`, `Z.btn`, `Z.icon`, `Z.iconBtn`, `Z.iconButton`, `Z.chip`, `Z.badge`, `Z.card`, `Z.colorSwatch`, `Z.dialogueBox`, `Z.kbd`, `Z.input`, `Z.codeEditor`, `Z.slider`, `Z.dragValue`, `Z.checkbox`, `Z.toggle`, `Z.dropdown`, `Z.datePicker`, `Z.radioGroup`, `Z.selectableList`, `Z.image`, `Z.viewport`, `Z.progressBar`, `Z.richText`, `Z.fader`, `Z.knob`, `Z.meter`, `Z.healthBar`, `Z.hotbar`, `Z.minimap`, `Z.hbox`, `Z.vbox`, `Z.grid`, `Z.split`, `Z.sides`, `Z.panel`, `Z.section`, `Z.scroll`, `Z.collapsible`, `Z.window`, `Z.modal`, `Z.popup`, `Z.contextMenu`, `Z.scene`, `Z.area`, `Z.anchor`, `Z.topPanel`, `Z.bottomPanel`, `Z.leftPanel`, `Z.rightPanel`, `Z.centralPanel`, `Z.spacer`, `Z.flex`, `Z.sep`, `Z.plot`, `Z.graph`, `Z.dndList`, `Z.canvas`, `Z.tabs`, `Z.tree`, `Z.filterRow`, `Z.statRow`, `Z.statusLabel`, `Z.node` - `Z.lsp` — LSP composite-widget namespace. - `Z.fs` — Filesystem composite-widget namespace. - `Z.widget` — full widget namespace (for explicit access). Layer 2 (theme tokens): - `Z.theme` — active default token table. - `Z.themeWith(overrides)` — derive a theme with token overrides. - `Z.themeNames()` — list known token names. - `Z.Theme` — the underlying module. Shared utils (re-exported): - `Z.round`, `Z.fmt`, `Z.fmtVec3`, `Z.id`, `Z.eventValue`, `Z.map`, `Z.when`, `Z.compact`, `Z.Utils`. Layers 3-7 (app lifecycle + advanced features): - `Z.app(name, builder)` — App lifecycle wrapper (`Z.App.create`). - `Z.dynamic(list, fn)` — one-screen-per-list-item bucket. - `Z.dataSource(fetchFn, opts)` — TTL + gated cached fetcher. - `Z.highlight` — syntax-highlighting dispatch and per-language modules. - `Z.shell` — pre-built shell shapes (docked, canvas, inspector). - `Z.tags`, `Z.screens` — Luau-owned screen tag registry + lifecycle helpers. - `Z.defineWidget(name, fn)` / `Z.unregisterWidget(name)` — register Luau builders for raw `{ type = name, ... }` widget tables. - `Z.widgetState(id, key, default)` / `Z.widgetState.set(id, key, value)` — per-widget reactive state. - `Z.VERSION`, `Z.SPEC` — version string and spec link. ## Usage ```luau local Z = require("@builtin::modules.zui") local C = Z.theme local function tree() return Z.centralPanel({ Z.topPanel({ Z.hbox({ Z.lbl("◈ MY APP", { color = C.accent, fontSize = 14, bold = true }), Z.flex(), Z.btn("Save", "save:click", { bg = C.accent, color = C.bg, bold = true }), }, { style = { gap = 8, padding = { 6, 12, 6, 12 } } }), }), Z.section("CONTROLS", { Z.slider("vol", 0.5, 0, 1, { onChange = "vol:set" }), Z.btn("Reset", "vol:reset"), }, { bg = C.panel, border = C.border, padding = { 8, 12, 8, 12 } }), }) end ui.registerScreen("my-app", tree(), 0) ui.showScreen("my-app") ``` ## Notes - This module's surface is mostly re-exports: every `Z.*` widget alias is `Widget.*` (see `widget.module/`), every theme alias is `Theme.*`, every util is `Utils.*`. The aliases exist for ergonomics only — explicit access via `Z.widget`, `Z.Theme`, `Z.Utils`, etc. works the same way. - The module registers a handful of `ui.defineWidget` handlers at load time (tabs, card, sides, badge, colorSwatch, separator, progressBar, iconButton, dialogueBox, hotbar) so raw `{ type = "<name>", ... }` widget tables in existing demos and editor tools keep rendering after their Rust-side definitions were deleted. Re-running registration on hot-reload is safe — the engine replaces the previous closure. - `Z.defineWidget` requires the engine `ui` global; off-host execution raises a clear error. - `Z.widgetState` is a callable read path with a `.set` write sub-method — no metatables on the caller side. Returns the `default` when the engine global is missing. - The library is built on a stable Rust substrate (`ui.registerScreen`, `ui.getToken`, `ui.defineWidget`, etc.). Other Lua UI libraries can build on the same substrate and coexist with `zui`; the engine has no concept of a privileged library. - See `docs/specs/ui-v3-architecture.md` for the full layered design, Rust binding gaps, and follow-up roadmap. ## Tall content inside an anchored panel A `Z.anchor("Center", ...) { Z.panel(rows) }` whose `rows` exceed the viewport will silently clip top + bottom — the player has no scrollbar and no way to reach the off-screen entries. Opt into in-place scrolling on the panel: ```luau Z.anchor("Center", {}, { Z.panel(rows, { scroll = true, maxHeight = 480 }), }) ``` `scroll = true` wraps the children in a `scrollArea` so overflow stays reachable. Without `scroll = true`, `maxHeight` only clips. See the [panel README](widget.module/panel.module/README.md) for `scrollMaxHeight`, header/footer pinning, and the full pattern (closes #3270).
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# _axes Shared tick generation + axes + gridline drawing helpers for canvas-hosted charts. Used by `Z.plot` for axes/gridlines/ticks and by `Z.graph` for the optional axis labels + gridlines. Single source of truth for the nice-tick algorithm so both widgets render identical- looking tick scales. Underscore prefix marks it as a package-internal helper — not exported from `zui.widget` and not part of the `Z.*` surface. ## Exports - `M.computeTicks(minV: number, maxV: number, nTicks: number?, customFormat: TickFormatter?) -> TickResult` — D3-style nice ticks. Snaps step to `{1, 2, 5} × 10^n`. `nTicks` default 5. - `M.axisCommands(viewport: Viewport, bounds: Bounds, opts: AxesOpts?) -> { CanvasCommand }` — axes + tick marks + tick labels. - `M.gridCommands(viewport: Viewport, bounds: Bounds, opts: AxesOpts?) -> { CanvasCommand }` — gridlines, one per major tick. Types: - `Viewport = { x: number, y: number, width: number, height: number }` — canvas-local pixel rect. - `Bounds = { xMin: number, xMax: number, yMin: number, yMax: number }` — data-space range. - `TickResult = { ticks: { number }, labels: { string } }` - `TickFormatter = (number) -> string` — custom label formatter. - `AxesOpts` — visibility + styling (`showXAxis`, `axisColor`, `labelSize`, `tickLength`, `xTickFormat`, etc.). - `CanvasCommand = { [string]: any }` — opaque canvas-renderer command. ## Usage ```luau local Axes = require("@builtin::modules.zui.widget._axes") local viewport = { x = 0, y = 0, width = 200, height = 120 } local bounds = { xMin = 0, xMax = 100, yMin = 0, yMax = 1 } local axisCmds = Axes.axisCommands(viewport, bounds, {}) local gridCmds = Axes.gridCommands(viewport, bounds, { gridY = false }) local r = Axes.computeTicks(0, 100) -- nice ticks: {0, 20, 40, ...} -- Custom formatter: Axes.axisCommands(viewport, bounds, { xTickFormat = function(v) return "$" .. v end, }) ``` ## Notes - Coordinate convention matches canvas: origin top-left, `+y` down. Y values from `bounds` are flipped at render time so `yMax` is drawn at the top of the viewport, matching plot conventions. - `mergedOpts` overlays caller opts on `DEFAULTS` and passes unknown keys through — additive opts (`xTickFormat`, `yTickFormat`, future ones) don't need bookkeeping in `DEFAULTS`. - `computeTicks` caps iteration at 64 ticks to avoid pathological loops on degenerate input. - Out-of-range ticks (after the data → pixel mapping) are filtered silently so neither labels nor lines bleed past the viewport.
▲ 0↑ born
▣
module · born here
❒asset
# _markers Marker-shape canvas command generators used by `Z.plot.points`. Mirrors the marker shapes that the deleted `egui_plot::MarkerShape` enum exposed: `circle`, `square`, `diamond`, `cross`, `plus`, `up`, `down`, `left`, `right`, `asterisk`. Unknown shapes fall back to circle with a console warning (parity with the deleted Rust path). ## Exports - `M.commands(shape: MarkerShape, cx: number, cy: number, radius: number, opts: MarkerOpts?) -> { CanvasCommand }` — list of canvas commands rendering the marker. Types: - `MarkerShape = "circle" | "square" | "diamond" | "cross" | "plus" | "up" | "down" | "left" | "right" | "asterisk" | string` — `string` fallthrough warns and falls back to circle. - `MarkerOpts = { fill: string?, stroke: string?, strokeWidth: number? }` - `CanvasCommand = { [string]: any }` — opaque canvas-renderer command. ## Usage ```luau local Markers = require("@builtin::modules.zui.widget._markers") local cmds = Markers.commands("diamond", 50, 50, 4, { fill = "#fff" }) -- cmds is a list of canvas commands ready to splice into a canvas. ``` ## Notes - Each shape returns one or more canvas commands centered at `(cx, cy)` with bounding-circle radius `radius`. - `cross`, `plus`, `asterisk`, `up/down/left/right` are stroke-based and fall back to `opts.fill` (or `"#FFFFFF"`) for the line color when no `stroke` is supplied. - Unknown shapes warn via `log.warn` (when available) and fall back to the circle marker — parity with the deleted Rust enum's default arm. - Pure module — no engine state, no state owned here. Safe to call from any context.
▲ 0↑ born
▣
module · born here
❒asset
# _rowCanvas Shared selectable-row factory used by `Z.radioGroup` and `Z.selectableList`. Each row is a single focusable `canvas` widget that draws its background + optional radio glyph + label in widget-local coords. Because the whole row is one canvas (not a composition of glyph-canvas + label + container with click handlers), Tab cycles row-by-row naturally via egui's focus chain, click commits selection via the canvas's `onClick` prop, and arrow keys reach the parent group's nav handler via the canvas's `onKey` prop. Underscore prefix marks it as a package-internal helper — not exported from `zui.widget` and not part of the `Z.*` surface. ## Exports This module returns the row factory function directly. There is no returned table. - `rowCanvas(opts: RowOpts?) -> Node` — build a selectable-row canvas node. Types: - `RowOpts = { groupId?: string, index?: number, label?: string, selected?: boolean, kind?: "radio" | "option", width?: number, height?: number, bg?: string, fg?: string, selectedBg?: string, selectedFg?: string }` - `Node = { [string]: any }` — opaque canvas widget node. ## Usage ```luau local rowCanvas = require("@builtin::modules.zui.widget._rowCanvas") return Z.vbox{ rowCanvas{ groupId = "themes", index = 1, label = "Dark", selected = true, kind = "radio" }, rowCanvas{ groupId = "themes", index = 2, label = "Light", selected = false, kind = "radio" }, } ``` ## Notes - Click commits selection via `onClick = <groupId>:select:<index>`; the parent group's callback subscribes to that event. - Arrow keys raise `onKey = <groupId>:nav` so the parent group handles keyboard navigation in one place. - The whole row is a single canvas — focus moves row-by-row through egui's focus chain naturally (no manual focus management needed). - DOM mirror sees `<canvas role="radio"|"option" aria-selected="true|false">` via the generic `role` + `aria*` pass-through on canvas. - Defaults: `200 × 22` row, `"#00000000"` bg, `"#dcdcdc"` fg, selected `"#3a4a64"` bg + `"#ffffff"` fg.
▲ 0↑ born
▣
module · born here
❒asset
# _viewport Pan/zoom viewport helper for canvas-hosted charts. Persists `{ xMin, xMax, yMin, yMax }` in `ui.widgetState(plotId, "viewport")` so the bounds survive across renders; the first render seeds from `defaultBounds`. Subsequent renders read the cached state; pan/zoom handlers (`viewport:pan`, `viewport:zoom`) mutate the state in place and write it back to widgetState. Underscore prefix marks it as a package-internal helper — not exported from `zui.widget` and not part of the `Z.*` surface. ## Exports - `Viewport.make(plotId: string?, rect: Rect?, defaultBounds: Bounds?) -> ViewportInstance` — construct a viewport over `rect`. `plotId` enables widgetState persistence. - `vp:dataToScreen(x: number, y: number) -> (number, number)` — data → canvas-local pixels. - `vp:screenToData(sx: number, sy: number) -> (number, number)` — canvas-local pixels → data. - `vp:pan(dxScreen: number, dyScreen: number, axisAllow: AxisFilter?)` — pan and write-back. - `vp:zoom(factor: number, anchorScreen: ScreenPoint?, axisAllow: AxisFilter?)` — zoom around anchor and write-back. - `vp:setBounds(bounds: Bounds)` — replace bounds and write-back. - `Viewport.bounds(vp) -> Bounds` — snapshot bounds as a fresh table. Types: - `Rect = { x: number, y: number, width: number, height: number }` — canvas-local plot body. - `Bounds = { xMin: number, xMax: number, yMin: number, yMax: number }` — data-space. - `ScreenPoint = { x: number, y: number }` - `AxisFilter = { x: boolean?, y: boolean? }` - `ViewportInstance` — instance shape; carries `rect`, bounds fields, `invertX/Y`, and the bound methods. ## Usage ```luau local Viewport = require("@builtin::modules.zui.widget._viewport") local vp = Viewport.make("plot1", { x = 0, y = 0, width = 200, height = 100 }, { xMin = 0, xMax = 100, yMin = 0, yMax = 1 }) -- Inside onDrag handler: vp:pan(dragDx, dragDy) -- Inside onScroll handler: vp:zoom(1 + scrollAmount * 0.1, { x = mx, y = my }) -- Reset: vp:setBounds({ xMin = 0, xMax = 100, yMin = 0, yMax = 1 }) ``` ## Notes - Coordinate convention: origin top-left, `+y` down. yMax in data space maps to `rect.y` (top of viewport) so the cursor sits above the data point during pan. - Persistence is opt-in — passing `plotId = nil` skips widgetState reads / writes; bounds live only on the instance for the duration of the call. - Caller can reset by clearing widgetState explicitly: `ui.widgetStateSet(plotId, "viewport", nil)`. - `Viewport.bounds(vp)` is a free function (no colon) — it's a thin snapshot helper, useful when you want a fresh bounds table without mutating the instance. - `invertX` / `invertY` flip the axis mapping in both `dataToScreen` and `screenToData`; default `false` for both.
▲ 0↑ born
▣
module · born here
❒asset
# anchor Corners a child to a screen edge. Root-only — must be the top-level widget of its registered screen. `anchor` is one of `TopLeft / TopCenter / TopRight / CenterLeft / Center / CenterRight / BottomLeft / BottomCenter / BottomRight`. `margin` is `{ top, right, bottom, left }`. ## Exports This module returns the widget factory directly. There is no returned table. - `anchor(anchor: AnchorPos, margin: Margin, children: { Node }?, opts: AnchorOpts?) -> Node` — build an anchor widget node. Types: - `AnchorPos = "TopLeft" | "TopCenter" | "TopRight" | "CenterLeft" | "Center" | "CenterRight" | "BottomLeft" | "BottomCenter" | "BottomRight"` - `Margin = { number }` — `{ top, right, bottom, left }`. - `Node = { [string]: any }` — opaque widget node. - `AnchorOpts = { id: string?, props: { [string]: any }?, style: { [string]: any }? }` ## Usage ```luau local anchor = require("@builtin::modules.zui.widget.anchor") local closeBtn = anchor("BottomRight", { 0, 16, 16, 0 }, { Z.btn("Close", { onClick = "close" }), }) ui.registerScreen("close", closeBtn) ``` ## Notes - The widget MUST be the top-level node of its registered screen — the engine's `anchor` decoder only resolves at the screen root. - `margin` is injected into `props.margin`; `anchor` into `props.anchor`. `opts.props` is mutated in place — pass a fresh table if you reuse it across calls. - Single-child `children` (a single widget node) is normalised by `node`, so callers don't need to wrap a single child in `{ ... }`.
▲ 0↑ born
▣
module · born here
❒asset
# area Free-positioned area. Root-only — must be the top-level widget of its registered screen. The `pos` field is at the widget root level (not inside `props`) because that's what the engine's `area` decoder expects. Pass `movable = true` to let the user drag the area; the dragged position is held in egui's persistent memory. ## Exports This module returns the widget factory directly. There is no returned table. - `area(children: { Node }?, opts: AreaOpts?) -> Node` — build a free-positioned area node. Types: - `Pos = { number }` — `{ x, y }` in screen pixels. - `Pivot = string` — engine-defined anchor name (`"center"`, `"topleft"`, ...). - `Node = { [string]: any }` — opaque widget node. - `AreaOpts = { id: string?, pos: Pos?, pivot: Pivot?, movable: boolean?, interactable: boolean?, style: { [string]: any }? }` ## Usage ```luau local area = require("@builtin::modules.zui.widget.area") local hud = area({ Z.btn("Drag me"), }, { pos = { 100, 100 }, movable = true, interactable = true, }) ui.registerScreen("draggable", hud) ``` ## Notes - The widget MUST be the top-level node of its registered screen. - `pos`, `pivot`, `movable`, `interactable`, `style` are written at the widget root level (NOT inside `props`) — the engine's `area` decoder reads them from there. - Lua-side read-back of dragged position for movable areas is a planned Rust extension; today the dragged position is held in egui's persistent memory and is not exposed back to Luau. - Each `nil` field is omitted from the resulting node so the engine sees only the fields the caller explicitly set.
▲ 0↑ born
▣
module · born here
❒asset
# args Shared argument resolver for zui container builders (`panel`, `vbox`, `hbox`, `grid`, …). Container builders historically took `(children, opts)` with children first, but the intuitive call is a single options table `Z.panel({ style = …, children = {…} })`. Passed to a `(children, opts)` builder, that table landed in the children slot and its `children` key was silently dropped. `resolve` removes that pit: it accepts every unambiguous shape and routes it correctly, and raises a precise error when a call is genuinely ambiguous instead of silently dropping content. ## Exports - `resolve(builderName: string, a: any, b: any) -> ({ any }, { [string]: any })` — returns `(children, opts)` — an array of child widget tables (possibly empty) and an options table (possibly empty). ## Accepted shapes ```luau resolve("panel", { childA, childB }, opts?) -- canonical array + opts resolve("panel", singleChildNode, opts?) -- a lone widget (auto-wrapped) resolve("panel", { style = …, children = {…} }) -- single options table resolve("panel", { style = … }) -- options only (no children) resolve("panel", nil, opts?) -- no children ``` ## Errors (never silent) - a children array that ALSO carries a `children` key - an options-shaped first argument passed alongside a 2nd options argument - `opts.children` that isn't an array ## Usage ```luau local resolve = require("@builtin::modules.zui.widget.args") local children, opts = resolve("panel", a, b) ```
▲ 0↑ born
▣
module · born here
❒asset
# badge Alias for chip — kept as its own module for vocabulary clarity. Pick whichever name reads better at the call site; both compile to the same widget under the hood. ## Exports This module re-exports `modules.zui.widget.chip` directly. There are no typed functions or types declared here — the public surface is whatever `chip` exposes. - `badge(...) -> Node` — identical to `chip(...)`. ## Usage ```luau local badge = require("@builtin::modules.zui.widget.badge") local newBadge = badge("New", { variant = "info" }) ``` ## Notes - This module is a thin re-export — `badge == chip` (same function reference). Hot-reloading either reloads both. - See `modules.zui.widget.chip` for the full argument / option surface and type signature. - The alias exists purely so call sites can say `badge` when that reads better (e.g. "new feature" badge) and `chip` when that reads better (e.g. a removable tag chip). There is no behavioural difference.
▲ 0↑ born
▣
module · born here
❒asset
# bottomPanel Bottom-docked panel. Root-only — must be the top-level widget of its registered screen. Pairs with `centralPanel` (and optionally `topPanel` / `leftPanel` / `rightPanel`) for a docked app shell — register each as its own screen with appropriate layer ordering. ## Exports This module returns the widget factory directly. There is no returned table. - `bottomPanel(children: { Node }?, opts: PanelOpts?) -> Node` — build a bottom-docked panel node. Types: - `Node = { [string]: any }` — opaque widget node. - `PanelOpts = { id: string?, classes: ({ string } | string)?, class: ({ string } | string)?, props: { [string]: any }?, style: { [string]: any }? }` ## Usage ```luau local bottomPanel = require("@builtin::modules.zui.widget.bottomPanel") local statusBar = bottomPanel({ Z.hbox{ Z.lbl("Status: OK"), Z.flex(), Z.lbl("v0.1.0") }, }, { id = "status" }) ui.registerScreen("statusBar", statusBar) ``` ## Notes - Root-only — the widget MUST be the top-level node of its registered screen. - Children can be a single Node or an array; `node` normalises a single child to `{ child }` so callers don't have to wrap manually. - `classes` / `class` accept either an array of strings or a single space-separated string — `node` normalises both forms. - Pairs with the other panel widgets for a docked app shell; register each on its own screen so layer ordering / visibility can be controlled independently.
▲ 0↑ born
▣
module · born here
❒asset
# button Clickable button widget. Emits a callback id (string) routed by the engine to `component.onCallback`, or wires up a closure when called inside a `Z.app` builder. Accepts top-level style shortcuts (bg, color, fontSize, bold, padding, border, borderWidth, minWidth, minHeight, tooltip, enabled). ## Exports - `button(text: string?, callbackId: any?, opts: ButtonOpts?) -> WidgetNode` — build a button widget node. The 2nd arg may be a string callback id, a closure (auto-wired in `Z.app`), or an options table when no 3rd argument is provided. Types: - `ButtonOpts = { id?, classes?, class?, style?, onClick?, bg?, color?, fontSize?, bold?, padding?, border?, borderWidth?, minWidth?, minHeight?, tooltip?, enabled?, paint? }` ## Usage ```luau local btn = require("@builtin::modules.zui.widget.button") btn("Save", "save:click") -- string callback id btn("Save", function() print("clicked") end) -- closure (inside Z.app) btn("Save", { onClick = "save:click", bg = "#222" }) -- JS-style opts shape ``` ## Notes - Passing a closure outside a `Z.app` builder errors at builder time — closures can't cross the Luau↔Rust FFI boundary, so they only auto-wire inside an app router. - The 2-arg `Z.btn(text, opts)` shape is supported: if slot 2 is a table and slot 3 is missing, the callback is pulled from `opts.onClick`. Guards against the silent-dead-button bug tracked by #3059. - See `docs/guides/zui-cheatsheet.md` for the three callback patterns.
▲ 0↑ born
▣
module · born here
❒asset
# canvas Immediate-mode 2D-paint widget. The body is a sequence of paint commands (`line`, `bezier`, `polyline`, `rect`, `circle`, `text`) drawn in widget-local coordinates with origin at the top-left and +y going down (egui-standard, opposite of plot). Wires through pointer, drag, double-click, key, and scroll handlers, and forwards `role` / `tag` / `aria*` props for accessibility. ## Exports - `canvas(id: string?, opts: CanvasOpts?) -> WidgetNode` — build a canvas widget node from a list of paint commands plus optional interaction handlers. Types: - `CanvasOpts = { commands?, width?, height?, onClick?, onPointerDown?, onPointerUp?, onPointerMove?, onDrag?, onDoubleClick?, onKey?, onScroll?, role?, tag?, style?, class?, classes? }` ## Usage ```luau local canvas = require("@builtin::modules.zui.widget.canvas") canvas("my-canvas", { commands = { { kind = "line", a = {10,10}, b = {200,150}, color = "#7AA8FF", width = 2 }, { kind = "circle", center = {100,100}, radius = 30, fill = "#FF8855" }, { kind = "text", pos = {10,180}, text = "hello", color = "#fff", fontSize = 14 }, }, onClick = "canvas:click", style = { width = 600, height = 400, background = "#161B22" }, }) ``` ## Notes - Pointer events surface widget-local cursor coords as either a `"x,y"` string (legacy) or a structured Luau table (modern handlers). - `onClick` / `onPointerDown` / `onPointerUp` carry `data.button` (0 = left, 1 = right, 2 = middle) identifying which mouse button triggered the event. - `onKey` fires only while the canvas has keyboard focus; `onScroll` fires while it is hovered. - Any `opts.aria*` key passes through verbatim — no wrapper-side allowlist needed. - Command kinds and per-kind fields are defined in `crates/zero_ui_protocol/src/canvas.rs`.
▲ 0↑ born
▣
module · born here
❒asset
# card Card composite — title + description + optional image placeholder + arbitrary children inside a styled panel, built from `panel` + `label` primitives. Pure Luau composition; the engine has no dedicated `card` arm. ## Exports - `card(opts: CardOpts?) -> WidgetNode` — build a card widget node. Types: - `CardOpts = { id?, classes?, class?, title?, titleColor?, description?, descriptionColor?, image?, children?, bg?, border?, borderWidth?, padding?, gap? }` ## Usage ```luau local card = require("@builtin::modules.zui.widget.card") card({ title = "Card title", description = "muted secondary text", image = "asset/path", -- optional 100px tall placeholder bar children = { ... }, -- arbitrary widgets below id = "myCard", padding = 8, }) ``` ## Notes - Click handling is not exposed directly — wrap in a clickable wrapper (e.g. an outer `Z.btn` with empty text) if needed. - The image slot currently renders a `panel_alt`-coloured placeholder rect; once an image-loader pipeline lands, callers can pass a real `Z.image` instead. - Theme defaults (`panel`, `border`, `text`, `text_dim`) come from `modules.zui.theme`'s `default` table.
▲ 0↑ born
▣
module · born here
❒asset
# centralPanel Central panel that fills the area not consumed by the docked edge panels. Root-only — must be the body of a docked-shell layout, with edge panels (e.g. `topPanel`, `bottomPanel`, `leftPanel`, `rightPanel`) docked around it. ## Exports - `centralPanel(children: { any }?, opts: { [string]: any }?) -> WidgetNode` — build a centralPanel widget node wrapping the given children. ## Usage ```luau local centralPanel = require("@builtin::modules.zui.widget.centralPanel") centralPanel({ heading("Body"), -- ... }) ``` ## Notes - Root-only — the renderer expects it at the top level of a screen, alongside any docked edge panels. - Passes `opts` straight through to the underlying `node` constructor; no widget-specific shortcuts.
▲ 0↑ born
▣
module · born here
❒asset
# checkbox Check-mark style boolean input with a trailing label. ## Exports - `checkbox(label: string?, id: string?, checked: any?, opts: CheckboxOpts?) -> WidgetNode` — build a checkbox widget node. Types: - `CheckboxOpts = { props?, onChange?, style? }` ## Usage ```luau local checkbox = require("@builtin::modules.zui.widget.checkbox") checkbox("Enabled", "cb1", true, { onChange = "cb1:change" }) ``` ## Notes - Initial `checked` is coerced via `value == true` — anything that isn't strictly `true` becomes `false`. - `onChange` receives a `ValueChanged(boolean)` event when the user toggles the box.
▲ 0↑ born
▣
module · born here
❒asset
# chip Compact tag/badge. Variants (`"info"`, `"success"`, `"warning"`, `"error"`) pick a foreground/background colour pair from the active theme. Built as a `panel + label` composition so the engine has no dedicated arm for what is fundamentally a coloured rounded text-box. ## Exports - `chip(text: string?, opts: ChipOpts?) -> WidgetNode` — build a chip widget node. Types: - `ChipVariant = "info" | "success" | "warning" | "warn" | "error" | "danger"` - `ChipOpts = { id?, variant?, bg?, color?, fontSize?, padding?, borderRadius? }` ## Usage ```luau local chip = require("@builtin::modules.zui.widget.chip") chip("New", { variant = "success" }) chip("Beta", { bg = "#222", color = "#fff" }) ``` ## Notes - `bg` and `color` per-call override the variant-selected colours. - Defaults: `fontSize = 11`, `padding = { 1, 6, 1, 6 }`, `borderRadius = 4`. - Colours pull from `Theme.default` (`success`, `warn`, `danger`, `info`, `bg_deep`).
▲ 0↑ born
▣
module · born here
❒asset
# codeEditor Code-editing variant of `input`. Defaults `multiline = true`, `codeEditor = true`, `language = "lua"`, `lineNumbers = true`. The `language` opt dispatches to a Luau highlighter from `zui.highlight.*`; the resulting segments are passed to TextInput as `props.segments` so the renderer builds a coloured LayoutJob without a Rust-side highlighter call. ## Exports - `codeEditor(id: string?, value: any?, opts: CodeEditorOpts?) -> WidgetNode` — build a code-editor widget node. Types: - `CodeEditorOpts = { multiline?, codeEditor?, lineNumbers?, folding?, language?, foldLanguage?, segments?, ... }` — accepts any opts the underlying `input` widget supports. ## Usage ```luau local codeEditor = require("@builtin::modules.zui.widget.codeEditor") codeEditor("editor1", "local x = 1\n", { language = "lua" }) codeEditor("editor1", code, { language = "lua", folding = true }) ``` ## Notes - Folding is opt-in via `folding = true`. When combined with `language = "lua"`, the wrapper sets `foldLanguage = "lua"` so the gutter UI runs the Rust-side fold detector on the editor's live text. Other languages can't fold (no detector implemented yet). - `opts.language` is consumed by the wrapper and never reaches `input.module`; the highlighter output lands in `opts.segments` instead. - The engine's built-in highlighters have been deleted — language→segments mapping lives entirely in Luau.
▲ 0↑ born
▣
module · born here
❒asset
# collapsible Collapsible section with a header bar. Click the header to expand or collapse the children. Controlled by Luau — the wrapper auto-stashes open/closed state via `ui.widgetState(id, "open")` and registers a one-time click handler with the surrounding `Z.app` so existing demos that pass `defaultOpen = true` keep working unchanged. ## Exports - `collapsible(header: any?, children: { any }?, opts: CollapsibleOpts?) -> WidgetNode` — build a collapsible widget node with the supplied header and children. Types: - `CollapsibleOpts = { id?, props?, style?, defaultOpen?, app? }` ## Usage ```luau local collapsible = require("@builtin::modules.zui.widget.collapsible") collapsible(label("Advanced"), { -- collapsible children here }, { id = "advanced-section", defaultOpen = false }) ``` ## Notes - Inside `Z.app`: state is auto-managed via `ui.widgetState` + a one-time `app:on` handler keyed off `id`. - Outside `Z.app`: falls back to a static render keyed off `defaultOpen`; the caller must drive `props.open` and react to `ValueChanged(new_open)` themselves. - State APIs are flat top-level FFI functions — `ui.widgetState(id, key)` and `ui.widgetStateSet(id, key, value)`, **not** `ui.widgetState.set`. - `id` is required for cross-frame state to persist. Without an id, the widget renders but the toggle won't reflect — the wrapper logs no warning, just degrades gracefully.
▲ 0↑ born
▣
module · born here
❒asset
# colorSwatch Click-only coloured swatch — a small (default 24×24) coloured rect with a white border. Used as a click target for material chips, palette swatches, or any visual indicator that doubles as a click target. Implemented as an empty-text `button` so the engine has no dedicated arm. ## Exports - `colorSwatch(color: any?, callbackId: any?, opts: ColorSwatchOpts?) -> WidgetNode` — build a coloured-swatch widget node. Types: - `ColorSwatchOpts = { id?, size?, style?, border?, borderWidth?, tooltip? }` ## Usage ```luau local colorSwatch = require("@builtin::modules.zui.widget.colorSwatch") colorSwatch("#ff8855", "swatch:click", { id = "row5" }) colorSwatch("#5588ff", "pick", { size = 32, tooltip = "Pick blue" }) ``` ## Notes - Defaults: `size = 24`, `border = "#ffffff"`, `borderWidth = 1`, `borderRadius = 4`, `padding = 0`. - Click handling reuses the button widget — same callback-id semantics, same closure auto-wiring inside `Z.app`. - Per-swatch routing pattern: encode the swatch identity into the callback id (e.g. `"swatch-" .. hex`) and match with `app:on("^swatch%-(.+)$", ...)`.
▲ 0↑ born
▣
module · born here
❒asset
# contextMenu Convenience wrapper over `Z.popup` for the context-menu pattern — a popup pinned to a widget you typically open on right-click. Same ordering constraint as `Z.popup`: the anchor must render before the menu in tree order. ## Exports - `contextMenu(anchorId: string?, items: { any }?, opts: ContextMenuOpts?) -> WidgetNode` — build a context-menu popup attached to the anchor widget. Types: - `ContextMenuOpts = { id?, open?, onDismiss?, pivot?, focusable?, style? }` ## Usage ```luau local contextMenu = require("@builtin::modules.zui.widget.contextMenu") -- in your Z.app builder: Z.btn("Item", "row1"), -- anchor Z.contextMenu("row1", { Z.btn("Cut", "ctx:cut"), Z.btn("Copy", "ctx:copy"), Z.btn("Paste", "ctx:paste"), }, { open = menuOpen, onDismiss = "ctx:dismiss" }) ``` ## Notes - Wave 3 ships caller-managed `open` state. Right-click sensing on interactive widgets (and self-toggling menus) is future work — for now, drive `open` yourself in `onClick` / `onCallback` handlers. - Default pivot is `"belowLeft"`. - The wrapper is a thin pass-through; any popup-supported field can be reached by adding it to the surrounding popup directly if needed.
▲ 0↑ born
▣
module · born here
❒asset
# dataView Filterable, multi-selectable data list bound to a named `modules.api.editor.selection` scope. `props.mode` ("list" | "table" | "grid", default "list") selects the rendering strategy: - **list** — one focusable canvas row per visible item (icon + label + badge) inside a virtualized `scrollArea`. - **table** — the same focusable-row shape extended to multiple columns, with a sortable header. - **grid** — a wrapping `Z.grid` of selectable thumbnail cells, for asset browsers. ## Exports - `dataView(props: Props?) -> any` — build a DataView widget. The module returns this function directly. Types: - `Props = { id: string?, items: { any }?, key: ((any) -> string)?, row: ((any) -> RowSpec)?, selection: Scope?, onActivate: string?, filter: string?, rowHeight: number?, rowWidth: number?, maxHeight: number?, mode: string?, app: any?, columns: { ColumnSpec }?, gridColumns: number?, commands: { string }? }` - `RowSpec = { label: string, icon: string?, badge: string?, columns: { [string]: string }?, thumb: string? }` - `ColumnSpec = { id: string, label: string, width: number?, align: ("left" | "right" | "center")?, sort: boolean? }` - `Scope = { name: string }` — matches `editorSelection.Scope`. `id`, `key`, `row`, and `selection` are required at runtime (a missing one raises an `error`); `columns` is additionally required for table mode. Every `Props` field is typed optional so `props or {}` at the registration boundary stays well-typed. ## Usage ```luau local dataView = require("@builtin::modules.zui.widget.dataView") local Selection = require("@builtin::modules.api.editor.selection") local scope = Selection.scope("asset") -- list mode dataView{ id = "assets", items = assets, selection = scope, key = function(a) return a.guid end, row = function(a) return { label = a.name, icon = a.icon } end, onActivate = "assets:open", filter = state.filterText, } -- table mode dataView{ id = "assetsTable", mode = "table", items = assets, selection = scope, key = function(a) return a.guid end, row = function(a) return { columns = { name = a.name, type = a.assetType, size = tostring(a.size) } } end, columns = { { id = "name", label = "Name", width = 160, sort = true }, { id = "type", label = "Type", width = 100, sort = true }, { id = "size", label = "Size", width = 80, align = "right", sort = true }, }, } -- grid mode dataView{ id = "assetsGrid", mode = "grid", items = assets, selection = scope, key = function(a) return a.guid end, row = function(a) return { label = a.name, thumb = a.path .. "/preview.png" } end, gridColumns = 4, } -- right-click command menu (list/table mode) local Commands = require("@builtin::modules.api.editor.commands") Commands.declare({ id = "asset.rename", title = "Rename", category = "Assets", run = function(ctx) ... end }) dataView{ id = "assets", items = assets, selection = scope, key = function(a) return a.guid end, row = function(a) return { label = a.name } end, commands = { "asset.rename", "-", "asset.delete" }, } ``` ## Notes - Selection is read from and written to `props.selection` in every mode — the widget holds no selected-ids of its own, so every other view sharing the scope always agrees with what's drawn. Only the click anchor, keyboard focus position, and (table mode) sort key/direction live in `ui.widgetState`, because none of them has a home in the selection model. - Row click resolves through `selectionModel.resolveClick` (plain / ctrl / shift semantics) using positions in the CURRENT filtered/ sorted view, not indices into `props.items` — a shift-range always spans what's visually between the anchor and the click. - The canvas `onClick` payload carries only the cursor position and button, never modifier keys, so ctrl/shift state is read from `input.isDown("ControlLeft" | "ControlRight" | "ShiftLeft" | "ShiftRight")` at the moment of the click — the same substrate `modules.zinput.rebind` polls for modifier-aware interactions. - Keyboard nav (list/table row canvas `onKey`): `ArrowDown` / `ArrowUp` move focus and single-select the new row, `Home` / `End` jump to the first/last visible row, `Enter` dispatches `props.onActivate` for the focused row. Double-clicking a row also dispatches `props.onActivate`. Grid cells are panels (generic `onClick` only) — they select but don't carry keyboard nav or double-click. - Table mode's column header cells dispatch a `<id>:sort:<colId>` click for any column with `sort = true`, cycling none/other-column -> ascending -> descending -> none. Sort compares `row(item).columns[col.id]` (the same text shown in the cell), so a numeric-looking column sorts lexicographically unless the cell text is itself a plain unpadded number. - Every scrollable body (`virtual = true` list/table rows, the grid's wrapping `Z.grid`) is wrapped with BOTH `maxHeight` and `minHeight` set to the same value. Without `minHeight`, nesting the scrollArea under a header (table mode) or wrapping `Z.grid` directly (grid mode) makes its column an indefinite-height ancestor, and the viewport collapses to a sliver well under the intended bound. - Grid cells set `width` / `height` (not `minWidth` / `minHeight`) on the panel — `Z.grid`'s content-driven column sizing reads each child's `width` style to pick a cell size, and a `minWidth`-only cell measures as 0 there. - An empty filtered view renders a single muted "No items" label instead of an empty scroll area (table mode keeps the header above it). - Handlers (click, double-click, keyboard nav, column sort) are registered once per widget id (deduped via `app._dataViewHandlers`); item list, filter, column and callback swaps land without re-registering, mirroring `selectableList`'s handler dedup. - `props.commands` (list/table mode only — see below) adds a right- click command menu, driven by `modules.api.editor.commands`. Each entry is a command id (`Commands.get(id).title` labels it, `Commands.isEnabled(id, ctx)` gates it); a `"-"` entry is a separator. Right-clicking a row selects it alone first if it wasn't already part of the selection, then opens the menu anchored to that row. `ctx` passed to `enabledWhen`/`run` is `Selection.context()` (this DataView's selection scope, since selecting the row focuses it) plus `view` (this widget's id) and `item` (the row's underlying item). Clicking an entry runs the command and closes the menu; clicking elsewhere or Escape also closes it (`Z.popup` auto-dismiss). - Grid mode does not support `props.commands` — its cells are `Z.panel`s (generic `onClick` only), and `onPointerDown` (right-click sensing) is decoded by the Canvas widget type alone.
▲ 0↑ born
▣
module · born here
❒asset
# datePicker Calendar-popup date picker. Wraps `egui_extras::DatePickerButton` (egui_extras 0.34 with the `datepicker` feature, enabled in `crates/zero_ui/Cargo.toml`). Value flows as an ISO-style date string; format defaults to `"%Y-%m-%d"` and can be overridden with a jiff strftime spec. ## Exports - `datePicker(id: string?, value: any?, opts: DatePickerOpts?) -> WidgetNode` — build a date-picker widget node. Types: - `DatePickerOpts = { id?, props?, style?, format?, onChange?, enabled?, focusable?, tooltip? }` ## Usage ```luau local datePicker = require("@builtin::modules.zui.widget.datePicker") datePicker("startDate", "2026-05-03", { onChange = "startDateChanged", }) datePicker("birthday", "1990/01/15", { format = "%Y/%m/%d", onChange = "birthdayChanged", focusable = true, -- Wave 5 (#2416) prep; informational today }) ``` ## Notes - `onChange` receives the newly-formatted date string as `value`. - The widget id falls back to `opts.id` when the `id` positional is nil. - Date format follows the jiff strftime specifier syntax (e.g. `"%Y-%m-%d"`, `"%Y/%m/%d"`).
▲ 0↑ born
▣
module · born here
❒asset
# dialogueBox Narrative dialogue panel — speaker name + dialogue text + per-option response buttons inside a styled panel. Built as a primitive composition (`panel + label + btn`), so the engine has no dedicated arm. ## Exports - `dialogueBox(opts: DialogueBoxOpts?) -> WidgetNode` — build a dialogue widget node. Types: - `DialogueOption = { text?, enabled?, tooltip? }` - `DialogueBoxOpts = { id?, speaker?, dialogue?, options?, bg?, border?, borderWidth?, padding?, gap? }` ## Usage ```luau local dialogueBox = require("@builtin::modules.zui.widget.dialogueBox") dialogueBox({ id = "dlg1", speaker = "Captain", dialogue = "We've got incoming. Pick your move.", options = { { text = "Engage" }, { text = "Retreat", enabled = false }, { text = "Hail", enabled = true }, }, }) ``` ## Notes - Each option emits a click event keyed `<id>-<index>` (1-based), so a single pattern handler captures every response: ```luau app:on("^dlg1%-(%d+)$", function(_, _, idx) handleResponse(tonumber(idx)) end) ``` - When `opts.id` is omitted, the callback prefix falls back to `"dialogueBox"`. - `option.enabled` defaults to `true` (the wrapper checks `option.enabled ~= false`). - Styling defaults pull from `Theme.default` (`panel`, `border`, `text`, `text_bright`).
▲ 0↑ born
▣
module · born here
❒asset
# dndList Drag-and-drop reorderable list. Each child renders inside a vbox of two stacked nodes — the original child plus an overlay canvas sized to the row that captures drag and pointer-move events. Built on `onDrag` + `onPointerMove` canvas interactions; the engine has no dedicated dnd-list arm. ## Exports - `dndList(id: string?, children: { any }?, opts: DndListOpts?) -> WidgetNode` — build a drag-reorderable list widget node. Types: - `DndListOpts = { rowHeight?, rowWidth?, style?, app?, onReorder?, classes?, barColor?, dragStroke?, label? }` ## Usage ```luau local dndList = require("@builtin::modules.zui.widget.dndList") dndList("playlist", { Z.lbl("Track 1"), Z.lbl("Track 2"), Z.lbl("Track 3"), }, { onReorder = "playlist:reorder" }) -- Then in `onCallback`: -- data.value = { from = 1, to = 3, value = "1,3" } -- Caller re-renders with the children rearranged. ``` ## Notes - Reorder events fire only on drag stop (`dragStopped = true`) and only when `from ≠ to`. The payload includes both numeric `from` / `to` and a legacy `"from,to"` string that the engine test suite asserts on. - Defaults: `rowHeight = 28`, `rowWidth = 240`, `barColor = "#78b4ff"`, `dragStroke = "#78b4ff78"`. - Auto-registers `<id>:drag:<index>` and `<id>:hover:<index>` handlers on the surrounding `Z.app`, deduped by id. Drag state lives in `app._dndListState[id]` and persists across renders. - Outside `Z.app` (no app in scope), the widget renders but the overlay never draws the insertion bar — drag interaction is inert. - The outer Panel exposes `role = "list"` and `ariaLabel = opts.label or "Reorderable list"` for accessibility.
▲ 0↑ born
▣
module · born here
❒asset
# dockArea A docking container backed by egui_dock. Holds a persistent `DockState` keyed by the widget id, so split/tab arrangements and user drags survive across frames. Children are `dockPanel`s, each supplying a tab id, a title, and a content subtree. The dockArea must be the root of a registered screen tree. Each frame it reconciles: it adds tabs for newly-present dockPanel ids and removes tabs whose dockPanel disappeared. Closing a tab emits a `<panelId>-close` interaction so the app can drop the panel from its children. The module returns the widget builder function directly. ## Builder `dockArea(opts?) -> widget node`. `opts` fields: - `id` — the widget id the DockState is keyed by. - `layout` — a serialized `DockState` JSON string that seeds the split/tab arrangement on first render. Read the live arrangement back with `ui.getDockLayout(id)` and pass it here to restore. - `restoreLayout` / `restoreEpoch` — re-apply a saved layout to an already-live dock: pass the JSON in `restoreLayout` and bump `restoreEpoch` to a new value; the dock re-deserializes once per new epoch, then stays freely draggable. - `overlayType` — `"widgets"` (icon drop buttons) or `"highlightedAreas"` (quadrant highlights — edges split, middle ring appends as a tab, dead-center floats the tab into its own window). - `leafCollapseButtons` / `leafCloseAllButtons` — the collapse arrow and close-all button on each leaf's tab bar. - `allowedSplits` — how a dragged tab resolves over a drop area: `"all"`, `"none"`, `"leftRight"`, `"topBottom"`. - `children` — the dockPanels. - `props`, `style` — the `style` block carries the standard widget keys (`borderWidth`, `borderRadius`, `padding`) plus the dock-specific chrome keys typed as `ZuiDockStyle`. ## Usage ```luau local dockArea = require("modules.zui.widget.dockArea") local widget = dockArea({ id = "editor", children = { Z.dockPanel({ id = "scene", title = "Scene", children = { Z.lbl("Scene body") } }), Z.dockPanel({ id = "props", title = "Properties", children = { Z.lbl("Inspector body") } }), }}) ```
▲ 0↑ born
▣
module · born here
❒asset
# dockPanel A single dockable panel inside a `dockArea`. Its widget `id` is the stable tab id the dockArea uses for reconciliation and for the `<id>-close` interaction fired when the tab is closed. Children form the tab's content subtree, rendered by the dockArea's TabViewer. The module returns the widget builder function directly. ## Builder `dockPanel(opts?) -> widget node`. `opts` fields: - `id` (required) — the stable tab id. - `title` — the tab label shown in the dock tab bar; defaults to the panel `id`. - `closable` — whether the tab shows a close button (default true). On close the dockArea emits `<id>-close`. - `float` — when a NEW panel first appears with `float = true`, the dockArea opens it as a floating, movable, resizable dock-window over the content behind instead of in the focused leaf. - `children`, `props`, `style`. ## Usage ```luau local dockPanel = require("modules.zui.widget.dockPanel") local widget = dockPanel({ id = "props", title = "Properties", children = { Z.lbl("Inspector body"), }}) ```
▲ 0↑ born
▣
module · born here
❒asset
# dragValue DragValue is a Luau builder over `canvas`. Emits a drag-to-change numeric scrubber: background rect + centered numeric readout + an `onDrag` handler that accumulates the per-frame delta scaled by `opts.speed`. Range clamping + Shift-fine-drag (10× finer) ride on top. Auto-registers `<id>:drag` per id (deduped) and dispatches `opts.onChange` (or the widget id) with `{ value = newNumber }` on every drag tick. ## Exports - `dragValue(id: string, value: number?, lo: number?, hi: number?, opts: DragValueOpts?) -> WidgetNode` — build the scrubber canvas. Default range is `[0, 1]`; default speed is `0.01`; default format is `%.2f`. Types: - `DragValueOpts = { style: DragValueStyle?, width: number?, height: number?, speed: number?, format: string?, onChange: string?, label: string?, classes: (string | { string })?, app: any? }` - `DragValueStyle = { width: number?, height: number?, background: string?, color: string?, borderColor: string?, borderRadius: number?, fontSize: number?, [string]: any }` - `WidgetNode = { [string]: any }` ## Usage ```luau local Z = require("@builtin::modules.zui") Z.dragValue("plug:gain", state.gain, -24, 24, { speed = 0.1, format = "%.1f", onChange = "plug:gain", -- defaults to widget id }) ``` ## Notes - Live value lives in `ui.widgetState(id, "value")`; the first render seeds it from the caller's `value` arg. - Inside a `Z.app` context the drag handler is auto-registered and deduped per id. Outside, the canvas's `onDrag` is routed directly to the user's callback id (legacy raw-onCallback mode) — the handler receives the structured drag payload rather than a scalar. - Inline text-edit on double-click is deferred. If a use case needs typing, swap to `Z.input` via widgetState.
▲ 0↑ born
▣
module · born here
❒asset
# dropdown Combobox-style dropdown — Luau builder over `Z.btn` + `Z.popup` + `Z.selectableList`. Click-to-open, click-to-select, click-outside / Escape closes. Full keyboard navigation: the inner SelectableList handles ArrowUp/Down/Home/End + Enter/Esc, and the trigger button accepts ArrowDown / Enter / Space to open via generalised `onKey`. ## Exports - `dropdown(id: string, options: { string }?, selected: number?, opts: DropdownOpts?) -> WidgetNode` — build the trigger + popup. `selected` is the 1-based index of the active option; first call seeds `ui.widgetState(id, "selected")`. Types: - `DropdownOpts = { style: { [string]: any }?, classes: (string | { string })?, onChange: string?, pivot: string?, rowWidth: number?, rowHeight: number?, ariaLabel: string?, app: any? }` - `WidgetNode = { [string]: any }` ## Usage ```luau local Z = require("@builtin::modules.zui") Z.dropdown("env", { "Dev", "Stage", "Prod" }, state.env, { onChange = "env:change", }) app:on("env:change", function(v) state.env = v -- v is the new 1-based index end) ``` ## Notes - ARIA roles: trigger is `role="combobox"` / `aria-expanded`; popup hosts a listbox. Focus moves to the row matching the current selection on open, and returns to the trigger on close. - Per-id handlers (`<id>-trigger`, `<id>:triggerKey`, `<id>:commit`, `<id>:dismiss`) are registered once and deduped — re-renders don't accumulate handlers. - `selected` is clamped to `[1, #options]` so out-of-range inputs don't crash the popup.
▲ 0↑ born
▣
module · born here
❒asset
# fader DAW-style vertical fader built on `canvas` interaction props. Draws track + fill + cap as `rect` commands plus a centred grip `line`, and wires drag / click / double-click handlers via `widgetState`. Drag and click both project the pointer Y onto the track — grabbing or clicking anywhere snaps the cap to that Y. Double-click resets to `defaultValue` when set. ## Exports - `fader(id: string, value: number?, lo: number?, hi: number?, opts: FaderOpts?) -> WidgetNode` — build the fader canvas. Auto-registers `<id>:drag`, `<id>:click`, `<id>:reset` handlers (deduped). When `opts.tooltip` is set, the canvas is wrapped in a transparent panel that hosts the tooltip. Types: - `FaderOpts = { defaultValue: number?, onChange: string?, tooltip: string?, style: { [string]: any }?, width: number?, height: number?, trackColor: string?, capColor: string?, fillColor: string?, app: any? }` - `WidgetNode = { [string]: any }` ## Usage ```luau local Z = require("@builtin::modules.zui") Z.fader("master-volume", state.volume, 0, 1, { defaultValue = 0.8, onChange = "audio:volume", }) app:on("audio:volume", function(v) state.volume = v -- the new numeric value end) ``` ## Notes - Track top = `hi`, track bottom = `lo` (egui +y is down). Pointer Y is projected onto the track range to produce the new value, then clamped to `[min(lo,hi), max(lo,hi)]`. - Per-id handlers are deduped — re-renders don't accumulate listeners. Mutating width/height between renders means the *first* render's geometry wins (matches every other auto-stash widget). - Double-click is a no-op when `opts.defaultValue` is nil.
▲ 0↑ born
▣
module · born here
❒asset
# filterRow Composite filter row — search input + toggle checkboxes + sort dropdown + counter + action buttons. Captures the pattern every list view re-implements (entities, logs, assets, scripts, animation_browser). Caller owns all bound values; the widget is stateless. Each piece is optional; an empty `opts` produces an empty hbox. ## Exports - `filterRow(opts: FilterRowOpts?) -> WidgetNode` — assemble the row from the optional pieces in `opts`. Callback ids fire as `<id>-search`, `<id>-toggle-<key>`, `<id>-sort`, plus each action's `onClick` verbatim. Types: - `FilterRowOpts = { id: string?, search: SearchOpts?, toggles: { ToggleOpts }?, sort: SortOpts?, counter: CounterOpts?, actions: { ActionOpts }?, searchClass: string?, toggleClass: string?, sortClass: string?, class: string?, gap: number?, marginBottom: number?, padding: any?, align: string? }` - `SearchOpts = { value: any?, placeholder: string?, class: string?, minWidth: number? }` - `ToggleOpts = { key: any, label: string?, value: boolean?, class: string? }` - `SortOpts = { options: { string }?, selected: number?, class: string?, minWidth: number? }` - `CounterOpts = { visible: number?, total: number?, class: string?, fontSize: number? }` - `ActionOpts = { label: string?, onClick: string?, variant: string?, padding: { number }?, minWidth: number?, tooltip: string?, class: string? }` - `WidgetNode = { [string]: any }` ## Usage ```luau local Z = require("@builtin::modules.zui") return Z.filterRow({ id = "logs-filter", search = { value = state.q, placeholder = "Search...", minWidth = 160 }, toggles = { { key = "info", label = "Info", value = state.showInfo }, { key = "warn", label = "Warn", value = state.showWarn }, }, sort = { options = LOG_TYPES, selected = state.typeIdx }, counter = { visible = #filtered, total = #all }, actions = { { label = "Clear", onClick = "logs-clear", variant = "danger" } }, }) ``` ## Notes - Stateless. Caller owns every bound value; the widget only routes events through the `<id>-*` callback ids. - When `counter` or `actions` are present, a flex spacer is inserted first so they right-align to the trailing edge of the row. - Action `variant` of `"primary"` / `"danger"` selects a coloured background from the active theme; anything else falls back to `theme.panel_alt`.
▲ 0↑ born
▣
module · born here
❒asset
# flex Flex-grow spacer. Pushes siblings apart inside an hbox/vbox — use to right-align items in a horizontal row, etc. The engine treats a spacer with no `space` set as flex-grow. ## Exports - `flex() -> Spacer` — build a flex-grow spacer widget table. Types: - `Spacer = { type: string }` ## Usage ```luau local Z = require("@builtin::modules.zui") local row = Z.hbox({ Z.lbl("left"), Z.flex(), Z.lbl("right") }) ``` ## Notes - Pure function — no engine calls, no state. Safe at module load time. - The returned table is interoperable with hand-written widget trees; any container that accepts a `{ type = "spacer" }` child will treat it as flex-grow.
▲ 0↑ born
▣
module · born here
❒asset
# fs Filesystem composite widgets — pure builders that turn VFS listing data into widget trees. Four stateless pieces every caller composes with their own state + polling. ## Exports - `M.tree(roots, opts) -> WidgetNode` — folder tree (recursive) built on `Z.tree` with VFS-friendly defaults. - `M.fileList(entries, opts) -> WidgetNode` — flat list of files; folders first; selected file highlighted. - `M.preview(data, opts) -> WidgetNode` — file preview pane (text or note). - `M.breadcrumb(path, opts) -> WidgetNode` — clickable path crumbs. ## Usage ```luau local Fs = require("@builtin::modules.zui.widget.fs") return Z.hbox({ Fs.tree(state.roots, { onSelect = "files-tree-select" }), Fs.fileList(state.entries, { onSelect = "files-row", selectedKey = state.name }), Fs.preview({ name = state.name, text = state.text }, { id = "files-preview" }), }) ``` ## Notes - All four builders are stateless: pass data in, get a widget tree out. They never touch the VFS — the caller owns reads, listing, and cache eviction. - `Z.fs.*` is what Layer B (`engine/ui/file_manager`) and Layer C (system_tools' "Files" tab) compose with their own polling. You can build your own file-browser by composing these directly.
▲ 0↑ born
▣
module · born here
❒asset
# graph Sparkline-class chart built on `canvas`. Bar mode emits N rect commands; line mode emits a single polyline. Y axis auto-ranges from the data unless the caller passes `minValue` / `maxValue`. Optional gridlines + axis labels via the shared `_axes` helper; optional on-hover tooltip pinned to the nearest data point. Plot has the more featureful chart — graph is the cheap visual. ## Exports - `graph(data: { number }?, opts: GraphOpts?) -> WidgetNode` — build the chart canvas. Empty data arrays render just the background; 1-element arrays render the background (the polyline path needs 2+ points). Types: - `GraphOpts = { id: string?, classes: (string | { string })?, style: { [string]: any }?, width: number?, height: number?, bg: string?, background: string?, color: string?, graphType: string?, minValue: number?, maxValue: number?, showAxes: boolean?, gridlines: boolean?, tooltip: boolean?, gridColor: string?, axisColor: string?, labelColor: string?, labelSize: number?, xLabels: boolean?, yLabels: boolean?, xTickFormat: any?, yTickFormat: any?, label: string?, app: any? }` - `WidgetNode = { [string]: any }` ## Usage ```luau local Z = require("@builtin::modules.zui") Z.graph({ 1, 4, 9, 16, 25, 36 }) -- line, auto-range Z.graph(samples, { graphType = "bar", color = "#7AA8FF" }) Z.graph(samples, { minValue = 0, maxValue = 1 }) Z.graph(samples, { id = "fps", tooltip = true }) -- hover tooltip Z.graph(samples, { showAxes = true, gridlines = true }) ``` ## Notes - `tooltip` requires `opts.id` — the hover handler stashes the active index in `ui.widgetState(id, "hover")`. The tooltip persists until the next hover; the engine doesn't emit pointer-leave for canvases. - When `showAxes` is set, the plot body shrinks to reserve room for tick labels. With axes off, the polyline spans the full canvas height (preserved by the `respects explicit minValue/maxValue range` test). - DOM mirror exposes the resulting canvas as `<canvas role="img" aria-label="Chart">` via the generic prop pass-through.
▲ 0↑ born
▣
module · born here
❒asset
# grid Fixed-column grid container. Children flow left-to-right then wrap. `columns` is required. ## Exports - `grid(children: { WidgetNode }?, columns: number, opts: GridOpts?) -> WidgetNode` — build a fixed-column grid container. Types: - `GridOpts = { id: string?, classes: (string | { string })?, props: { [string]: any }?, style: { [string]: any }? }` - `WidgetNode = { [string]: any }` — widget table (interoperable with hand-written widget trees). ## Usage ```luau local Z = require("@builtin::modules.zui") local g = Z.grid({ a, b, c, d }, 2, { style = { gap = 4 } }) ``` ## Notes - Pure builder — no state, no engine calls. Safe at module load. - `children` defaults to `{}` when nil; `columns` has no default and must be provided. - The `columns` value is forwarded to the engine as `props.columns`; any other props the caller passes survive untouched.
▲ 0↑ born
▣
module · born here
❒asset
# hbox Horizontal layout container. Children laid out left-to-right; spacing via `style.gap`; alignment via `style.align` (`"start"` | `"center"` | `"end"`). ## Exports - `hbox(children: { WidgetNode }?, opts: HboxOpts?) -> WidgetNode` — build a horizontal-layout container. Types: - `HboxOpts = { id: string?, classes: (string | { string })?, props: { [string]: any }?, style: { [string]: any }? }` - `WidgetNode = { [string]: any }` — widget table (interoperable with hand-written widget trees). ## Usage ```luau local Z = require("@builtin::modules.zui") local row = Z.hbox({ Z.lbl("a"), Z.lbl("b") }, { style = { gap = 6, align = "center" }, }) ``` ## Notes - Pure builder over `modules.zui.widget.node` — no state, no engine calls. Safe at module load. - `children` defaults to `{}` when nil so an empty hbox is a no-op. - `style.gap` is the inter-child spacing in pixels; `style.align` controls cross-axis alignment.
▲ 0↑ born
▣
module · born here
❒asset
# healthBar Game-HUD horizontal health bar. Luau builder over `canvas` — emits a background rect plus a foreground fill rect scaled by `current / max`, with an optional centered numeric label. The wrapping canvas exposes ARIA `progressbar` semantics for the DOM mirror so screen readers report the current value. ## Exports - `healthBar(current: number, max: number, opts: HealthBarOpts?) -> WidgetNode` — module returns the builder function directly. Call `Z.healthBar(...)` via the zui re-export. Options: - `showText: boolean?` — overlay `"current/max"` centered. Default `true`. - `background: string?` — empty-bar tint. Default `"#3c1e1e"`. - `color: string?` — fill color when `gradient` is not set. Default `"#c83c3c"`. - `width: number?`, `height: number?` — bar size. Defaults `200 x 24`. - `label: string?` — `aria-label` override. Default `"Health"`. - `gradient: boolean | {{number, string}}?` — `true` for the default 3-stop red→yellow→green, or a sorted `{{ratio, "#rrggbb"}}` list. - `id`, `classes`, `style` — standard widget plumbing. ## Usage ```luau local Z = require("@builtin::modules.zui") Z.healthBar(state.hp, state.hp_max, { showText = true, gradient = true, label = "Player health", }) -- Custom gradient stops: Z.healthBar(hp, max, { gradient = { { 0, "#000000" }, { 0.5, "#777777" }, { 1, "#ffffff" }, }, }) ``` ## Notes - Stops must be sorted ascending by ratio. Ratios outside `[0, 1]` are clamped. Fewer than 2 stops falls back to the default 3-stop palette. - The fill rect is omitted when `ratio == 0` so an empty bar's edge doesn't render a 0-width sliver. - `gradient` (when truthy) overrides `color`. - Stateless — no module state, safe to call every frame.
▲ 0↑ born
▣
module · born here
❒asset
# hotbar Game-HUD hotbar. Composes an hbox of per-slot buttons with overlaid count + slot-index labels, highlighting the slot indicated by `selectedSlot`. Clicking a slot fires the callback `<opts.id or "hotbar">-<index>`. The caller is the source of truth for the selection (controlled-component pattern). ## Exports - `hotbar(slots: {Slot}, selectedSlot: number?, opts: HotbarOpts?) -> WidgetNode` — module returns the builder function directly. Per-slot shape: - `Slot = { icon: string?, count: number?, tooltip: string? }` Options: - `id: string?` — callback prefix. Default `"hotbar"`. - `style: table?` — hbox style. Default `{ gap = 4 }`. ## Usage ```luau local Z = require("@builtin::modules.zui") Z.hotbar(state.slots, state.selectedSlot, { id = "hotbar" }) -- Pattern-match the per-slot callbacks: app:on("^hotbar%-(%d+)$", function(_, _, idx) state.selectedSlot = tonumber(idx) end) ``` ## Notes - Slot indices start at 1. - Empty `slots` (or `nil`) renders an empty hbox. - The widget is stateless — the parent app owns selection state. - Each button's `id` is `<prefix>-slot-<i>`; the click callback is `<prefix>-<i>` (note: different separator pattern by design).
▲ 0↑ born
▣
module · born here
❒asset
# icon Icon glyph rendered via a glyph font (default `phosphor`). Effectively a `label` with the `fontFamily` fixed to the icon font, kept as its own widget so callers don't have to remember which fontFamily the icons live under. ## Exports - `icon(text: string, opts: IconOpts?) -> WidgetNode` — module returns the builder function directly. Options: - `font: string?` — override the font family. Default `"phosphor"`. - `size: number?` — sets `style.fontSize`. - `color: string?` — sets `style.color`. - `padding: any?` — sets `style.padding`. - `id: string?`, `style: table?` — standard widget plumbing. ## Usage ```luau local Z = require("@builtin::modules.zui") Z.icon("", { size = 16, color = "#dcdcdc" }) Z.icon("", { font = "fontawesome" }) ``` ## Notes - Stateless — returns a fresh widget table each call. - `text` is whatever the icon font renders for the codepoint you pass. - Top-level shortcuts (`size`, `color`, `padding`, `font`) overwrite any conflicting field on `style`.
▲ 0↑ born
▣
module · born here
❒asset
# iconBtn Button rendered with the phosphor glyph font. Convenience wrapper over `Z.btn` for the common case of an icon-only button — defaults the `fontFamily` to `phosphor` so callers don't repeat it on every click target. ## Exports - `iconBtn(iconText: string, callbackId: string, opts: table?) -> WidgetNode` — module returns the builder function directly. `opts` is forwarded to `Z.btn`; this wrapper only injects `opts.style.fontFamily = "phosphor"` when none is set. ## Usage ```luau local Z = require("@builtin::modules.zui") Z.iconBtn("", "save:click", { id = "save", tooltip = "Save" }) ``` ## Notes - Equivalent to `Z.iconButton` (which simply re-requires this module). - The wrapper mutates the supplied `opts` table in place. If you reuse the same `opts` across calls, expect `style.fontFamily` to persist. - Any explicit `opts.style.fontFamily` wins; the default only fires when the field is missing.
▲ 0↑ born
▣
module · born here
❒asset
# iconButton Square button rendered with the phosphor glyph font. Thin alias for `Z.iconBtn` — re-requires the same builder so the call shapes are identical. Pick whichever name reads better at the call site. ## Exports - `iconButton(iconText: string, callbackId: string, opts: table?) -> WidgetNode` — module returns the `iconBtn` builder directly. ## Usage ```luau local Z = require("@builtin::modules.zui") Z.iconButton("", "save:click", { id = "save", tooltip = "Save" }) ``` ## Notes - This module is a one-line `return require("modules.zui.widget.iconBtn")`. - See `iconBtn` for full option semantics. - Useful when reading code: `iconButton` reads more naturally in some call sites, `iconBtn` is shorter for inline use.
▲ 0↑ born
▣
module · born here
❒asset
# image Static image widget. Wraps a single `image` node whose `src` is fed to the engine's image loader. Sizing comes from `style.width` and `style.height`. ## Exports - `image(src: string, opts: ImageOpts?) -> WidgetNode` — module returns the builder function directly. Options: - `id: string?` — widget id. - `style: table?` — passes through to the underlying node; supply `width`/`height` here. ## Usage ```luau local Z = require("@builtin::modules.zui") -- By VFS path. Z.image("/source/libs/@builtin/textures/uv_checker_bw.png", { style = { width = 64, height = 64 } }) -- By asset identity. Z.image("@builtin::textures.uv_checker_bw", { style = { width = 64, height = 64 } }) ``` ## Notes - Stateless — no side effects beyond producing a widget table. - `src` reaches the engine's image loader, which resolves these spellings: a VFS path (`/source/...` or `/zero/source/...`), an asset identity (`@builtin::textures.uv_checker_bw`), an asset guid, and an http URL. A path anchors at the VFS root, so name it from there. - A `src` the loader cannot reach paints a `[failed <src>]` placeholder in place of the image. - Aspect ratio is whatever the engine's image renderer applies — this widget does not impose one.
▲ 0↑ born
▣
module · born here
❒asset
# input Text input widget. Single-line by default; pass `multiline = true` for a textarea. The code-editor variant (syntax-highlight, line numbers, folding) lives in `zui.widget.codeEditor` — same underlying widget kind, more defaults, plus the Luau highlighter producing `segments`. ## Exports - `input(id: string, value: string?, opts: InputOpts?) -> WidgetNode` — module returns the builder function directly. Options forwarded to the node's `props`: - `placeholder: string?` - `onChange: string?` — callback id - `submitOnEnter: boolean?` - `multiline: boolean?` - `password: boolean?` - `codeEditor: boolean?` - `segments: { Segment }?` — pre-highlighted layout segments - `foldLanguage: string?` — opt into the Rust fold detector (`"lua"` etc.) - `lineNumbers: boolean?`, `folding: boolean?` - `props`, `classes`, `style` — standard widget plumbing. ## Usage ```luau local Z = require("@builtin::modules.zui") Z.input("name", state.name, { placeholder = "Your name", onChange = "name:edit", }) -- Multiline: Z.input("notes", state.notes, { multiline = true }) ``` ## Notes - When `props.segments` is supplied, the renderer builds the LayoutJob from segments instead of the plain `text` string — but `text` is still set, so the underlying value continues to work for callbacks. - For syntax-highlighted code editing, prefer `Z.codeEditor`, which bundles `foldLanguage`, `lineNumbers`, `folding`, and a default highlighter call. - Stateless — the parent owns the buffer; this widget only renders.
▲ 0↑ born
▣
module · born here
❒asset
# kbd Keybind widget — a Luau builder over `hbox` + `button` (or a focusable `canvas` while capturing). Uses canvas `onKey` and `ui.focus(id)` to capture the next pressed key from primitives. Has two call shapes that produce the same widget kind: a display-only form for showing a key combo, and an interactive capture form for rebinding. ## Exports - `kbd(keysOrOpts: string | KbdOpts, opts: table?) -> WidgetNode` — module returns the builder function directly. The first arg is polymorphic. Display-only call: `kbd("Ctrl+S")` or `kbd("Ctrl+S", { style = {...} })`. Interactive call (table form): - `id: string` — required; identifies the widget for state + handlers. - `label: string?` — left-side label. Default `"Key"`. - `key: string?` — current binding text. Default `"None"`. - `onChange: string?` — callback id; fires on commit with `{ value = "F1" }`. - `app: App?` — explicit app; falls back to `App.current()`. - `buttonWidth`, `buttonHeight`, `gap`, `labelStyle`, `buttonStyle`, `canvasStyle`, `style`, `classes` — layout/style overrides. ## Usage ```luau local Z = require("@builtin::modules.zui") -- Display only: Z.kbd("Ctrl+S") -- Interactive: Z.kbd({ id = "settings:bind:jump", label = "Jump", key = state.jumpKey, onChange = "ui:bind:jump", -- fires with { value = "F1" } }) ``` ## Notes - Behaviour while capturing: clicking the `[<key>]` button transitions to capturing and focuses the per-instance hidden canvas. Any key press commits and leaves capturing. `Escape` cancels (capturing flips off, key unchanged). - Captured state lives in `ui.widgetState(id, "capturing"|"key")` so the wrapper survives screen rerenders without the caller threading it through. - `onChange` fires only on commit, not on cancel. - Per-id handler registration is deduped via `app._keybindHandlers` (mirrors the `radioGroup` pattern); `onChange` swaps land on each render without re-registering. - Calling the table form without an `id` falls back to display-only.
▲ 0↑ born
▣
module · born here
❒asset
# knob Rotary knob built on `canvas` interaction props. Renders a 270° dial as a track polyline + fill polyline + needle line, and wires drag/reset handlers via `widgetState`. Auto-registers per-id handlers on the current `Z.app` so callers only need to supply `onChange`. ## Exports - `knob(id: string, value: number, lo: number?, hi: number?, opts: KnobOpts?) -> WidgetNode` — module returns the builder function directly. Options: - `defaultValue: number?` — restored on double-click. Omit to disable reset. - `onChange: string?` — callback id; fires with `{ value = number }` on drag/reset commit. - `tooltip: string?` — when set, wraps the canvas in a transparent tooltip panel. - `width: number?`, `height: number?` — canvas size. Default `34 × 34`. - `arcWidth: number?` — track/fill stroke width. Default `4`. - `trackColor: string?`, `fillColor: string?`, `capColor: string?` — palette overrides. - `style: table?`, `app: App?` — standard plumbing. ## Usage ```luau local Z = require("@builtin::modules.zui") Z.knob("master-gain", state.gain, 0, 1, { defaultValue = 0.75, onChange = "audio:gain", tooltip = "Master gain", }) ``` ## Notes - Vertical drag updates the value: `1px = 0.5% of span`, or `0.05%` when Shift is held (10× finer for fine adjustment). Drag *up* increases. - Double-click restores `defaultValue` if set; otherwise no-op. - Live state lives in `ui.widgetState(id, "value")` — first render seeds from the supplied `value`, thereafter the drag handler owns it. This mirrors the `Z.collapsible` auto-stash pattern. - Handlers are registered once per id via `app._knobHandlers`; subsequent renders are no-ops on the handler side. - The fill polyline is omitted when only one segment is filled so the renderer's min-2-points guard isn't tripped. - The tooltip wrap is only added when `opts.tooltip` is set so non-tooltip knobs stay flat.
▲ 0↑ born
▣
module · born here
❒asset
# label Label widget — read-only text. Accepts top-level shortcuts for the most commonly styled fields (`color`, `fontSize`, `bold`, `bg`, `padding`, `minWidth`, `font`) so callers don't have to nest everything under `style = { ... }`. ## Exports - `label(text: any, opts: LabelOpts?) -> WidgetNode` — module returns the builder function directly. Also exposed as `Z.lbl`. Options (top-level shortcuts mapped onto `style`): - `color: string?` - `fontSize: number?` - `fontWeight: string?` - `bold: boolean?` — sets `fontWeight = "bold"` when true. - `bg: string?` — sets `style.background`. - `padding: any?` - `minWidth: number?` - `font: string?` — sets `style.fontFamily`. - `id`, `classes`, `class`, `style` — standard widget plumbing. ## Usage ```luau local Z = require("@builtin::modules.zui") Z.lbl("Hello") Z.lbl("dim text", { color = "#888", bold = true }) Z.lbl(42, { fontSize = 18, font = "monospace" }) ``` ## Notes - `text` is coerced to a string via `tostring(text or "")`. Passing `nil` yields the empty string. - The shortcut props overwrite any conflicting field on `style`. - Stateless — no module state, no engine calls.
▲ 0↑ born
▣
module · born here
❒asset
# leftPanel Left-docked panel. Root-only — must be a top-level child of the screen builder, not nested inside another container. Wraps the supplied children in a `leftPanel` node that the layout engine docks against the left edge of its parent screen. ## Exports - `leftPanel(children: { WidgetNode }, opts: table?) -> WidgetNode` — module returns the builder function directly. `opts` is forwarded verbatim to the underlying node. ## Usage ```luau local Z = require("@builtin::modules.zui") Z.app(function() return { Z.leftPanel({ Z.lbl("Sidebar"), -- ... }), Z.centralPanel({ -- main view }), } end) ``` ## Notes - Must be a root-level child of the screen builder; nesting it inside another container is unsupported. - `nil` children are treated as an empty array. - Stateless.
▲ 0↑ born
▣
module · born here
❒asset
# lsp LSP composite widgets — pure builders that turn the engine's `lsp.*` data shapes into widget trees. Six pieces gathered onto a single `M` table so callers can `require` the namespace and pick out individual builders. These are stateless: pass data in, get a widget tree out. ## Exports - `M.severitySummary(counts, opts?) -> WidgetNode` — hbox of count chips per severity. - `M.diagnosticsList(diags, onSelect, opts?) -> WidgetNode` — scrollable vbox of diagnostic rows. - `M.diagnosticDetail(diag, opts?) -> WidgetNode` — detail panel for one diagnostic. - `M.docBrowser(state, opts?) -> WidgetNode` — namespaces / methods / describe browser. - `M.strictModeToggle(mode, onChange, opts?) -> WidgetNode` — three-button radio. - `M.directiveBadge(skipMode, opts?) -> WidgetNode?` — chip indicating the file's `--!skip` mode, or `nil`. Each export is the builder module's `return function(...)`; see the sibling `*.module/README.md` files for the per-builder signatures. ## Usage ```luau local Z = require("@builtin::modules.zui") local lsp = require("@builtin::modules.zui.widget.lsp") return Z.vbox({ lsp.severitySummary(counts), lsp.diagnosticsList(diags, "lsp:row:select"), lsp.diagnosticDetail(diags[selectedIndex], { source = source }), }) ``` ## Notes - Layer B (`lsp_ui.module`) and Layer C (system_tools' LSP tab) compose these with their own state + polling — but you don't have to: roll your own. - Each entry is loaded lazily by `require`. Hot-reloading any sibling re-imports it on next call. - This namespace owns no state. The composite layers above this one do.
▲ 0↑ born
▣
module · born here
❒asset
# meter Peak-style level meter built on `canvas`. Renders either a continuous fill or N segmented LED rects, plus an optional peak indicator line. Read-only — no interaction props. Peak-hold state lives in `ui.widgetState(id, ...)` when an `id` is supplied; without one, the meter still renders but the peak indicator is skipped. ## Exports - `meter(value: number?, opts: Opts?) -> any` — build the meter canvas node. Returned directly by the module. Types: - `Style = { width?, height?, background?, fillColor?, warnColor?, clipColor?, offColor? }` - `Opts = { id?, orientation?, segments?, warnThreshold?, clipThreshold?, peakHoldMs?, peak?, width?, height?, background?, fillColor?, warnColor?, clipColor?, offColor?, style? }` ## Usage ```luau local meter = require("@builtin::modules.zui.widget.meter") local widget = meter(state.master_l, { id = "master-meter-l", orientation = "horizontal", style = { width = 220, height = 8, fillColor = "#3cc864", warnColor = "#dcc83c", clipColor = "#dc463c", background = "#101214" }, }) ``` ## Notes - `value` is clamped to `[0, 1.5]` so out-of-range inputs paint into the clip-coloured band without spilling outside the canvas. - Peak hold defaults to 1500ms then decays at 1.5/sec — matches the deleted Rust `render_meter` algorithm. - Pass `opts.peak` to override the computed peak — useful for ganged-channel meters where a single source value drives multiple visuals. - Vertical default is 12×160; horizontal flips to 160×12.
▲ 0↑ born
▣
module · born here
❒asset
# minimap Luau builder over `canvas` for a square minimap. Renders a 150×150 dark-green square with a single light-green centre marker by default, and accepts an optional `markers` array so callers can paint arbitrary points (entities, waypoints, POIs) without needing a new Rust widget kind. Marker coordinates are normalised — `[0..1]` × `[0..1]` — clamped so out-of-bounds entries paint at the edge. ## Exports - `minimap(opts: Opts?) -> any` — build the canvas node. Returned directly by the module. Types: - `Marker = { x?: number, y?: number, color?: string, radius?: number }` - `Style = { width?: number, height?: number, background?: string }` - `Opts = { id?, classes?, label?, size?, background?, borderColor?, borderWidth?, borderRadius?, markers?: { Marker }, style?: Style }` ## Usage ```luau local minimap = require("@builtin::modules.zui.widget.minimap") local widget = minimap({ size = 200, markers = { { x = 0.5, y = 0.5, color = "#FFC832", radius = 5 }, -- player { x = 0.2, y = 0.8, color = "#64C8FF" }, -- ally { x = 0.7, y = 0.3, color = "#C84040" }, -- enemy }, }) ``` ## Notes - DOM mirror exposes the widget as `<canvas role="img" aria-label="Minimap">` via the generic `role` / `aria*` prop pass-through. - Legacy single-dot fallback ensures existing demos keep rendering even when no markers are supplied. - The marker `radius` and `color` are per-marker — use them to encode per-entity context (size for distance, colour for faction, etc.).
▲ 0↑ born
▣
module · born here
❒asset
# modal Top-layer modal dialog with backdrop dim, click-outside + Escape dismissal. Wraps `egui::Modal` (added 0.30, refined through 0.34). Root-only — the render arm dispatches at the screen-root path, like Window. Pass `dismissible = false` to force a button-only answer. ## Exports - `modal(opts: Opts?) -> any` — build the modal widget node. Returned directly by the module. Types: - `Opts = { id?, title?, open?, onClose?, dismissible?, focusable?, tooltip?, children?, props?, style? }` ## Usage ```luau local modal = require("@builtin::modules.zui.widget.modal") local widget = modal({ title = "Confirm", open = state.dialogOpen, onClose = "dialog:close", children = { Z.lbl("Are you sure?"), Z.hbox({ Z.btn("Yes", "dialog:yes"), Z.btn("No", "dialog:no"), }), }, }) ``` ## Notes - Known limitation (pending Wave 5 / #2416): Tab navigation isn't trapped inside the modal — keyboard focus can still reach widgets behind the backdrop. The visible "this is modal" contract is otherwise complete. - Must render at the screen root for the backdrop to cover the whole surface; nesting inside a `panel` will clip the dim layer. - `dismissible = false` is the right choice for confirm dialogs whose answer affects irreversible state.
▲ 0↑ born
▣
module · born here
❒asset
# node Core widget-table constructor used by every zui widget. Returned tables are interoperable with hand-written widget trees, so user-built widgets can reuse the exact same primitive without depending on the rest of zui. ## Exports - `node(widgetType: string, opts: Opts?, children: any?) -> any` — build the widget table. Returned directly by the module. Types: - `Opts = { id?: string, classes?: any, class?: any, props?: any, style?: any }` ## Usage ```luau local node = require("@builtin::modules.zui.widget.node") -- User-built widget composed on top of node: return function(text, opts) return node("label", { props = { text = text } }) end ``` ## Notes - Accepts either a single child table or an array of children; single children are wrapped automatically (matches `scroll.module`'s fix from #2331). - `classes` may be a string (space-separated) or an array — both normalise to an array of class names. - `class` is accepted as an alias for `classes` for ergonomic call sites. - Returning a function (not a table) keeps consumers terse and avoids callers needing to know whether the constructor lives on `M.x` or the module itself.
▲ 0↑ born
▣
module · born here
❒asset
# panel Bordered surface for grouping widgets. Accepts top-level shortcut keys (`bg`, `border`, `borderWidth`, `padding`, `gap`, `minWidth`, `minHeight`, `maxWidth`, `maxHeight`) so the common case doesn't require nesting under `style = { ... }`. ## Exports - `panel(children: { any }?, opts: Opts?) -> any` — build the panel widget node. Returned directly by the module. Types: - `Opts = { id?, classes?, props?, style?, bg?, border?, borderWidth?, padding?, gap?, minWidth?, minHeight?, maxWidth?, maxHeight?, scroll?, scrollMaxHeight?, scrollId? }` ## Usage ```luau local panel = require("@builtin::modules.zui.widget.panel") local widget = panel({ Z.lbl("Header"), Z.btn("Action", "click"), }, { bg = "#101010", border = "#3a3a3a", borderWidth = 1, padding = 8, }) ``` ## Tall panels — scrolling overflow (closes #3270) `Z.anchor("Center", ...) { Z.panel(rows) }` with more rows than the viewport will silently clip top + bottom of the list — content scrolls off-screen with no affordance. Opt into in-place scrolling by passing `scroll = true` alongside a `maxHeight`: ```luau local rows = {} for i = 1, 100 do rows[#rows + 1] = Z.lbl("row " .. i) end -- Header + footer pin to the panel; the rows scroll between them. return Z.anchor("Center", {}, { Z.panel({ Z.lbl("Codex", { bold = true }), Z.sep(), Z.panel(rows, { scroll = true, maxHeight = 480 }), Z.sep(), Z.btn("Close", "codex:close"), }, { bg = "#101010", padding = 12 }), }) ``` Without `scroll = true`, `maxHeight` only clips — `scroll = true` wraps the children in a `scrollArea` so overflow rows are reachable via the scrollbar. `scrollMaxHeight` (optional) bounds only the scroll region when you want the panel itself to size to the header + footer and let the scroll region claim what's left. ## Notes - Top-level shortcuts merge into `opts.style` (caller-supplied keys take precedence — explicit `style.background` wins over `opts.bg` only because the shortcut writes to `style.background` after `style` is captured). - Defers to `node("panel", ...)` so any panel-specific renderer arm changes flow through automatically. - Children default to `{}` — an empty panel renders as a bordered spacer. - `scroll = true` inserts a single `scrollArea` child wrapping the caller's children. Pair with a `maxHeight` (or `scrollMaxHeight` to bound only the scroll region) so the scrollArea has something to scroll against — without a bound the scrollArea fills its parent and no scrolling is observed.
▲ 0↑ born
▣
module · born here
❒asset
# plot Plot is a Luau builder over `canvas` interaction props (`onDrag`, `onScroll`, `onPointerMove`). Rebuilds the pan/zoom/legend/crosshair UX from canvas primitives so there is no `egui_plot` dependency. The module returns a callable table — `Z.plot(id, spec, opts)` builds the widget, while `.line` / `.points` / `.heatmap` / `.boxPlot` / `.bezierLine` are element helpers that tag specs with the right `type`. ## Exports - `Plot.line(spec: LineSpec) -> LineSpec` — tag a spec with `type = "line"`. - `Plot.points(spec: PointsSpec) -> PointsSpec` — tag a spec with `type = "points"`. - `Plot.heatmap(spec: HeatmapSpec) -> HeatmapSpec` — tag a spec with `type = "heatmap"`. - `Plot.boxPlot(spec: BoxPlotSpec) -> BoxPlotSpec` — tag a spec with `type = "boxPlot"`. - `Plot.bezierLine(p0, p1, p2, p3, samples?, opts?) -> LineSpec` — sample a cubic bezier into a `LineSpec`. - `__call(id, plotSpec, opts) -> any` — calling the table builds the plot widget itself. Types: - `Point = { number }` (2-element) - `LineSpec = { type?, id?, name?, color?, points?, y?, width?, gradient?, fillY?, fillColor?, lineStyle? }` - `PointsSpec = { type?, id?, name?, color?, points?, shape?, radius?, filled? }` - `HeatmapSpec = { type?, id?, name?, values?, cols?, palette?, showLabels? }` - `BoxPlotSpec = { type?, id?, name?, boxes?, horizontal? }` - `PlotOptions = { showAxes?, showGrid?, showCrosshair?, invertX?, invertY?, legend?, showCoordinates?, coordinatesCorner?, allowZoom?, allowDrag?, allowScroll?, linkGroup?, linkAxis?, linkCursor?, xTickFormat?, yTickFormat? }` - `PlotSpec = { options?: PlotOptions, elements?: { any } }` - `PlotOpts = { width?, height?, style?, background?, classes?, label?, app? }` ## Usage ```luau local Plot = require("@builtin::modules.zui.widget.plot") local widget = Plot("chart-1", { options = { showGrid = true, legend = { show = true } }, elements = { Plot.line({ points = { { 0, 0 }, { 1, 0.5 }, { 2, 0.2 } }, name = "A" }), Plot.points({ points = { { 1, 0.5 } }, radius = 6 }), }, }, { width = 480, height = 240 }) ``` ## Notes - Pan/zoom state lives in `ui.widgetState(id, ...)`; the same `id` across renders preserves the viewport. - `linkGroup` syncs pan/zoom across multiple plots — set `linkAxis` / `linkCursor` per axis to opt each plot in. - Shift+Drag triggers boxed-zoom marquee selection; non-shift drag pans. - `xTickFormat` / `yTickFormat` accept `function(v: number) -> string` to override the default D3 nice-tick labelling. - Heatmap `cols` defines column count — rows are inferred from `#values / cols`.
▲ 0↑ born
▣
module · born here
❒asset
# popup Anchored floating popup. Wraps `egui::Popup` (added 0.32). Pins itself to a referenced widget's rect — useful for button-anchored dropdowns, autocompletes, callouts. The anchor MUST render before the popup in the tree, otherwise the rect lookup misses and the popup silently does not paint. ## Exports - `popup(opts: Opts?) -> any` — build the popup widget node. Returned directly by the module. Types: - `Opts = { id?, anchor: string, pivot?, open?, onDismiss?, focusable?, children?, props?, style? }` ## Usage ```luau local popup = require("@builtin::modules.zui.widget.popup") Z.btn("Save", "saveBtn"), popup({ anchor = "saveBtn", open = state.popupOpen, onDismiss = "save:popup-dismiss", children = { Z.btn("PNG", "save:png"), Z.btn("JPG", "save:jpg"), }, }) ``` ## Notes - The `anchor` is required — the wrapper asserts and errors loudly on nil rather than silently failing. - Pivot maps to egui's `RectAlign`: `"belowLeft"` → BOTTOM_START (default), `"belowRight"` → BOTTOM_END, `"aboveLeft"` → TOP_START, `"aboveRight"` → TOP_END. - Auto-dismiss fires `onDismiss` on click-outside / Escape so the caller can mirror the new state. Falls back to `<id>-dismiss` if `onDismiss` is omitted. - The Lua-facing key is `anchor`; the underlying Rust prop is `anchorTo` (egui already claims `anchor` for an enum). The wrapper bridges the two names automatically.
▲ 0↑ born
▣
module · born here
❒asset
# progressBar Horizontal progress bar — a Luau builder over `canvas` with `fillWidth` + `"N%"` coords. Emits two stacked `kind = "rect"` commands: a full-width track plus a percent-of-width fill clipped to the value. `value` is clamped to `[0, 1]` so out-of-range inputs don't paint outside the canvas. ## Exports - `progressBar(value: number?, opts: Opts?) -> any` — build the canvas widget node. Returned directly by the module. Types: - `Style = { height?: number, color?: string, borderRadius?: number }` - `Opts = { id?: string, classes?: any, color?: string, trackColor?: string, style?: Style }` ## Usage ```luau local progressBar = require("@builtin::modules.zui.widget.progressBar") local widget1 = progressBar(0.42) local widget2 = progressBar(value, { color = "#7BC97B", trackColor = "#222" }) ``` ## Notes - A11y: the canvas declares `role = "progressbar"` and ARIA value attrs (`ariaValueNow`, `ariaValueMin = 0`, `ariaValueMax = 1`) so the DOM mirror exposes the same shape as a native `<progress>`. - `style.borderRadius` defaults to `height / 2` for a pill silhouette. Override to 0 for a sharp-cornered look. - The fill rect's `max.x` is a `"N%"` string — width resolves at paint time via `fillWidth = true`, so the builder doesn't need to know the canvas's pixel width at construction time.
▲ 0↑ born
▣
module · born here
❒asset
# radioGroup Luau builder for a single-select radio group composed of focusable per-row canvases inside a panel. Each row draws its own background highlight + circle glyph + label; click commits the selection, arrow-keys cycle it. Wraps the rows in a panel with `role="radiogroup"` so assistive tech reads it as a group. ## Exports - `radioGroup(id: string, options: { any }?, selected: number?, opts: RadioGroupOpts?) -> any` — build a radio-group widget. The module returns this function directly. Types: - `RadioGroupOpts = { style: { [string]: any }?, classes: (string | { string })?, rowWidth: number?, rowHeight: number?, onChange: string?, app: any?, ariaLabel: string?, label: string? }` ## Usage ```luau local radioGroup = require("@builtin::modules.zui.widget.radioGroup") radioGroup("difficulty", { "Easy", "Normal", "Hard" }, state.diff, { onChange = "ui:diff:change", }) ``` ## Notes - Single-select; `selected` is a 1-based index. The first call seeds `ui.widgetState(id, "selected")` from the caller's arg; subsequent ticks read state back from there. - Keyboard nav (per-row canvas `onKey`): `ArrowDown` / `ArrowRight` next, `ArrowUp` / `ArrowLeft` previous, `Home` -> 1, `End` -> `#options`. Tab / Shift+Tab walk the per-row canvases via egui's focus chain. Click also commits selection. - `opts.onChange` is dispatched as a callback id on the surrounding `Z.app` router with `data.value` set to the new 1-based index. Without an app in scope, only `ui.widgetState` updates. - Handlers are registered once per group id (deduped via `app._radioGroupHandlers`); option-count and `onChange` swaps land without re-registering.
▲ 0↑ born
▣
module · born here
❒asset
# richText Read-only multi-colored text widget. Pass a flat list of `{ text, color, italic?, monospace? }` segments and the renderer builds a single-flow `LayoutJob` from them — no edit buffer, no cursor, no input handling. The same segment shape is consumed by `TextInput` when `props.segments` is set; `Z.codeEditor` builds segments via the highlighter modules under `zui.highlight.*` and passes them to `TextInput`. ## Exports - `richText(segments: { RichTextSegment }?, opts: RichTextOpts?) -> any` — build a styled-text widget. The module returns this function directly. Types: - `RichTextSegment = { text: string, color: string?, italic: boolean?, monospace: boolean? }` - `RichTextOpts = { id: string?, classes: (string | { string })?, style: { [string]: any }? }` ## Usage ```luau local richText = require("@builtin::modules.zui.widget.richText") richText({ { text = "hello ", color = "#fff" }, { text = "world", color = "#0E639C" }, }) ``` ## Notes - Read-only: no edit buffer, no cursor, no input handling. Use `Z.codeEditor` or `TextInput` directly when text needs to be editable. - The renderer produces a single-flow `LayoutJob` from the segments, so a mid-sentence color change does not break line wrapping.
▲ 0↑ born
▣
module · born here
❒asset
# rightPanel Right-docked panel. Root-only — must be the top-level widget of its registered screen. ## Exports - `rightPanel(children: { any }?, opts: RightPanelOpts?) -> any` — build a right-docked panel widget. The module returns this function directly. Types: - `RightPanelOpts = { id: string?, classes: (string | { string })?, class: (string | { string })?, props: { [string]: any }?, style: { [string]: any }? }` ## Usage ```luau local rightPanel = require("@builtin::modules.zui.widget.rightPanel") rightPanel({ child1, child2 }) ``` ## Notes - Root-only: must be the top-level widget of its registered screen. Nesting a `rightPanel` inside another container is unsupported.
▲ 0↑ born
▣
module · born here
❒asset
# scene Pan/zoom 2D viewport. Wraps `egui::containers::Scene` (added 0.34). Children render inside a transformed coordinate space — mouse-wheel zooms, primary-drag pans (right-click stays free for context menus). Transform state persists in egui's per-id temp data, so pan/zoom carries across frames without caller plumbing. ## Exports - `scene(opts: SceneOpts?) -> any` — build a pan/zoom 2D viewport widget. The module returns this function directly. Types: - `SceneOpts = { id: string?, props: { [string]: any }?, style: { [string]: any }?, children: { any }?, zoomRange: { number }?, initialPan: { x: number, y: number }?, initialZoom: number?, onTransformChange: string?, focusable: boolean? }` ## Usage ```luau local scene = require("@builtin::modules.zui.widget.scene") scene({ zoomRange = { 0.25, 4.0 }, onTransformChange = "graph:moved", children = { Z.canvas({ commands = drawNodes(state) }), Z.area({ id = "node-1", pos = nodePos[1] }, { ... }), }, }) ``` ## Notes - `zoomRange` defaults to `[0.25, 4.0]` (overriding egui's default of `0.0..=1.0` so wheel-zoom works in both directions). - `initialPan` and `initialZoom` only apply on the first frame; once the user pans or zooms the persisted rect takes over. - `onTransformChange` payload is a comma-separated string `"panX,panY,zoom"` — UiValue is scalar-only. Parse with `string.split(value, ",")`. Falls back to `<id>-transform` when not set. - Pan is primary-button only — right-click stays free for `Z.contextMenu` handlers on inner widgets.
▲ 0↑ born
▣
module · born here
❒asset
# scroll Scrollable container. Pass `maxHeight` (or `maxWidth`) to bound the visible region; children beyond that get scrolled. Top-level shortcuts `maxHeight` / `maxWidth` / `minHeight` / `minWidth` / `stickToBottom` merge into `style` / `props` so the common case doesn't require nesting under `style = { ... }`. ## Exports - `scroll(children: any?, opts: ScrollOpts?) -> any` — build a scrollable container. The module returns this function directly. Types: - `ScrollOpts = { id?, classes?, props?, style?, maxHeight?, maxWidth?, minHeight?, minWidth?, stickToBottom? }` ## Usage ```luau local scroll = require("@builtin::modules.zui.widget.scroll") -- Shortcut form (preferred): scroll(rows, { maxHeight = 200 }) -- Equivalent verbose form: scroll(rows, { style = { maxHeight = 200 } }) ``` For tall content inside `Z.panel`, prefer `Z.panel(rows, { scroll = true, maxHeight = N })` — see the [panel README](../panel.module/README.md) for the pattern documented under issue #3270. ## Notes - Either a single widget table or an array of widgets is accepted as `children` — single widgets do not need to be wrapped in `{ ... }` (closes #2331). - Without a `maxHeight` / `maxWidth` (top-level or `style.*`), the scroll area fills the parent's available rect and scrolls on overflow (renderer applies `auto_shrink([false, false])` in that case). With a bound, the scrollArea sizes to it and scrolls past that bound.
▲ 0↑ born
▣
module · born here
❒asset
# section Panel with a bold header label on top. Convenience composition — every demo had its own version before this widget existed. The header style defaults to a small accent label; override via `opts.headerStyle`. ## Exports - `section(title: any, children: { any }?, opts: SectionOpts?) -> any` — build a panel with a bold header label. The module returns this function directly. Types: - `SectionOpts = { id: string?, classes: (string | { string })?, props: { [string]: any }?, style: { [string]: any }?, headerStyle: { [string]: any }?, titleColor: string?, bg: string?, border: string?, borderWidth: number?, padding: any?, gap: number?, minWidth: number?, minHeight: number?, maxWidth: number?, maxHeight: number? }` ## Usage ```luau local section = require("@builtin::modules.zui.widget.section") section("Settings", { Z.label("hello") }) ``` ## Notes - The `title` is coerced via `tostring`, so non-string values are accepted as headers. - `opts.headerStyle` fully replaces the default header style — pass a table containing every field you want set, not a partial override. - All standard panel options (`bg`, `border`, `padding`, ...) flow through to the wrapping panel.
▲ 0↑ born
▣
module · born here
❒asset
# selectableList Luau builder for a single-select listbox composed of focusable per-row canvases inside a panel. Same shape as `Z.radioGroup`, but rows draw no glyph — they're highlight-on-selection labels — and the wrapping panel declares `role="listbox"` / row `role="option"` so screen readers read it as a single-select list. ## Exports - `selectableList(id: string, options: { any }?, selected: number?, opts: SelectableListOpts?) -> any` — build a listbox widget. The module returns this function directly. Types: - `SelectableListOpts = { style: { [string]: any }?, classes: (string | { string })?, rowWidth: number?, rowHeight: number?, onChange: string?, app: any?, ariaLabel: string?, label: string? }` ## Usage ```luau local selectableList = require("@builtin::modules.zui.widget.selectableList") selectableList("category", CATEGORIES, state.cat, { onChange = "ui:cat:change", }) ``` ## Notes - Single-select; `selected` is a 1-based index. The first call seeds `ui.widgetState(id, "selected")` from the caller's arg; subsequent ticks read state back from there. - Keyboard nav (per-row canvas `onKey`): `ArrowDown` / `ArrowRight` next, `ArrowUp` / `ArrowLeft` previous, `Home` -> 1, `End` -> `#options`. Tab walks the per-row canvases via egui's focus chain. - For sidebar / asset-browser / category-picker patterns where every option is visible at once. For long lists where only a few items fit, wrap the result in `Z.scroll(...)`. - `opts.onChange` is dispatched as a callback id on the surrounding `Z.app` router with `data.value` set to the new 1-based index. Without an app in scope, only `ui.widgetState` updates. - Handlers are registered once per group id (deduped via `app._selectableListHandlers`); option-count and `onChange` swaps land without re-registering.
▲ 0↑ born
▣
module · born here
❒asset
# sep Horizontal separator — a Luau builder over `canvas` with `fillWidth` + `"N%"` coords. Emits a single `kind = "line"` command spanning `[0, mid]` -> `["100%", mid]`. `fillWidth = true` makes the canvas claim the parent's full available width. 4 px of vertical breathing room is baked in (thickness + 4) so the line has a clear top/bottom gap. ## Exports - `sep(opts: SepOpts?) -> any` — build a horizontal-line separator widget. The module returns this function directly. Types: - `SepOpts = { id: string?, classes: (string | { string })?, style: { [string]: any }? }` ## Usage ```luau local sep = require("@builtin::modules.zui.widget.sep") sep() sep({ style = { color = "#0E639C", height = 2 } }) ``` ## Notes - Style overrides: `style.height` (default 1.0) sets line thickness in pixels, `style.color` (default `#3C3C3C`) sets line color. - Pure builder — no side effects, no state.
▲ 0↑ born
▣
module · born here
❒asset
# sides Two-slot horizontal row that anchors the first child left, the second child right, and stretches a flexible gap between them. Designed for window title bars, toolbars, status footers — any "title left, controls right" pattern. ## Exports - `sides(opts: SidesOpts?) -> any` — build a two-slot left/right row. The module returns this function directly. Types: - `SidesOpts = { left: any?, right: any?, [number]: any, id: string?, style: { [string]: any }? }` ## Usage ```luau local sides = require("@builtin::modules.zui.widget.sides") -- Positional shape sides({ leftWidget, rightWidget }) -- Named-slot shape sides({ left = Z.hbox({ Z.lbl("◆"), Z.lbl("zero") }), right = Z.hbox({ Z.iconBtn("─"), Z.iconBtn("□"), Z.iconBtn("✕") }), }) ``` ## Notes - When more than two widgets are needed on either side, wrap them in a layout container first (`Z.hbox` / `Z.vbox`). - Implemented as an `hbox` containing `[left, flex(), right]`; the flex-grow spacer makes the right slot hug the row's right edge. - Children render in source order on both sides.
▲ 0↑ born
▣
module · born here
❒asset
# slider Horizontal slider for a numeric value in `[min, max]`. Emits the given `onChange` callback id with `data.value` set to the new value. Pass `step` to constrain to discrete increments. ## Exports - `slider(id: string, value: number?, lo: number?, hi: number?, opts: SliderOpts?) -> any` — build a horizontal slider widget. The module returns this function directly. Types: - `SliderOpts = { props: { [string]: any }?, style: { [string]: any }?, onChange: string?, step: number? }` ## Usage ```luau local slider = require("@builtin::modules.zui.widget.slider") slider("volume", 0.5, 0, 1, { onChange = "ui:volume:change", step = 0.05 }) ``` ## Notes - `value`, `lo`, `hi` all default to `0`, `0`, `1` respectively when nil. - `onChange` callbacks fire with `data.value` set to the new numeric value; route them via `component.onCallback` or a `Z.app` router.
▲ 0↑ born
▣
module · born here
❒asset
# spacer Fixed-size gap widget. Default 4 px. Use `Z.flex()` for a flex-grow spacer that pushes siblings apart. ## Exports - `spacer(n: number?) -> any` — build a fixed-size spacer widget. The module returns this function directly. ## Usage ```luau local spacer = require("@builtin::modules.zui.widget.spacer") spacer() -- 4 px gap spacer(12) -- 12 px gap ``` ## Notes - Pure builder — no side effects, no state. Returns a widget table the zui renderer understands directly. - Use `Z.flex()` when you need a spacer that grows to fill remaining space rather than a fixed size.
▲ 0↑ born
▣
module · born here
❒asset
# split Two-pane resizable split. `direction` is `"horizontal"` or `"vertical"`; `firstSize` is the first pane's size in pixels (or `0..1` for proportional). Children must be exactly two widgets. ## Exports - `split(children: { any }?, opts: SplitOpts?) -> any` — build a two-pane resizable split widget. The module returns this function directly. Types: - `SplitOpts = { id: string?, direction: string?, firstSize: number?, props: { [string]: any }?, style: { [string]: any }? }` ## Usage ```luau local split = require("@builtin::modules.zui.widget.split") split({ leftWidget, rightWidget }, { direction = "horizontal", firstSize = 200, }) ``` ## Notes - `direction` defaults to `"horizontal"` when omitted. - `firstSize` accepts pixels (>= 1) or a proportion in `0..1`. - Children must be exactly two widgets — wrap multiple widgets in a layout container if more pane content is needed.
▲ 0↑ born
▣
module · born here
❒asset
# statRow Label-value row — `[label] [value]` — with consistent label width, theme-token defaults, and an optional value class/color. Used wherever a stats panel hand-rolls a tiny `Z.hbox(label, value)` (runtime / profiler / physics tabs). `value` may be a string/number (rendered as a label) or a widget table (rendered as-is — e.g. `Z.statusLabel`). ## Exports - `statRow(labelText: any, value: any, opts: StatRowOpts?) -> any` — build a label-value row widget. The module returns this function directly. Types: - `StatRowOpts = { id: string?, class: (string | { string })?, fontSize: number?, labelWidth: number?, labelMinWidth: number?, labelColor: string?, labelClass: (string | { string })?, valueColor: string?, valueClass: (string | { string })?, bold: boolean?, gap: number?, align: string?, padding: any? }` ## Usage ```luau local statRow = require("@builtin::modules.zui.widget.statRow") statRow("Heap allocated", "12.4 MB", { labelWidth = 140, valueClass = "debug-good", fontSize = 11, }) statRow("Frame", Z.statusLabel(dtMs, { good = 16, warn = 33 })) ``` ## Notes - Label width defaults to 140 px, font size to 11. - Label / value colors fall back to the active theme's `text_dim` / `text` tokens respectively. - The value slot accepts a widget table directly, which is the idiomatic way to inline status-coloured values.
▲ 0↑ born
▣
module · born here
❒asset
# statusLabel Numeric label that auto-picks a class by threshold ranges. The caller passes `{ good = X, warn = Y }` (sorted ascending). Values below `good` get the `good` class; below `warn` get `warn`; the rest get `bad`. Non-numeric values get the `bad` class so a missing/erroring data source is visually loud. Defaults to the engine theme's `debug-timing-good/warn/bad` classes shipped in `themes/debug.yaml`. ## Exports - `statusLabel(value, thresholds, opts?) -> Node` — returns a coloured label widget. Module returns the builder function directly. Types: - `StatusClasses = { good: string, warn: string, bad: string }` - `StatusThresholds = { good: number?, warn: number? }` - `StatusLabelOpts = { id?, classes?, ratioOf?, fmt?, bold?, color?, fontSize? }` ## Usage ```luau local statusLabel = require("@builtin::modules.zui.widget.statusLabel") statusLabel(dtMs, { good = 16, warn = 33 }) statusLabel(used, { good = 0.8, warn = 0.95 }, { ratioOf = max, fmt = function(v) return string.format("%.1f%%", v * 100) end, }) ``` ## Notes - Pass `ratioOf` to compare `value / ratioOf` against the thresholds while still feeding the *raw* value to `fmt`. Non-positive `ratioOf` values are ignored. - Pass custom `classes = { good, warn, bad }` to retarget styling. The default classes live in `themes/debug.yaml`. - Non-numeric `value` always renders in the `bad` class so a broken data source is visually loud.
▲ 0↑ born
▣
module · born here
❒asset
# svg Inline vector graphics widget. Wraps a single `svg` node that rasterizes an SVG document at the element's display resolution, so it stays crisp from a 16px line icon to a 600px chart. Sizing comes from `style.width` / `style.height`; `opts.fit` chooses the viewBox mapping. ## Exports - `svg(source: string?, opts: SvgOpts?) -> WidgetNode` — module returns the builder function directly. Options: - `id: string?` — widget id. - `style: table?` — passes through to the underlying node; supply `width`/`height` here, and `color` to ink `fill="currentColor"`. - `fit: string?` — viewBox mapping: `"meet"` (default, uniform + letterbox), `"none"`/`"stretch"` (fill both axes), `"slice"` (uniform + crop). `preserveAspectRatio` keywords (e.g. `"xMidYMid meet"`) are accepted too. - `src: string?` — render a `.svg` texture-asset reference instead of an inline `source` string. ## Usage ```luau local Z = require("@builtin::modules.zui") -- Inline document; currentColor inks the element's CSS color. Z.svg("<svg viewBox='0 0 24 24'><path d='M4 12h16' stroke='currentColor' stroke-width='2'/></svg>", { style = { width = 24, height = 24, color = "#3b82f6" }, }) -- From a .svg asset file. Z.svg(nil, { src = "@builtin::icons.logo", style = { width = 64, height = 64 } }) ``` ## Notes - Stateless — returns a fresh widget table each call. - `fill="currentColor"` resolves to the element's CSS `color`; `fill`/`stroke` gradients, the full path grammar, basic shapes, `viewBox`, `preserveAspectRatio`, and `<g>` grouping all render. - Pass either an inline `source` string or `opts.src` — `source` is the document text, `src` points at a `.svg` asset file. - Rasterization happens at the resolved display size, so scaling the widget re-renders sharp rather than sampling a fixed bitmap.
▲ 0↑ born
▣
module · born here
❒asset
# tabs Tab strip — a horizontal row of buttons with one highlighted as active. Each tab emits the callback id `<onChange>-<key>` so a single pattern handler can capture every click and update the active tab in state. Under a `Z.app` context the strip also intercepts arrow-key navigation (with wrap-around, skipping disabled tabs) and dispatches the same event a click would. ## Exports - `tabs(items, activeKey, onChange, opts?) -> Node` — returns the tab-strip widget node. Module returns the builder function directly. Types: - `TabItem = { key: string, label?: string, icon?: string, enabled?: boolean }` - `TabsOpts = { id?, app?, padding?, gap?, minTabWidth?, containerPadding?, style? }` ## Usage ```luau local tabs = require("@builtin::modules.zui.widget.tabs") local items = { { key = "entities", label = "Entities" }, { key = "logs", label = "Logs" }, } tabs(items, state.activeTab, "main-tabs") app:on("^main%-tabs%-(.+)$", function(_v, _id, key) state.activeTab = key end) ``` ## Notes - The wrapper emits `{ type = "tabs", ... }`; the `Z.defineWidget("tabs", ...)` registration in `zui.module/init.luau` decodes that into the primitive `hbox` + `btn` tree the Rust renderer sees. - `activeKey` and `items` are mirrored into `widgetState` so the per-strip key handler reads the live values at event time — closures never go stale across re-renders. - Tab itself stays the focus-traversal key (egui's built-in cycle); only arrow keys are intercepted by the strip.
▲ 0↑ born
▣
module · born here
❒asset
# ZuiTerminal A terminal pane widget. Renders the live vt100 grid of a terminal from the engine's terminal registry (created with `terminal.create`, run with `terminal.spawn`) inside the UI tree. Captures the keyboard while focused; the scroll wheel and Shift+PageUp/PageDown page the scrollback. Backed by a host PTY, so terminals run on native targets.
▲ 0↑ born
▣
module · born here
❒asset
# toggle iOS-style on/off switch — a Luau builder over `canvas`. Draws a rounded pill background, a white circle knob sliding along its axis, and an optional text label next to it. Operable by mouse (click) and keyboard (Tab to focus, Enter/Space to flip). ## Exports - `toggle(label, id, checked, opts?) -> Node` — returns the toggle widget node. Module returns the builder function directly. Types: - `ToggleOpts = { style?, width?, height?, onColor?, offColor?, knobColor?, onChange?, classes?, ariaLabel?, app? }` ## Usage ```luau local toggle = require("@builtin::modules.zui.widget.toggle") toggle("Lights on", "lights", state.lights, { onChange = "lights:toggle" }) -- Without onChange, the callback id defaults to the widget id: toggle("Bypass", "fx:bypass", state.bypass) ``` ## Notes - Under `Z.app` the internal `<id>:click` handler reads `widgetState(id, "checked")`, flips it, and dispatches `onChange` (or the widget id) with `{ value = newBool }`. The Router unpacks `data.value` so `function(v) end` receives the new bool. - Without `Z.app` the canvas's `onClick` routes directly to the caller's callback id and the legacy `onCallback(id, _)` pattern (caller flips its own local state) keeps working. - DOM mirror surfaces as `<canvas role="switch" aria-checked="…">` for screen-reader support.
▲ 0↑ born
▣
module · born here
❒asset
# topPanel Top-docked panel. Root-only. Pairs with `centralPanel` (and optionally `bottomPanel`/`leftPanel`/`rightPanel`) for a docked app shell — register each as its own screen with appropriate layer ordering. ## Exports - `topPanel(children, opts?) -> Node` — returns the top-panel widget node. Module returns the builder function directly. Types: - `TopPanelOpts = { id?, classes?, props?, style? }` ## Usage ```luau local topPanel = require("@builtin::modules.zui.widget.topPanel") topPanel({ menubar, breadcrumb }) ``` ## Notes - Must be the top-level widget of the screen it's registered to — nested topPanels render incorrectly. - Pair with `centralPanel`/`bottomPanel`/`leftPanel`/`rightPanel` for full docked-shell layouts; each lives on its own screen so their layer ordering controls the dock.
▲ 0↑ born
▣
module · born here
❒asset
# tree Recursive expandable tree. Each node renders as a clickable row (chevron + optional icon + label, indented by `depth * indentPx`, with optional right-side action buttons). Clicking the chevron emits `<onToggle>-<key>`; clicking the label emits `<onSelect>-<key>`. The tree itself is stateless — toggle/select state lives in the caller's state. ## Exports - `tree(nodes, opts?) -> Node` — render a list of root nodes into a vbox of rows. Reached via the module's `__call` metamethod. - `tree.fromFlat: (items, opts?) -> (roots, nodeById)` — re-exports the `fromFlat` sub-module that builds recursive nodes from a flat parent-pointer list. Node shape: - `{ key, label, children?, expandable?, expanded?, icon?, actions?, payload? }` ## Usage ```luau local Z = { tree = require("@builtin::modules.zui.widget.tree") } local function buildTree() return Z.tree(state.nodes, { onSelect = "ent-select", onToggle = "ent-toggle", selectedKey = state.selectedId, }) end app:on("^ent%-select%-(.+)$", function(_v, _id, key) state.selectedId = key end) app:on("^ent%-toggle%-(.+)$", function(_v, _id, key) state.expanded[key] = not state.expanded[key] end) ``` ## Notes - The module returns a callable table — `tree(...)` works as a function, and `tree.fromFlat(...)` reaches the sub-module. This is a dynamic-dispatch shape; the `typed function` directive is skipped here per the module-conversion spec rule 7. - A row gets a chevron when it has children OR when `expandable == true` (lazy-load: the caller fetches children on the toggle event). - `actions` callback ids are emitted verbatim — no key suffixing — so a row-level action ("× delete") reads as the same callback whether invoked from the tree, a context menu, or a toolbar. - Hard depth cap (32) protects against accidentally cyclic node data.
▲ 0↑ born
▣
module · born here
❒asset
# vbox Vertical layout container. Children stacked top-to-bottom; spacing via `style.gap`; horizontal alignment via `style.align` (`"start"` | `"center"` | `"end"`). ## Exports - `vbox(children, opts?) -> Node` — returns the vertical-layout widget node. Module returns the builder function directly. Types: - `VboxOpts = { id?, classes?, props?, style? }` ## Usage ```luau local vbox = require("@builtin::modules.zui.widget.vbox") vbox({ header, body, footer }, { style = { gap = 8, align = "center" } }) ``` ## Notes - Pairs with `hbox` for horizontal layouts and `wrap`/`grid` widgets for more complex flows. - `style.gap` is the spacing between children, not padding around the container — use `style.padding` for the latter.
▲ 0↑ born
▣
module · born here
❒asset
# viewport Embedded 3D viewport rendered into the UI. `target` is a render-target handle id (paired with the Camera component's `renderTarget` setting). Use for in-UI minimaps, picture-in-picture, scene previewers. ## Exports - `viewport(target, opts?) -> Node` — returns the viewport widget node. Module returns the builder function directly. Types: - `ViewportOpts = { id?, props?, style? }` ## Usage ```luau local viewport = require("@builtin::modules.zui.widget.viewport") viewport(minimapTarget, { style = { width = 200, height = 200 } }) ``` ## Notes - Pair with a Camera component whose `renderTarget` matches `target` — the widget itself doesn't choose what to render, it just displays the target's contents. - Sizing comes from `style.width` / `style.height` (or layout); the target's pixel resolution is set when the render target is created.
▲ 0↑ born
▣
module · born here
❒asset
# window Floating window. Root-only — must be the top-level widget of its registered screen. Pass `movable`, `resizable`, `closable`, `pos` (default position, only honored on first frame), and `onClose` to wire the close button. ## Exports - `window(title, children, opts?) -> Node` — returns the window widget node. Module returns the builder function directly. Types: - `WindowOpts = { id?, movable?, resizable?, closable?, pos?, onClose?, props?, style? }` ## Usage ```luau local window = require("@builtin::modules.zui.widget.window") window("Inspector", { body }, { movable = true, closable = true, onClose = "inspector:close", }) ``` ## Notes - Signature is `window(title, children, opts)`; the older `window(children, opts)` shape now warns loudly via `log.warn` instead of falling back silently to the default `"Window"` header (closes #2342). - The decoder also accepts the raw form `{ type = "window", title = "...", children = {...} }`; `title` is promoted into `props.title` automatically. - `pos` is only honored on first frame — subsequent renders preserve the user's drag position so callers never fight egui for window placement.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# cascade Pure-Luau `$variable` cascade resolver for theme tokens and styles. Walks a theme table's tokens and styles, expands every `$tokenName` reference into a literal value with cycle detection, and returns a flat result. Used by `Z.theme.register` so the engine receives fully-resolved `(token, value)` and `(selector, prop, value)` pairs — no runtime variable indirection. Cycle detection surfaces broken theme inputs as structured `register` errors instead of silently leaving `Variable(name)` in place and producing garbage colors. ## Exports - `M.resolveValue(value, tokens, visited, order) -> (any, string?)` — single-value resolver. Exposed for tests; not part of the typed surface. - `M.resolveTokens(theme: any) -> (TokenMap?, string?)` — flatten `theme.tokens`. Returns `(nil, errMsg)` on first cycle / unknown reference. - `M.resolveStyles(theme: any, resolvedTokens: any) -> (StyleMap?, string?)` — flatten `theme.styles` against pre-resolved tokens. Types: - `TokenMap = { [string]: any }` - `StyleMap = { [string]: { [string]: any } }` - `Theme = { tokens: TokenMap?, styles: StyleMap? }` ## Usage ```luau local Cascade = require("@builtin::modules.zui.theme.cascade") local tokens, err = Cascade.resolveTokens(theme) if err then error(err) end local styles, err2 = Cascade.resolveStyles(theme, tokens) if err2 then error(err2) end -- `tokens` and `styles` now contain no `$variable` strings. ``` ## Notes - Pure module — no engine calls, no module state. Safe to invoke during any phase (boot, test, runtime). - Error messages include the offending token / selector / prop so cascade failures are diagnosable from the structured error alone. - `resolveStyles` resolves against pre-flattened `resolvedTokens` (not raw `theme.tokens`) so chained references (`$a → $b → c`) are already collapsed by the time style values are walked. - Numbers, booleans, and arrays pass through unchanged — only strings beginning with `$` are treated as references.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# dataView.selectionModel Pure selection-interaction algebra for `Z.dataView`. Maps a row click plus modifier state to a new selected-index set and anchor, following the standard desktop model: plain click replaces, ctrl toggles, shift selects the contiguous range from the anchor. ## Exports - `M.resolveClick(index, mods, current, anchor) -> { selected: { number }, anchor: number }` — 1-based indices; `mods` is `{ ctrl?, shift? }`; `current` is the current selected-index array; `anchor` is the current anchor or nil. ## Notes - Pure functions, no engine calls — unit-tested in the `editor_dataview` suite. - Callers translate the returned indices into selection-service refs.
▲ 0↑ born
▣
module · born here
❒asset
# dataView.view Pure view computation for `Z.dataView` — filter then sort an item array into an ordered list of 1-based indices. ## Exports - `M.computeView(items, spec) -> { number }` — `spec` is `{ filter?, sortKey?, sortDir?, textOf, valueOf }`. Filter is a case-insensitive substring over `textOf(item)`; sort compares `valueOf(item, sortKey)` numerically or case-insensitively, ascending or descending, with a stable tiebreak on the original index. ## Notes - Pure functions, no engine calls — unit-tested in the `editor_dataview` suite.
▲ 0↑ born
▣
module · born here
❒asset
# dataView.virtual Pure virtualization window math for `Z.dataView` — given a scroll position and geometry, compute the inclusive range of row indices that are visible. ## Exports - `M.windowFor(scrollTop, viewportH, rowH, count, overscan) -> { first, last }` — 1-based inclusive; `last < first` when the dataset is empty. `overscan` extends the window above and below the visible band. ## Notes - Pure functions, no engine calls — unit-tested in the `editor_dataview` suite. - Backs the row-window rendering; the same math would drive a future construction-virtualization provider.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# breadcrumb Breadcrumb path widget — renders `/zero/runtime/scenes/...` as a row of clickable segments. Each segment emits a callback id carrying the absolute path up to and including that segment, so a single router rule reaches every navigation target. Stateless — caller passes the current absolute path and a callback prefix; receives a hbox. ## Exports - `breadcrumb(path: string, opts: BreadcrumbOpts?) -> WidgetNode` — build a hbox of breadcrumb segments. Home (`⌂`) segment always renders first; each `/`-delimited segment renders as a button whose callback id is `<onNavigate>-<absolute-path>`. Types: - `BreadcrumbOpts = { id: string?, onNavigate: string? }` - `WidgetNode = { [string]: any }` ## Usage ```luau local Z = require("@builtin::modules.zui") local crumb = Z.fs.breadcrumb("/zero/runtime/scenes/", { onNavigate = "files-nav", }) app:on("^files%-nav%-(.+)$", function(_v, _id, path) state.currentPath = path end) ``` ## Notes - Stateless. No state ownership, no engine calls beyond the widget primitives. - A trailing slash on the input path is stripped before splitting unless the whole input is `/`. - The home crumb's callback id is `<onNavigate>-/` so a single router rule (`^files%-nav%-(.+)$`) covers root too.
▲ 0↑ born
▣
module · born here
❒asset
# controller The state half of the generic Explorer. The `Z.fs.*` builders are stateless (data in, widgets out); this controller owns the VFS-backed navigation state and the callback routing that drives them, so both the Files panel and the save/load dialog reuse one model and one router. It operates on a caller-owned `slot` table (so two Explorers — e.g. the panel and a dialog — keep independent state) and a caller-chosen callback `prefix` (so their widgets' callbacks don't collide). The controller caches directory listings, tracks the current directory, the expanded folder set, the selected path, and a file preview. ## API - `M.init(slot, opts?) -> slot` — idempotently seed a controller state slot. `opts.root` defaults to `/zero/`. - `M.model(slot)` — build the render model the `Z.fs.explorer` / `Z.fs.dialog` builders consume: `{ root, currentDir, breadcrumbPath, treeRoots, listEntries, selectedPath, expanded, preview }`. - `M.handle(slot, prefix, callbackId, data?) -> boolean` — route an Explorer callback against this slot; returns true when handled. - `M.invalidate(slot, pathPrefix?)` — drop cached listings under `pathPrefix` (or all when nil) so fresh writes show. - `M.readPreview(path) -> (text|nil, note, size)` — read a file preview; refuses binary, truncates large text. - `M.confirmTarget(slot, mode, filename) -> path|nil` — resolve the absolute target a dialog confirm should act on. In `"save"` mode it's `currentDir .. filename` (nil if filename is empty); in `"open"` mode it's the selected file (nil if nothing or a folder is selected). ## Callback grammar All callbacks carry the absolute path verbatim: - `<prefix>-nav-<path>` — breadcrumb segment - `<prefix>-tree-select-<path>` — tree row - `<prefix>-tree-toggle-<path>` — tree chevron - `<prefix>-list-<d|f>-<path>` — details-list row (`d` = dir, `f` = file) - `<prefix>-up` — parent directory - `<prefix>-refresh` — drop cache for the visible subtree ## Usage ```luau local fsc = require("modules.zui.widget.fs.controller") fsc.init(state.files, { root = "/zero/" }) local model = fsc.model(state.files) local widget = Z.fs.explorer(model, { prefix = "files" }) -- in onCallback: if fsc.handle(state.files, "files", callbackId, data) then end ```
▲ 0↑ born
▣
module · born here
❒asset
# detailsList The right pane of the two-pane Explorer: a details list of one directory's entries with an icon, name, and kind column. Folders sort first (warm accent), then files. Stateless — the caller passes the entries (each already carrying its absolute `path`) and a callback prefix, and receives a scrollable column of clickable rows. A row click emits `<onActivate>-<d|f>-<path>` — the `d`/`f` marker tells the handler folder-vs-file without a second lookup, and the absolute path is carried verbatim. Each row shows a phosphor icon, a clickable name button, and a kind column derived from the file extension. The module returns the builder function directly. ## Builder `build(entries?, opts?) -> widget node`. - `entries` — array of `{ name, isDirectory?, path }` (path = the entry's absolute path). - `opts` (`DetailsListOpts`) — `id`, `onActivate` (callback prefix; rows emit `<onActivate>-<d|f>-<path>`), `selectedPath` (the highlighted row), `maxHeight` (caps the scroll height; otherwise the pane fills available height via flex), `showHeader` (the Name · Kind header row, default on). Returns a scroll-wrapped column with an optional header row and one row per entry; an empty list renders `(empty)`. ## Usage ```luau Z.fs.detailsList(entries, { onActivate = "files-list", selectedPath = state.selectedPath, }) ```
▲ 0↑ born
▣
module · born here
❒asset
# dialog A modal save / load file picker: the same two-pane Explorer (`Z.fs.explorer`) inside a `Z.modal`, plus a filename field (save mode) and Open/Save + Cancel buttons. The panel and the dialog share one Explorer and one controller, so navigating feels identical in both. Stateless builder: pass the controller model and the dialog options; the host owns `open`, the filename string, and the confirm/cancel wiring. The module returns the builder function directly. ## Builder `build(model, opts?) -> widget node` (a `Z.modal` tree; renders nothing when `open` is false). - `model` — the `Z.fs.controller.model(slot)` result for the dialog's own controller slot. - `opts` (`DialogOpts`) — `prefix` (callback prefix, default `"fsdlg"`), `open` (modal visibility, host-owned), `title`, `mode` (`"save"` for a filename field, `"open"` default), `filename` (save-mode field value), `confirmLabel` (defaults to "Save"/"Open" by mode), `width` (default 640), `height` (default 460), `treeFrac`, `id`. ## Callbacks Beyond the Explorer's `<prefix>-*` navigation: - `<prefix>-filename` — filename field changed (value in the event payload) - `<prefix>-confirm` — Open/Save pressed - `<prefix>-cancel` — Cancel pressed or the modal dismissed ## Usage ```luau -- host state: state.dlg = {} (controller slot), open, filename Z.fs.dialog(Z.fs.controller.model(state.dlg), { prefix = "savedlg", open = state.open, mode = "save", title = "Save Layout", filename = state.filename, confirmLabel = "Save", }) -- host onCallback: -- Z.fs.controller.handle(state.dlg, "savedlg", cb, data) -- navigation -- "savedlg-filename" → state.filename = Z.eventValue(data) -- "savedlg-confirm" → path = Z.fs.controller.confirmTarget(state.dlg, "save", state.filename) -- "savedlg-cancel" → state.open = false ```
▲ 0↑ born
▣
module · born here
❒asset
# explorer A two-pane filesystem Explorer composite: a breadcrumb plus up/refresh actions on top, a folder tree on the left, and a details list (icon · name · kind) of the current directory on the right. Stateless — pass the model from `Z.fs.controller.model(slot)` and a callback `prefix`; the controller's `handle(slot, prefix, ...)` routes the callbacks this emits. The same composite is the body of `Z.fs.dialog` (the modal save/load picker), so the panel and the dialog look and behave identically. The explorer fills its parent's height via flex, so the host panel just needs to give it room. The module returns the builder function directly. ## Builder `build(model, opts?) -> widget node`. - `model` — the `Z.fs.controller.model(slot)` result: `{ root, currentDir, breadcrumbPath, treeRoots, listEntries, selectedPath, expanded }`. - `opts` (`ExplorerOpts`) — `prefix` (callback prefix, default `"fs"`), `treeFrac` (tree pane fraction of width, 0..1, default 0.4), `id`. Returns a vbox of the action row (up + refresh + breadcrumb) and a resizable horizontal split of the tree and details-list panes. ## Usage ```luau local model = Z.fs.controller.model(state.files) Z.fs.explorer(model, { prefix = "files", treeFrac = 0.4 }) ```
▲ 0↑ born
▣
module · born here
❒asset
# fileList Flat row list of files in a directory. Folders first (orange), then files (selected file highlighted). Stateless — caller passes the entries array `[{name, isDirectory}]` and a callback prefix; receives a scrollable vbox of clickable rows. Each click emits `<onSelect>-<index>` where index is 1-based into the *original* entries array, so the caller doesn't need a sorted-vs-original map. ## Exports - `fileList(entries: { FileEntry }?, opts: FileListOpts?) -> WidgetNode` — build a scrollable vbox of rows. Folders style differently from files; the selected file row is highlighted. Types: - `FileEntry = { name: string, isDirectory: boolean? }` - `FileListOpts = { id: string?, onSelect: string?, selectedKey: string?, sort: string?, maxHeight: number? }` - `WidgetNode = { [string]: any }` ## Usage ```luau local Z = require("@builtin::modules.zui") return Z.fs.fileList(state.entries, { onSelect = "files-row", selectedKey = state.selectedName, sort = "dirs_first", -- or "alpha" }) app:on("^files%-row%-(%d+)$", function(_v, _id, idx) state.selectedName = state.entries[tonumber(idx)].name end) ``` ## Notes - Stateless. The caller owns the entries array and the selection key. - The callback id carries an index (1-based) rather than the file name, so unusual characters in paths can't break the router. - `sort` defaults to `"dirs_first"` (folders alphabetically, then files alphabetically). `"alpha"` ignores type. Any other value preserves the caller's order. - An empty entries array produces a single `(empty directory)` label.
▲ 0↑ born
▣
module · born here
❒asset
# preview File preview pane — header, optional note line ("Size: 12345 bytes" / "Truncated: ..."), separator, and the body. Body is monospace text when `text` is non-nil, or a "(no preview)" line when nil. Stateless: caller decides what `text`/`note` should be by reading the file (e.g. via `vfs.read`). ## Exports - `preview(data: PreviewData?, opts: PreviewOpts?) -> WidgetNode` — build the preview pane. When all three `data` fields are nil, renders a "Select a file to preview." placeholder. Types: - `PreviewData = { name: string?, text: string?, note: string? }` - `PreviewOpts = { id: string?, scrollHeight: number?, onCopyPath: string? }` - `WidgetNode = { [string]: any }` ## Usage ```luau local Z = require("@builtin::modules.zui") return Z.fs.preview({ name = state.name, text = state.text, note = "Size: " .. tostring(state.size) .. " bytes", }, { id = "files-preview", scrollHeight = 480, onCopyPath = "files-copy", }) ``` ## Notes - Stateless. Caller owns the file read and decides what `text` and `note` should be (truncation messaging, byte counts, hexdumps, etc. are all the caller's job). - `scrollHeight` defaults to 480; only applies when `data.text` is non-nil (the body is wrapped in a `scroll` container). - The copy-path button is only rendered when `opts.onCopyPath` is set; the click emits that callback id verbatim so the caller routes it.
▲ 0↑ born
▣
module · born here
❒asset
# tree Folder tree composite. Wraps `Z.tree` with folder-friendly defaults: folder icon for directories, file icon for leaves, callback ids that carry the absolute path. Stateless — caller owns the recursive node table. Use this when you want a single deep tree of folders + files; for the classic two-pane ("folder tree | file list") layout, use this for the left pane and `Z.fs.fileList` for the right. ## Exports - `tree(roots: { FsNode }?, opts: FsTreeOpts?) -> WidgetNode` — build a recursive folder tree. Callback ids: `<onSelect>-<path>` (label click), `<onToggle>-<path>` (chevron click). Types: - `FsNode = { name: string, path: string?, isDirectory: boolean?, children: { FsNode }?, expanded: boolean? }` - `FsTreeOpts = { id: string?, onSelect: string?, onToggle: string?, selectedKey: string?, expanded: { [string]: boolean }?, basePath: string?, indentPx: number?, gap: number? }` - `WidgetNode = { [string]: any }` ## Usage ```luau local Z = require("@builtin::modules.zui") return Z.fs.tree(state.roots, { onSelect = "files-tree-select", onToggle = "files-tree-toggle", selectedKey = state.selectedPath, expanded = state.expandedSet, }) app:on("^files%-tree%-select%-(.+)$", function(_v, _id, path) state.selectedPath = path end) app:on("^files%-tree%-toggle%-(.+)$", function(_v, _id, path) state.expandedSet[path] = not state.expandedSet[path] end) ``` ## Notes - Stateless. Caller owns selection, expansion, and on-demand listing (the `onToggle` handler is where you'd `vfs.list` and refill `children` for the next render). - Folders without listed children (`children == nil`) still get a chevron via `expandable = true`. This is the lazy-load shape. - `basePath` defaults to `/`. Used when an `FsNode` has no `path` — the recursive walk builds the absolute path on the fly.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# diagnosticDetail Detail pane for a single LSP diagnostic. Renders a severity / code header, an optional `file:line:col` row (with optional copy-path button), the message and suggestion, and an optional source snippet around the offending line. Stateless builder — pass a diagnostic in, get a widget tree out. ## Exports - `diagnosticDetail(d: Diagnostic?, opts: DiagnosticDetailOpts?) -> WidgetNode` — module returns the builder function directly. Also reachable via `Z.lsp.diagnosticDetail`. Options: - `id: string?` — id forwarded to the outer panel. - `source: string?` — full file contents. Enables the snippet panel. - `snippetContext: number?` — lines of context around the focused line. Default `2`. - `onCopyPath: string?` — callback prefix for the copy-path button. Emits `<prefix>-copy` when clicked. Omit to hide the button. `d` shape (from `lsp.check*`): `{ severity, code?, path?, line?, col?, message?, suggestion? }`. ## Usage ```luau local Z = require("@builtin::modules.zui") Z.lsp.diagnosticDetail(diags[selected], { source = source, onCopyPath = "lsp:path", }) ``` ## Notes - When `d` is `nil`, renders a placeholder panel ("Select a diagnostic to see details.") rather than crashing. - The widget never copies to the clipboard itself — it only emits a callback. The parent app decides what "copy" means in its host (some have `ui.copy(text)`, others log to stdout). - The snippet renders only when both `opts.source` and `d.line` are provided. - Severity colours come from `Theme.default` (`danger`, `warn`, `info`, `text_dim`); unknown severities fall back to `theme.text`.
▲ 0↑ born
▣
module · born here
❒asset
# diagnosticsList Scrollable list of LSP diagnostics. Input shape matches the array returned by `lsp.check()` / `lsp.checkAll().diagnostics` / `lsp.checkDirty()`. Each row shows a severity dot, location (`path:line:col`), code chip, and message. Clicking a row emits the callback id `<onSelect>-<idx>` (1-based) so the owning app can route it back into its selection state. ## Exports - `diagnosticsList(diags: { Diagnostic }?, onSelect: string?, opts: Opts?) -> any` — build the scrollable widget. Returned directly by the module. Types: - `Diagnostic = { severity?: string, line?: number, col?: number, code?: string, message?: string, suggestion?: string, path?: string }` - `Filter = { severity?: string, code?: string, pathSubstr?: string }` - `Opts = { id?: string, filter?: Filter, maxRows?: number, messageMax?: number, maxHeight?: number, emptyMessage?: string }` ## Usage ```luau local diagList = require("@builtin::modules.zui.widget.lsp.diagnosticsList") local widget = diagList(lsp.check(), "lsp-row", { filter = { severity = "error" }, maxRows = 50, maxHeight = 320, }) app:on("^lsp%-row%-(%d+)$", function(_v, _id, idx) state.selected = tonumber(idx) end) ``` ## Notes - `opts.maxRows` defaults to 200 — overflow paints a "+N more" footer chip rather than rendering thousands of rows. - Messages are truncated to `opts.messageMax` characters (default 200) with an ellipsis so a single huge diagnostic doesn't push the layout. - Severity colors flow from the active theme — `danger` / `warn` / `info` / `text_dim` for `error` / `warning` / `info` / `hint`. - The list is presentational only — it does NOT call `lsp.*`. The caller supplies the diagnostics array (and re-renders with a new array when the underlying check refreshes).
▲ 0↑ born
▣
module · born here
❒asset
# directiveBadge Colored chip showing a file's `--!nocheck` / `--!nolint` / `--!nocheck:<codes>` directive state. Input is exactly what `lsp.readDirectives(source)` returns: `{ mode = "none" | "all" | "lint" | "codes", codes? = {string} }`. Returns `nil` when `mode == "none"` (no directive present) so the caller can drop the result into a layout without conditionals cluttering the call site. Pass `opts.showNone = true` to render a neutral "no directive" chip for layouts that need the slot occupied. ## Exports - `directiveBadge(skipMode: SkipMode?, opts: Opts?) -> any?` — build the chip. Returns `nil` for `mode == "none"` unless `opts.showNone` is set. Returned directly by the module. Types: - `SkipMode = { mode?: string, codes?: { string } }` - `Opts = { id?: string, padding?: { number }, showNone?: boolean }` ## Usage ```luau local directiveBadge = require("@builtin::modules.zui.widget.lsp.directiveBadge") children[#children + 1] = directiveBadge(lsp.readDirectives(source)) -- or, always-occupy-slot variant: children[#children + 1] = directiveBadge(d, { showNone = true }) ``` ## Notes - The chip color tracks severity: `danger` for `--!nocheck` (all passes skipped), `warn` for `--!nolint` and `--!nocheck:<codes>`. - Tooltip text spells out exactly which passes / codes the directive silences so an inspector view doesn't need a separate explanation panel. - Pure presentational — no side effects, no LSP calls. The caller is responsible for refreshing the directive state when the source changes.
▲ 0↑ born
▣
module · born here
❒asset
# docBrowser Three-pane API doc browser: namespace tree (left), method list (middle), describe pane (right). State carries the current selection plus a search query; the widget itself is stateless — the owning app prefetches `lsp.namespaces()` / `lsp.methods(ns)` / `lsp.describe(path)` and threads them through. Drives the LSP UI's "Docs" tab. ## Exports - `docBrowser(state: State?, opts: Opts?) -> any` — build the three-pane split widget. Returned directly by the module. Types: - `State = { namespaces?, selectedNamespace?, methods?, selectedMethod?, describe?, searchQuery?, searchResults?, kindFilter? }` - `Opts = { id?: string, onSelect?: string, sizes?: { number } }` ## Usage ```luau local docBrowser = require("@builtin::modules.zui.widget.lsp.docBrowser") local widget = docBrowser(state, { onSelect = "lsp-docs" }) app:on("^lsp%-docs%-ns%-(.+)$", function(_v, _id, ns) state.selectedNamespace = ns end) app:on("^lsp%-docs%-method%-(.+)$", function(_v, _id, path) state.selectedMethod = path:gsub("%.", "/") end) app:on("lsp-docs-search", function(v) state.searchQuery = v end) app:on("^lsp%-docs%-kind%-(.+)$", function(_v, _id, k) state.kindFilter = k end) ``` ## Notes - Callback ids can't contain `/`, so the widget substitutes `.` for `/` in method paths on emit. The receiving handler must undo it. - The fetch logic (calling `lsp.*`) lives in the parent app, not in this widget — that keeps it cheap to test without an active LSP. - The split sizes default to 20% / 30% / 50%. Override via `opts.sizes` for inspector layouts that need different widths.
▲ 0↑ born
▣
module · born here
❒asset
# severitySummary Colored badge row of LSP diagnostic severity counts. Input shape matches `lsp.summary()` / `lsp.checkAll()` — `{ errors, warnings, info, hints }`. Severities with zero count are hidden by default; pass `opts.showZero = true` to keep them in the row. When all counts are zero the widget falls back to a neutral "0 diagnostics" chip so the header doesn't collapse. ## Exports - `severitySummary(counts: Counts?, opts: Opts?) -> any` — build the hbox of severity chips. Returned directly by the module. Types: - `Counts = { errors?: number, warnings?: number, info?: number, hints?: number }` - `Opts = { id?: string, showZero?: boolean, tooltips?: boolean, gap?: number, padding?: { number } }` ## Usage ```luau local severitySummary = require("@builtin::modules.zui.widget.lsp.severitySummary") local widget = severitySummary(lsp.summary(), { tooltips = true }) ``` ## Notes - Chip colors flow from `Z.theme.danger / warn / info / success` so the palette automatically tracks theme overrides. - The "0 diagnostics" fallback prevents the parent layout from jumping when diagnostics first appear — important for fixed-height headers. - Pure presentational — no LSP calls, no side effects.
▲ 0↑ born
▣
module · born here
❒asset
# strictModeToggle Three-button radio for the LSP strict gate. Pass the current mode (`"off" | "soft" | "strict"` — exactly what `lsp.getStrictMode()` returns) and a callback prefix; emits `<onChange>-<mode>` when a button is clicked. The widget is presentational only — the owning app calls `lsp.setStrictMode()` from its handler and mirrors the change back into its render state. ## Exports - `strictModeToggle(currentMode: string?, onChange: string?, opts: Opts?) -> any` — build the hbox of mode buttons. Returned directly by the module. Types: - `Opts = { id?: string, label?: any, padding?: { number }, containerPadding?: { number } }` ## Usage ```luau local strictToggle = require("@builtin::modules.zui.widget.lsp.strictModeToggle") local widget = strictToggle(lsp.getStrictMode(), "lsp-strict") app:on("^lsp%-strict%-(.+)$", function(_v, _id, mode) lsp.setStrictMode(mode) state.strictMode = mode end) ``` ## Notes - Decouples the visual from the side effect — matches the rest of the zui composite-widget convention so screens stay testable without an active LSP. - Pass `opts.label = false` to drop the leading label entirely (e.g. for compact inspectors that already provide context). - Tooltips on each button explain exactly which classes of error are gated at that level.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# fromFlat Build the recursive node table `Z.tree` expects from a flat array of items where each item carries a parent link. Useful for ECS entity snapshots (each entity has `parentId`) and any other parent-pointer dataset. ## Exports - `fromFlat(items, opts?) -> (roots, nodeById)` — returns the array of root nodes and a stringified-id → node map. Module returns the builder function directly. Types: - `FromFlatOpts = { idKey?, parentKey?, labelFn?, iconFn?, actionsFn?, expanded?, defaultExpanded? }` - `TreeNode = { key, label, children?, payload, icon?, actions?, expanded? }` ## Usage ```luau local fromFlat = require("@builtin::modules.zui.widget.tree.fromFlat") local roots, byId = fromFlat(entities, { idKey = "id", parentKey = "parentId", labelFn = function(e) return e.name or "#" .. e.id end, iconFn = function(e) return e.children and "▣" or "·" end, expanded = state.expanded, defaultExpanded = false, }) ``` ## Notes - Items missing the id field are silently skipped — we cannot place them in the tree without a key, and crashing on bad data would kill the inspector. - Items whose `parentKey` value isn't found in the input array are treated as roots, matching typical ECS behaviour where a filtered list may contain children whose parents were filtered out. - The `expanded` map is consulted by stringified key first, then by the raw id; absent keys fall back to `defaultExpanded`.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# state Reactive state container for `Z.app`. Backs the Layer 3 example in `docs/specs/ui-v3-architecture.md` — `state:default(...)`, `state.nodes`, `state:set("size", n)`, etc. Writes via the data-store path (`state.foo = bar`) or the method path (`state:set("foo", bar)`) both auto-mark the owning App dirty. ## Exports - `State.create(initial: { [string]: any }?, app: App?) -> StateInstance` — make a new container. - Instance methods (also reachable via reactive `state.foo = bar` writes): - `state:default(initial)` — write only keys not already present; idempotent. Does NOT mark dirty. - `state:get(key)` — typed read of a data-store key. - `state:set(key, value)` — write one key; marks dirty on change. - `state:update(updates)` — bulk write; single dirty-mark per call. - `state:all()` — return the raw data-store table. Types: - `App = { markDirty: ((App) -> ())? }` — minimum owning-App contract. - `StateInstance` — the metatable-bound container; supports `state.foo` field access in addition to the methods. ## Usage ```luau local State = require("@builtin::modules.zui.state") local s = State.create({ count = 0 }, app) s:default({ size = 10 }) -- idempotent, no dirty mark s.nodes = { ... } -- reactive __newindex, marks dirty s:set("count", 5) -- method path, marks dirty ``` ## Notes - The methods table is consulted by `__index` BEFORE the data store, so `state:default` resolves to the method even if a `default` data key exists. - `:default` is a setup-time merge and intentionally does NOT mark the App dirty — it's safe to call inside an `App.create` builder. - `:update` collapses multiple key writes into a single dirty-mark; use it in hot loops where the per-key `__newindex` path would mark dirty on every assignment.
▲ 0↑ born
▣
module · born here
❒asset
# router Layer 4 callback router for `Z.app`. Pattern-matched dispatch with auto-`data.value` extraction and a one-shot warning on unhandled ids so authors find stale callbacks during iteration. Each router instance owns its own handler map, pattern list, and warning gate. ## Exports - `Router.create() -> RouterInstance` — make a fresh router. - `Router:on(idOrPattern: string, handler: Handler) -> RouterInstance` — register a handler; patterns are auto-detected. - `Router:cb(id: string, handler: Handler) -> string` — inline-register shorthand; returns the id so it can be bound to a widget in one expression. - `Router:dispatch(callbackId: string, data: any?) -> boolean` — look up and invoke a handler; logs a one-shot warning on unhandled ids. - `Router:_resetWarnings()` — test-only: clear the unhandled-id warning gate. - `Router:_wasWarned(id: string) -> boolean` — test-only: query whether an id has been warned. Types: - `Handler = (value: any, callbackId: string, ...any) -> ()` - `RouterInstance` — the metatable-bound router object. ## Usage ```luau local Router = require("@builtin::modules.zui.router") local router = Router.create() router:on("save:click", function(value, id) end) router:on("cell:%d+:%d+", function(value, id, row, col) end) router:dispatch("save:click", data) ``` ## Notes - Pattern detection is heuristic: strings containing any of `% ( ) [ ] + * ? ^ $ .` are treated as Lua patterns. `-` is excluded because it is common in literal ids ("system-tools-tab-entities"). - Handlers receive `(value, callbackId, ...captures)`. `value` is pre-extracted via `Utils.eventValue(data)` so handlers don't unwrap `data` manually. - Unhandled ids log Warn exactly once per id; subsequent dispatches of the same id are silent so a per-frame fired callback doesn't drown the log.
▲ 0↑ born
▣
module · born here
❒asset
# dynamic `Z.dynamic(list, fn)` — one screen per list item. The `Z.app` mount/tick loop recognises the wrapper and registers `<base>-<item.id>` screens; on every tick it diffs against the previously-registered ids and unregisters screens whose items disappeared. ## Exports - `Dynamic.create(list: { any }, fn: (any, number) -> any) -> Wrapper` — build a dynamic-list bucket consumed by `Z.app`. - `Dynamic.is(value: any) -> boolean` — identify a `Z.dynamic(...)` wrapper. - `Dynamic.itemId(item: any, index: number) -> string` — derive a stable per-item id (falls back to the array index). Types: - `Wrapper = { list, fn, ... }` — opaque wrapper carrying the `__zuiDynamic = true` tag. ## Usage ```luau local Dynamic = require("@builtin::modules.zui.dynamic") local Z = require("@builtin::modules.zui") return Z.app("inventory", function(state) return { items = Z.dynamic(state.items, function(item, index) return Z.lbl(item.label) end), } end) ``` ## Notes - The wrapper carries an internal `__zuiDynamic = true` tag — call `Dynamic.is(v)` instead of inspecting keys directly. - Each item should expose a stable `.id` field; without one, the screen name uses the array index, which makes ordering changes feel like add/remove events. - The list is read by reference on every tick — mutating it between ticks is the normal way to drive add/remove/reorder. - Unregistration is automatic: when an item disappears from the list, its per-item screen is unregistered on the next tick.
▲ 0↑ born
▣
module · born here
❒asset
# tags Luau-owned screen-tag registry for zui. Tags are pure derived metadata over the `ui.hideScreen` / `ui.showScreen` / `ui.listScreens` capabilities; the source of truth lives here, not in the engine. Typical use is the editor F1 toggle — every editor-owned UI screen is tagged `"editor"` at register time and the editor's default scene flips them all in one call via `hideByTag` / `showByTag`. ## Exports - `M.set(name: string, tags: { string }?)` — replace `name`'s tag set with the array. Nil/empty clears. - `M.add(name: string, tag: string)` — add a single tag, creating the entry if absent. - `M.remove(name: string, tag: string)` — drop a tag; drops the registry entry when the set becomes empty. - `M.clear(name: string)` — drop every tag for `name` (idempotent). - `M.get(name: string) -> { string }` — sorted array of tags for `name` (empty when no entry). - `M.findByTag(tag: string) -> { string }` — sorted screens currently registered AND visible-via-`ui.listScreens` carrying `tag`. - `M.hideByTag(tag: string)` — hide every currently-visible screen tagged with `tag`. - `M.showByTag(tag: string)` — show every currently-hidden screen tagged with `tag`. - `M._registry: Registry` — internal `{ [name] = { [tag] = true } }` map. Inspectable but treat as private. Types: - `TagSet = { [string]: boolean }` - `Registry = { [string]: TagSet }` - `ScreenInfo = { name: string, visible: boolean }` - `LiveScreens = { [string]: ScreenInfo }` ## Usage ```luau local Tags = require("@builtin::modules.zui.tags") Tags.set("inspector", { "editor" }) Tags.add("console", "editor") Tags.hideByTag("editor") -- hides every visible "editor"-tagged screen Tags.showByTag("editor") -- shows every hidden "editor"-tagged screen local screens = Tags.findByTag("editor") -- sorted live matches ``` ## Notes - Registry is not auto-pruned. `ui.listScreens()` is one frame behind, so same-frame register-then-tag flows must keep their entries intact; bulk ops filter via the live screen snapshot. - Memory is bounded by total session screen count. - `set` with `nil` / `{}` is the only way to clear an entry through `set`; use `clear(name)` for the explicit variant. - All bulk ops are silent on missing FFI bindings (`ui.hideScreen`, `ui.showScreen`, `ui.listScreens`) — safe to call before the UI layer is wired.
▲ 0↑ born
▣
module · born here
❒asset
# shell Pre-built shells for common app shapes. Each shell is a function taking a single props table; missing fields fall back to sensible defaults. See `docs/specs/ui-v3-architecture.md` Layer 5 for the canonical signatures. ## Exports - `Shell.docked` — toolbar + body + status layout. See `docked.module/`. - `Shell.canvas` — node-graph editor with bezier connections. See `canvas.module/`. - `Shell.inspector` — form-with-fields-and-actions pane. See `inspector.module/`. - `Shell.tabApp` — visibility-driven tabbed app with per-tab refresh scheduler. See `tabApp.module/`. ## Usage ```luau local Shell = require("@builtin::modules.zui.shell") Shell.docked { top = ..., center = ..., bottom = ... } Shell.tabApp { name = "system_tools", tabs = ..., tabOrder = ... } ``` ## Notes - This module is a pure aggregator: it does nothing but re-export the four shell submodules. Each shell owns its own behaviour, defaults, and contract; consult the respective submodule for the prop shapes. - Missing slots in any shell are skipped cleanly — there are no empty containers, no wasted space.
▲ 0↑ born
▣
module · born here
❒asset
# utils Formatting and dispatch helpers shared across zui layers. Anything domain-agnostic and useful both inside zui and to library consumers goes here — numeric formatting (WASM-safe, no `string.format` dependency), stable hierarchical widget id construction, callback payload unwrap, and small list comprehensions. ## Exports - `M.round(v: number?, decimals: number?) -> number` — round to `decimals` places (default 2). `nil` → 0. - `M.fmt(v: number?, decimals: number?) -> string` — `tostring(round(v, decimals))`. - `M.fmtVec3(vec: Vec3Array?, decimals: number?) -> string` — format `{x, y, z}` as `"(x, y, z)"`. - `M.id(parent: any, child: any) -> string` — stable widget id (`"parent/child"`); empty parent drops the prefix. - `M.eventValue(data: EventEnvelope?) -> any` — unwrap `data.value` from v2 event envelopes; pass through raw payloads (`nil` returns `nil`). - `M.map(items: { any }?, fn: (any, number) -> any?) -> { any }` — map and drop `nil`s. - `M.when(cond: any, widget: any) -> any` — `widget` if `cond`, else `nil`. Pairs with `compact`. - `M.compact(widgets: { any }?) -> { any }` — drop `nil` entries. Types: - `Vec3Array = { number }` — array form, `{ x, y, z }`. - `EventEnvelope = { value: any? } | any` ## Usage ```luau local Utils = require("@builtin::modules.zui.utils") Utils.round(1.2345) -- 1.23 Utils.fmt(1.2345, 1) -- "1.2" Utils.fmtVec3({1, 2, 3}) -- "(1.00, 2.00, 3.00)" Utils.id("toolbar", "save") -- "toolbar/save" local rows = Utils.map(items, function(r) return Z.lbl(r.name) end) local kids = Utils.compact{ Z.lbl("a"), Utils.when(showFoo, Z.lbl("foo")) } ``` ## Notes - Pure module — no engine calls, no module state. Safe to call from any context. - `round` / `fmt` / `fmtVec3` are WASM-safe; they avoid `string.format` for the rounding step and use repeated multiplication for `10^n`. - `eventValue` is the canonical onCallback unwrap — preferred over manual `data.value` access because it transparently handles the older raw-payload flow too. - `map` / `compact` / `when` are the canonical list comprehensions; demos rely on the `nil` filtering for conditional widgets.
▲ 0↑ born
▣
module · born here
❒asset
# highlight Syntax-highlighting dispatch + per-language modules. Each language module exports `(text: string) -> { Segment }` where `Segment = { text, color, monospace?, italic? }`. `Z.codeEditor` calls into `dispatch` at update time and passes the resulting segments to TextInput, which builds the egui LayoutJob in pure Luau. Library authors can ship their own `@author/zui_highlight_<lang>` modules and slot them into `Z.codeEditor` via the same `language` opt. ## Exports - `M.dispatch(language: string, text: string) -> { Segment }` — pick a per-language highlighter (with `yml` / `md` aliases) and tokenize; unknown languages fall through to a single plain segment. - `M.lua: Highlighter` — Lua highlighter. - `M.json: Highlighter` — JSON highlighter. - `M.yaml: Highlighter` — YAML highlighter. - `M.wgsl: Highlighter` — WGSL highlighter. - `M.markdown: Highlighter` — Markdown highlighter. Types: - `Segment = { text: string, color: string, monospace: boolean?, italic: boolean? }` - `Highlighter = (string) -> { Segment }` ## Usage ```luau local Highlight = require("@builtin::modules.zui.highlight") local segs = Highlight.dispatch("lua", source) -- segs = { { text = "local", color = "#...", monospace = true }, ... } -- Or call a specific language directly: local segs2 = Highlight.json(jsonText) ``` ## Notes - Per-language colors come from the active theme's `code.*` tokens (`ui.getToken("code.keyword")` etc.). Switching themes (`ui.setTheme("light")`) automatically re-colors on the next call. - A single-slot LRU cache keyed by `(language, text, theme)` makes same-text re-emits free (focus-loss redraws, no-op editor pushes). Cache returns a shared array reference — callers must not mutate it. - Aliases: `"yml"` → `yaml`, `"md"` → `markdown`. Unknown languages return a single default-colored monospace segment so user input never crashes the dispatcher. ## Roles the Lua highlighter reads `code.keyword`, `code.string`, `code.number`, `code.comment`, `code.bool` (`true` / `false` / `nil`), `code.attribute` (`--!` directives), `code.type` (a name after `:`, `->`, `::` or `type`), `code.function` (a name a call is made through, or one `function` declares), `code.property` (a member reached by `.` that is not called), `code.builtin` (the standard library's globals), and `code.default` for the rest.
▲ 0↑ born
▣
module · born here
❒asset
# screens Derived screen-lifecycle helpers on top of the `ui.*` capability surface. Every operation is computed from `ui.listScreens()` + `ui.hideScreen` / `ui.showScreen`; the module owns no engine state of its own. Deliberately does NOT wrap `ui.registerScreen` / `ui.updateScreen` / `ui.unregisterScreen` — those are capabilities the engine needs to manage directly. ## Exports - `M.exists(name: string) -> boolean` — does the screen exist in the current snapshot? - `M.visible(name: string) -> boolean` — does it exist AND is it currently shown? - `M.toggle(name: string)` — flip a visible screen hidden, or a hidden screen visible. Unknown screens are no-ops. - `M.list() -> { ScreenEntry }` — pass-through to `ui.listScreens()`. - `M.byName() -> { [string]: ScreenEntry }` — same data, map shape. - `M.hideAll()` — hide every currently-visible screen. Types: - `ScreenEntry = { name: string, visible: boolean, layer: number?, hasRoot: boolean? }` ## Usage ```luau local Screens = require("@builtin::modules.zui.screens") if Screens.visible("hud") then Screens.toggle("hud") end ``` ## Notes - The screen snapshot is one frame behind on register/update commands — a freshly registered screen returns `false` from `exists` until the next frame. - `toggle` is a state-driven flip, not a flag write — it reads the current state before deciding to show or hide. - All operations are safe when the `ui` global is unavailable; they no-op or return empty values.
▲ 0↑ born
▣
module · born here
❒asset
# editorSelection Named selection scopes for the editor. A scope is an independently tracked, ordered set of typed refs plus a primary (the last ref added or clicked). Distinct scopes never clobber each other, so viewport, outliner, inspector and asset browser share one answer to "what is selected" per kind. A ref is `{ kind: string, id: string }` — a stable id, never a display string, so rename/move never invalidates a selection. ## Exports - `M.scope(name) -> Scope` — get/create a named scope handle. - `M.set(scope, refs)` — replace refs in order; primary becomes the last. - `M.get(scope) -> { Ref }` — refs in click order (fresh array). - `M.primary(scope) -> Ref?` — last-clicked ref, or nil. - `M.clear(scope)` — empty a scope. - `M.toggle(scope, ref)` — add if absent, remove if present. - `M.add(scope, refs)` — add each ref not already present; primary becomes the last added. - `M.remove(scope, refs)` — remove each of `refs`; primary becomes the last remaining or nil. - `M.contains(scope, ref) -> boolean` — membership by `(kind, id)`. - `M.count(scope) -> number` — the scope's selection size. - `M.subscribe(scope, fn) -> handle` / `M.unsubscribe(scope, handle) -> boolean`. - `M.context() -> { scope, refs, primary }` — last-focused scope, for command ctx. ## Usage ```luau local Selection = require("@builtin::modules.api.editor.selection") local scope = Selection.scope("entity") Selection.set(scope, { { kind = "entity", id = "player_1" } }) Selection.subscribe(scope, function() refreshInspector() end) ``` ## Notes - State lives in a fixed `_G` slot, seeded pre-seal by the boot chain (`prelude.luau`); it survives hot-reload and edit↔play flips. Mutations after boot write into nested tables only. - `set` / `toggle` / `clear` mark their scope as last-focused, which drives `context()` and therefore which scope commands act on. - The service holds the editor's selection state in Luau. Refs handed back by `get` / `primary` / `context` are copies, so a caller can hold or mutate them without touching internal state.
▲ 0↑ born
▣
module · born here
❒asset
# editorCommands The command registry — the single place an editor action is declared. One declaration is rendered by four surfaces: the menu bar, DataView context menus, the command palette, and the keymap. `EditorRegistry`'s `addMenuItem` is a thin wrapper over `declare`, so every menu action is a command. A command is `{ id, title, category, menu?, order?, keys?, enabledWhen?, run }`. `ctx` passed to `enabledWhen`/`run` is the selection service's `context()` augmented by the caller: `{ scope, refs, primary, view?, item? }`. ## Exports - `M.declare(cmd) -> boolean` — register/replace by id (duplicate replaces). - `M.get(id) -> Command?` - `M.list() -> { Command }` — sorted by (category, title). - `M.remove(id) -> boolean` - `M.isEnabled(id, ctx) -> boolean` — true when no predicate, else its result. - `M.run(id, ctx) -> boolean` — runs only when enabled; returns whether it ran. - `M.conflicts() -> { { keys, ids } }` — keys claimed by more than one command. - `M.commandForKey(keys) -> id?` — the last declarer wins a contested key. ## Usage ```luau local Commands = require("@builtin::modules.api.editor.commands") Commands.declare({ id = "asset.delete", title = "Delete Asset", category = "Assets", keys = "Delete", enabledWhen = function(ctx) return #ctx.refs > 0 end, run = function(ctx) ... end, }) ``` ## Notes - State lives in a fixed `_G` slot, seeded pre-seal by the boot chain; it survives hot-reload and edit↔play flips. Mutations after boot write into the nested `byId` / `keyClaims` tables only. - A duplicate id replaces the prior declaration and rebinds its key. - Conflicts are surfaced (`conflicts()`), never silently dropped; the last declarer wins the live binding via `commandForKey`.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# canvas Pre-built node-graph canvas shell — `Z.shell.canvas { width, height, background?, nodes, connections, onSelectNode?, onMoveNode?, onConnect? }`. Maps a node-graph state into Z.canvas paint commands (background, bezier connections, rounded rect nodes with labels). The module returns the shell function directly. ## Exports - `canvasShell(opts: CanvasOpts?) -> canvas-widget` — render a node-graph canvas from a node/connection state. Types: - `Node = { id: string, x: number, y: number, label: string?, color: string? }` - `Connection = { fromId: string, toId: string, color: string?, width: number? }` - `CanvasOpts = { id?, width?, height?, background?, nodes?, connections?, onSelectNode?, onMoveNode?, onConnect? }` ## Usage ```luau local canvasShell = require("@builtin::modules.zui.shell.canvas") local widget = canvasShell { width = 800, height = 600, nodes = { { id = "a", x = 10, y = 20 } }, connections = {}, onSelectNode = "graph:select", } ``` ## Notes - Pointer events come through `onClick` / `onPointerMove` with the widget-local `"x,y"` value. The caller is responsible for hit-testing against `opts.nodes` in the App's `onCallback` to map the click to a node id. - `onConnect` (drag-from-A-to-B) is not yet wired — the canvas widget doesn't surface drag-completion events. Tracked for a future update. - Bezier connections use horizontal-tangent control points on the mid-x line, producing smooth left-to-right flow between nodes.
▲ 0↑ born
▣
module · born here
❒asset
# dockApp A visibility-driven docked-app handle. Wraps `Z.app` with a per-panel refresh scheduler over the same tab contract as `Z.shell.tabApp`, but renders the open panels as `Z.dockPanel`s inside a single `Z.dockArea` (real egui_dock tabs, splits, and drag). The app costs zero engine work while hidden; each open panel polls on its own `refresh` interval, and the open model is an ordered set of open panel keys rather than a single active tab. Each entry in `tabs` follows the tab contract `{ label, refresh, build, onMount?, onCallback?, tick?, float? }`. A tab's own `float` overrides the app-level `float` default — e.g. a viewport tab sets `float = false` to dock into the main surface while tool panels float. ## Create `M.create(opts) -> DockAppHandle` — the module table is callable, so `DockApp(opts)` works too. Required `opts`: `name`, `tabs`, `tabOrder` (may be empty). Optional: `initialOpen`, `tabAliases`, `statusClock`, `onStatusTick`, `extraScreens`, `appOpts`, `initialState`, `dockLayout`, `float`, and dock presentation keys (`overlayType`, `leafCollapseButtons`, `leafCloseAllButtons`, `allowedSplits`, `dockStyle`). ## Handle The returned `DockAppHandle` carries: - `app`, `state`, `screenName` - `update(dt)` — runs the scheduler and ticks the app. - `onCallback(callbackId, data) -> boolean` — routes dock close, app dispatch, and tab/extraScreen callbacks. - `destroy()`, `setVisible(v)` - `openPanel(key)`, `closePanel(key)`, `isOpen(key) -> boolean`, `togglePanel(key)` — drive the open set. - `getLayout() -> string?`, `restoreLayout(json)` — read and re-apply the live dock split/tab arrangement. ## Usage ```luau local DockApp = require("modules.zui.shell.dockApp") local handle = DockApp { name = "system_tools", tabs = TABS, tabOrder = { "entities", "logs" }, initialOpen = { "entities" }, } ```
▲ 0↑ born
▣
module · born here
❒asset
# docked Pre-built docked-panel shell — `Z.shell.docked { top, left, center, right, bottom }`. Captures the wrapper shape every demo's "toolbar + body + status" layout reinvents: outer vbox with gap=0, theme bg, the center slot flex-grows, and missing slots are skipped cleanly. The module returns the shell function directly. ## Exports - `dockedShell(opts: DockedOpts?) -> vbox-widget` — render a docked layout from top/left/center/right/bottom slots. Types: - `DockedOpts = { id?, class?, classes?, top?, left?, center?, right?, bottom?, background?, minWidth?, minHeight? }` ## Usage ```luau local dockedShell = require("@builtin::modules.zui.shell.docked") dockedShell { top = toolbar, center = body, bottom = status } ``` ## Notes - The center slot is stamped with `flex = 1` so it fills the middle row horizontally. The caller's widget table is NOT mutated — its style is shallow-cloned before stamping. - `nil` slots disappear from the rendered tree. The middle row is omitted entirely when left/center/right are all `nil`. - Defaults: `gap = 0`, theme `bg` background, `minWidth = 100`, `minHeight = 100`.
▲ 0↑ born
▣
module · born here
❒asset
# inspector Pre-built inspector pane — `Z.shell.inspector { title, fields, actions }`. Captures the form-with-fields-and-actions pattern every editor reinvents. Field editors are auto-selected from the value type; `field.kind` overrides (e.g. for color swatches or free-form int/float inputs). The module returns the shell function directly. ## Exports - `inspectorShell(opts: InspectorOpts?) -> panel-widget` — render an inspector pane from a title + fields/sections + action buttons. Types: - `InspectorField = { label?, value?, onChange?, readonly?, kind?, hint?, id?, min?, max?, minWidth? }` - `InspectorAction = { label?, onClick?, variant? }` — `variant in "primary" | "secondary" | "danger"`. - `InspectorSection = { title?, fields?, defaultOpen?, id? }` - `InspectorOpts = { id?, class?, classes?, title?, fields?, sections?, actions?, message? }` ## Usage ```luau local inspectorShell = require("@builtin::modules.zui.shell.inspector") inspectorShell { title = "Entity", fields = { { label = "x", value = 1, onChange = "x:set" } }, actions = { { label = "Apply", onClick = "apply", variant = "primary" } }, } ``` ## Notes - Editor selection: `boolean` → checkbox, `number` → slider (min/max default 0/1), `kind = "int"|"float"` → free-form input, `kind = "color"` → hex button swatch, `string` → text input. Setting `readonly = true` renders a label regardless of type. - `sections` takes precedence over a flat `fields` list — each section becomes a collapsible, expanded by default unless `defaultOpen = false`. - Actions render right-aligned in a row at the bottom. The `variant` field maps to theme tokens: `primary` → accent, `danger` → danger, anything else → panel_alt.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# json JSON syntax highlighter. Pure `(text) -> { Segment }`. Mirrors the algorithm of the deleted Rust `highlight_json` (renderer.rs): emits object keys via lookahead-for-`:`, handles `\` escapes inside strings, recognises numbers with scientific notation, and colors structural punctuation (`{ } [ ] , :`). Colors flow from `ui.getToken("code.<role>")` via the active theme; hardcoded fallbacks match the deleted Rust values when no theme is registered. ## Exports - `highlight(text: string) -> { Segment }` — tokenize a JSON string into colored segments for the zui code renderer. The module returns this function directly. Types: - `Segment = { text: string, color: string, monospace: boolean }` ## Usage ```luau local highlight = require("@builtin::modules.zui.highlight.json") local segments = highlight('{"a": 1, "b": "two"}') ``` ## Notes - Pure function — no side effects, no state. Safe to call any time. - The `ui.getToken` lookup runs once per highlighter call (5 FFI calls) rather than per byte, so the inner tokenize loop is allocation-light. - Keys are disambiguated from string values by a lookahead past whitespace for `:` — matches the deleted Rust algorithm exactly.
▲ 0↑ born
▣
module · born here
❒asset
# lua Luau syntax highlighter. Pure `(text) -> { Segment }`. Mirrors the algorithm of the deleted Rust `highlight_lua` / `highlight_lua_code_part` pair (renderer.rs pre-Phase-3): `--`-line comments, single- and double-quoted strings (no escape handling), digit runs with optional dots (no exponent), and identifiers dispatched against the Luau keyword set. Colors flow from `ui.getToken("code.<role>")` via the active theme, with hardcoded fallbacks matching the deleted Rust constants. ## Exports - `highlight(text: string) -> { Segment }` — tokenize a Luau string into colored segments for the zui code renderer. The module returns this function directly. Types: - `Segment = { text: string, color: string, monospace: boolean }` ## Usage ```luau local highlight = require("@builtin::modules.zui.highlight.lua") local segments = highlight("local x = 1 -- pi-ish") ``` ## Notes - Pure function — no side effects, no state. Safe to call any time. - The `ui.getToken` lookup runs once per highlighter call (5 FFI calls), then locals are used in the hot tokenize loop — cost is per-call, not per-byte. - Strings have no escape handling (`"a\"b"` would terminate at the middle quote). This matches the deleted Rust implementation exactly.
▲ 0↑ born
▣
module · born here
❒asset
# markdown Markdown syntax highlighter. Pure `(text) -> { Segment }`. Mirrors the algorithm of the deleted Rust `highlight_markdown` / `highlight_markdown_inline` pair (renderer.rs pre-Phase-3): block-level pass over `split_inclusive('\n')` recognises fenced code (```), headings (#), blockquotes (>), bullet/ordered lists; an inline pass within each non-block line recognises inline code (`...`), bold (**...**), and links ([text](url)). ## Exports - `highlight(text: string) -> { Segment }` — tokenize a Markdown string into colored segments for the zui code renderer. The module returns this function directly. Types: - `Segment = { text: string, color: string, monospace: boolean }` ## Usage ```luau local highlight = require("@builtin::modules.zui.highlight.markdown") local segments = highlight("# Title\n**bold**\n") ``` ## Notes - The module-level color upvalues are reassigned at every highlighter entry so the inline helper sees the active palette without per-call args. Multiple concurrent calls are NOT safe — this module is designed for serial UI rendering. - Inline code reuses the `code.string` theme role; treat the two as the same colour band. - Fenced code (```) toggles a multi-line fence — the highlighter tracks fence state across the input.
▲ 0↑ born
▣
module · born here
❒asset
# wgsl WGSL syntax highlighter. Pure `(text) -> { Segment }`. Mirrors the algorithm of the deleted Rust `highlight_wgsl` (renderer.rs): `//` line comments, `/* ... */` block comments, `"..."` strings with `\` escapes, `@attribute` tokens, numeric literals (digits + dots + alphanumerics for suffixes like `1u`, `0xFF`, `1.0_f32`), and identifiers dispatched against the WGSL keyword and built-in type tables. Colors flow from `ui.getToken("code.<role>")` via the active theme; hardcoded fallbacks match the deleted Rust values when no theme is active. ## Exports - `highlight(text: string) -> { Segment }` — tokenize a WGSL string into colored segments for the zui code renderer. The module returns this function directly. Types: - `Segment = { text: string, color: string, monospace: boolean }` ## Usage ```luau local highlight = require("@builtin::modules.zui.highlight.wgsl") local segments = highlight("@vertex fn main() -> vec4<f32> {}") ``` ## Notes - Pure function — no side effects, no state. Safe to call any time. - Keyword and type tables match the deleted Rust constants verbatim (renderer.rs:6245-6263). - Strings are escape-aware (`"a\"b"` is one segment); WGSL has no single-quoted strings.
▲ 0↑ born
▣
module · born here
❒asset
# yaml YAML syntax highlighter. Pure `(text) -> { Segment }`. Mirrors the algorithm of the deleted Rust `highlight_yaml` / `highlight_yaml_content` / `highlight_yaml_scalar` trio (renderer.rs): per-line comment splitting, `key: value` recognition (split on first `:`), and scalar typing (quoted string / true|false|null / numeric / plain). Colors flow from `ui.getToken("code.<role>")` via the active theme; hardcoded fallbacks match the deleted Rust values when no theme is active. ## Exports - `highlight(text: string) -> { Segment }` — tokenize a YAML string into colored segments for the zui code renderer. The module returns this function directly. Types: - `Segment = { text: string, color: string, monospace: boolean }` ## Usage ```luau local highlight = require("@builtin::modules.zui.highlight.yaml") local segments = highlight("name: zero\n# comment\n") ``` ## Notes - The module-level color upvalues are reassigned at every highlighter entry so the inner scalar/content helpers see the active palette without per-call args. Multiple concurrent calls are NOT safe — this module is designed for serial UI rendering. - Numeric detection allows `.`, `-`, `+` alongside digits, so dotted versions and negatives parse as numbers (matches the Rust behaviour).
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# icons Phosphor icon constants. The phosphor font is registered as a named family `phosphor` in the engine, so render icons by setting `style.fontFamily = "phosphor"` on the label that contains them. The font is ALSO appended to the proportional family as a fallback, but the fallback path is unreliable for PUA codepoints in egui 0.34 (see issue #2191) — always pass `fontFamily = "phosphor"` for icons. Curated set covering DAW transport, media controls, file management, desktop shell, editor chrome, and common state indicators. For icons not listed here, see https://phosphoricons.com — pass the codepoint as a UTF-8 string literal directly. ## Exports - `Icon.styled(extra: LabelOpts?) -> LabelOpts` — build a `UI.label` opts table with `style.fontFamily = "phosphor"` applied. Merges with the supplied `extra`. - `Icon.<Name>: string` — UTF-8 codepoint constants. Categories: - Transport / media: `Play`, `Pause`, `Stop`, `Record`, `FastForward`, `Rewind`, `SkipForward`, `SkipBack`, `Repeat`, `Shuffle`. - Audio / DAW: `Microphone`, `Headphones`, `Equalizer`, `Sliders`, `Metronome`, `MusicNote`, `Waveform`, `SpeakerHigh`, `SpeakerLow`, `SpeakerNone`, `SpeakerSimpleHigh`, `SpeakerMuted`. - File / folder: `Folder`, `FolderOpen`, `File`, `FileText`, `FileAudio`, `FileVideo`, `FileImage`, `FileCode`, `FloppyDisk`, `Trash`, `UploadSimple`, `DownloadSimple`. - Desktop / shell: `Desktop`, `DesktopTower`, `Monitor`, `Keyboard`, `Mouse`, `House`, `Sidebar`, `Cpu`. - Editor: `Pencil`, `Plus`, `Minus`, `Check`, `MagnifyingGlass(Plus|Minus)?`, `Gear`, `Power`, `Eye`, `EyeSlash`, `Lock`, `LockOpen`, `Bell`, `Palette`, `Funnel`, `Clipboard`. - Code / dev: `Code`, `Terminal`, `Bug`, `GitBranch`, `GitCommit`, `GitMerge`, `Database`, `Cloud`. - Arrows / nav: `ArrowUp/Down/Left/Right`, `ArrowsClockwise`, `ArrowsIn`, `ArrowsOut`, `CaretUp/Down/Left/Right`. - Layout / list: `List`, `GridFour`, `DotsThree`, `DotsThreeVertical`. - State / status: `Info`, `Warning`, `WarningCircle`, `CheckCircle`, `XCircle`, `Question`, `Heart`, `Star`, `Tag`, `Hash`, `At`. - Shapes: `Circle`, `Square`, `Triangle`. - Productivity / shell apps: `Calculator`, `Notepad`, `NotePencil`, `Note`. - Games / cards: `GameController`, `Bomb`, `Cards`, `Diamond`, `Spade`, `Club`. Types: - `LabelStyle = { fontFamily?: string, fontSize?: number, color?: string, [string]: any }` - `LabelOpts = { style?: LabelStyle, [string]: any }` ## Usage ```luau local Icon = require("@builtin::modules.icons") UI.label(Icon.Play, { style = { fontFamily = "phosphor", fontSize = 24 } }) UI.label(Icon.Folder .. " Documents", { style = { fontFamily = "phosphor", fontSize = 14 }, }) -- Or use the helper to enforce the phosphor family: UI.label(Icon.Play, Icon.styled({ style = { fontSize = 24, color = "#fff" } })) ``` ## Notes - The constants are plain string values — each is a single UTF-8 Private Use Area codepoint. Concatenate freely with surrounding text. - `Icon.styled` mutates the supplied opts table (`extra.style` is forced). Pass a fresh table per call site if you need to reuse opts. - Curated subset only — for icons outside this set, paste the codepoint from https://phosphoricons.com directly.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# editor_observe What an editor action committed, and why it committed less than it was asked for. Every editor action — a gizmo drag, a delete, a duplicate, a command dispatch, a selection gesture — closes by publishing one record here, and returns that same record to its caller. Nothing is measured per frame: a record is written where the work happens and read lazily. ```lua local Obs = require("@builtin::modules.api.editor.editor_observe") Obs.observe() -- the whole document: last of each kind, history, counts Obs.last() -- the most recent action of any kind Obs.last("drag") -- the most recent drag Obs.history() -- every retained action, oldest first ``` The same reading is a tool: `tools.use("editor", "observe")`. ## What every record carries - `action` — `drag`, `grab`, `delete`, `duplicate`, `command` or `select`. - `outcome` — `committed`, `partial`, `refused`, `cancelled` or `noop`. - `reason` — the nearest cause, from the closed set below, when the outcome is anything but a clean commit. - `detail` — the engine's own words for that cause. - `entities` — one row per entity the action touched or tried to, each with `before`, `requested` and `after`, and its own `reason` when it is not a clean commit. - `committed` / `changed` / `refused` — how many rows fall in each. - `seq`, `atMs`, `durationMs` — which action this is and how long it was open. ## The reason set `selectionEmpty`, `entityMissing`, `writeRefused`, `writeDiverged`, `pivotLost`, `userCancelled`, `commitFailed`, `duplicateRefused`, `despawnRefused`, `unchanged`, `commandMissing`, `commandDisabled`, `predicateRaised`, `bodyRaised`, `hitNothing`, `pointerBlocked`. `Obs.REASONS` maps each to its one-line meaning, so a caller can enumerate the set rather than guess at it. ## A drag that moved less than asked The drag record separates the three quantities that are usually conflated: - `pointerAsked` — what the pointer's position asked for, before snapping. - `applied` — what the gizmo handed the engine, after snapping. `snapping` and `snapIncrement` say why the two differ. - each entity's `after` — the transform the engine **holds**, read back from the engine rather than recomputed from the drag's own arithmetic. An entity whose `after` differs from its `requested` reads `writeDiverged`; one whose write raised reads `writeRefused` with the message; one despawned mid-drag reads `entityMissing`. A drag that ended on Esc reads `cancelled` / `userCancelled`, and one whose selection emptied under it reads `cancelled` / `pivotLost` — three terminal states a single "the object did not move" cannot tell apart. ## A drag that never began A press that lands on a handle and starts no drag publishes a `grab` record instead — `Obs.last("grab")`, or `Gizmo.lastGrab()`. `pointerBlocked` says the UI layer held pointer focus, so the press never reached the handle. A press away from every handle is a selection click rather than a grab, and records nothing. One record per press: a frame that ticks the gizmo more than once reports the press once. `unit` names what `pointerAsked` and `applied` are measured in: `metres` for a translate or plane drag (a world-space `{x,y,z}` delta), `radians` for a rotate (`{radians}`), `factor` for a scale (`{factor}`). ## Per-frame cost The editor's own per-frame work is named in the profiler rather than pooled into `lua_update`: `script.editor.gizmo.tick`, `script.editor.viewport.select`, `script.editor.selection.highlight`, and `script.editor.panel.<id>` for each dock panel's rebuild. Read them with `profiler.stats()`.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
backing path · modules/deprecated/zui.module/shell.module/tabApp.module

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.