UI - uosagas/assistant GitHub Wiki

Overview

The scripting engine provides a comprehensive UI system for Lua scripts, allowing developers to create custom windows and controls. This system is separate from the game's native UI and provides modern GUI capabilities for script-based interfaces.

Table of Contents

  1. Getting Started
  2. UI Global Table
  3. Window Management
  4. UI Controls
  5. Event Handling
  6. Examples
  7. Best Practices

Getting Started

The UI system is accessed through the global UI table in Lua scripts. All UI creation and management starts with this table.

-- Create a simple window
local window = UI.CreateWindow('myWindow', 'My First Window')
window:SetPosition(100, 100)
window:SetSize(400, 300)
-- Window is visible by default

-- IMPORTANT: Main loop is required for window rendering
while true do
    -- Your script logic here
    Pause(50)  -- Wait 50ms between updates
end

UI Global Table

The UI global table provides the following window management functions:

UI.CreateWindow(name, [title])

Creates a new window with the specified name and optional title.

  • Parameters:
    • name (string): Unique identifier for the window
    • title (string, optional): Display title (defaults to name if not provided)
  • Returns: LuaUIWindow object
  • Throws: Error if a window with the same name already exists
local window = UI.CreateWindow('mainWindow', 'Main Application Window')

UI.GetWindow(name)

Retrieves an existing window by name.

  • Parameters: name (string): Window identifier
  • Returns: LuaUIWindow object or nil if not found
local existingWindow = UI.GetWindow('mainWindow')
if existingWindow then
    existingWindow:Show()
end

UI.DestroyWindow(name)

Destroys a window and all its controls.

  • Parameters: name (string): Window identifier
  • Returns: boolean (true if successful)
UI.DestroyWindow('mainWindow')

UI.DestroyAllWindows()

Destroys all created windows.

  • Returns: boolean (always true)
UI.DestroyAllWindows() -- Clean up all windows

UI.HideAllWindows()

Hides all windows without destroying them.

  • Returns: boolean (always true)

UI.ShowAllWindows()

Shows all hidden windows.

  • Returns: boolean (always true)

UI.WindowExists(name)

Checks if a window with the given name exists.

  • Parameters: name (string): Window identifier
  • Returns: boolean
if UI.WindowExists('mainWindow') then
    Messages.Print("Window exists!")
end

UI.GetWindowCount()

Returns the number of created windows.

  • Returns: number (integer)
Messages.Print("Total windows: " .. UI.GetWindowCount())

Window Management

LuaUIWindow Object

Window objects provide methods for positioning, sizing, and managing windows.

Properties (Read-Only)

  • Name: Window identifier
  • Title: Window title
  • IsOpen: Whether the window is open
  • IsVisible: Whether the window is visible

Methods

window:SetPosition(x, y)

Sets the window position in pixels.

window:SetPosition(100, 100)
window:SetSize(width, height)

Sets the window size in pixels.

window:SetSize(400, 300)
window:Show()

Makes the window visible.

window:Show()
window:Hide()

Hides the window without closing it.

window:Hide()
window:Close()

Closes the window (sets IsOpen to false).

window:Close()
window:ClearControls()

Removes all controls from the window.

window:ClearControls()
window:SetResizable(resizable)

Sets whether the window can be resized by the user.

window:SetResizable(true)

UI Controls

Windows can contain various types of controls. All controls share common properties and methods, with specific controls adding their own functionality.

Common Control Properties (Read-Only)

  • ID: Unique control identifier
  • X: X position in pixels
  • Y: Y position in pixels
  • Width: Width in pixels
  • Height: Height in pixels
  • Visible: Visibility state
  • Enabled: Enabled state

Common Control Methods

All controls support these methods:

  • SetPosition(x, y): Sets control position
  • SetSize(width, height): Sets control size
  • SetVisible(visible): Sets visibility
  • SetEnabled(enabled): Sets enabled state

Button

Creates a clickable button.

Creation

