Main/resources/[standalone]/menuv/menuv/menuv.lua
2025-06-07 08:51:21 +02:00

293 lines
No EOL
10 KiB
Lua

----------------------- [ MenuV ] -----------------------
-- GitHub: https://github.com/ThymonA/menuv/
-- License: GNU General Public License v3.0
-- https://choosealicense.com/licenses/gpl-3.0/
-- Author: Thymon Arens <contact@arens.io>
-- Name: MenuV
-- Version: 1.4.1
-- Description: FiveM menu library for creating menu's
----------------------- [ MenuV ] -----------------------
local assert = assert
local load = assert(load)
local xpcall = assert(xpcall)
local lower = assert(string.lower)
local upper = assert(string.upper)
local rawget = assert(rawget)
local rawset = assert(rawset)
local traceback = assert(debug.traceback)
local setmetatable = assert(setmetatable)
--- FiveM globals
local GetInvokingResource = assert(GetInvokingResource)
local LoadResourceFile = assert(LoadResourceFile)
local RegisterKeyMapping = assert(RegisterKeyMapping)
local RegisterCommand = assert(RegisterCommand)
local SendNUIMessage = assert(SendNUIMessage)
local RegisterNUICallback = assert(RegisterNUICallback)
local IsScreenFadedOut = assert(IsScreenFadedOut)
local IsPauseMenuActive = assert(IsPauseMenuActive)
local PlaySoundFrontend = assert(PlaySoundFrontend)
local CreateThread = assert(Citizen.CreateThread)
local Wait = assert(Citizen.Wait)
local exports = assert(exports)
--- MenuV globals
---@type Utilities
local Utilities = assert(Utilities)
--- Load a file from `menuv`
---@param path string Path in `menuv`
---@return any|nil Results of nil
local function load_file(path)
if (path == nil or type(path) ~= 'string') then return nil end
local raw_file = LoadResourceFile('menuv', path)
if (raw_file) then
local raw_func, _ = load(raw_file, ('menuv/%s'):format(path), 't', _ENV)
if (raw_func) then
local ok, result = xpcall(raw_func, traceback)
if (ok) then
return result
end
end
end
return nil
end
load_file('menuv/components/translations.lua')
local MenuV = setmetatable({
---@type string
__class = 'MenuV',
---@type string
__type = 'MenuV',
---@type boolean
Loaded = false,
---@type number
ThreadWait = Utilities:Ensure((Config or {}).HideInterval, 250),
---@type table<string, string>
Translations = translations or {},
---@type table<string, table>
Sounds = Utilities:Ensure((Config or {}).Sounds, {}),
---@type boolean
Hidden = false
}, {})
MenuV.Keys = setmetatable({ data = {}, __class = 'MenuVKeys', __type = 'keys' }, {
__index = function(t, k)
return rawget(t.data, k)
end,
__newindex = function(t, k, v)
k = Utilities:Ensure(k, 'unknown')
if (k == 'unknown') then return end
local rawKey = rawget(t.data, k)
local keyExists = rawKey ~= nil
local prevState = Utilities:Ensure((rawKey or {}).status, false)
local newState = Utilities:Ensure(v, false)
if (keyExists and not MenuV.Hidden) then
rawset(t.data[k], 'status', newState)
if (prevState ~= newState) then
local action = newState and not prevState and 'KEY_PRESSED' or 'KEY_RELEASED'
local key = Utilities:Ensure(rawKey.action, 'UNKNOWN')
SendNUIMessage({ action = action, key = key })
end
end
end,
__call = function(t, k, a, inputType)
k = Utilities:Ensure(k, 'unknown')
a = Utilities:Ensure(a, 'UNKNOWN')
inputType = Utilities:Ensure(inputType, 0)
if (k == 'unknown') then return end
local rawKey = rawget(t.data, k)
local keyExists = rawKey ~= nil
if (keyExists) then
if not rawKey.inputTypes[inputType] then
rawKey.inputTypes[inputType] = true
end
return
end
rawset(t.data, k, { status = false, action = a, inputTypes = { [inputType] = true } })
end
})
--- Register a `action` with custom keybind
---@param action string Action like: UP, DOWN, LEFT...
---@param description string Description of keybind
---@param defaultType string Type like: keyboard, mouse etc.
---@param defaultKey string Default key for this keybind
function MenuV:RegisterKey(action, description, defaultType, defaultKey)
action = Utilities:Ensure(action, 'UNKNOWN')
description = Utilities:Ensure(description, 'unknown')
defaultType = Utilities:Ensure(defaultType, 'KEYBOARD')
defaultType = upper(defaultType)
defaultKey = Utilities:Ensure(defaultKey, 'F12')
action = Utilities:Replace(action, ' ', '_')
action = upper(action)
local typeGroup = Utilities:GetInputTypeGroup(defaultType)
if (self.Keys[action] and self.Keys[action].inputTypes[typeGroup]) then return end
self.Keys(action, action, typeGroup)
local k = lower(action)
if typeGroup > 0 then
local inputGroupName = Utilities:GetInputGroupName(typeGroup)
k = ('%s_%s'):format(lower(inputGroupName), k)
end
k = ('menuv_%s'):format(k)
RegisterKeyMapping(('+%s'):format(k), description, defaultType, defaultKey)
RegisterCommand(('+%s'):format(k), function() MenuV.Keys[action] = true end)
RegisterCommand(('-%s'):format(k), function() MenuV.Keys[action] = false end)
end
--- Load translation
---@param k string Translation key
---@return string Translation or 'MISSING TRANSLATION'
local function T(k)
k = Utilities:Ensure(k, 'unknown')
return Utilities:Ensure(MenuV.Translations[k], 'MISSING TRANSLATION')
end
RegisterNUICallback('loaded', function(_, cb)
MenuV.Loaded = true
cb('ok')
end)
RegisterNUICallback('sound', function(info, cb)
local key = upper(Utilities:Ensure(info.key, 'UNKNOWN'))
if (MenuV.Sounds == nil and MenuV.Sounds[key] == nil) then cb('ok') return end
local sound = Utilities:Ensure(MenuV.Sounds[key], {})
local soundType = lower(Utilities:Ensure(sound.type, 'unknown'))
if (soundType == 'native') then
local name = Utilities:Ensure(sound.name, 'UNKNOWN')
local library = Utilities:Ensure(sound.library, 'UNKNOWN')
PlaySoundFrontend(-1, name, library, true)
end
cb('ok')
end)
--- Trigger the NUICallback for the right resource
---@param name string Name of callback
---@param info table Info returns from callback
---@param cb function Trigger this when callback is done
local function TriggerResourceCallback(name, info, cb)
local r = Utilities:Ensure(info.r, 'menuv')
if (r == 'menuv') then cb('ok') return end
local resource = exports[r] or nil
if (resource == nil) then cb('ok') return end
local nuiCallback = resource['NUICallback'] or nil
if (nuiCallback == nil) then cb('ok') return end
exports[r]:NUICallback(name, info, cb)
end
RegisterNUICallback('submit', function(info, cb) TriggerResourceCallback('submit', info, cb) end)
RegisterNUICallback('close', function(info, cb) TriggerResourceCallback('close', info, cb) end)
RegisterNUICallback('switch', function(info, cb) TriggerResourceCallback('switch', info, cb) end)
RegisterNUICallback('update', function(info, cb) TriggerResourceCallback('update', info, cb) end)
RegisterNUICallback('open', function(info, cb) TriggerResourceCallback('open', info, cb) end)
RegisterNUICallback('opened', function(info, cb) TriggerResourceCallback('opened', info, cb) end)
RegisterNUICallback('close_all', function(info, cb) TriggerResourceCallback('close_all', info, cb) end)
--- MenuV exports
exports('IsLoaded', function(cb)
cb = Utilities:Ensure(cb, function() end)
if (MenuV.Loaded) then
cb()
return
end
CreateThread(function()
local callback = cb
repeat Wait(0) until MenuV.Loaded
callback()
end)
end)
exports('SendNUIMessage', function(input)
local r = Utilities:Ensure(GetInvokingResource(), 'menuv')
if (Utilities:Typeof(input) == 'table') then
if (input.menu) then
rawset(input.menu, 'resource', r)
rawset(input.menu, 'defaultSounds', MenuV.Sounds)
rawset(input.menu, 'hidden', MenuV.Hidden)
end
SendNUIMessage(input)
end
end)
--- Register `MenuV` keybinds
MenuV:RegisterKey('UP', T('keybind_key_up'), 'KEYBOARD', 'UP')
MenuV:RegisterKey('DOWN', T('keybind_key_down'), 'KEYBOARD', 'DOWN')
MenuV:RegisterKey('LEFT', T('keybind_key_left'), 'KEYBOARD', 'LEFT')
MenuV:RegisterKey('RIGHT', T('keybind_key_right'), 'KEYBOARD', 'RIGHT')
MenuV:RegisterKey('ENTER', T('keybind_key_enter'), 'KEYBOARD', 'RETURN')
MenuV:RegisterKey('CLOSE', T('keybind_key_close'), 'KEYBOARD', 'BACK')
MenuV:RegisterKey('CLOSE_ALL', T('keybind_key_close_all'), 'KEYBOARD', 'PLUS')
MenuV:RegisterKey('UP', ('%s - %s'):format(T('controller'), T('keybind_key_up')), 'PAD_ANALOGBUTTON', 'LUP_INDEX')
MenuV:RegisterKey('DOWN', ('%s - %s'):format(T('controller'), T('keybind_key_down')), 'PAD_ANALOGBUTTON', 'LDOWN_INDEX')
MenuV:RegisterKey('LEFT', ('%s - %s'):format(T('controller'), T('keybind_key_left')), 'PAD_ANALOGBUTTON', 'LLEFT_INDEX')
MenuV:RegisterKey('RIGHT', ('%s - %s'):format(T('controller'), T('keybind_key_right')), 'PAD_ANALOGBUTTON', 'LRIGHT_INDEX')
MenuV:RegisterKey('ENTER', ('%s - %s'):format(T('controller'), T('keybind_key_enter')), 'PAD_ANALOGBUTTON', 'RDOWN_INDEX')
MenuV:RegisterKey('CLOSE', ('%s - %s'):format(T('controller'), T('keybind_key_close')), 'PAD_ANALOGBUTTON', 'RRIGHT_INDEX')
MenuV:RegisterKey('CLOSE_ALL', ('%s - %s'):format(T('controller'), T('keybind_key_close_all')), 'PAD_ANALOGBUTTON', 'R3_INDEX')
--- Hide menu when screen is faded out or pause menu ia active
CreateThread(function()
MenuV.Hidden = false
while true do
repeat Wait(0) until MenuV.Loaded
local new_state = IsScreenFadedOut() or IsPauseMenuActive()
if (MenuV.Hidden ~= new_state) then
SendNUIMessage({ action = 'UPDATE_STATUS', status = not new_state })
end
MenuV.Hidden = new_state
Wait(MenuV.ThreadWait)
end
end)
--- When resource is stopped
AddEventHandler('onResourceStop', function(resourceName)
SendNUIMessage({ action = 'RESOURCE_STOPPED', resource = resourceName })
end)