Builtin Library
README.md
CanvasAppLayout
The layout module for the canvas_app scaffold. It builds a single UI screen
from the engine's raw, CSS-parity widget tree: a header bar over a dark canvas
holding two absolutely-positioned node cards joined by an SVG bezier connector.
Clicking a node selects it — the card highlights and the header's "last click"
readout updates.
This is the file you edit. Add nodes and connections to state; the tree is
rebuilt from that data.
The contract
A UiPanel component points its source at this module and calls
mount(ctx), where ctx = { entity, entityId, data, state? }. mount returns
the handlers the panel drives each frame:
function M.mount(ctx)
local state = ctx.state or { nodes = {...}, connections = {...}, selected = nil, lastClick = "(none)" }
local function build() return { --[[ widget tree ]] } end
ui.registerScreen("canvas_app", build()) -- register the tree as a screen
return {
state = state,
update = function(dt) end, -- per-frame work (no-op here)
onCallback = function(id, data) -- an interaction lands here
local nodeId = string.match(id, "^graph:click:(.+)$")
if nodeId then
state.selected = nodeId
ui.updateScreen("canvas_app", build()) -- mutate, then re-render
end
end,
destroy = function() ui.unregisterScreen("canvas_app") end,
}
end
stateis returned so the panel hands it back on a live-reload — your selection survives an edit.update(dt)runs every frame; leave it a no-op when nothing animates.onCallback(id, data)receives every widget's callback id. Mutate state, then callui.updateScreen(name, build())to publish the new tree.destroy()unregisters the screen when the panel goes away.
The raw widget tree
A node is a table: { type = "...", props = {...}, style = {...}, children = {...} }.
- Containers lay out their children:
vertical(column),horizontal(row),spacer(flexes to push siblings apart). - Leaves carry content:
label(text inprops),svg(an inline SVG string inprops.svg, drawn at display resolution). propsis the widget's data and behaviour — a label'stext, an SVG's markup, a clickable widget'sonClickcallback id.styleis CSS:position,left,top,width,height,background,color,fontSize,fontWeight,padding,gap,borderRadius,borderWidth,borderColor,boxShadow,overflow, and the rest of the box model.
The tree is a pure function of state: to change what shows, rebuild the tree
and call ui.updateScreen. There is no retained widget object to mutate.
The node-graph layout
A node-graph canvas is built with absolute positioning:
- The canvas is a
verticalwithstyle = { position = "relative" }and a fixedwidth/height, so its children position against its top-left corner. - Each node card is a
verticalwithstyle = { position = "absolute", left = x, top = y, ... }— its(x, y)come straight from the node's data. - The connection lines are one
svgleaf sized to the canvas and placedposition = "absolute", left = 0, top = 0so it sits behind the cards (it is the first child). Each connection is a bezier<path>from one node's right edge to another's left edge.
Interactivity
Each node card carries a string callback id in props
(onClick = "graph:click:" .. node.id). onClick works on any widget — the
whole card is clickable, not just a button inside it. The node id is encoded in
the callback id; onCallback parses it back out with string.match, records the
selection, and re-renders so the selected card highlights.
Interaction runs in play, not edit: enter play and click a node to select it and watch the "last click" readout update.
Discover the surface
- The
guitoolbox's widgetTypes tool lists everytype. - Its widgetProps tool lists one type's
props. @builtin::examples.ui.*are complete CSS-parity screens (dashboard, sci-fi HUD, RPG inventory) in this exact model — read one before building.- The UI and styling guides cover the model and the full style surface.