local button = window:AddButton(x, y, text, [width], [height])
  • Parameters:
    • x, y: Position in pixels
    • text: Button label
    • width, height (optional): Size (auto-sized if 0 or omitted)

Properties

  • Text: Button label (read-only)

Methods

  • SetText(text): Updates button text
  • SetOnClick(function): Sets click handler

Example

local button = window:AddButton(10, 10, 'Click Me', 100, 30)
button:SetOnClick(function()
    Messages.Print("Button clicked!")
end)

Label

Displays static or dynamic text.

Creation

local label = window:AddLabel(x, y, text)

Properties

  • Text: Label text (read-only)

Methods

  • SetText(text): Updates label text
  • SetColor(r, g, b, [a]): Sets text color (RGBA values 0-1)

Example

local label = window:AddLabel(10, 50, 'Status: Ready')
label:SetColor(0, 1, 0, 1) -- Green text

TextBox

Single-line text input control.

Creation

local textBox = window:AddTextBox(x, y, width, [initialText])

Properties

  • Text: Current text content (read-only)

Methods

  • SetText(text): Sets textbox content
  • GetText(): Returns current text
  • SetOnTextChanged(function): Sets change handler

Example

local textBox = window:AddTextBox(10, 80, 200, 'Enter name...')
textBox:SetOnTextChanged(function(newText)
    Messages.Print("Text changed to: " .. newText)
end)

Checkbox

Boolean toggle control with label.

Creation

local checkbox = window:AddCheckbox(x, y, text, [initialChecked])

Properties

  • Text: Checkbox label (read-only)
  • Checked: Current state (read-only)

Methods

  • SetText(text): Updates label
  • SetChecked(checked): Sets checked state
  • SetOnCheckedChanged(function): Sets change handler

Example

local checkbox = window:AddCheckbox(10, 110, 'Enable feature', false)
checkbox:SetOnCheckedChanged(function(isChecked)
    if isChecked then
        Messages.Print("Feature enabled!")
    else
        Messages.Print("Feature disabled!")
    end
end)

Slider

Numeric value slider control.

Creation

local slider = window:AddSlider(x, y, width, [minValue], [maxValue], [initialValue])
  • Default values: min=0, max=100, initial=0

Properties

  • Value: Current value (read-only)
  • MinValue: Minimum value (read-only)
  • MaxValue: Maximum value (read-only)

Methods

  • SetValue(value): Sets current value
  • SetRange(min, max): Sets min/max values
  • SetOnValueChanged(function): Sets change handler

Example

local slider = window:AddSlider(10, 140, 200, 0, 100, 50)
slider:SetOnValueChanged(function(value)
    Messages.Print("Slider value: " .. value)
end)

ProgressBar

Visual progress indicator.

Creation

local progressBar = window:AddProgressBar(x, y, width, height, [initialValue])
  • initialValue: 0-1 (0% to 100%)

Properties

  • Value: Current progress (0-1, read-only)
  • Overlay: Text overlay (read-only)

Methods

  • SetValue(value): Sets progress (0-1)
  • SetColor(r, g, b, [a]): Sets bar color
  • SetOverlay(text): Sets overlay text
  • BindToValue(function, [updateInterval]): Binds to dynamic value

Example

local progress = window:AddProgressBar(10, 170, 200, 20, 0.5)
progress:SetColor(0, 1, 0, 1) -- Green bar
progress:SetOverlay("50%")

-- Bind to dynamic value
progress:BindToValue(function()
    return GetSomeProgress() -- Should return 0-1
end, 100) -- Update every 100ms

Event Handling

Events are handled through callback functions set on controls. Each control type has specific events:

Button Events

button:SetOnClick(function()
    -- No parameters
    Messages.Print("Button clicked!")
end)

TextBox Events

textBox:SetOnTextChanged(function(newText)
    -- Receives: new text string
    Messages.Print("New text: " .. newText)
end)

Checkbox Events

