Merge pull request #100 from The-Order-Of-The-Sacred-Framework/main
Wrong branch
This commit is contained in:
3 files changed
+444
-325
No files matched your search
Vendored
+170
-51
@@ -1,91 +1,146 @@
|
||||
local Cache = {}
|
||||
---@class CacheEntry
|
||||
---@field Name string
|
||||
---@field Compare fun(): any
|
||||
---@field WaitTime integer | nil
|
||||
---@field LastChecked integer|nil
|
||||
---@field OnChange fun(new:any, old:any)[]
|
||||
---@field Value any
|
||||
|
||||
---@class CacheModule
|
||||
---@field Caches table<string, CacheEntry>
|
||||
---@field LoopRunning boolean
|
||||
---@field Create fun(name:string, compare:fun():any, waitTime:integer | nil): CacheEntry
|
||||
---@field Get fun(name:string): any
|
||||
---@field OnChange fun(name:string, onChange:fun(new:any, old:any))
|
||||
---@field Remove fun(name:string)
|
||||
local Cache = {} ---@type CacheModule
|
||||
|
||||
local Config = Require("settings/sharedConfig.lua")
|
||||
local max = 5000
|
||||
local CreateThread = CreateThread
|
||||
local Wait = Wait
|
||||
local GetGameTimer = GetGameTimer
|
||||
|
||||
local resourceCallbacks = {} -- Add callbacks from resources
|
||||
|
||||
Cache.Caches = Cache.Caches or {}
|
||||
Cache.LoopRunning = Cache.LoopRunning or false
|
||||
|
||||
local function print(...)
|
||||
---@param ... any
|
||||
local function debugPrint(...)
|
||||
if Config.DebugLevel == 0 then return end
|
||||
print("^2[Cache]^0", ...)
|
||||
end
|
||||
|
||||
local function HasActiveCaches()
|
||||
for _, cache in pairs(Cache.Caches) do
|
||||
if cache.WaitTime ~= nil then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function processCacheEntry(now, cache)
|
||||
cache.LastChecked = cache.LastChecked or now
|
||||
cache.WaitTime = tonumber(cache.WaitTime) or max
|
||||
local elapsed = now - cache.LastChecked
|
||||
local remaining = cache.WaitTime - elapsed
|
||||
|
||||
if remaining <= 0 then
|
||||
local oldValue = cache.Value
|
||||
cache.Value = cache.Compare()
|
||||
if cache.Value ~= oldValue and cache.OnChange then
|
||||
for i, onChange in ipairs(cache.OnChange) do
|
||||
Citizen.CreateThreadNow(function()
|
||||
Wait(0)
|
||||
onChange(cache.Value, oldValue)
|
||||
end)
|
||||
end
|
||||
end
|
||||
cache.LastChecked = now
|
||||
remaining = cache.WaitTime
|
||||
end
|
||||
|
||||
return remaining
|
||||
end
|
||||
|
||||
local function getNextWait(now)
|
||||
local minWait = nil
|
||||
for name, cache in pairs(Cache.Caches) do
|
||||
if cache.Compare and cache.WaitTime ~= nil then
|
||||
local remaining = processCacheEntry(now, cache)
|
||||
if not minWait or remaining < minWait then
|
||||
minWait = remaining
|
||||
if minWait <= 0 then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
return minWait
|
||||
end
|
||||
|
||||
local function StartLoop()
|
||||
if Cache.LoopRunning then return end
|
||||
if not HasActiveCaches() then return end
|
||||
Cache.LoopRunning = true
|
||||
CreateThread(function()
|
||||
while Cache.LoopRunning do
|
||||
local now = GetGameTimer()
|
||||
local minWait = nil
|
||||
local caches = Cache.Caches
|
||||
for name, cache in pairs(caches) do
|
||||
if cache.Compare then
|
||||
cache.LastChecked = cache.LastChecked or now
|
||||
cache.WaitTime = cache.WaitTime or max
|
||||
local elapsed = now - cache.LastChecked
|
||||
local remaining = cache.WaitTime - elapsed
|
||||
if remaining <= 0 then
|
||||
local oldValue = cache.Value
|
||||
cache.Value = cache.Compare()
|
||||
if cache.Value ~= oldValue and cache.OnChange then
|
||||
for _, onChange in pairs(cache.OnChange) do
|
||||
onChange(cache.Value, oldValue)
|
||||
end
|
||||
end
|
||||
cache.LastChecked = now
|
||||
remaining = cache.WaitTime
|
||||
end
|
||||
if not minWait or remaining < minWait then
|
||||
minWait = remaining
|
||||
if minWait <= 0 then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
local minWait = getNextWait(now)
|
||||
if minWait then
|
||||
Wait(math.max(0, minWait))
|
||||
else
|
||||
Wait(max)
|
||||
end
|
||||
if not HasActiveCaches() then
|
||||
Cache.LoopRunning = false
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param compare fun():any
|
||||
---@param waitTime integer | nil
|
||||
---@return CacheEntry | nil
|
||||
function Cache.Create(name, compare, waitTime)
|
||||
assert(name, "Cache name is required.")
|
||||
assert(compare, "Comparison function is required.")
|
||||
local _name = tostring(name) -- Ensure name is a string
|
||||
if type(waitTime) ~= "number" then
|
||||
waitTime = nil
|
||||
end
|
||||
local _name = tostring(name)
|
||||
local cache = Cache.Caches[_name]
|
||||
if cache and cache.Compare == compare then
|
||||
print(name .. " already exists with the same comparison function.")
|
||||
debugPrint(_name .. " already exists with the same comparison function.")
|
||||
return cache
|
||||
end
|
||||
cache = nil -- clean variable, so we can use the same name for the new cache
|
||||
local s, r = pcall(function() -- Try to create the cache and catch any errors
|
||||
local _cache <const> = {
|
||||
Name = _name,
|
||||
Compare = compare,
|
||||
WaitTime = waitTime or max,
|
||||
LastChecked = nil,
|
||||
OnChange = {},
|
||||
Value = compare()
|
||||
}
|
||||
Cache.Caches[_name] = _cache
|
||||
for _, onChange in pairs(_cache.OnChange) do
|
||||
onChange(_cache.Value, nil)
|
||||
end
|
||||
end)
|
||||
if not s then
|
||||
print("Error creating cache '" .. _name .. "': " .. tostring(r))
|
||||
local ok, result = pcall(compare)
|
||||
if not ok then
|
||||
debugPrint("Error creating cache '" .. _name .. "': " .. tostring(result))
|
||||
return nil
|
||||
end
|
||||
---@type CacheEntry
|
||||
local newCache = {
|
||||
Name = _name,
|
||||
Compare = compare,
|
||||
WaitTime = waitTime, -- can be nil
|
||||
LastChecked = nil,
|
||||
OnChange = {},
|
||||
Value = result
|
||||
}
|
||||
Cache.Caches[_name] = newCache
|
||||
|
||||
return StartLoop()
|
||||
debugPrint(_name .. " created with initial value: " .. tostring(result))
|
||||
for _, onChange in pairs(newCache.OnChange) do
|
||||
onChange(newCache.Value, 0)
|
||||
end
|
||||
StartLoop()
|
||||
return newCache
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@return any
|
||||
function Cache.Get(name)
|
||||
assert(name, "Cache name is required.")
|
||||
local _name = tostring(name)
|
||||
@@ -93,22 +148,86 @@ function Cache.Get(name)
|
||||
return cache and cache.Value or nil
|
||||
end
|
||||
|
||||
--- This add a callback to the cache entry that will be called when the value changes.
|
||||
--- The callback will be called with the new value and the old value.
|
||||
--- you can call the value again to delete the callback.
|
||||
---@param name string
|
||||
---@param onChange fun(new:any, old:any)
|
||||
function Cache.OnChange(name, onChange)
|
||||
assert(name, "Cache name is required.")
|
||||
local _name = tostring(name) -- Ensure name is a string
|
||||
local _name = tostring(name)
|
||||
local cache = Cache.Caches[_name]
|
||||
assert(cache, "Cache with name '" .. _name .. "' does not exist.")
|
||||
cache.OnChange[#cache.OnChange + 1] = onChange
|
||||
|
||||
-- Figure out which resource is trying to register this callback
|
||||
local invokingResource = GetInvokingResource()
|
||||
if not invokingResource then return end
|
||||
|
||||
-- Make sure we have a place to store callbacks for this resource
|
||||
resourceCallbacks[invokingResource] = resourceCallbacks[invokingResource] or {}
|
||||
|
||||
-- Add the new callback to our list
|
||||
local callbackIndex = #cache.OnChange + 1
|
||||
cache.OnChange[callbackIndex] = onChange
|
||||
|
||||
-- Keep track of this callback so we can clean it up later
|
||||
table.insert(resourceCallbacks[invokingResource], {
|
||||
cacheName = _name,
|
||||
index = callbackIndex
|
||||
})
|
||||
|
||||
-- Watch for when the resource stops and clean up its callbacks
|
||||
AddEventHandler('onResourceStop', function(resourceName)
|
||||
if resourceName == invokingResource then
|
||||
-- Clean up any callbacks this resource registered
|
||||
local callbacks = resourceCallbacks[resourceName]
|
||||
if callbacks then
|
||||
for _, cb in ipairs(callbacks) do
|
||||
local targetCache = Cache.Caches[cb.cacheName]
|
||||
if targetCache then
|
||||
-- Remove the callback from our list
|
||||
table.remove(targetCache.OnChange, cb.index)
|
||||
debugPrint(("Removed OnChange callback from cache '%s' - resource '%s' stopped"):format(
|
||||
cb.cacheName,
|
||||
resourceName
|
||||
))
|
||||
end
|
||||
end
|
||||
-- Clear out all callbacks for this resource
|
||||
resourceCallbacks[resourceName] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
debugPrint(("Added new OnChange callback to cache '%s' from resource '%s'"):format(_name, invokingResource))
|
||||
end
|
||||
|
||||
---@param name string
|
||||
function Cache.Remove(name)
|
||||
assert(name, "Cache name is required.")
|
||||
local _name = tostring(name) -- Ensure name is a string
|
||||
local _name = tostring(name)
|
||||
local cache = Cache.Caches[_name]
|
||||
if cache then
|
||||
Cache.Caches[_name] = nil
|
||||
debugPrint(_name .. " removed from cache.")
|
||||
if next(Cache.Caches) == nil then
|
||||
Cache.LoopRunning = false -- Stop the loop if no caches are left
|
||||
Cache.LoopRunning = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param newValue any
|
||||
function Cache.Update(name, newValue)
|
||||
assert(name, "Cache name is required.")
|
||||
local _name = tostring(name)
|
||||
local cache = Cache.Caches[_name]
|
||||
assert(cache, "Cache with name '" .. _name .. "' does not exist.")
|
||||
local oldValue = cache.Value
|
||||
if oldValue ~= newValue then
|
||||
cache.Value = newValue
|
||||
for _, onChange in pairs(cache.OnChange) do
|
||||
onChange(newValue, oldValue)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+273
-273
@@ -1,329 +1,329 @@
|
||||
-- DUI (Direct User Interface) Manager for FiveM
|
||||
-- Basic table-based implementation with full mouse support
|
||||
-- -- DUI (Direct User Interface) Manager for FiveM
|
||||
-- -- Basic table-based implementation with full mouse support
|
||||
|
||||
---@class DUI
|
||||
DUI = {}
|
||||
-- ---@class DUI
|
||||
-- DUI = {}
|
||||
|
||||
-- Constants
|
||||
local DEFAULT_WIDTH <const> = 1280
|
||||
local DEFAULT_HEIGHT <const> = 720
|
||||
-- -- Constants
|
||||
-- local DEFAULT_WIDTH <const> = 1280
|
||||
-- local DEFAULT_HEIGHT <const> = 720
|
||||
|
||||
-- Store active DUI instances
|
||||
local activeInstances = {}
|
||||
local instanceCounter = 0
|
||||
-- -- Store active DUI instances
|
||||
-- local activeInstances = {}
|
||||
-- local instanceCounter = 0
|
||||
|
||||
-- Mouse tracking state
|
||||
local mouseState = {
|
||||
tracking = false,
|
||||
activeId = nil,
|
||||
lastX = 0,
|
||||
lastY = 0,
|
||||
isPressed = false,
|
||||
currentButton = nil
|
||||
}
|
||||
-- -- Mouse tracking state
|
||||
-- local mouseState = {
|
||||
-- tracking = false,
|
||||
-- activeId = nil,
|
||||
-- lastX = 0,
|
||||
-- lastY = 0,
|
||||
-- isPressed = false,
|
||||
-- currentButton = nil
|
||||
-- }
|
||||
|
||||
-- Mouse button constants
|
||||
local MOUSE_BUTTONS <const> = {
|
||||
LEFT = "left",
|
||||
MIDDLE = "middle",
|
||||
RIGHT = "right"
|
||||
}
|
||||
-- -- Mouse button constants
|
||||
-- local MOUSE_BUTTONS <const> = {
|
||||
-- LEFT = "left",
|
||||
-- MIDDLE = "middle",
|
||||
-- RIGHT = "right"
|
||||
-- }
|
||||
|
||||
---@class DUIInstance
|
||||
---@field id number Unique identifier for this DUI instance
|
||||
---@field url string URL loaded in the DUI
|
||||
---@field width number Width of the DUI
|
||||
---@field height number Height of the DUI
|
||||
---@field handle number DUI browser handle
|
||||
---@field txd string Texture dictionary name
|
||||
---@field txn string Texture name
|
||||
---@field active boolean Whether the DUI is active
|
||||
---@field trackMouse boolean Whether mouse tracking is enabled for this instance
|
||||
---@field mouseScale table<string, number> Scale factors for mouse coordinates
|
||||
-- ---@class DUIInstance
|
||||
-- ---@field id number Unique identifier for this DUI instance
|
||||
-- ---@field url string URL loaded in the DUI
|
||||
-- ---@field width number Width of the DUI
|
||||
-- ---@field height number Height of the DUI
|
||||
-- ---@field handle number DUI browser handle
|
||||
-- ---@field txd string Texture dictionary name
|
||||
-- ---@field txn string Texture name
|
||||
-- ---@field active boolean Whether the DUI is active
|
||||
-- ---@field trackMouse boolean Whether mouse tracking is enabled for this instance
|
||||
-- ---@field mouseScale table<string, number> Scale factors for mouse coordinates
|
||||
|
||||
-- Create a new DUI instance
|
||||
---@param url string URL to load
|
||||
---@param width? number Width of the DUI (default: 1280)
|
||||
---@param height? number Height of the DUI (default: 720)
|
||||
---@return number|nil id The DUI instance ID if successful, nil if failed
|
||||
function DUI.Create(url, width, height)
|
||||
if not url or type(url) ~= "string" then
|
||||
return print("DUI.Create: URL is required and must be a string")
|
||||
end
|
||||
-- -- Create a new DUI instance
|
||||
-- ---@param url string URL to load
|
||||
-- ---@param width? number Width of the DUI (default: 1280)
|
||||
-- ---@param height? number Height of the DUI (default: 720)
|
||||
-- ---@return number|nil id The DUI instance ID if successful, nil if failed
|
||||
-- function DUI.Create(url, width, height)
|
||||
-- if not url or type(url) ~= "string" then
|
||||
-- return print("DUI.Create: URL is required and must be a string")
|
||||
-- end
|
||||
|
||||
width = width or DEFAULT_WIDTH
|
||||
height = height or DEFAULT_HEIGHT
|
||||
instanceCounter = instanceCounter + 1
|
||||
-- width = width or DEFAULT_WIDTH
|
||||
-- height = height or DEFAULT_HEIGHT
|
||||
-- instanceCounter = instanceCounter + 1
|
||||
|
||||
local id = instanceCounter
|
||||
local handle = CreateDui(url, width, height)
|
||||
-- local id = instanceCounter
|
||||
-- local handle = CreateDui(url, width, height)
|
||||
|
||||
if not handle then
|
||||
return print("DUI.Create: Failed to create DUI instance")
|
||||
end
|
||||
-- if not handle then
|
||||
-- return print("DUI.Create: Failed to create DUI instance")
|
||||
-- end
|
||||
|
||||
local instance = {
|
||||
id = id,
|
||||
url = url,
|
||||
width = width,
|
||||
height = height,
|
||||
handle = handle,
|
||||
txd = "dui_" .. id,
|
||||
txn = "texture_" .. id,
|
||||
active = true,
|
||||
trackMouse = false,
|
||||
mouseScale = {
|
||||
x = 1.0,
|
||||
y = 1.0
|
||||
}
|
||||
}
|
||||
-- local instance = {
|
||||
-- id = id,
|
||||
-- url = url,
|
||||
-- width = width,
|
||||
-- height = height,
|
||||
-- handle = handle,
|
||||
-- txd = "dui_" .. id,
|
||||
-- txn = "texture_" .. id,
|
||||
-- active = true,
|
||||
-- trackMouse = false,
|
||||
-- mouseScale = {
|
||||
-- x = 1.0,
|
||||
-- y = 1.0
|
||||
-- }
|
||||
-- }
|
||||
|
||||
-- Create runtime texture
|
||||
local duiHandle = GetDuiHandle(handle)
|
||||
CreateRuntimeTextureFromDuiHandle(CreateRuntimeTxd(instance.txd), instance.txn, duiHandle)
|
||||
-- -- Create runtime texture
|
||||
-- local duiHandle = GetDuiHandle(handle)
|
||||
-- CreateRuntimeTextureFromDuiHandle(CreateRuntimeTxd(instance.txd), instance.txn, duiHandle)
|
||||
|
||||
activeInstances[id] = instance
|
||||
return id
|
||||
end
|
||||
-- activeInstances[id] = instance
|
||||
-- return id
|
||||
-- end
|
||||
|
||||
-- Destroy a DUI instance
|
||||
---@param id number DUI instance ID
|
||||
---@return boolean success Whether the operation was successful
|
||||
function DUI.Destroy(id)
|
||||
local instance = activeInstances[id]
|
||||
if not instance then return false end
|
||||
-- -- Destroy a DUI instance
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@return boolean success Whether the operation was successful
|
||||
-- function DUI.Destroy(id)
|
||||
-- local instance = activeInstances[id]
|
||||
-- if not instance then return false end
|
||||
|
||||
if instance.handle then
|
||||
DestroyDui(instance.handle)
|
||||
end
|
||||
-- if instance.handle then
|
||||
-- DestroyDui(instance.handle)
|
||||
-- end
|
||||
|
||||
activeInstances[id] = nil
|
||||
return true
|
||||
end
|
||||
-- activeInstances[id] = nil
|
||||
-- return true
|
||||
-- end
|
||||
|
||||
-- Set URL for a DUI instance
|
||||
---@param id number DUI instance ID
|
||||
---@param url string New URL to load
|
||||
---@return boolean success Whether the operation was successful
|
||||
function DUI.SetURL(id, url)
|
||||
local instance = activeInstances[id]
|
||||
if not instance then return false end
|
||||
-- -- Set URL for a DUI instance
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@param url string New URL to load
|
||||
-- ---@return boolean success Whether the operation was successful
|
||||
-- function DUI.SetURL(id, url)
|
||||
-- local instance = activeInstances[id]
|
||||
-- if not instance then return false end
|
||||
|
||||
SetDuiUrl(instance.handle, url)
|
||||
instance.url = url
|
||||
return true
|
||||
end
|
||||
-- SetDuiUrl(instance.handle, url)
|
||||
-- instance.url = url
|
||||
-- return true
|
||||
-- end
|
||||
|
||||
-- Send a message to the DUI
|
||||
---@param id number DUI instance ID
|
||||
---@param message table Message to send (will be JSON encoded)
|
||||
---@return boolean success Whether the operation was successful
|
||||
function DUI.SendMessage(id, message)
|
||||
local instance = activeInstances[id]
|
||||
if not instance then return false end
|
||||
-- -- Send a message to the DUI
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@param message table Message to send (will be JSON encoded)
|
||||
-- ---@return boolean success Whether the operation was successful
|
||||
-- function DUI.SendMessage(id, message)
|
||||
-- local instance = activeInstances[id]
|
||||
-- if not instance then return false end
|
||||
|
||||
SendDuiMessage(instance.handle, json.encode(message))
|
||||
return true
|
||||
end
|
||||
-- SendDuiMessage(instance.handle, json.encode(message))
|
||||
-- return true
|
||||
-- end
|
||||
|
||||
-- Mouse interaction functions
|
||||
-- -- Mouse interaction functions
|
||||
|
||||
-- Move mouse cursor on DUI
|
||||
---@param id number DUI instance ID
|
||||
---@param x number X coordinate
|
||||
---@param y number Y coordinate
|
||||
---@return boolean success Whether the operation was successful
|
||||
function DUI.MoveMouse(id, x, y)
|
||||
local instance = activeInstances[id]
|
||||
if not instance then return false end
|
||||
-- -- Move mouse cursor on DUI
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@param x number X coordinate
|
||||
-- ---@param y number Y coordinate
|
||||
-- ---@return boolean success Whether the operation was successful
|
||||
-- function DUI.MoveMouse(id, x, y)
|
||||
-- local instance = activeInstances[id]
|
||||
-- if not instance then return false end
|
||||
|
||||
SendDuiMouseMove(instance.handle, x, y)
|
||||
return true
|
||||
end
|
||||
-- SendDuiMouseMove(instance.handle, x, y)
|
||||
-- return true
|
||||
-- end
|
||||
|
||||
-- Simulate mouse button press
|
||||
---@param id number DUI instance ID
|
||||
---@param button "left"|"middle"|"right" Mouse button to simulate
|
||||
---@return boolean success Whether the operation was successful
|
||||
function DUI.MouseDown(id, button)
|
||||
local instance = activeInstances[id]
|
||||
if not instance then return false end
|
||||
-- -- Simulate mouse button press
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@param button "left"|"middle"|"right" Mouse button to simulate
|
||||
-- ---@return boolean success Whether the operation was successful
|
||||
-- function DUI.MouseDown(id, button)
|
||||
-- local instance = activeInstances[id]
|
||||
-- if not instance then return false end
|
||||
|
||||
if not MOUSE_BUTTONS[button:upper()] then
|
||||
return print("DUI.MouseDown: Invalid button. Must be 'left', 'middle', or 'right'")
|
||||
end
|
||||
-- if not MOUSE_BUTTONS[button:upper()] then
|
||||
-- return print("DUI.MouseDown: Invalid button. Must be 'left', 'middle', or 'right'")
|
||||
-- end
|
||||
|
||||
-- Update mouse state
|
||||
if instance.trackMouse then
|
||||
mouseState.isPressed = true
|
||||
mouseState.currentButton = button
|
||||
end
|
||||
-- -- Update mouse state
|
||||
-- if instance.trackMouse then
|
||||
-- mouseState.isPressed = true
|
||||
-- mouseState.currentButton = button
|
||||
-- end
|
||||
|
||||
SendDuiMouseDown(instance.handle, button)
|
||||
return true
|
||||
end
|
||||
-- SendDuiMouseDown(instance.handle, button)
|
||||
-- return true
|
||||
-- end
|
||||
|
||||
-- Simulate mouse button release
|
||||
---@param id number DUI instance ID
|
||||
---@param button "left"|"middle"|"right" Mouse button to simulate
|
||||
---@return boolean success Whether the operation was successful
|
||||
function DUI.MouseUp(id, button)
|
||||
local instance = activeInstances[id]
|
||||
if not instance then return false end
|
||||
-- -- Simulate mouse button release
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@param button "left"|"middle"|"right" Mouse button to simulate
|
||||
-- ---@return boolean success Whether the operation was successful
|
||||
-- function DUI.MouseUp(id, button)
|
||||
-- local instance = activeInstances[id]
|
||||
-- if not instance then return false end
|
||||
|
||||
if not MOUSE_BUTTONS[button:upper()] then
|
||||
return print("DUI.MouseUp: Invalid button. Must be 'left', 'middle', or 'right'")
|
||||
end
|
||||
-- if not MOUSE_BUTTONS[button:upper()] then
|
||||
-- return print("DUI.MouseUp: Invalid button. Must be 'left', 'middle', or 'right'")
|
||||
-- end
|
||||
|
||||
-- Update mouse state
|
||||
if instance.trackMouse then
|
||||
mouseState.isPressed = false
|
||||
mouseState.currentButton = nil
|
||||
end
|
||||
-- -- Update mouse state
|
||||
-- if instance.trackMouse then
|
||||
-- mouseState.isPressed = false
|
||||
-- mouseState.currentButton = nil
|
||||
-- end
|
||||
|
||||
SendDuiMouseUp(instance.handle, button)
|
||||
return true
|
||||
end
|
||||
-- SendDuiMouseUp(instance.handle, button)
|
||||
-- return true
|
||||
-- end
|
||||
|
||||
-- Simulate mouse wheel movement
|
||||
---@param id number DUI instance ID
|
||||
---@param deltaY number Vertical scroll amount
|
||||
---@param deltaX number Horizontal scroll amount
|
||||
---@return boolean success Whether the operation was successful
|
||||
function DUI.MouseWheel(id, deltaY, deltaX)
|
||||
local instance = activeInstances[id]
|
||||
if not instance then return false end
|
||||
-- -- Simulate mouse wheel movement
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@param deltaY number Vertical scroll amount
|
||||
-- ---@param deltaX number Horizontal scroll amount
|
||||
-- ---@return boolean success Whether the operation was successful
|
||||
-- function DUI.MouseWheel(id, deltaY, deltaX)
|
||||
-- local instance = activeInstances[id]
|
||||
-- if not instance then return false end
|
||||
|
||||
SendDuiMouseWheel(instance.handle, deltaY, deltaX)
|
||||
return true
|
||||
end
|
||||
-- SendDuiMouseWheel(instance.handle, deltaY, deltaX)
|
||||
-- return true
|
||||
-- end
|
||||
|
||||
-- Click helper function (combines MoveMouse, MouseDown, and MouseUp)
|
||||
---@param id number DUI instance ID
|
||||
---@param x number X coordinate
|
||||
---@param y number Y coordinate
|
||||
---@param button? "left"|"middle"|"right" Mouse button to click (default: "left")
|
||||
---@return boolean success Whether the operation was successful
|
||||
function DUI.Click(id, x, y, button)
|
||||
button = button or "left"
|
||||
-- -- Click helper function (combines MoveMouse, MouseDown, and MouseUp)
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@param x number X coordinate
|
||||
-- ---@param y number Y coordinate
|
||||
-- ---@param button? "left"|"middle"|"right" Mouse button to click (default: "left")
|
||||
-- ---@return boolean success Whether the operation was successful
|
||||
-- function DUI.Click(id, x, y, button)
|
||||
-- button = button or "left"
|
||||
|
||||
if not DUI.MoveMouse(id, x, y) then return false end
|
||||
if not DUI.MouseDown(id, button) then return false end
|
||||
-- if not DUI.MoveMouse(id, x, y) then return false end
|
||||
-- if not DUI.MouseDown(id, button) then return false end
|
||||
|
||||
-- Small delay to simulate real click
|
||||
Citizen.Wait(50)
|
||||
-- -- Small delay to simulate real click
|
||||
-- Citizen.Wait(50)
|
||||
|
||||
return DUI.MouseUp(id, button)
|
||||
end
|
||||
-- return DUI.MouseUp(id, button)
|
||||
-- end
|
||||
|
||||
-- Enable or disable mouse tracking for a DUI instance
|
||||
---@param id number DUI instance ID
|
||||
---@param enabled boolean Whether to enable mouse tracking
|
||||
---@param scaleX? number Scale factor for X coordinates (default: 1.0)
|
||||
---@param scaleY? number Scale factor for Y coordinates (default: 1.0)
|
||||
---@return boolean success Whether the operation was successful
|
||||
function DUI.TrackMouse(id, enabled, scaleX, scaleY)
|
||||
local instance = activeInstances[id]
|
||||
if not instance then return false end
|
||||
-- -- Enable or disable mouse tracking for a DUI instance
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@param enabled boolean Whether to enable mouse tracking
|
||||
-- ---@param scaleX? number Scale factor for X coordinates (default: 1.0)
|
||||
-- ---@param scaleY? number Scale factor for Y coordinates (default: 1.0)
|
||||
-- ---@return boolean success Whether the operation was successful
|
||||
-- function DUI.TrackMouse(id, enabled, scaleX, scaleY)
|
||||
-- local instance = activeInstances[id]
|
||||
-- if not instance then return false end
|
||||
|
||||
instance.trackMouse = enabled
|
||||
instance.mouseScale.x = scaleX or 1.0
|
||||
instance.mouseScale.y = scaleY or 1.0
|
||||
-- instance.trackMouse = enabled
|
||||
-- instance.mouseScale.x = scaleX or 1.0
|
||||
-- instance.mouseScale.y = scaleY or 1.0
|
||||
|
||||
if enabled then
|
||||
mouseState.tracking = true
|
||||
mouseState.activeId = id
|
||||
elseif mouseState.activeId == id then
|
||||
mouseState.tracking = false
|
||||
mouseState.activeId = nil
|
||||
end
|
||||
-- if enabled then
|
||||
-- mouseState.tracking = true
|
||||
-- mouseState.activeId = id
|
||||
-- elseif mouseState.activeId == id then
|
||||
-- mouseState.tracking = false
|
||||
-- mouseState.activeId = nil
|
||||
-- end
|
||||
|
||||
return true
|
||||
end
|
||||
-- return true
|
||||
-- end
|
||||
|
||||
-- Update mouse coordinates from screen position
|
||||
---@param screenX number Screen X coordinate
|
||||
---@param screenY number Screen Y coordinate
|
||||
---@return boolean updated Whether coordinates were updated
|
||||
local function UpdateMousePosition(screenX, screenY)
|
||||
if not mouseState.tracking or not mouseState.activeId then return false end
|
||||
-- -- Update mouse coordinates from screen position
|
||||
-- ---@param screenX number Screen X coordinate
|
||||
-- ---@param screenY number Screen Y coordinate
|
||||
-- ---@return boolean updated Whether coordinates were updated
|
||||
-- local function UpdateMousePosition(screenX, screenY)
|
||||
-- if not mouseState.tracking or not mouseState.activeId then return false end
|
||||
|
||||
local instance = activeInstances[mouseState.activeId]
|
||||
if not instance or not instance.trackMouse then return false end
|
||||
-- local instance = activeInstances[mouseState.activeId]
|
||||
-- if not instance or not instance.trackMouse then return false end
|
||||
|
||||
-- Scale coordinates based on DUI dimensions and scale factors
|
||||
local scaledX = screenX * instance.mouseScale.x
|
||||
local scaledY = screenY * instance.mouseScale.y
|
||||
-- -- Scale coordinates based on DUI dimensions and scale factors
|
||||
-- local scaledX = screenX * instance.mouseScale.x
|
||||
-- local scaledY = screenY * instance.mouseScale.y
|
||||
|
||||
-- Only update if position changed
|
||||
if scaledX ~= mouseState.lastX or scaledY ~= mouseState.lastY then
|
||||
mouseState.lastX = scaledX
|
||||
mouseState.lastY = scaledY
|
||||
DUI.MoveMouse(mouseState.activeId, scaledX, scaledY)
|
||||
return true
|
||||
end
|
||||
-- -- Only update if position changed
|
||||
-- if scaledX ~= mouseState.lastX or scaledY ~= mouseState.lastY then
|
||||
-- mouseState.lastX = scaledX
|
||||
-- mouseState.lastY = scaledY
|
||||
-- DUI.MoveMouse(mouseState.activeId, scaledX, scaledY)
|
||||
-- return true
|
||||
-- end
|
||||
|
||||
return false
|
||||
end
|
||||
-- return false
|
||||
-- end
|
||||
|
||||
-- Mouse tracking thread
|
||||
Citizen.CreateThread(function()
|
||||
while true do
|
||||
if mouseState.tracking and mouseState.activeId then
|
||||
local instance = activeInstances[mouseState.activeId]
|
||||
if instance and instance.trackMouse then
|
||||
-- Get current cursor position
|
||||
local screenX, screenY = GetNuiCursorPosition()
|
||||
UpdateMousePosition(screenX, screenY)
|
||||
-- -- Mouse tracking thread
|
||||
-- Citizen.CreateThread(function()
|
||||
-- while true do
|
||||
-- if mouseState.tracking and mouseState.activeId then
|
||||
-- local instance = activeInstances[mouseState.activeId]
|
||||
-- if instance and instance.trackMouse then
|
||||
-- -- Get current cursor position
|
||||
-- local screenX, screenY = GetNuiCursorPosition()
|
||||
-- UpdateMousePosition(screenX, screenY)
|
||||
|
||||
-- Handle held mouse buttons
|
||||
if mouseState.isPressed and mouseState.currentButton then
|
||||
-- Keep the button pressed state
|
||||
SendDuiMouseDown(instance.handle, mouseState.currentButton)
|
||||
end
|
||||
end
|
||||
end
|
||||
Citizen.Wait(0)
|
||||
end
|
||||
end)
|
||||
-- -- Handle held mouse buttons
|
||||
-- if mouseState.isPressed and mouseState.currentButton then
|
||||
-- -- Keep the button pressed state
|
||||
-- SendDuiMouseDown(instance.handle, mouseState.currentButton)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- Citizen.Wait(0)
|
||||
-- end
|
||||
-- end)
|
||||
|
||||
-- Utility functions
|
||||
-- -- Utility functions
|
||||
|
||||
-- Get textures for a DUI instance
|
||||
---@param id number DUI instance ID
|
||||
---@return string|nil txd, string|nil txn Texture dictionary and name, or nil if instance not found
|
||||
function DUI.GetTextures(id)
|
||||
local instance = activeInstances[id]
|
||||
if not instance then return nil, nil end
|
||||
-- -- Get textures for a DUI instance
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@return string|nil txd, string|nil txn Texture dictionary and name, or nil if instance not found
|
||||
-- function DUI.GetTextures(id)
|
||||
-- local instance = activeInstances[id]
|
||||
-- if not instance then return nil, nil end
|
||||
|
||||
return instance.txd, instance.txn
|
||||
end
|
||||
-- return instance.txd, instance.txn
|
||||
-- end
|
||||
|
||||
-- Check if a DUI instance exists
|
||||
---@param id number DUI instance ID
|
||||
---@return boolean exists Whether the instance exists and is active
|
||||
function DUI.Exists(id)
|
||||
local instance = activeInstances[id]
|
||||
return instance ~= nil and instance.active
|
||||
end
|
||||
-- -- Check if a DUI instance exists
|
||||
-- ---@param id number DUI instance ID
|
||||
-- ---@return boolean exists Whether the instance exists and is active
|
||||
-- function DUI.Exists(id)
|
||||
-- local instance = activeInstances[id]
|
||||
-- return instance ~= nil and instance.active
|
||||
-- end
|
||||
|
||||
-- Get all active DUI instances
|
||||
---@return table<number, DUIInstance> instances Table of active DUI instances
|
||||
function DUI.GetActiveInstances()
|
||||
return activeInstances
|
||||
end
|
||||
-- -- Get all active DUI instances
|
||||
-- ---@return table<number, DUIInstance> instances Table of active DUI instances
|
||||
-- function DUI.GetActiveInstances()
|
||||
-- return activeInstances
|
||||
-- end
|
||||
|
||||
-- Cleanup all DUI instances
|
||||
function DUI.CleanupAll()
|
||||
for id in pairs(activeInstances) do
|
||||
DUI.Destroy(id)
|
||||
end
|
||||
end
|
||||
-- -- Cleanup all DUI instances
|
||||
-- function DUI.CleanupAll()
|
||||
-- for id in pairs(activeInstances) do
|
||||
-- DUI.Destroy(id)
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- Automatic cleanup on resource stop
|
||||
AddEventHandler('onResourceStop', function(resourceName)
|
||||
if GetCurrentResourceName() ~= resourceName then return end
|
||||
DUI.CleanupAll()
|
||||
end)
|
||||
-- -- Automatic cleanup on resource stop
|
||||
-- AddEventHandler('onResourceStop', function(resourceName)
|
||||
-- if GetCurrentResourceName() ~= resourceName then return end
|
||||
-- DUI.CleanupAll()
|
||||
-- end)
|
||||
|
||||
return DUI
|
||||
-- return DUI
|
||||
+1
-1
@@ -80,7 +80,7 @@ cLib.ClientStateBag = ClientStateBag or Require("lib/statebags/client/client.lua
|
||||
cLib.Marker = Marker or Require("lib/markers/client/markers.lua")
|
||||
cLib.Anim = Anim or Require("lib/anim/client/client.lua")
|
||||
cLib.Cutscene = Cutscene or Require("lib/cutscenes/client/cutscene.lua")
|
||||
cLib.DUI = DUI or Require("lib/dui/client/dui.lua")
|
||||
--cLib.DUI = DUI or Require("lib/dui/client/dui.lua")
|
||||
cLib.Particle = Particle or Require("lib/particles/client/particles.lua")
|
||||
|
||||
return cLib
|
||||
Reference in new issue
Block a user