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

1080 lines
40 KiB
Lua
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

----------------------- [ 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
---@type Utilities
local U = assert(Utilities)
local type = assert(type)
local next = assert(next)
local pairs = assert(pairs)
local ipairs = assert(ipairs)
local lower = assert(string.lower)
local upper = assert(string.upper)
local sub = assert(string.sub)
local insert = assert(table.insert)
local remove = assert(table.remove)
local pack = assert(table.pack)
local unpack = assert(table.unpack)
local encode = assert(json.encode)
local rawset = assert(rawset)
local rawget = assert(rawget)
local setmetatable = assert(setmetatable)
--- FiveM globals
local GET_CURRENT_RESOURCE_NAME = assert(GetCurrentResourceName)
local GET_INVOKING_RESOURCE = assert(GetInvokingResource)
local HAS_STREAMED_TEXTURE_DICT_LOADED = assert(HasStreamedTextureDictLoaded)
local REQUEST_STREAMED_TEXTURE_DICT = assert(RequestStreamedTextureDict)
--- MenuV local variable
local current_resource = GET_CURRENT_RESOURCE_NAME()
--- Returns default empty table for items
---@returns items
function CreateEmptyItemsTable(data)
data = U:Ensure(data, {})
data.ToTable = function(t)
local tempTable = {}
local index = 0
for _, option in pairs(t) do
index = index + 1
tempTable[index] = {
index = index,
type = option.__type,
uuid = U:Ensure(option.UUID, 'unknown'),
icon = U:Ensure(option.Icon, 'none'),
label = U:Ensure(option.Label, 'Unknown'),
description = U:Ensure(option.Description, ''),
value = 'none',
values = {},
min = U:Ensure(option.Min, 0),
max = U:Ensure(option.Max, 0),
disabled = U:Ensure(option.Disabled, false)
}
if (option.__type == 'button' or option.__type == 'menu') then
tempTable[index].value = 'none'
elseif (option.__type == 'checkbox' or option.__type == 'confirm') then
tempTable[index].value = U:Ensure(option.Value, false)
elseif (option.__type == 'range') then
tempTable[index].value = U:Ensure(option.Value, 0)
if (tempTable[index].value <= tempTable[index].min) then
tempTable[index].value = tempTable[index].min
elseif (tempTable[index].value >= tempTable[index].max) then
tempTable[index].value = tempTable[index].max
end
elseif (option.__type == 'slider') then
tempTable[index].value = 0
end
local _values = U:Ensure(option.Values, {})
local vIndex = 0
for valueIndex, value in pairs(_values) do
vIndex = vIndex + 1
tempTable[index].values[vIndex] = {
label = U:Ensure(value.Label, 'Option'),
description = U:Ensure(value.Description, ''),
value = vIndex
}
if (option.__type == 'slider') then
if (U:Ensure(option.Value, 0) == valueIndex) then
tempTable[index].value = (valueIndex - 1)
end
end
end
end
return tempTable
end
data.ItemToTable = function(t, i)
local tempTable = {}
local index = 0
local uuid = U:Typeof(i) == 'Item' and i.UUID or U:Ensure(i, '00000000-0000-0000-0000-000000000000')
for _, option in pairs(t) do
index = index + 1
if (option.UUID == uuid) then
tempTable = {
index = index,
type = option.__type,
uuid = U:Ensure(option.UUID, 'unknown'),
icon = U:Ensure(option.Icon, 'none'),
label = U:Ensure(option.Label, 'Unknown'),
description = U:Ensure(option.Description, ''),
value = 'none',
values = {},
min = U:Ensure(option.Min, 0),
max = U:Ensure(option.Max, 0),
disabled = U:Ensure(option.Disabled, false)
}
if (option.__type == 'button' or option.__type == 'menu') then
tempTable.value = 'none'
elseif (option.__type == 'checkbox' or option.__type == 'confirm') then
tempTable.value = U:Ensure(option.Value, false)
elseif (option.__type == 'range') then
tempTable.value = U:Ensure(option.Value, 0)
if (tempTable.value <= tempTable.min) then
tempTable.value = tempTable.min
elseif (tempTable.value >= tempTable.max) then
tempTable.value = tempTable.max
end
elseif (option.__type == 'slider') then
tempTable.value = 0
end
local _values = U:Ensure(option.Values, {})
local vIndex = 0
for valueIndex, value in pairs(_values) do
vIndex = vIndex + 1
tempTable.values[vIndex] = {
label = U:Ensure(value.Label, 'Option'),
description = U:Ensure(value.Description, ''),
value = vIndex
}
if (option.__type == 'slider') then
if (U:Ensure(option.Value, 0) == valueIndex) then
tempTable.value = (valueIndex - 1)
end
end
end
return tempTable
end
end
return tempTable
end
data.AddItem = function(t, item)
if (U:Typeof(item) == 'Item') then
local newIndex = #(U:Ensure(rawget(t, 'data'), {})) + 1
rawset(t.data, newIndex, item)
if (t.Trigger ~= nil and type(t.Trigger) == 'function') then
t:Trigger('update', 'AddItem', item)
end
end
return U:Ensure(rawget(t, 'data'), {})
end
local item_pairs = function(t, k)
local _k, _v = next((rawget(t, 'data') or {}), k)
if (_v ~= nil and type(_v) ~= 'table') then
return item_pairs(t, _k)
end
return _k, _v
end
local item_ipairs = function(t, k)
local _k, _v = next((rawget(t, 'data') or {}), k)
if (_v ~= nil and (type(_v) ~= 'table' or type(_k) ~= 'number')) then
return item_ipairs(t, _k)
end
return _k, _v
end
_G.item_pairs = item_pairs
_ENV.item_pairs = item_pairs
---@class items
return setmetatable({ data = data, Trigger = nil }, {
__index = function(t, k)
return rawget(t.data, k)
end,
__newindex = function(t, k, v)
local oldValue = rawget(t.data, k)
rawset(t.data, k, v)
if (t.Trigger ~= nil and type(t.Trigger) == 'function') then
if (oldValue == nil) then
t:Trigger('update', 'AddItem', v)
elseif (oldValue ~= nil and v == nil) then
t:Trigger('update', 'RemoveItem', oldValue)
elseif (oldValue ~= v) then
t:Trigger('update', 'UpdateItem', v, oldValue)
end
end
end,
__call = function(t, func)
rawset(t, 'Trigger', U:Ensure(func, function() end))
end,
__pairs = function(t)
local k = nil
return function()
local v
k, v = item_pairs(t, k)
return k, v
end, t, nil
end,
__ipairs = function(t)
local k = nil
return function()
local v
k, v = item_ipairs(t, k)
return k, v
end, t, 0
end,
__len = function(t)
local items = U:Ensure(rawget(t, 'data'), {})
local itemCount = 0
for _, v in pairs(items) do
if (U:Typeof(v) == 'Item') then
itemCount = itemCount + 1
end
end
return itemCount
end
})
end
--- Load a texture dictionary if not already loaded
---@param textureDictionary string Name of texture dictionary
local function LoadTextureDictionary(textureDictionary)
textureDictionary = U:Ensure(textureDictionary, 'menuv')
if (HAS_STREAMED_TEXTURE_DICT_LOADED(textureDictionary)) then return end
REQUEST_STREAMED_TEXTURE_DICT(textureDictionary, true)
end
--- Create a new menu item
---@param info table Menu information
---@return Menu New item
function CreateMenu(info)
info = U:Ensure(info, {})
local namespace = U:Ensure(info.Namespace or info.namespace, 'unknown')
local namespace_available = MenuV:IsNamespaceAvailable(namespace)
if (not namespace_available) then
error(("[MenuV] Namespace '%s' is already taken, make sure it is unique."):format(namespace))
end
local theme = lower(U:Ensure(info.Theme or info.theme, 'default'))
if (theme ~= 'default' and theme ~= 'native') then
theme = 'default'
end
if (theme == 'native') then
info.R, info.G, info.B = 255, 255, 255
info.r, info.g, info.b = 255, 255, 255
end
local item = {
---@type string
Namespace = namespace,
---@type boolean
IsOpen = false,
---@type string
UUID = U:UUID(),
---@type string
Title = not (info.Title or info.title) and ' ' or U:Ensure(info.Title or info.title, 'MenuV'),
---@type string
Subtitle = U:Ensure(info.Subtitle or info.subtitle, ''),
---@type string | "'topleft'" | "'topcenter'" | "'topright'" | "'centerleft'" | "'center'" | "'centerright'" | "'bottomleft'" | "'bottomcenter'" | "'bottomright'"
Position = U:Ensure(info.Position or info.position, 'topleft'),
---@type table
Color = {
R = U:Ensure(info.R or info.r, 0),
G = U:Ensure(info.G or info.g, 0),
B = U:Ensure(info.B or info.b, 255)
},
---@type string | "'size-100'" | "'size-110'" | "'size-125'" | "'size-150'" | "'size-175'" | "'size-200'"
Size = U:Ensure(info.Size or info.size, 'size-110'),
---@type string
Dictionary = U:Ensure(info.Dictionary or info.dictionary, 'menuv'),
---@type string
Texture = U:Ensure(info.Texture or info.texture, 'default'),
---@type table
Events = U:Ensure(info.Events or info.events, {}),
---@type string
Theme = theme,
---@type Item[]
Items = CreateEmptyItemsTable({}),
---@param t Menu
---@param event string Name of Event
Trigger = function(t, event, ...)
event = lower(U:Ensure(event, 'unknown'))
if (event == 'unknown') then return end
if (U:StartsWith(event, 'on')) then
event = 'On' .. sub(event, 3):gsub('^%l', upper)
else
event = 'On' .. event:gsub('^%l', upper)
end
if (not U:Any(event, (t.Events or {}), 'key')) then
return
end
if (event == 'OnOpen') then rawset(t, 'IsOpen', true)
elseif (event == 'OnClose') then rawset(t, 'IsOpen', false) end
local args = pack(...)
for _, v in pairs(t.Events[event]) do
if (type(v) == 'table' and U:Typeof(v.func) == 'function') then
CreateThread(function()
if (event == 'OnClose') then
v.func(t, unpack(args))
else
local threadId = coroutine.running()
if (threadId ~= nil) then
insert(t.data.Threads, threadId)
v.func(t, unpack(args))
for i = 0, #(t.data.Threads or {}), 1 do
if (t.data.Threads[i] == threadId) then
remove(t.data.Threads, i)
return
end
end
end
end
end)
end
end
end,
---@type thread[]
Threads = {},
---@param t Menu
DestroyThreads = function(t)
for _, threadId in pairs(t.data.Threads or {}) do
local threadStatus = coroutine.status(threadId)
if (threadStatus ~= nil and threadStatus ~= 'dead') then
coroutine.close(threadId)
end
end
t.data.Threads = {}
end,
---@param t Menu
---@param event string Name of event
---@param func function|Menu Function or Menu to trigger
---@return string UUID of event
On = function(t, event, func)
local ir = GET_INVOKING_RESOURCE()
local resource = U:Ensure(ir, current_resource)
event = lower(U:Ensure(event, 'unknown'))
if (event == 'unknown') then return end
if (U:StartsWith(event, 'on')) then
event = 'On' .. sub(event, 3):gsub('^%l', upper)
else
event = 'On' .. event:gsub('^%l', upper)
end
if (not U:Any(event, (t.Events or {}), 'key')) then
return
end
func = U:Ensure(func, function() end)
local uuid = U:UUID()
insert(t.Events[event], {
__uuid = uuid,
__resource = resource,
func = func
})
return uuid
end,
---@param t Menu
---@param event string Name of event
---@param uuid string UUID of event
RemoveOnEvent = function(t, event, uuid)
local ir = GET_INVOKING_RESOURCE()
local resource = U:Ensure(ir, current_resource)
event = lower(U:Ensure(event, 'unknown'))
if (event == 'unknown') then return end
if (U:StartsWith(event, 'on')) then
event = 'On' .. sub(event, 3):gsub('^%l', upper)
else
event = 'On' .. event:gsub('^%l', upper)
end
if (not U:Any(event, (t.Events or {}), 'key')) then
return
end
uuid = U:Ensure(uuid, '00000000-0000-0000-0000-000000000000')
for i = 1, #t.Events[event], 1 do
if (t.Events[event][i] ~= nil and
t.Events[event][i].__uuid == uuid and
t.Events[event][i].__resource == resource) then
remove(t.Events[event], i)
end
end
end,
---@param t Item
---@param k string
---@param v string
Validate = U:Ensure(info.Validate or info.validate, function(t, k, v)
return true
end),
---@param t Item
---@param k string
---@param v string
Parser = function(t, k, v)
if (k == 'Position' or k == 'position') then
local position = lower(U:Ensure(v, 'topleft'))
if (U:Any(position, {'topleft', 'topcenter', 'topright', 'centerleft', 'center', 'centerright', 'bottomleft', 'bottomcenter', 'bottomright'}, 'value')) then
return position
else
return 'topleft'
end
end
return v
end,
---@param t Item
---@param k string
---@param v string
NewIndex = U:Ensure(info.NewIndex or info.newIndex, function(t, k, v)
end),
---@type function
---@param t Menu MenuV menu
---@param info table Information about button
---@return Item New item
AddButton = function(t, info)
info = U:Ensure(info, {})
info.Type = 'button'
info.Events = { OnSelect = {} }
info.PrimaryEvent = 'OnSelect'
info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false)
info.__menu = t
if (U:Typeof(info.Value or info.value) == 'Menu') then
info.Type = 'menu'
end
local item = CreateMenuItem(info)
if (info.Type == 'menu') then
item:On('select', function() item.Value() end)
end
if (info.TriggerUpdate) then
t.Items:AddItem(item)
else
local items = rawget(t.data, 'Items')
if (items) then
local newIndex = #items + 1
rawset(items.data, newIndex, item)
return items.data[newIndex] or item
end
end
return t.Items[#t.Items] or item
end,
---@type function
---@param t Menu MenuV menu
---@param info table Information about checkbox
---@return Item New item
AddCheckbox = function(t, info)
info = U:Ensure(info, {})
info.Type = 'checkbox'
info.Value = U:Ensure(info.Value or info.value, false)
info.Events = { OnChange = {}, OnCheck = {}, OnUncheck = {} }
info.PrimaryEvent = 'OnCheck'
info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false)
info.__menu = t
info.NewIndex = function(t, k, v)
if (k == 'Value') then
local value = U:Ensure(v, false)
if (value) then
t:Trigger('check', t)
else
t:Trigger('uncheck', t)
end
end
end
local item = CreateMenuItem(info)
if (info.TriggerUpdate) then
t.Items:AddItem(item)
else
local items = rawget(t.data, 'Items')
if (items) then
local newIndex = #items + 1
rawset(items.data, newIndex, item)
return items.data[newIndex] or item
end
end
return t.Items[#t.Items] or item
end,
---@type function
---@param t Menu MenuV menu
---@param info table Information about slider
---@return SliderItem New slider item
AddSlider = function(t, info)
info = U:Ensure(info, {})
info.Type = 'slider'
info.Events = { OnChange = {}, OnSelect = {} }
info.PrimaryEvent = 'OnSelect'
info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false)
info.__menu = t
---@class SliderItem : Item
---@filed private __event string Name of primary event
---@field public UUID string UUID of Item
---@field public Icon string Icon/Emoji for Item
---@field public Label string Label of Item
---@field public Description string Description of Item
---@field public Value any Value of Item
---@field public Values table[] List of values
---@field public Min number Min range value
---@field public Max number Max range value
---@field public Disabled boolean Disabled state of Item
---@field private Events table<string, function[]> List of registered `on` events
---@field public Trigger fun(t: Item, event: string)
---@field public On fun(t: Item, event: string, func: function)
---@field public Validate fun(t: Item, k: string, v:any)
---@field public NewIndex fun(t: Item, k: string, v: any)
---@field public GetValue fun(t: Item):any
---@field public AddValue fun(t: Item, info: table)
---@field public AddValues fun(t: Item)
local item = CreateMenuItem(info)
--- Add a value to slider
---@param info table Information about slider
function item:AddValue(info)
info = U:Ensure(info, {})
local value = {
Label = U:Ensure(info.Label or info.label, 'Value'),
Description = U:Ensure(info.Description or info.description, ''),
Value = info.Value or info.value
}
insert(self.Values, value)
end
--- Add values to slider
---@vararg table[] List of values
function item:AddValues(...)
local arguments = pack(...)
for _, argument in pairs(arguments) do
if (U:Typeof(argument) == 'table') then
local hasIndex = argument[1] or nil
if (hasIndex and U:Typeof(hasIndex) == 'table') then
self:AddValues(unpack(argument))
else
self:AddValue(argument)
end
end
end
end
local values = U:Ensure(info.Values or info.values, {})
if (#values > 0) then
item:AddValues(values)
end
if (info.TriggerUpdate) then
t.Items:AddItem(item)
else
local items = rawget(t.data, 'Items')
if (items) then
local newIndex = #items + 1
rawset(items.data, newIndex, item)
return items.data[newIndex] or item
end
end
return t.Items[#t.Items] or item
end,
---@type function
---@param t Menu MenuV menu
---@param info table Information about range
---@return RangeItem New Range item
AddRange = function(t, info)
info = U:Ensure(info, {})
info.Type = 'range'
info.Events = { OnChange = {}, OnSelect = {}, OnMin = {}, OnMax = {} }
info.PrimaryEvent = 'OnSelect'
info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false)
info.__menu = t
info.Value = U:Ensure(info.Value or info.value, 0)
info.Min = U:Ensure(info.Min or info.min, 0)
info.Max = U:Ensure(info.Max or info.max, 0)
info.Validate = function(t, k, v)
if (k == 'Value' or k == 'value') then
v = U:Ensure(v, 0)
if (t.Min > v) then return false end
if (t.Max < v) then return false end
end
return true
end
if (info.Min > info.Max) then
local min = info.Min
local max = info.Max
info.Min = min
info.Max = max
end
if (info.Value < info.Min) then info.Value = info.Min end
if (info.Value > info.Max) then info.Value = info.Max end
---@class RangeItem : Item
---@filed private __event string Name of primary event
---@field public UUID string UUID of Item
---@field public Icon string Icon/Emoji for Item
---@field public Label string Label of Item
---@field public Description string Description of Item
---@field public Value any Value of Item
---@field public Values table[] List of values
---@field public Min number Min range value
---@field public Max number Max range value
---@field public Disabled boolean Disabled state of Item
---@field private Events table<string, function[]> List of registered `on` events
---@field public Trigger fun(t: Item, event: string)
---@field public On fun(t: Item, event: string, func: function)
---@field public Validate fun(t: Item, k: string, v:any)
---@field public NewIndex fun(t: Item, k: string, v: any)
---@field public GetValue fun(t: Item):any
---@field public SetMinValue fun(t: any)
---@field public SetMaxValue fun(t: any)
local item = CreateMenuItem(info)
--- Update min value of range
---@param input number Minimum value of Range
function item:SetMinValue(input)
input = U:Ensure(input, 0)
self.Min = input
if (self.Value < self.Min) then
self.Value = self.Min
end
if (self.Min > self.Max) then
self.Max = self.Min
end
end
--- Update max value of range
---@param input number Minimum value of Range
function item:SetMaxValue(input)
input = U:Ensure(input, 0)
self.Min = input
if (self.Value > self.Max) then
self.Value = self.Max
end
if (self.Min < self.Max) then
self.Min = self.Max
end
end
if (info.TriggerUpdate) then
t.Items:AddItem(item)
else
local items = rawget(t.data, 'Items')
if (items) then
local newIndex = #items + 1
rawset(items.data, newIndex, item)
return items.data[newIndex] or item
end
end
return t.Items[#t.Items] or item
end,
---@type function
---@param t Menu MenuV menu
---@param info table Information about confirm
---@return ConfirmItem New Confirm item
AddConfirm = function(t, info)
info = U:Ensure(info, {})
info.Type = 'confirm'
info.Value = U:Ensure(info.Value or info.value, false)
info.Events = { OnConfirm = {}, OnDeny = {}, OnChange = {} }
info.PrimaryEvent = 'OnConfirm'
info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false)
info.__menu = t
info.NewIndex = function(t, k, v)
if (k == 'Value') then
local value = U:Ensure(v, false)
if (value) then
t:Trigger('confirm', t)
else
t:Trigger('deny', t)
end
end
end
---@class ConfirmItem : Item
---@filed private __event string Name of primary event
---@field public UUID string UUID of Item
---@field public Icon string Icon/Emoji for Item
---@field public Label string Label of Item
---@field public Description string Description of Item
---@field public Value any Value of Item
---@field public Values table[] List of values
---@field public Min number Min range value
---@field public Max number Max range value
---@field public Disabled boolean Disabled state of Item
---@field private Events table<string, function[]> List of registered `on` events
---@field public Trigger fun(t: Item, event: string)
---@field public On fun(t: Item, event: string, func: function)
---@field public Validate fun(t: Item, k: string, v:any)
---@field public NewIndex fun(t: Item, k: string, v: any)
---@field public GetValue fun(t: Item):any
---@field public Confirm fun(t: Item)
---@field public Deny fun(t: Item)
local item = CreateMenuItem(info)
--- Confirm this item
function item:Confirm() item.Value = true end
--- Deny this item
function item:Deny() item.Value = false end
if (info.TriggerUpdate) then
t.Items:AddItem(item)
else
local items = rawget(t.data, 'Items')
if (items) then
local newIndex = #items + 1
rawset(items.data, newIndex, item)
return items.data[newIndex] or item
end
end
return t.Items[#t.Items] or item
end,
--- Create child menu from properties of this object
---@param t Menu|string MenuV menu
---@param overrides table<string, string|number> Properties to override in menu object (ignore parent)
---@param namespace string Namespace of menu
InheritMenu = function(t, overrides, namespace)
return MenuV:InheritMenu(t, overrides, namespace)
end,
--- Add control key for specific menu
---@param t Menu|string MenuV menu
---@param action string Name of action
---@param func function This will be executed
---@param description string Key description
---@param defaultType string Default key type
---@param defaultKey string Default key
AddControlKey = function(t, action, func, description, defaultType, defaultKey)
if (U:Typeof(t.Namespace) ~= 'string' or t.Namespace == 'unknown') then
error('[MenuV] Namespace is required for assigning keys.')
return
end
MenuV:AddControlKey(t, action, func, description, defaultType, defaultKey)
end,
--- Assign key for opening this menu
---@param t Menu|string MenuV menu
---@param defaultType string Default key type
---@param defaultKey string Default key
OpenWith = function(t, defaultType, defaultKey)
t:AddControlKey('open', function(m)
MenuV:CloseAll(function()
MenuV:OpenMenu(m)
end)
end, MenuV:T('open_menu'):format(t.Namespace), defaultType, defaultKey)
end,
--- Change title of menu
---@param t Menu
---@param title string Title of menu
SetTitle = function(t, title)
t.Title = U:Ensure(title, 'MenuV')
end,
--- Change subtitle of menu
---@param t Menu
---@param subtitle string Subtitle of menu
SetSubtitle = function(t, subtitle)
t.Subtitle = U:Ensure(subtitle, '')
end,
--- Change subtitle of menu
---@param t Menu
---@param position string | "'topleft'" | "'topcenter'" | "'topright'" | "'centerleft'" | "'center'" | "'centerright'" | "'bottomleft'" | "'bottomcenter'" | "'bottomright'"
SetPosition = function(t, position)
t.Position = U:Ensure(position, 'topleft')
end,
--- Clear all Menu items
---@param t Menu
ClearItems = function(t, update)
update = U:Ensure(update, true)
local items = CreateEmptyItemsTable({})
items(function(_, trigger, key, index, value, oldValue)
t:Trigger(trigger, key, index, value, oldValue)
end)
rawset(t.data, 'Items', items)
if (update and t.Trigger ~= nil and type(t.Trigger) == 'function') then
t:Trigger('update', 'Items', items)
end
end,
Open = function(t)
MenuV:OpenMenu(t)
end,
Close = function(t)
MenuV:CloseMenu(t)
end,
--- @see Menu to @see table
---@param t Menu
---@return table
ToTable = function(t)
local tempTable = {
theme = U:Ensure(t.Theme, 'default'),
uuid = U:Ensure(t.UUID, '00000000-0000-0000-0000-000000000000'),
title = U:Ensure(t.Title, 'MenuV'),
subtitle = U:Ensure(t.Subtitle, ''),
position = U:Ensure(t.Position, 'topleft'),
size = U:Ensure(t.Size, 'size-110'),
dictionary = U:Ensure(t.Dictionary, 'menuv'),
texture = U:Ensure(t.Texture, 'default'),
color = {
r = U:Ensure(t.Color.R, 0),
g = U:Ensure(t.Color.G, 0),
b = U:Ensure(t.Color.B, 255)
},
items = {}
}
local items = rawget(t.data, 'Items')
if (items ~= nil and items.ToTable ~= nil) then
tempTable.items = items:ToTable()
end
if (tempTable.color.r <= 0) then tempTable.color.r = 0 end
if (tempTable.color.r >= 255) then tempTable.color.r = 255 end
if (tempTable.color.g <= 0) then tempTable.color.g = 0 end
if (tempTable.color.g >= 255) then tempTable.color.g = 255 end
if (tempTable.color.b <= 0) then tempTable.color.b = 0 end
if (tempTable.color.b >= 255) then tempTable.color.b = 255 end
return tempTable
end
}
if (lower(item.Texture) == 'default' and lower(item.Dictionary) == 'menuv' and theme == 'native') then
item.Texture = 'default_native'
end
item.Events.OnOpen = {}
item.Events.OnClose = {}
item.Events.OnSelect = {}
item.Events.OnUpdate = {}
item.Events.OnSwitch = {}
item.Events.OnChange = {}
item.Events.OnIChange = {}
if (not U:Any(item.Size, { 'size-100', 'size-110', 'size-125', 'size-150', 'size-175', 'size-200' }, 'value')) then
item.Size = 'size-110'
end
local mt = {
__index = function(t, k)
return rawget(t.data, k)
end,
---@param t Menu
__tostring = function(t)
return encode(t:ToTable())
end,
__call = function(t)
MenuV:OpenMenu(t)
end,
__newindex = function(t, k, v)
local whitelisted = { 'Title', 'Subtitle', 'Position', 'Color', 'R', 'G', 'B', 'Size', 'Dictionary', 'Texture', 'Theme' }
local key = U:Ensure(k, 'unknown')
local oldValue = rawget(t.data, k)
if (not U:Any(key, whitelisted, 'value') and oldValue ~= nil) then
return
end
local checkInput = t.Validate ~= nil and type(t.Validate) == 'function'
local inputParser = t.Parser ~= nil and type(t.Parser) == 'function'
local updateIndexTrigger = t.NewIndex ~= nil and type(t.NewIndex) == 'function'
if (checkInput) then
local result = t:Validate(key, v)
result = U:Ensure(result, true)
if (not result) then
return
end
end
if (inputParser) then
local parsedValue = t:Parser(key, v)
v = parsedValue or v
end
rawset(t.data, k, v)
if (key == 'Theme' or key == 'theme') then
local theme_value = string.lower(U:Ensure(v, 'default'))
if (theme_value == 'native') then
rawset(t.data, 'color', { R = 255, G = 255, B = 255 })
local texture = U:Ensure(rawget(t.data, 'Texture'), 'default')
if (texture == 'default') then
rawset(t.data, 'Texture', 'default_native')
end
elseif (theme_value == 'default') then
local texture = U:Ensure(rawget(t.data, 'Texture'), 'default')
if (texture == 'default_native') then
rawset(t.data, 'Texture', 'default')
end
end
end
if (updateIndexTrigger) then
t:NewIndex(key, v)
end
if (t.Trigger ~= nil and type(t.Trigger) == 'function') then
t:Trigger('update', key, v, oldValue)
end
end,
__len = function(t)
return #t.Items
end,
__pairs = function(t)
return pairs(rawget(t.data, 'Items') or {})
end,
__ipairs = function(t)
return ipairs(rawget(t.data, 'Items') or {})
end,
__metatable = 'MenuV',
}
---@class Menu
---@field public IsOpen boolean `true` if menu is open, otherwise `false`
---@field public UUID string UUID of Menu
---@field public Title string Title of Menu
---@field public Subtitle string Subtitle of Menu
---@field public Position string | "'topleft'" | "'topcenter'" | "'topright'" | "'centerleft'" | "'center'" | "'centerright'" | "'bottomleft'" | "'bottomcenter'" | "'bottomright'"
---@field public Texture string Name of texture example: "default"
---@field public Dictionary string Name of dictionary example: "menuv"
---@field public Color table<string, number> Color of Menu
---@field private Events table<string, fun[]> List of registered `on` events
---@field public Items Item[] List of items
---@field public Trigger fun(t: Item, event: string)
---@field public On fun(t: Menu, event: string, func: function|Menu): string
---@field public RemoveOnEvent fun(t: Menu, event: string, uuid: string)
---@field public Validate fun(t: Menu, k: string, v:any)
---@field public NewIndex fun(t: Menu, k: string, v: any)
---@field public Parser fun(t: Menu, k: string, v: any)
---@field public AddButton fun(t: Menu, info: table):Item
---@field public AddCheckbox fun(t: Menu, info: table):Item
---@field public AddSlider fun(t: Menu, info: table):SliderItem
---@field public AddRange fun(t: Menu, info: table):RangeItem
---@field public AddConfirm fun(t: Menu, info: table):ConfirmItem
---@field public AddControlKey fun(t: Menu, action: string, func: function, description: string, defaultType: string, defaultKey: string)
---@field public OpenWith fun(t: Menu, defaultType: string, defaultKey: string)
---@field public SetTitle fun(t: Menu, title: string)
---@field public SetSubtitle fun(t: Menu, subtitle: string)
---@field public SetPosition fun(t: Menu, position: string)
---@field public ClearItems fun(t: Menu)
---@field public Open fun(t: Menu)
---@field public Close fun(t: Menu)
---@field public ToTable fun(t: Menu):table
local menu = setmetatable({ data = item, __class = 'Menu', __type = 'Menu' }, mt)
menu.Items(function(items, trigger, key, index, value, oldValue)
menu:Trigger(trigger, key, index, value, oldValue)
end)
for k, v in pairs(info or {}) do
local key = U:Ensure(k, 'unknown')
if (key == 'unknown') then return end
menu:On(key, v)
end
LoadTextureDictionary(menu.Dictionary)
return menu
end
_ENV.CreateMenu = CreateMenu
_G.CreateMenu = CreateMenu