Files
jim_bridge/shared/contextmenus.lua
Jim Shield 9c4c92e696 Move GTA notify, target, skillcheck to /ui_modules
I've separated the built-in custom native GTA Notifications, skillcheck and draw text targets to separate files to be loaded along side `jim_bridge`

This now makes it so they only load once instead of with every single script, and each one can call on it

This change also makes these systems able to be called outside of `jim_bridge`

For example in my test server I'm currently using:

```lua
function QBCore.Functions.Notify(text, texttype, length, icon)
    CreateThread(function()
        exports.jim_bridge:Notify(nil, text, icon)
    end)
end
```
2025-06-06 00:45:08 +01:00

316 lines
12 KiB
Lua

--[[
Menu Opening Module
---------------------
This module provides a unified function to open menus using the configured menu system.
Supported systems include:
• jim_bridge (built-in nui menu that works on any framework)
• ox (or ox_context)
• qb (using QBMenuExport)
• gta (using WarMenu)
• esx (using ESX.UI.Menu)
]]
--- Opens a menu using the configured menu system.
---
--- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
---
---@param Menu table A table containing the menu options to display.
--- Each menu item can include:
--- - header (`string`): The text to display for the menu item.
--- - txt (`string`, optional): Additional text or description.
--- - icon (`string`, optional): Icon to display with the menu item.
--- - onSelect (`function`, optional): Function to execute when the menu item is selected.
--- - arrow (`boolean`, optional): Whether to display an arrow next to the item (for certain menus).
--- - params (`table`, optional): Additional parameters, such as events and arguments.
--- - isMenuHeader (`boolean`, optional): Marks the item as a header.
--- - disabled (`boolean`, optional): Disables the menu item if `true`.
---
---@param data table A table containing configuration data for the menu.
--- - header (`string`): The header/title of the menu.
--- - headertxt (`string`, optional): Additional header text.
--- - onBack (`function`, optional): Function to call when the "Return" option is selected.
--- - onExit (`function`, optional): Function to call when the menu is exited.
--- - onSelected (`function`, optional): Function to call when a menu item is selected (for certain menu systems).
--- - canClose (`boolean`, optional): Whether the menu can be closed by the user.
---
---@usage
--- ```lua
--- openMenu({
--- { header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
--- { header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
--- }, {
--- header = "Main Menu",
--- headertxt = "Select an option",
--- onBack = function() print("Return selected") end,
--- onExit = function() print("Menu closed") end,
--- canClose = true,
--- })
--- ```
function openMenu(Menu, data)
if Config.System.Menu == "jim" then
if data.onBack then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
header = " ",
txt = "Return",
params = {
isAction = true,
event = data.onBack,
},
})
elseif data.canClose then
table.insert(Menu, 1, {
icon = "fas fa-circle-xmark",
header = " ",
txt = "Close",
params = {
isAction = true,
event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end),
},
})
end
if data.header ~= nil then
local tempMenu = {}
for k, v in pairs(Menu) do tempMenu[k + 1] = v end
tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true }
Menu = tempMenu
end
for k in pairs(Menu) do
if not Menu[k].params or not Menu[k].params.event then
Menu[k].params = {
isAction = true,
event = Menu[k].onSelect or function() end,
}
end
if not Menu[k].header then Menu[k].header = " " end
if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end
Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable
end
TriggerEvent("jim_bridge:client:openMenu", Menu)
elseif Config.System.Menu == "ox" then
local index = nil
if data.onBack and not data.onSelected then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
title = "Return",
onSelect = data.onBack,
label = "Return",
})
end
for k in pairs(Menu) do
if data.onSelected and Menu[k].arrow then
Menu[k].icon = "fas fa-angle-right"
end
-- If no title, use header or txt as title/label.
if not Menu[k].title then
if Menu[k].header ~= nil and Menu[k].header ~= "" then
Menu[k].title = Menu[k].header
Menu[k].label = Menu[k].header
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
else
Menu[k].title = Menu[k].txt
Menu[k].label = Menu[k].txt
end
end
-- Copy parameters from 'params' if available.
if Menu[k].params then
Menu[k].event = Menu[k].params.event
Menu[k].args = Menu[k].params.args or {}
end
if Menu[k].isMenuHeader then
Menu[k].readOnly = true
end
end
local menuID = 'Menu'
(data.onSelected and lib.registerMenu or lib.registerContext)({
id = menuID,
title = data.header..br..br..(data.headertxt and data.headertxt or ""),
position = 'top-right',
options = Menu,
canClose = data.canClose and data.canClose or nil,
onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil,
onExit = data.onExit and data.onExit or nil,
onSelected = data.onSelected and (function(selected) index = selected end) or nil,
}, data.onSelected and (function(x, y, args)
if Menu[x].refresh then
if Menu[x].onSelect then
Menu[x].onSelect()
end
lib.showMenu(menuID, index)
else
if Menu[x].onSelect then
Menu[x].onSelect()
else
lib.showMenu(menuID, index)
end
end
end) or nil)
if data.onSelected then
lib.showMenu(menuID, 1)
else
lib.showContext(menuID)
end
elseif Config.System.Menu == "qb" then
if data.onBack then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
header = " ",
txt = "Return",
params = {
isAction = true,
event = data.onBack,
},
})
elseif data.canClose then
table.insert(Menu, 1, {
icon = "fas fa-circle-xmark",
header = " ",
txt = "Close",
params = {
isAction = true,
event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end),
},
})
end
if data.header ~= nil then
local tempMenu = {}
for k, v in pairs(Menu) do tempMenu[k + 1] = v end
tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true }
Menu = tempMenu
end
for k in pairs(Menu) do
if not Menu[k].params or not Menu[k].params.event then
Menu[k].params = {
isAction = true,
event = Menu[k].onSelect or function() end,
}
end
if not Menu[k].header then Menu[k].header = " " end
if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end
Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable
end
exports[QBMenuExport]:openMenu(Menu)
elseif Config.System.Menu == "gta" then
WarMenu.CreateMenu(tostring(Menu), data.header, " ", {
titleColor = { 222, 255, 255 },
maxOptionCountOnScreen = 15,
width = 0.25,
x = 0.7,
})
if WarMenu.IsAnyMenuOpened() then return end
WarMenu.OpenMenu(tostring(Menu))
CreateThread(function()
local close = true
while true do
if WarMenu.Begin(tostring(Menu)) then
if data.onBack then
if WarMenu.SpriteButton("Return", 'commonmenu', "arrowleft", 127, 127, 127) then
WarMenu.CloseMenu()
Wait(10)
data.onBack()
end
end
for k in pairs(Menu) do
local pressed = WarMenu.Button(Menu[k].header)
if not Menu[k].header then
Menu[k].header = Menu[k].txt
Menu[k].txt = nil
end
if Menu[k].txt and Menu[k].txt ~= "" and WarMenu.IsItemHovered() then
if Menu[k].disabled or Menu[k].isMenuHeader then
WarMenu.ToolTip("~r~"..Menu[k].txt, 0.18, true)
else
WarMenu.ToolTip(
(Menu[k].blip and "~BLIP_".."8".."~ " or "")..
Menu[k].txt:gsub("%:", ":~g~"):gsub("%\n", "\n~s~"), 0.18,
true)
end
end
if pressed and not Menu[k].isMenuHeader then
WarMenu.CloseMenu()
close = false
Wait(10)
Menu[k].onSelect()
end
end
WarMenu.End()
else
return
end
if not WarMenu.IsAnyMenuOpened() and close then
stopTempCam(cam)
if data.onExit then data.onExit() end
end
Wait(0)
end
end)
elseif Config.System.Menu == "esx" then
for k in pairs(Menu) do
Menu[k].label = Menu[k].header
Menu[k].name = "button"..k
end
if data.canClose then
table.insert(Menu, 1, {
icon = "fas fa-circle-xmark",
label = "Close",
name = "close",
onSelect = data.onExit,
})
end
if data.onBack then
table.insert(Menu, 1, {
icon = "fas fa-circle-arrow-left",
label = "Return",
name = "return",
onSelect = data.onBack,
})
end
ESX.UI.Menu.Open("default", getScript(), "Example_Menu", {
title = data.header,
align = 'top-right',
elements = Menu,
},
function(menuData, menu)
for k in pairs(Menu) do
if menuData.current.name == Menu[k].name then
menu.close()
Wait(10)
Menu[k].onSelect()
end
end
end,
function(data, menu)
menu.close()
end)
end
end
--- A line break constant used for menu header formatting.
br = (Config.System.Menu == "ox" or Config.System.Menu == "gta") and "\n" or "<br>"
--- Checks if the current menu system is 'ox' or 'gta' for formatting purposes.
--- @return boolean boolean True if using ox or gta menus, otherwise false.
--- @usage
--- ```lua
--- if isOx() then
--- -- Use specific formatting
--- end
--- ```
function isOx() return (Config.System.Menu == "ox" or Config.System.Menu == "gta") end
--- Checks if any WarMenu menu is currently open.
---
--- @return boolean boolean Returns `true` if a WarMenu menu is open; otherwise, `false`.
---
--- @usage
--- ```lua
--- if isWarMenuOpen() then
--- -- Do something
--- end
--- ```
function isWarMenuOpen() if Config.System.Menu == "gta" then return WarMenu.IsAnyMenuOpened() else return false end end