From 6c0c2bb8203ab9a10787bf51d0761e5ae0b73205 Mon Sep 17 00:00:00 2001 From: Xcaliber407 Date: Thu, 9 Jul 2026 23:43:17 -0400 Subject: [PATCH 1/4] feat(radial): add support for directly opening a radial menu as root --- resource/interface/client/radial.lua | 60 +++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/resource/interface/client/radial.lua b/resource/interface/client/radial.lua index a66e3d69c..8c32e0bcf 100644 --- a/resource/interface/client/radial.lua +++ b/resource/interface/client/radial.lua @@ -38,6 +38,10 @@ local menuHistory = {} ---@type RadialMenuProps? local currentRadial = nil +---The registered menu opened directly with lib.showRadialMenu. +---@type RadialMenuProps? +local rootRadial = nil + ---Open a the global radial menu or a registered radial submenu with the given id. ---@param id string? ---@param option number? @@ -65,7 +69,7 @@ local function showRadial(id, option) action = 'openRadialMenu', data = { items = radial and radial.items or menuItems, - sub = radial and true or nil, + sub = radial and radial ~= rootRadial or nil, option = option } }) @@ -138,6 +142,7 @@ function lib.hideRadial() isOpen = false currentRadial = nil + rootRadial = nil end ---Registers an item or array of items in the global radial menu. @@ -304,6 +309,7 @@ RegisterNUICallback('radialClose', function(_, cb) isOpen = false currentRadial = nil + rootRadial = nil end) RegisterNUICallback('radialTransition', function(_, cb) @@ -327,6 +333,58 @@ function lib.disableRadial(state) end end +---Shows a registered menu as root without affecting global menus. +---@param id string +---@param option number? +function lib.showRadialMenu(id, option) + local radial = id and menus[id] + if not radial then return end + + if isDisabled then return end + + if isOpen then + return lib.hideRadial() + end + + local radialItems = radial.items + if #radialItems == 0 or IsNuiFocused() or IsPauseMenuActive() then return end + + isOpen = true + currentRadial = radial + rootRadial = radial + table.wipe(menuHistory) + + SendNUIMessage({ + action = 'openRadialMenu', + data = false + }) + + Wait(100) + + if not isOpen then return end + + lib.setNuiFocus(true) + SetCursorLocation(0.5, 0.5) + + SendNUIMessage({ + action = 'openRadialMenu', + data = { + items = radialItems, + option = option + } + }) + + 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 + lib.addKeybind({ name = 'ox_lib-radial', description = locale('open_radial_menu'), From 34773a806de294f3b5d7856642a52c95968488a8 Mon Sep 17 00:00:00 2001 From: Xcaliber407 Date: Fri, 10 Jul 2026 00:48:49 -0400 Subject: [PATCH 2/4] fix(radial): Root menus now transition between each other --- resource/interface/client/radial.lua | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/resource/interface/client/radial.lua b/resource/interface/client/radial.lua index 8c32e0bcf..7c4fd3fe0 100644 --- a/resource/interface/client/radial.lua +++ b/resource/interface/client/radial.lua @@ -335,15 +335,21 @@ end ---Shows a registered menu as root without affecting global menus. ---@param id string ----@param option number? -function lib.showRadialMenu(id, option) +function lib.showRadialMenu(id) local radial = id and menus[id] if not radial then return end if isDisabled then return end if isOpen then - return lib.hideRadial() + --Transitions between different root menus + if rootRadial == radial then + return lib.hideRadial() + end + + rootRadial = radial + table.wipe(menuHistory) + return showRadial(id) end local radialItems = radial.items @@ -370,7 +376,6 @@ function lib.showRadialMenu(id, option) action = 'openRadialMenu', data = { items = radialItems, - option = option } }) @@ -393,12 +398,22 @@ lib.addKeybind({ if isDisabled then return end if isOpen then + --Transitions between different root menus + if rootRadial then + rootRadial = nil + table.wipe(menuHistory) + return showRadial() + end + return lib.hideRadial() end if #menuItems == 0 or IsNuiFocused() or IsPauseMenuActive() then return end isOpen = true + currentRadial = nil + rootRadial = nil + table.wipe(menuHistory) SendNUIMessage({ action = 'openRadialMenu', From ac164e107afb040cdf3d6d3fef2afbe46c2ab7ad Mon Sep 17 00:00:00 2001 From: Xcaliber407 Date: Fri, 10 Jul 2026 02:06:39 -0400 Subject: [PATCH 3/4] fix(radial): add showRadialMenu ts export --- package/client/interface/radial.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/client/interface/radial.ts b/package/client/interface/radial.ts index c1190b82a..09e536f9f 100644 --- a/package/client/interface/radial.ts +++ b/package/client/interface/radial.ts @@ -19,6 +19,8 @@ export const registerRadial = (radial: { id: string; items: Omit exports.ox_lib.getCurrentRadialId(); +export const showRadialMenu = (menuId: string) => exports.ox_lib.showRadialMenu(menuId); + export const hideRadial = () => exports.ox_lib.hideRadial(); export const disableRadial = (state: boolean) => exports.ox_lib.disableRadial(state); From 9f78c6512fe25b284c0ca4d4a23bbef4d9e7b7ea Mon Sep 17 00:00:00 2001 From: Xcaliber407 Date: Fri, 17 Jul 2026 16:15:52 -0400 Subject: [PATCH 4/4] refactor(radial): cleaned up duplicated radial setup --- resource/interface/client/radial.lua | 139 +++++++++------------------ 1 file changed, 45 insertions(+), 94 deletions(-) diff --git a/resource/interface/client/radial.lua b/resource/interface/client/radial.lua index 7c4fd3fe0..c411dd1eb 100644 --- a/resource/interface/client/radial.lua +++ b/resource/interface/client/radial.lua @@ -26,6 +26,8 @@ local isOpen = false +local isDisabled = false + ---@type table local menus = {} @@ -114,6 +116,46 @@ local function refreshRadial(menuId) showRadial() end +---Validates requirements and shows the requested radial menu. +---@param id string? If not provided, the global radial menu will be shown. +local function onRadialOpen(id) + local radial = id and menus[id] + + if isDisabled or (id and not radial) then return end + + if isOpen then + if rootRadial == radial then + return lib.hideRadial() + end + + rootRadial = radial + table.wipe(menuHistory) + return showRadial(id) + end + + local items = radial and radial.items or menuItems + if #items == 0 or IsNuiFocused() or IsPauseMenuActive() then return end + + isOpen = true + rootRadial = radial + table.wipe(menuHistory) + + lib.setNuiFocus(true) + SetCursorLocation(0.5, 0.5) + + showRadial(id) + + 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 + ---Registers a radial sub menu with predefined options. ---@param radial RadialMenuProps function lib.registerRadial(radial) @@ -321,8 +363,6 @@ RegisterNUICallback('radialTransition', function(_, cb) cb(true) end) -local isDisabled = false - ---Disallow players from opening the radial menu. ---@param state boolean function lib.disableRadial(state) @@ -336,58 +376,7 @@ end ---Shows a registered menu as root without affecting global menus. ---@param id string function lib.showRadialMenu(id) - local radial = id and menus[id] - if not radial then return end - - if isDisabled then return end - - if isOpen then - --Transitions between different root menus - if rootRadial == radial then - return lib.hideRadial() - end - - rootRadial = radial - table.wipe(menuHistory) - return showRadial(id) - end - - local radialItems = radial.items - if #radialItems == 0 or IsNuiFocused() or IsPauseMenuActive() then return end - - isOpen = true - currentRadial = radial - rootRadial = radial - table.wipe(menuHistory) - - SendNUIMessage({ - action = 'openRadialMenu', - data = false - }) - - Wait(100) - - if not isOpen then return end - - lib.setNuiFocus(true) - SetCursorLocation(0.5, 0.5) - - SendNUIMessage({ - action = 'openRadialMenu', - data = { - items = radialItems, - } - }) - - 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 + onRadialOpen(id) end lib.addKeybind({ @@ -395,45 +384,7 @@ lib.addKeybind({ description = locale('open_radial_menu'), defaultKey = 'z', onPressed = function() - if isDisabled then return end - - if isOpen then - --Transitions between different root menus - if rootRadial then - rootRadial = nil - table.wipe(menuHistory) - return showRadial() - end - - return lib.hideRadial() - end - - if #menuItems == 0 or IsNuiFocused() or IsPauseMenuActive() then return end - - isOpen = true - currentRadial = nil - rootRadial = nil - table.wipe(menuHistory) - - 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 + onRadialOpen() end, -- onReleased = lib.hideRadial, }) @@ -446,4 +397,4 @@ AddEventHandler('onClientResourceStop', function(resource) table.remove(menuItems, i) end end -end) +end) \ No newline at end of file