module edui.app
The edui reactive core: an `App` owns one `ui.*` screen, maps author
closures to `ui.*` string callback ids, dispatches the broadcast that
`ui.registerCallbackEnv` delivers, and rebuilds the widget tree on state
change. This is the layer that lets editor chrome be written with
closures (`onClick = function() ... end`) instead of hand-managed string
ids — the footgun the deprecated `zui` grew three shapes for.
new(screenName: string, opts: { [string]: any }?) → any
Create an editor app that owns the `ui.*` screen `screenName`.
`layer` is a render-layer membership mask (e.g. the EditorUI bit) applied
via `ui.setScreenRenderLayer` on first register; omit for the default.
`order` is the screen's Z-ORDER — higher paints on top; a floating
surface (the command palette) states one to stand over the dock.
| arg | type | description |
|---|
| screenName | string | Unique screen id (also the callback-id namespace prefix). |
| opts | { [string]: any }? | `{ layer? = <renderLayerMask>, order? = <number>, callbackKey? = <string> }`. |
examples
local app = edui.app.new("myPanel", { layer = editorMask })collectHandlers(self: any, fn: () →
Collect every callback registered while `fn` runs into a named bucket,
returned alongside `fn`'s result. A host that CACHES the subtree `fn`
built re-adopts the bucket on later builds (`adoptHandlers`), so the
cached tree's callback ids stay live across rebuilds that skipped it.
| arg | type | description |
|---|
| self | any | |
| fn | ( | The builder to run. |
examples
local tree, bucket = app:collectHandlers(function() return panel.build() end)
adoptHandlers(self: any, bucket: { [string]: (any) → void
Re-register a bucket of handlers collected by `collectHandlers` into
the current build, keeping a cached subtree's callbacks live.
| arg | type | description |
|---|
| self | any | |
| bucket | { [string]: (any | The handler bucket to adopt. |
examples
app:adoptHandlers(cached.bucket)
cb(self: any, closure: (any, key: ?) →
Allocate (or reuse) a callback id bound to `closure` for this build.
Pass a stable `key` so the id is identical across rebuilds — required for
anything the renderer tracks by id (focus, drag, `ui.widgetState`).
Without a key the id is a per-build sequence number (fine for a
fire-and-forget button).
| arg | type | description |
|---|
| self | any | |
| closure | (any | The handler `(data) -> ()` the id dispatches to. |
| key | ? | Optional stable key (unique within this screen). |
examples
props = { onClick = app:cb(function() doThing() end, "save") }cbRaw(self: any, id: string, closure: (any) →
Register `closure` under the EXACT id `id`, without the screen-name
prefix `cb` adds. For wiring engine-emitted interaction ids that a widget
publishes itself (e.g. a dockArea's `<panelId>-close`), which arrive
unprefixed. Re-issue it each build, like `cb`.
| arg | type | description |
|---|
| self | any | |
| id | string | The exact callback id the engine will deliver. |
| closure | (any | The handler `(data) -> ()`. |
examples
app:cbRaw(panelId .. "-close", function() closePanel(panelId) end)
dispatch(self: any, id: string, data: any) → boolean
Route a callback id to its closure and schedule a rebuild. This is
what the screen's `onCallback` broadcast calls. Returns true when the id
was handled by this app (an id from another surface returns false so a
host can keep routing).
mouseX, mouseY }`. The handler receives its `value`: a scalar for
`onChange` (checkbox bool, input string, select value), a
`{ dx, dy, shift, ctrl, alt }` table for a canvas `onDrag`/`onScroll`,
`nil` for a bare `onClick`.
| arg | type | description |
|---|
| self | any | |
| id | string | The callback id the broadcast delivered. |
| data | any | The engine event envelope `{ value, eventType, widgetId, button, |
examples
function onCallback(id, data) app:dispatch(id, data) end
markDirty(self: any) → void
Mark the app dirty and rebuild its tree. Coalesces a rebuild
requested WHILE a build is running into a single follow-up build (so a
handler that mutates state and a builder that reads it never recurse),
and one requested WHILE a dispatch's handler runs into the single
rebuild that dispatch performs after the handler returns.
| arg | type | description |
|---|
| self | any | |
rebuild(self: any) → void
Run the builder and push the resulting tree to the screen. First call
registers the screen (+ render layer); later calls update it. Builder
errors are logged, never thrown, so one bad build never wedges the editor.
| arg | type | description |
|---|
| self | any | |
unchanged(self: any, sig: string) → boolean
Whether the tree this build would produce is the one the app's screen
already shows. `sig` names every piece of state the build reads: an equal
signature means the published tree already says it, so the builder returns
nil and the App keeps what it published, at O(1) cost however large the
tree. The signature belongs to the App that published the tree, so an App
with nothing published reports a change and gets a tree to register.
| arg | type | description |
|---|
| self | any | |
| sig | string | The signature of the state this build reads. |
examples
if app:unchanged(buildSignature()) then return nil end
mount(self: any, builderFn: (any) →
Mount the app: register the broadcast callback env and do the first
build. `builderFn(app) -> widgetTree` is called now and on every rebuild;
inside it, `app:cb(...)` (or the `edui.*` primitives) wire closures.
| arg | type | description |
|---|
| self | any | |
| builderFn | (any | `(app) -> widgetTree`. |
examples
app:mount(function(a) return { type = "vertical", children = { ... } } end)onCallback(id: string, data: any) → void
| arg | type | description |
|---|
| id | string | |
| data | any | |
unmount(self: any) → void
Tear the app down: release the callback env and unregister the screen.
| arg | type | description |
|---|
| self | any | |
current( ) → any
The app whose builder is currently running, or nil outside a build.
The `edui.*` primitive builders read this to register their closures, so
authors don't thread the app through every call.
examples
local app = edui.app.current()
unchanged(sig: string) → boolean
`unchanged` for the build that is running right now: the ambient form a
panel builder uses, the way the `edui.*` primitives read `current()`.
Called outside a build it reports a change, so a builder invoked directly
still produces a tree.
| arg | type | description |
|---|
| sig | string | The signature of the state this build reads. |
examples
if edui.app.unchanged(buildSignature()) then return nil end