UI Scripting¶
Anomaly's UI system uses XML to define layout and Lua to drive logic. This page covers building custom windows — from a minimal dialog to a fully interactive menu.
For quick HUD messages and notifications without a custom window, see UI Functions.
How the system works¶
XML file Lua script
───────────────── ──────────────────────────────────────
Defines layout → class inherits CUIScriptWnd
Button positions __init() calls xml:ParseFile(...)
Control sizes InitControls() creates controls
Font, colour InitCallBacks() wires up events
OnKeyboard() handles key presses
The XML is loaded at runtime from gamedata/configs/ui/. Lua then instantiates controls from it and responds to events. The XML never changes at runtime — only Lua drives state.
File locations¶
gamedata/
configs/
ui/
my_mod_window.xml ← XML layout
scripts/
my_mod_ui.script ← Lua window class
my_mod.script ← main mod logic (opens/closes window)
Minimal window example¶
XML layout¶
<!-- gamedata/configs/ui/my_mod_window.xml -->
<w>
<window name="background">
<auto_static x="0" y="0" width="400" height="300"
texture="ui\ui_common_window" stretch="1"/>
</window>
<button name="btn_close" x="170" y="260" width="60" height="24">
<text>Close</text>
</button>
<static name="lbl_info" x="20" y="20" width="360" height="200">
<text font="letterica18" r="200" g="200" b="200"/>
</static>
</w>
Lua window class¶
-- my_mod_ui.script
class "my_mod_window" (CUIScriptWnd)
function my_mod_window:__init()
super()
self:InitControls()
self:InitCallBacks()
end
function my_mod_window:__finalize()
end
function my_mod_window:InitControls()
-- Set the window's position and size on screen
self:SetWndRect(Frect():set(200, 150, 400, 300))
-- Load and parse the XML file
local xml = CScriptXmlInit()
xml:ParseFile("my_mod_window.xml")
-- Initialise controls from XML (they become children of self)
xml:InitStatic("background", self)
self.lbl_info = xml:InitTextWnd("lbl_info", self)
self.btn_close = xml:Init3tButton("btn_close", self)
-- Register controls so callbacks can fire
self:Register(self.btn_close, "btn_close")
end
function my_mod_window:InitCallBacks()
-- Wire a Lua function to the button click event
self:AddCallback("btn_close", ui_events.BUTTON_CLICKED, self.OnClose, self)
end
function my_mod_window:OnClose()
self:HideDialog()
Unregister_UI("my_mod_window")
end
function my_mod_window:OnKeyboard(key, action)
-- action 1 = key down, 2 = key up
if action == ui_events.WINDOW_KEY_PRESSED then
if key == DIK_keys.DIK_ESCAPE then
self:OnClose()
return true -- consumed
end
end
return false
end
function my_mod_window:SetText(msg)
self.lbl_info:SetText(msg)
end
Opening and closing the window¶
-- my_mod.script
local GUI = nil
local function open_window(message)
if GUI then return end -- already open
GUI = my_mod_window()
GUI:SetText(message)
GUI:ShowDialog(true)
Register_UI("my_mod_window", "my_mod_ui", "GUI")
end
local function on_key_press(key)
if key == DIK_keys.DIK_F9 then
open_window("Hello from my mod!")
end
end
function on_game_start()
RegisterScriptCallback("on_key_press", on_key_press)
end
function on_game_end()
UnregisterScriptCallback("on_key_press", on_key_press)
GUI = nil
end
Control types and initialisation¶
All controls are initialised through CScriptXmlInit by calling a method named for the control type:
local xml = CScriptXmlInit()
xml:ParseFile("my_file.xml")
-- Controls are always: xml:InitXxx("xml_element_name", parent)
xml:InitStatic("my_bg", self) -- CUIStatic (image/background)
xml:InitTextWnd("my_label", self) -- CUITextWnd (text display)
xml:Init3tButton("my_btn", self) -- CUI3tButton (standard button)
xml:InitEditBox("my_input", self) -- CUIEditBox (text input)
xml:InitListBox("my_list", self) -- CUIListBox (scrollable list)
xml:InitComboBox("my_combo", self) -- CUIComboBox (dropdown)
xml:InitProgressBar("my_bar", self) -- CUIProgressBar
xml:InitFrame("my_frame", self) -- CUIFrame (border/panel)
xml:InitScrollView("my_scroll", self) -- CUIScrollView
xml:InitWindow("my_wnd", self) -- generic CUIWindow
Common control methods¶
Text controls (CUITextWnd, labels)¶
lbl:SetText("Hello world")
lbl:SetTextST("localization_key") -- translates automatically
local text = lbl:GetText()
lbl:SetTextColor(GetARGB(255, 255, 200, 0))
lbl:SetFont(GetFontLetterica18()) -- or GetFontSmall(), GetFontMedium()
lbl:Show(false) -- hide/show
Buttons (CUI3tButton)¶
btn:Enable(false) -- disable (greyed out, no clicks)
btn:Enable(true)
btn:Show(false)
btn:SetText("Click me")
Edit boxes (CUIEditBox)¶
local text = edit:GetText()
edit:SetText("default")
edit:SetNumbersOnly(true) -- restrict to numeric input
List boxes (CUIListBox)¶
list:AddTextItem("item label")
list:RemoveAll()
local index = list:GetSelectedIndex()
local text = list:GetSelectedText()
local count = list:GetSize()
list:SetSelectedIndex(0)
Progress bar¶
Visibility and layout¶
control:Show(true)
control:IsShown()
control:Enable(true)
control:SetWndRect(Frect():set(x, y, width, height))
control:SetWndPos(vector2():set(x, y))
control:SetWndSize(vector2():set(w, h))
local w = control:GetWidth()
local h = control:GetHeight()
Callback events¶
Register callbacks in InitCallBacks using AddCallback:
Common event types from ui_events:
| Event | Description |
|---|---|
ui_events.BUTTON_CLICKED |
Button clicked |
ui_events.LIST_ITEM_SELECT |
List item selected |
ui_events.EDIT_TEXT_COMMIT |
Edit box text confirmed |
ui_events.WINDOW_KEY_PRESSED |
Key pressed inside window |
ui_events.WINDOW_KEY_RELEASED |
Key released |
ui_events.CHECKBOX_CHANGED |
Checkbox toggled |
function my_mod_window:InitCallBacks()
self:AddCallback("btn_ok", ui_events.BUTTON_CLICKED, self.OnOK, self)
self:AddCallback("btn_cancel", ui_events.BUTTON_CLICKED, self.OnCancel, self)
self:AddCallback("list_items", ui_events.LIST_ITEM_SELECT, self.OnSelect, self)
end
function my_mod_window:OnOK()
local input = self.edit_name:GetText()
save_name(input)
self:HideDialog()
Unregister_UI("my_mod_window")
end
function my_mod_window:OnSelect()
local idx = self.list_items:GetSelectedIndex()
printf("selected index: %d", idx)
end
Reacting to any UI opening or closing¶
The GUI_on_show and GUI_on_hide callbacks fire whenever any UI window is registered or unregistered:
local function GUI_on_show(name, path)
printf("UI opened: %s (%s)", name, path)
end
local function GUI_on_hide(name, path)
printf("UI closed: %s (%s)", name, path)
end
function on_game_start()
RegisterScriptCallback("GUI_on_show", GUI_on_show)
RegisterScriptCallback("GUI_on_hide", GUI_on_hide)
end
Screen coordinates¶
The UI coordinate system in Anomaly uses a virtual 1024×768 space regardless of actual screen resolution. The engine scales automatically.
-- Centre a 400×300 window
self:SetWndRect(Frect():set(
(1024 - 400) / 2, -- x = 312
(768 - 300) / 2, -- y = 234
400, -- width
300 -- height
))
Tips and pitfalls¶
Always unregister on close. If you call Register_UI when opening, call Unregister_UI in your close handler. Leaving a UI registered blocks game input permanently.
Nil-check your window. If the player can open your window from a callback, guard against it being opened twice:
ShowDialog(true) vs Show(true). ShowDialog registers the window with the game's dialog system (enables input handling, proper z-ordering). Show just toggles visibility — use ShowDialog for top-level windows.
Keyboard handling. Override OnKeyboard in your window class to intercept keys. Return true to consume the event and prevent it propagating to the game. Always handle DIK_ESCAPE to let the player close your window.