-
Notifications
You must be signed in to change notification settings - Fork 113
lua
title: Lua Scripting description: The interface Lua API, widget reflection, and building whole applications in interface data published: true editor: markdown
Lua is the scripting layer of the interface: scripts see every widget as a live object, can rewire anything at runtime, and can carry entire applications. The showcase's Minesweeper, drag-and-drop inventory and the console below are pure XML+Lua — no game code in the binary.

<lua file="minesweeper.lua" /><lua> elements execute at parse time, in order, in one shared Lua state. A script error is fatal to interface loading (everything else in the parser merely warns — scripts are assumed load-bearing). Functions defined in one file are visible to all later files and to every handler.
Scripts hook into the UI through the lua action handler: onclick_l="lua" params_l="game.onClick(1)", onenter="lua" params="console.enter()", and the engine itself runs setOnDraw scripts and lua_class associations through the same handler.
What the library registers (available in any embedding that calls initLUA):
UI manipulation — setOnDraw(group, "script") (per-frame tick; the Minesweeper timer and the inventory gesture loop live here), getOnDraw, addOnDbChange(group, "@UI:A,@UI:B", "script") / removeOnDbChange (react to database changes), runAH(ctrlOrNil, "ah", "params"), runExpr("expr") / runFct("fct", ...) (interface expressions), getUIId(elt), deleteUI(elt), setCaptureKeyboard(ctrl) / resetCaptureKeyboard, setTopWindow(win), disableModalWindow, getCurrentWindowUnder(), getWindowSize(), getTextureSize("name").
Environment — runCommand (NeL console commands), fileExists, fileLookup, getPathContent (a filesystem listing — nothing to do with UI children), nltime.getPreciseLocalTime() (seconds, high resolution), nltime.getLocalTime() (ms), nltime.getSecondsSince1970(), nlfile.*, i18n.get("uiKey"), concatString, findReplaceAll, and the CRGBA/CVector2f classes.
From the embedder — getUI("ui:path") is not part of the library: every application supplies its own widget lookup (the client's does extra work; the sample registers a minimal one). The client additionally provides getUICaller(), getDbProp/setDbProp, formatUI, dumpUI, messageBox, createGroupInstance and a large game-side surface — check what your host registers. The sample adds getMousePos()/getMouseDown() for gesture work (below).
Lua 5.1 semantics apply: math.fmod not % on floats in old idioms, #t for length, loadstring for eval.
getUI returns a userdata wrapping the element. Three things work on it:
Properties — every reflected property reads and writes as a field: w.active = false, txt.hardtext = 'Hello', bmp.color = '150 255 150 255', eb.input_string, plus the read-only computed geometry x_real/y_real/w_real/h_real (see Layout). Wrong property names read as nil (safe to probe) but assigning an unknown property raises an error.
Child navigation — child elements are fields: win.content.buttons.ok, cell.cover.active = false. elt.parent walks up. elt.isNil is true if the underlying C++ object has been destroyed (the wrapper is a safe reference, not ownership).
Enumeration — widget userdata implement the __next metamethod: redefine next/pairs to honor it (the Ring scripts do exactly this) and pairs(widget) iterates children first (groups, then views, then ctrls — keys stringify like "_Group 3", values are the child widgets), then every reflected property (keys stringify to the property names):
local oldNext = next
function next(t, k)
local mt = getmetatable(t)
if mt and mt.__next then return mt.__next(t, k) end
return oldNext(t, k)
end
function pairs(t) return next, t, nil end
for k, v in pairs(getUI('ui:sample:views')) do
print(tostring(k), tostring(v))
endThis is the foundation of the console's ls() (children by kind) and props() (property dump) helpers — a complete UI inspector in ~40 lines of Lua.

The board is a <vector> of 81 cell templates; all rules live in Lua. A cell reveal is just cell.cover.active = false; flags toggle a marker bitmap; the timer is a setOnDraw script comparing nltime.getPreciseLocalTime(). Buttons pass their index through their params: onclick_l="lua" params_l="mines.reveal($i)" stamped by the vector.
The console log is a vector of text views repainted from a history table; the input is a stock edit box with onenter="lua" params="console.enter()" and enter_loose_focus="false" (the trap). Evaluation tries loadstring('return '..line) first so expressions print their value, then falls back to statement form; print is redirected into the log. Because it runs in the same state as every other script, it can poke any running demo live — indispensable for debugging wiring.

The library has no drag widget; the inventory does gestures in data using two embedder hooks and a per-frame tick:
-- inside setOnDraw of the inventory window
local mx, my = getMousePos()
local down = getMouseDown()
-- press edge: hit-test slots against x_real/y_real/w_real/h_real
-- while dragging: ghost.x = mx - content.x_real - 20 (BL-anchored ghost bitmap)
-- release edge: accept/swap/reject by comparing slot kind tags in LuaSlots are plain groups; the dragged item is a render_layer-raised ghost bitmap; the glow is an active toggle on a highlight bitmap; acceptance rules are table lookups. Coordinates from the mouse hooks are interface pixels — the same space as the reflected *_real geometry, so hit-testing is a four-comparison function. The hooks themselves (getMousePos, getMouseDown, and the pointer button mirroring they depend on) are part of the embedding contract.
- Ship a console (the sample's is ~150 lines of XML+Lua, reusable) — live inspection beats log archaeology.
-
runAHfrom the console exercises any wiring exactly as a click would. - Parse-time scripts can call
getUIon elements declared earlier in the parse; defer anything touching later elements to a firstsetOnDrawtick or anon_activehandler. - Errors inside AH-invoked scripts land in the application log, not on screen — keep it visible while iterating.