Scripts/resources/[standalone]/ox_lib/resource/interface/client/radial.lua

345 lines
7.9 KiB
Lua
Raw Normal View History

2024-12-29 20:02:43 +00:00
---@class RadialItem
---@field icon string | {[1]: IconProp, [2]: string};
---@field label string
---@field menu? string
---@field onSelect? fun(currentMenu: string | nil, itemIndex: number) | string
---@field [string] any
---@field keepOpen? boolean
---@class RadialMenuItem: RadialItem
---@field id string
---@class RadialMenuProps
---@field id string
---@field items RadialItem[]
---@field [string] any
local isOpen = false
---@type table<string, RadialMenuProps>
local menus = {}
---@type RadialMenuItem[]
local menuItems = {}
---@type table<{id: string, option: string}>
local menuHistory = {}
---@type RadialMenuProps?
local currentRadial = nil
---Open a the global radial menu or a registered radial submenu with the given id.
---@param id string?
---@param option number?
local function showRadial(id, option)
local radial = id and menus[id]
if id and not radial then
return error('No radial menu with such id found.')
end
currentRadial = radial
-- Hide current menu and allow for transition
SendNUIMessage({
action = 'openRadialMenu',
data = false
})
Wait(100)
-- If menu was closed during transition, don't open the submenu
if not isOpen then return end
SendNUIMessage({
action = 'openRadialMenu',
data = {
items = radial and radial.items or menuItems,
sub = radial and true or nil,
option = option
}
})
end
---Refresh the current menu items or return from a submenu to its parent.
local function refreshRadial(menuId)
if not isOpen then return end
if currentRadial and menuId then
if menuId == currentRadial.id then
return showRadial(menuId)
else
for i = 1, #menuHistory do
local subMenu = menuHistory[i]
if subMenu.id == menuId then
local parent = menus[subMenu.id]
for j = 1, #parent.items do
-- If we still have a path to the current submenu, refresh instead of returning
if parent.items[j].menu == currentRadial.id then
return -- showRadial(currentRadial.id)
end
end
currentRadial = parent
for j = #menuHistory, i, -1 do
menuHistory[j] = nil
end
return showRadial(currentRadial.id)
end
end
end
return
end
table.wipe(menuHistory)
showRadial()
end
---Registers a radial sub menu with predefined options.
---@param radial RadialMenuProps
function lib.registerRadial(radial)
menus[radial.id] = radial
radial.resource = GetInvokingResource()
if currentRadial then
refreshRadial(radial.id)
end
end
function lib.getCurrentRadialId()
return currentRadial and currentRadial.id
end
function lib.hideRadial()
if not isOpen then return end
SendNUIMessage({
action = 'openRadialMenu',
data = false
})
lib.resetNuiFocus()
table.wipe(menuHistory)
isOpen = false
currentRadial = nil
end
---Registers an item or array of items in the global radial menu.
---@param items RadialMenuItem | RadialMenuItem[]
function lib.addRadialItem(items)
local menuSize = #menuItems
local invokingResource = GetInvokingResource()
items = table.type(items) == 'array' and items or { items }
for i = 1, #items do
local item = items[i]
item.resource = invokingResource
if menuSize == 0 then
menuSize += 1
menuItems[menuSize] = item
else
for j = 1, menuSize do
if menuItems[j].id == item.id then
menuItems[j] = item
break
end
if j == menuSize then
menuSize += 1
menuItems[menuSize] = item
end
end
end
end
if isOpen and not currentRadial then
refreshRadial()
end
end
---Removes an item from the global radial menu with the given id.
---@param id string
function lib.removeRadialItem(id)
local menuItem
for i = 1, #menuItems do
menuItem = menuItems[i]
if menuItem.id == id then
table.remove(menuItems, i)
break
end
end
if not isOpen then return end
refreshRadial(id)
end
---Removes all items from the global radial menu.
function lib.clearRadialItems()
table.wipe(menuItems)
if isOpen then
refreshRadial()
end
end
RegisterNUICallback('radialClick', function(index, cb)
cb(1)
local itemIndex = index + 1
local item, currentMenu
if currentRadial then
item = currentRadial.items[itemIndex]
currentMenu = currentRadial.id
else
item = menuItems[itemIndex]
end
local menuResource = currentRadial and currentRadial.resource or item.resource
if item.menu then
menuHistory[#menuHistory + 1] = { id = currentRadial and currentRadial.id, option = item.menu }
showRadial(item.menu)
elseif not item.keepOpen then
lib.hideRadial()
end
local onSelect = item.onSelect
if onSelect then
if type(onSelect) == 'string' then
return exports[menuResource][onSelect](0, currentMenu, itemIndex)
end
onSelect(currentMenu, itemIndex)
end
end)
RegisterNUICallback('radialBack', function(_, cb)
cb(1)
local numHistory = #menuHistory
local lastMenu = numHistory > 0 and menuHistory[numHistory]
if not lastMenu then return end
menuHistory[numHistory] = nil
if lastMenu.id then
return showRadial(lastMenu.id, lastMenu.option)
end
currentRadial = nil
-- Hide current menu and allow for transition
SendNUIMessage({
action = 'openRadialMenu',
data = false
})
Wait(100)
-- If menu was closed during transition, don't open the submenu
if not isOpen then return end
SendNUIMessage({
action = 'openRadialMenu',
data = {
items = menuItems,
option = lastMenu.option
}
})
end)
RegisterNUICallback('radialClose', function(_, cb)
cb(1)
if not isOpen then return end
lib.resetNuiFocus()
isOpen = false
currentRadial = nil
end)
RegisterNUICallback('radialTransition', function(_, cb)
Wait(100)
-- If menu was closed during transition, don't open the submenu
if not isOpen then return cb(false) end
cb(true)
end)
local isDisabled = false
---Disallow players from opening the radial menu.
---@param state boolean
function lib.disableRadial(state)
isDisabled = state
if isOpen and state then
return lib.hideRadial()
end
end
lib.addKeybind({
name = 'ox_lib-radial',
description = 'Open radial menu',
defaultKey = 'z',
onPressed = function()
if isDisabled then return end
if isOpen then
return lib.hideRadial()
end
if #menuItems == 0 or IsNuiFocused() or IsPauseMenuActive() then return end
isOpen = true
SendNUIMessage({
action = 'openRadialMenu',
data = {
items = menuItems
}
})
lib.setNuiFocus(true)
SetCursorLocation(0.5, 0.5)
while isOpen do
DisablePlayerFiring(cache.playerId, true)
DisableControlAction(0, 1, true)
DisableControlAction(0, 2, true)
DisableControlAction(0, 142, true)
DisableControlAction(2, 199, true)
DisableControlAction(2, 200, true)
Wait(0)
end
end,
-- onReleased = lib.hideRadial,
})
AddEventHandler('onClientResourceStop', function(resource)
for i = #menuItems, 1, -1 do
local item = menuItems[i]
if item.resource == resource then
table.remove(menuItems, i)
end
end
end)