checkbox:SetOnCheckedChanged(function(isChecked)
    -- Receives: boolean checked state
    if isChecked then
        Messages.Print("Checked!")
    else
        Messages.Print("Unchecked!")
    end
end)

Slider Events

slider:SetOnValueChanged(function(value)
    -- Receives: numeric value
    Messages.Print("Value: " .. value)
end)

Examples

Example 1: Player Information Display

-- Variables to hold UI controls for updates
local window = nil
local nameLabel = nil
local coordinatesLabel = nil
local healthBar = nil

-- Create the main window
window = UI.CreateWindow('playerInfo', 'Player Information')
if window then
    window:SetPosition(200, 400)
    window:SetSize(300, 200)
    window:SetResizable(false)
    
    -- Add static labels
    window:AddLabel(30, 30, 'Information:'):SetColor(1, 1, 1, 1)
    window:AddLabel(30, 60, 'Player:'):SetColor(1, 1, 1, 1)
    window:AddLabel(30, 80, 'Coordinates:'):SetColor(1, 1, 1, 1)
    
    -- Add dynamic labels (will be updated in main loop)
    nameLabel = window:AddLabel(90, 60, 'n/a')
    nameLabel:SetColor(1, 0.5, 0, 1) -- Orange
    
    coordinatesLabel = window:AddLabel(120, 80, 'n/a')
    coordinatesLabel:SetColor(1, 0.5, 0, 1) -- Orange
    
    -- Add health progress bar
    healthBar = window:AddProgressBar(30, 110, 240, 20, 1.0)
    healthBar:SetColor(0, 1, 0, 1) -- Green
    healthBar:SetOverlay("100%")
    
    -- Window is visible by default
end

-- Main loop - REQUIRED for window rendering and updates
while true do
    -- Update player name and coordinates
    if nameLabel then
        nameLabel:SetText(Player.Name)
    end
    
    if coordinatesLabel then
        coordinatesLabel:SetText('(' .. Player.X .. ', ' .. Player.Y .. ', ' .. Player.Z .. ')')
    end
    
    -- Update health bar (example assumes Player.Hits/Player.HitsMax exists)
    if healthBar then
        local healthPercent = Player.Hits / Player.HitsMax
        healthBar:SetValue(healthPercent)
        healthBar:SetOverlay(math.floor(healthPercent * 100) .. '%')
        
        -- Change color based on health
        if healthPercent < 0.25 then
            healthBar:SetColor(1, 0, 0, 1) -- Red
        elseif healthPercent < 0.5 then
            healthBar:SetColor(1, 0.5, 0, 1) -- Orange
        elseif healthPercent < 0.75 then
            healthBar:SetColor(1, 1, 0, 1) -- Yellow
        else
            healthBar:SetColor(0, 1, 0, 1) -- Green
        end
    end
    
    Pause(50) -- Wait 50ms before next update
end

Example 2: Interactive Control Panel with Game Actions

-- Variables for UI controls and state
local window = nil
local statusLabel = nil
local actionButton = nil
local shouldExecuteAction = false

-- Clean up any existing windows
UI.DestroyAllWindows()

