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
- Getting Started
- UI Global Table
- Window Management
- UI Controls
- Event Handling
- Examples
- 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 windowtitle
(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 identifierTitle
: Window titleIsOpen
: Whether the window is openIsVisible
: 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 identifierX
: X position in pixelsY
: Y position in pixelsWidth
: Width in pixelsHeight
: Height in pixelsVisible
: Visibility stateEnabled
: Enabled state
Common Control Methods
All controls support these methods:
SetPosition(x, y)
: Sets control positionSetSize(width, height)
: Sets control sizeSetVisible(visible)
: Sets visibilitySetEnabled(enabled)
: Sets enabled state
Button
Creates a clickable button.
Creation
local button = window:AddButton(x, y, text, [width], [height])
- Parameters:
x
,y
: Position in pixelstext
: Button labelwidth
,height
(optional): Size (auto-sized if 0 or omitted)
Properties
Text
: Button label (read-only)
Methods
SetText(text)
: Updates button textSetOnClick(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 textSetColor(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 contentGetText()
: Returns current textSetOnTextChanged(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 labelSetChecked(checked)
: Sets checked stateSetOnCheckedChanged(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 valueSetRange(min, max)
: Sets min/max valuesSetOnValueChanged(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 colorSetOverlay(text)
: Sets overlay textBindToValue(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()
andShow()
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