diff --git a/lib/dui/client/dui.lua b/lib/dui/client/dui.lua index 7f0d7f0..1b9fd68 100644 --- a/lib/dui/client/dui.lua +++ b/lib/dui/client/dui.lua @@ -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 = 1280 -local DEFAULT_HEIGHT = 720 +-- -- Constants +-- local DEFAULT_WIDTH = 1280 +-- local DEFAULT_HEIGHT = 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 = { - LEFT = "left", - MIDDLE = "middle", - RIGHT = "right" -} +-- -- Mouse button constants +-- local MOUSE_BUTTONS = { +-- 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 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 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 instances Table of active DUI instances -function DUI.GetActiveInstances() - return activeInstances -end +-- -- Get all active DUI instances +-- ---@return table 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 diff --git a/lib/init.lua b/lib/init.lua index 7aa6a34..ef0d786 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -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