345 lines
7.9 KiB
Lua
345 lines
7.9 KiB
Lua
|
---@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)
|