Context and list menus
Two menu styles, both client only. Register a menu once under an id, then show it by id.
| Context menu | List menu | |
|---|---|---|
| Look | Titled panel of rich rows (description, image, progress bar, metadata, badge) | Compact keyboard menu in a screen corner |
| Navigation | Mouse, with nested menus and a back button | Arrow keys, with scrolling values, checkboxes and paging |
| Callbacks | Per row (onSelect, event, serverEvent) | One confirm function for the whole menu, plus onCheck, onSideScroll |
| Best for | Inventories, vehicle lists, choices with detail | Settings, toggles, option pickers |
Neither needs Lua after the menu opens until the player acts: filtering, scrolling and paging all happen in the page.
tm.ui.menuis a third, separate thing: the nested menu of the windows page. Use these two for the standard look, andtm.ui.menuwhen the menu has to live inside atm.ui.windowpage.
Context menus
tm.registerContext({
{
id = 'garage_main',
title = tm.locale.t('garage.title'),
icon = 'lucide:car',
options = {
{
title = tm.locale.t('garage.myVehicles'),
description = tm.locale.t('garage.myVehiclesDesc'),
icon = 'lucide:key-round',
menu = 'garage_vehicles', -- opens another registered menu
},
{
title = tm.locale.t('garage.impound'),
icon = 'lucide:lock',
badge = 2,
serverEvent = 'tm_garages:server:openImpound',
},
{ title = tm.locale.t('garage.closed'), disabled = true },
},
},
{
id = 'garage_vehicles',
title = tm.locale.t('garage.myVehicles'),
menu = 'garage_main', -- parent: shows a back button
searchable = true,
options = {
{
title = 'Karin Sultan',
description = tm.locale.t('garage.plate', { plate = 'TM 4821' }),
progress = 82, -- shown as a bar, 0 to 100
metadata = { 'Engine 82%', 'Fuel 40%' },
args = { plate = 'TM 4821' },
onSelect = function(args) spawnVehicle(args.plate) end,
},
},
},
})
tm.showContext('garage_main')| Function | Notes |
|---|---|
tm.registerContext(menu | menu[]) | Registers one menu or an array of them. Registering an id again replaces it. |
tm.showContext(id) | Shows a registered menu. Replaces the one on screen without running its onExit. Logs an error for an unknown id. |
tm.hideContext(runOnExit?) | Closes the open menu. Pass true to run its onExit too. |
tm.getOpenContextMenu() | The id of the menu on screen, or nil. |
A menu is built when it is registered, so register again to change its rows.
Menu fields
| Field | Meaning |
|---|---|
id, title | Required. |
options | Rows, as an array. A table keyed by name also works: it is sorted by key and the key becomes the title when a row has none. |
menu | Id of the parent menu. Shows a back button that returns to it. |
icon, iconColor | Icon next to the title. |
canClose | Escape and the X close the menu. Default true. |
searchable, searchPlaceholder | Adds a search box above the rows. It matches title, description and keywords. |
position | A corner name or { x, y, right } in pixels. |
onExit | Runs when the player closes the menu (X or Escape). |
onBack | Runs when the player presses back. |
Row fields
| Field | Meaning |
|---|---|
title, description | The text. |
icon, iconColor, iconRight, iconRightColor | Icons on the left and right. See icons. |
image | Picture beside the row: an item:<name> or an image URL. |
progress | 0 to 100, drawn as a bar. colorScheme colors it. |
badge | Small text or number tag. |
arrow | Show a chevron. Shown automatically for rows with menu. |
metadata | Detail lines: { 'a', 'b' }, { Label = 'value' } or { { label = 'Fuel', value = '40%', progress = 40 } }. |
keywords | Extra words the search box matches, never shown. |
disabled | Greyed out and not clickable. |
readOnly | Normal looking but not clickable. |
menu | Id of a registered menu to open when chosen. |
onSelect(args) | Function called when chosen. |
event, serverEvent | Client or server event triggered when chosen, with args. |
args | Passed to onSelect and to both events. It never goes to the page. |
When a row is chosen, the menu closes before its handlers run, so a handler is free to open a dialog or another menu. Rows with menu keep the interface open and switch to the child.
List menus
tm.registerMenu({
id = 'phone_settings',
title = tm.locale.t('phone.settings'),
position = 'top-right',
pageSize = 6,
options = {
{ label = tm.locale.t('phone.airplane'), checked = false, args = { key = 'airplane' } },
{ label = tm.locale.t('phone.ringtone'), values = { 'Classic', 'Pulse', 'Silent' }, defaultIndex = 1, args = { key = 'ringtone' } },
{ label = tm.locale.t('phone.storage'), progress = 64, description = tm.locale.t('phone.storageDesc'), close = false },
{ label = tm.locale.t('phone.reset'), icon = 'lucide:rotate-ccw', args = { key = 'reset' } },
},
onCheck = function(selected, checked, args) setSetting(args.key, checked) end,
onSideScroll = function(selected, scrollIndex, args) setSetting(args.key, scrollIndex) end,
onClose = function(keyPressed) print('closed with', keyPressed) end,
}, function(selected, scrollIndex, args)
-- a row was confirmed (Enter or click)
if args.key == 'reset' then resetPhone() end
end)
tm.showMenu('phone_settings')| Function | Notes |
|---|---|
tm.registerMenu(data, confirm?) | Registers a menu. confirm(selected, scrollIndex, args) runs when a row is confirmed. |
tm.showMenu(id) | Shows it. |
tm.hideMenu(runOnClose?) | Closes the open menu. Pass true to run its onClose (it receives no key). |
tm.getOpenMenu() | The id of the menu on screen, or nil. |
tm.setMenuOptions(id, options, index?) | Replaces every row, or just row index when it is given. A menu that is open updates in place and keeps the player's position. |
Indexes are 1-based everywhere in Lua (selected, scrollIndex, startItemIndex, defaultIndex, the index of setMenuOptions).
Menu fields
| Field | Meaning |
|---|---|
id, title, options | Required. |
position | 'top-left', 'top-right', 'bottom-left', 'bottom-right' or { x, y, right }. |
icon | Icon next to the title. |
canClose | Escape closes the menu. Default true. Backspace closes it either way. |
startItemIndex | Row highlighted first. |
pageSize | Rows per page. Leave out for no paging. |
onSelected(selected, scrollIndex, args) | The highlight moved to a row. |
onSideScroll(selected, scrollIndex, args) | A scroll row changed value. |
onCheck(selected, checked, args) | A checkbox row was toggled. |
onClose(keyPressed) | The player closed the menu. keyPressed is 'Escape' or 'Backspace'. |
Row fields
| Field | Meaning |
|---|---|
label | Required. |
icon, iconColor, iconAnimation | Icon, its color and an animation: spin, spinPulse, spinReverse, pulse, beat, fade, beatFade, bounce, shake. |
values | Makes the row scroll left and right through these entries (strings, or { label, description }). |
defaultIndex | Starting entry of a scroll row. |
checked | true or false turns the row into a checkbox. |
progress, colorScheme | A progress row, 0 to 100. |
description | Shown in the footer while the row is highlighted. |
disabled | Not selectable. |
close | false keeps the menu open after the row is confirmed. |
args | Passed to every callback of this row. It never goes to the page. |
The confirmed row's menu closes before confirm runs (unless the row has close = false), so confirm can open another menu.