Client
Last updated on
The menu contract on the client — open, update and close a list menu, and the spec it accepts.
local menu = OPX.Api.Get('menu')From client code inside opx_infinity. Every function answers a Result ({ ok = true, value } or { ok = false, error }). None yields, unless the spec opts in with yield = true. Codes: Refusal codes.
Open
Opens a menu from a spec. The spec is refused whole on the first bad field.
menu.Open(spec)Parameters
- spec:
table— see The menu spec
Returns
-
Result— value{ handle, id, nodes } -
Another owner's menu open:
menu_busy, unlessspec.steal = true. -
The same owner opening again replaces its menu (
closereasonreopened).
local menu = OPX.Api.Get('menu')
local opened = menu.Open({
owner = 'mymodule',
title = 'GARAGE',
items = {
{ id = 'out', label = 'Take out', icon = 'vehicle' },
{ id = 'lights', label = 'Lights', toggle = false },
{ label = 'Close', close = true },
},
on = function(p)
if p.action == 'select' and p.itemId == 'out' then --[[ ... ]] end
end,
})
if not opened.ok then print(opened.error) endClose
Closes the menu.
menu.Close(handle, reason)Parameters
- handle:
integer— required (handle_required) - reason?:
string- Max 32 characters, passed to the
closecallback. Defaultcaller.
- Max 32 characters, passed to the
Returns
Result— valuetrue
Update
Rebuilds the open menu and keeps the player's position (submenus stay open while a row with the same id still exists).
menu.Update(handle, patch)
menu.Update(patch)Parameters
- handle?:
integer - patch:
table- Patchable:
items,title,on,data,closeOnSelect,reportFocus,status,statusBad,cursor. itemsis all or nothing. Geometry andfocusare fixed at open.
- Patchable:
Returns
Result— valuetrue
menu.Update(opened.value.handle, { title = 'GARAGE (2)' })SetStatus
Shows text as a toast. There is no status line under the list any more.
menu.SetStatus(handle, text, bad)Parameters
- handle?:
integer - text:
string— max 120 characters - bad?:
boolean— red when true
Returns
Result— valuetrue
State
What is on screen now.
menu.State()Returns
Result— value{ open = false }or{ open, handle, owner, menu, title, depth, index, total, itemId, label, value }
Keys
For a caller printing its own key hint.
menu.Keys()Returns
Result— value{ backend, keys, labels }backend:page, ornonewhen no surface existskeys: mapsUP DOWN LEFT RIGHT SELECT BACKtoARROW UP…ENTER,BACKSPACElabels: the locale words
The menu spec
| Field | Type | Default | Meaning |
|---|---|---|---|
owner | string | required | Your module id. Letters, digits, _ : - ., max 64. If it names a module that stops, the menu closes (owner_stopped). |
on | function | required | on(payload) for every action. See The action payload. |
items | list | required | The rows of the root level. See Rows. |
id | string | owner | Menu id, echoed as payload.menu. |
title | string | owner upper-cased | Max 96 characters. |
data | table | — | Echoed as payload.menuData. Max 64 value nodes, 4 levels deep. |
focus | string | none | none: no input focus, the player keeps walking; Lua reads the six keys itself. cursor: the page gets the mouse. full: the page gets keyboard and mouse. true/false read as none. |
closable | boolean | true | false: Escape does not close it; the owner must call Close. |
closeOnSelect | boolean | false | Close after any select. |
reportFocus | boolean | false | Raise a focus action each time the cursor lands on a row. |
cursor | string or integer | first selectable row | Row id or index to start on. |
steal | boolean | false | Replace another owner's open menu. |
status, statusBad | string, boolean | — | Shown as a toast at open. |
anchor | string | ANCHOR | top-left, top-right, left, right or center. |
width | integer | WIDTH | Pixels, clamped 240..1200. |
maxHeight | integer | MAX_HEIGHT_VH | Viewport height %, clamped 20..100. |
height | integer | none | Fixed pixel height, clamped 120..1200. Absent: the rows decide. |
rows | integer | VISIBLE_ROWS | Rows drawn at once, clamped 3..24. |
yield | boolean | false | Open/Update only. When the caller runs on its own thread, rows are checked six at a time with a frame between, so a big menu stays inside one resume's instruction budget. The surface, the down state, menu_busy (or for Update, no_menu_open/stale_handle) are re-checked before the menu is installed. Ignored where the caller cannot yield. |
Limits: 200 rows per level, 400 rows across the whole tree, 8 levels deep. Only the visible window of rows is sent to the page. Checking a spec costs roughly 240 instructions per row: for more than a few dozen rows, page them or pass yield = true from a thread.
CreateThread(function()
local opened = menu.Open({ owner = 'mymodule', items = rows, on = onAction, yield = true })
if not opened.ok then print(opened.error) end
end)Rows
A row's kind comes from its shape. Tested in this order; the first match wins.
| Kind | Shape | Behaviour |
|---|---|---|
| separator | separator = true, optional label | A divider. Cannot be selected. Other fields are ignored. |
| submenu | items = { … }, optional title, value, cursor | Enter or Right opens the level (open action). |
| toggle | toggle = true/false, optional onLabel, offLabel | Enter, Left or Right flips it (change). value is the boolean. |
| choices | choices = { 'a', 'b' }, optional selected (index) | Left/Right cycles (change). value is the chosen string. |
| slider | slider = { min = 0, max = 100, step = 1, value, suffix } | Left/Right steps and clamps (change). value is the number. max must be above min. |
| back | back = true | Pops one level, or closes at the root. Escape does the same: it goes back one level and closes only at the top. |
| close | close = true and no act | Closes the menu (reason item). |
| action | anything else | Enter raises select. value (max 48) is the right-hand text. |
Fields every row may carry:
| Field | Meaning |
|---|---|
id | Row id, echoed as itemId. Default item_<index>. |
label (or text) | Required, max 96 characters. |
icon | A name from OPX.Glyphs; an unknown name refuses the spec (invalid_item_icon). |
description | Hint shown for the focused row, max 160. |
data | Echoed as payload.data. Max 64 nodes, 4 deep. |
disabled | Drawn greyed; the cursor skips it. |
act | act(payload), called before on for this row only. |
close | On an action row with act: close after it fires. |
submenu | true draws the arrow on a row with no items of its own (for owners that open one flat screen at a time). It still raises select. |
confirm | On a row with no items: true or { title?, yes?, no? }. See Confirm rows. |
Confirm rows
A row with confirm opens a two-row level instead of acting. The first row keeps things as they are (a back row, so Enter pressed without looking does nothing); the second does what the row said, under the same id and data, so on receives the select it always did.
| Field | Default (en / fr) |
|---|---|
title | {label}? / {label} ? (menu.confirm.title) |
no | No, keep it / Non, garder (menu.confirm.no) |
yes | Yes, {label} / Oui : {label} (menu.confirm.yes) |
items = {
{ id = 'delete', label = 'Delete outfit', icon = 'trash', confirm = { yes = 'Delete outfit', no = 'Keep it' } },
{ id = 'sell', label = 'Sell', confirm = true },
},
on = function(p)
if p.action == 'select' and p.itemId == 'delete' then --[[ only after the second row ]] end
end,The action payload
on(payload) and the opx:on:menu:action event receive the same table.
| Field | Meaning |
|---|---|
menu, handle, owner | Which menu. |
action | select, change, open, back, focus or close. |
itemId, label, value, data | The row involved. Absent on close and on a back from Left/Backspace. |
index, depth | Cursor position on the current screen (1-based) and stack depth (1 at the root). |
menuData | The spec's data. |
reason | On close: caller (or your own reason), select, item, back, dismissed, pause, reopened, superseded, owner_stopped, player_down, stopped. |
repeated | On focus: true when the key is held down. |