-- Create main window
window = UI.CreateWindow('controlPanel', 'Action Control Panel')
if window then
    window:SetPosition(200, 200)
    window:SetSize(400, 250)
    window:SetResizable(false)
    
    -- Title
    window:AddLabel(10, 10, 'Interactive Demo'):SetColor(0.2, 0.8, 1, 1)
    
    -- Main action button
    actionButton = window:AddButton(30, 40, 'Execute Action', 120, 30)
    
    -- Status display
    statusLabel = window:AddLabel(10, 80, 'Status: Ready')
    statusLabel:SetColor(1, 1, 1, 1)
    
    -- Input controls
    local textBox = window:AddTextBox(10, 110, 200, 'Type here...')
    local checkbox = window:AddCheckbox(10, 140, 'Enable feature', false)
    local slider = window:AddSlider(10, 170, 200, 0, 100, 50)
    
    -- Set up event handlers
    actionButton:SetOnClick(function()
        Messages.Print('Action button clicked!')
        statusLabel:SetText('Status: Action queued...')
        statusLabel:SetColor(1, 1, 0, 1) -- Yellow
        shouldExecuteAction = true -- Flag for main loop
    end)
    
    textBox:SetOnTextChanged(function(newText)
        statusLabel:SetText('Status: Text = "' .. newText .. '"')
        statusLabel:SetColor(0, 1, 1, 1) -- Cyan
    end)
    
    checkbox:SetOnCheckedChanged(function(isChecked)
        if isChecked then
            statusLabel:SetText('Status: Feature enabled')
            statusLabel:SetColor(0, 1, 0, 1) -- Green
        else
            statusLabel:SetText('Status: Feature disabled')
            statusLabel:SetColor(1, 0, 0, 1) -- Red
        end
    end)
    
    slider:SetOnValueChanged(function(value)
        local roundedValue = math.floor(value)
        statusLabel:SetText('Status: Slider = ' .. roundedValue)
        statusLabel:SetColor(0.5, 0.5, 1, 1) -- Light purple
    end)
    
    -- Window is visible by default
    Messages.Print("Window created and ready!")
end

-- Main loop - REQUIRED for window rendering and game actions
while true do
    -- Check if we need to execute a game action
    if shouldExecuteAction then
        shouldExecuteAction = false -- Reset flag
        
        statusLabel:SetText('Status: Executing action...')
        statusLabel:SetColor(1, 0.5, 0, 1) -- Orange
        
        -- Here you can put game actions that require yielding
        -- Example: Player.UseObject('1079060793')
        -- Example: Gumps.WaitForGump(0, 1000)
        -- Example: Targeting.GetNewTarget(5000)
        
        -- For demo, just wait a bit
        Pause(1000)
        
        statusLabel:SetText('Status: Action completed!')
        statusLabel:SetColor(0, 1, 0, 1) -- Green
        Messages.Print('Action sequence completed!')
    end
    
    Pause(50) -- Wait 50ms before next update
end

Example 3: Pet Health Monitor

-- Variables for UI and game state
local window = nil
local healthBar = nil
local petSerial = nil
local buttonSetPet = nil
local shouldSelectPet = false

-- Create pet monitoring window
window = UI.CreateWindow('petMonitor', 'Pet Health Monitor')
if window then
    window:SetPosition(300, 300)
    window:SetSize(300, 150)
    window:SetResizable(false)
    
    -- Instructions
    window:AddLabel(10, 10, 'Pet Health Monitor'):SetColor(1, 1, 1, 1)
    
    -- Set pet button
    buttonSetPet = window:AddButton(10, 40, 'Select Pet', 100, 30)
    
    -- Health bar for pet
    healthBar = window:AddProgressBar(10, 80, 280, 20, 0)
    healthBar:SetColor(0.5, 0.5, 0.5, 1) -- Gray until pet selected
    healthBar:SetOverlay("No pet selected")
    
    -- Set up button event
    buttonSetPet:SetOnClick(function()
        Messages.Print('Click on your pet to select it')
        shouldSelectPet = true -- Flag for main loop
    end)
    
    -- Window is visible by default
    Messages.Print("Pet monitor ready! Click 'Select Pet' to choose your pet.")
end

