Skip to content

Events ​

The page tells Lua about things the player did. You listen with the handle:

lua
win:on('save', function(data, eventName) ... end)

Callbacks always receive (data, eventName). data always has id (the window id).

Listener API ​

lua
win:on(event, fn)            -- add (several per event are fine)
win:once(event, fn)          -- removes itself after the first call
win:off(event, fn?)          -- remove one, or all for that event
win:onClick(event, fn)       -- alias of :on, reads better for buttons
win:onChange(path, fn)       -- shorthand for on('change:' .. path, fn)
win:onAny(fn)                -- every event; second argument is the event name

You can also pass listeners when opening: tm.ui.window(schema, { save = fn, close = { fn1, fn2 } }). A listener that errors is caught and printed; the others still run.

Event reference ​

EventWhendata
button eventA button is pressed (after validation, if submit = true).{ id, event, values?, args?, close? }. values only for submit buttons.
changeA field with notify = true changed (debounced 150 ms).{ id, path, value, old }
change:<path>Same, for one path.as change
selectA menu item was chosen.{ id, key } (menus: see menus)
checkA menu checkbox was toggled.{ id, key, checked }
list / table eventA row was clicked.{ id, event, key }
tile eventA tile was pressed.{ id, event, args?, close? }
timer eventA timer node reached zero (default event timeout).{ id, event, close? }
keydown / keyupWindow has keys = true and is on top.{ id, key } (key is the browser key name: 'a', 'Enter', 'ArrowUp')
closeThe player dismissed the window (Escape or the X).{ id }
*Every event above.as the event

close does not fire when you call win:close() yourself, or when a button with close = true closes the window (that button's own event fires instead).

Change events and old ​

old is Lua's last known value for that path: the initial values, or whatever the previous change / submit / win:set delivered. It is nil the first time if the schema had no initial value.

lua
win:onChange('volume', function(d)
    print(('volume %s -> %s'):format(d.old, d.value))
end)

Only fields with notify = true send changes. Everything else stays in the page until a submit, so typing costs nothing.

Keys ​

lua
local win = tm.ui.window({ title = 'Racer', keys = true, children = { ... } })
win:on('keydown', function(d) if d.key == 'r' then restart() end end)

Only the topmost window with keys = true forwards keys. Repeats from holding a key are not sent. Keys typed into inputs are also forwarded, so filter if you have text fields.

Submitting and waiting ​

lua
local values = tm.ui.window(schema):wait()

:wait() resolves on the first event that carries values (a submit = true button) with those values, or with nil when the window is dismissed or closed. Listeners still run, so you can use both: react to events and also await the result.

Order of things for one button press ​

  1. UI validates (submit buttons) and sends the event.
  2. Lua updates its value mirror, then runs the event's listeners, then the * listeners.
  3. :wait() resolves if the event carried values.
  4. If the button had close = true, the Lua handle is marked closed and NUI focus is released.