-- Main loop - REQUIRED for window rendering and updates
while true do
    -- Handle pet selection (requires yielding function)
    if shouldSelectPet then
        shouldSelectPet = false
        
        -- Get new target from player (this yields/waits)
        petSerial = Targeting.GetNewTarget(10000) -- 10 second timeout
        
        if petSerial then
            Messages.Print('Pet selected! Monitoring health...')
            healthBar:SetOverlay("Pet selected")
        else
            Messages.Print('No pet selected or timeout')
        end
    end
    
    -- Update pet health if we have a pet selected
    if petSerial then
        local pet = Mobiles.FindBySerial(petSerial)
        if pet then
            local healthPercent = pet.Hits / pet.HitsMax
            
            -- Update progress bar
            healthBar:SetValue(healthPercent)
            healthBar:SetOverlay(math.floor(healthPercent * 100) .. '%')
            
            -- Change color based on health level
            if healthPercent < 0.25 then
                healthBar:SetColor(1, 0, 0, 1) -- Red (critical)
            elseif healthPercent < 0.5 then
                healthBar:SetColor(1, 0.5, 0, 1) -- Orange (low)
            elseif healthPercent < 0.75 then
                healthBar:SetColor(1, 1, 0, 1) -- Yellow (moderate)
            else
                healthBar:SetColor(0, 1, 0, 1) -- Green (healthy)
            end
        else
            -- Pet not found, reset
            petSerial = nil
            healthBar:SetValue(0)
            healthBar:SetColor(0.5, 0.5, 0.5, 1)
            healthBar:SetOverlay("Pet not found")
        end
    end
    
    Pause(50) -- Wait 50ms before next update
end

Best Practices

1. Window Naming

Use descriptive, unique names for windows to avoid conflicts:

-- Good
local window = UI.CreateWindow('playerInventory', 'Inventory')

-- Bad
local window = UI.CreateWindow('window1', 'Window')

2. Clean Up

Always clean up windows when done:

-- At script start
UI.DestroyAllWindows()

-- Or specifically
UI.DestroyWindow('myWindow')

3. Event Handler Memory

Event handlers should be efficient as they may be called frequently:

-- Good - efficient handler
slider:SetOnValueChanged(function(value)
    label:SetText(tostring(math.floor(value)))
end)

-- Bad - expensive operations in handler
slider:SetOnValueChanged(function(value)
    -- Avoid heavy computations here
    for i = 1, 1000000 do
        -- ...
    end
end)

4. Position and Size

Use logical positioning and consider different screen sizes:

-- Consider screen boundaries
local screenWidth = 1920 -- Get actual screen size
local screenHeight = 1080

-- Center window
local windowWidth = 400
local windowHeight = 300
window:SetPosition(
    (screenWidth - windowWidth) / 2,
    (screenHeight - windowHeight) / 2
)

5. Manual Control Positioning

Plan your control layout carefully using consistent spacing:

local yOffset = 10
local spacing = 30

-- Manually position controls with consistent spacing
for i = 1, 5 do
    window:AddLabel(10, yOffset + (i-1) * spacing, 'Item ' .. i)
end

6. Error Handling

Check for nil returns and handle errors gracefully:

local window = UI.GetWindow('myWindow')
if window then
    window:Show()
else
    Messages.Print("Window not found!")
end

7. Main Loop Requirements

  • Always include a main loop with while true do ... end for window rendering
  • Use Pause(50) or similar in the main loop to avoid excessive CPU usage
  • Handle game actions (that yield/wait) in the main loop, not in event handlers
  • Use flags to communicate between event handlers and the main loop

8. Performance Considerations

  • Avoid creating/destroying windows frequently
  • Use Hide() and Show() instead of destroying and recreating
  • Limit the number of controls per window
  • Keep main loop updates efficient (avoid heavy calculations)

Technical Notes

  • All positions and sizes are in pixels
  • Colors use RGBA format with values from 0.0 to 1.0
  • Controls are automatically cleaned up when their parent window is destroyed
  • The system is separate from the game's native UI system
  • Windows support resizing and basic window management features

Limitations

  • No support for custom rendering or drawing
  • Limited to predefined control types
  • No automatic layout managers (manual positioning required)
  • No direct access to advanced rendering features
  • Event handlers must be Lua functions (no native callbacks)
  • No built-in animation or transition support
  • Single-line text input only (no multiline text areas)

Future Enhancements

Potential future additions to the UI system might include:

  • Additional control types (combo boxes, list boxes, trees)
  • Custom drawing capabilities
  • Animation support
  • Theme/styling options
  • Automatic layout managers (vertical, horizontal, grid)
  • Docking support
  • Multi-line text input
  • Image display controls