UI Kit
Deprecated — superseded by Essentials (Ess).
uilib.luais absorbed there as nativeEss.UI— the same nine widgets, the same backward-compatible menu builder — shipped in one drop-in1_Ess.lua. This page stays as historical reference for the standalone predecessor; new mods should build on Ess.
Status: new, already iterated once. Not a first draft — the header comment documents a full v1→v2 rewrite (“v1 drove input with an always-on 33Hz
IsKeyDownpoll + focus model that misbehaved in-game”) plus two further point-fixes for real bugs (aSetLocationcorner-coords mistake that made toasts and dialogs render as “a giant smear,” and aUI.Menutoggle that used to leak state). Treat the code as current and reasoned-through; it hasn’t yet accumulated the kind of extended live-test history this wiki’s “confirmed working” banners are reserved for.
uilib.lua (_G.UI) is a reusable widget kit — nine different HUD elements sharing one input/rendering engine, so a modder building custom UI never has to solve the “widget, input, drawing, lifecycle” problem from scratch the way ForgeMenu and the Contract Board each did independently. It’s the direct successor to both: the header comment says outright that its input handling, heartbeat, and warm-up repaint are built on “the exact plumbing that made ForgeMenu rock-solid,” and UI.Board reuses contracts.gfx, the identical Scaleform movie the Contract Board drives.
The nine widgets
| Widget | What it is |
|---|---|
UI.Menu | A ForgeMenu-style declarative drill-down: :entry/:category/:header/:switch, nests as deep as you like. |
UI.List | The raw scrolling list every other multi-row widget here is built on — 10 visible rows, section headers, a scrollbar, auto-resize. |
UI.Panel | A title bar plus up to 8 lines, body auto-resizing to fit. |
UI.Bar | A label plus a progress bar. |
UI.Toast | A transient notification, 3 stacked slots, auto-hiding. |
UI.Confirm | A modal yes/no dialog. |
UI.Input | A one-shot typed prompt. |
UI.Chat | A scrolling message log with an optional typed input line. |
UI.Board | A two-pane list-plus-details view — the same shape (and the same contracts.gfx movie) as the Contract Board. |
Every widget shares four calls: :show() :hide() :focus() :blur() :destroy() — chainable, and identical regardless of which widget you’re holding.
Why one shared engine instead of nine separate ones
The four hard parts of any custom Scaleform widget — reading input, keeping exactly one thing focused, running a heartbeat only while something’s actually alive, and papering over the movie’s async load — are solved once, centrally, and every widget just plugs into it:
- Input.
Loader.PopKeyEvents()(edge-triggered) drives navigation;Loader.GetKeyboardState()is read once per event batch just to check the Shift bit for typed text. See lua-bridge API: Loader for both. - Focus. Exactly one widget hears keys at a time (
UI.Focus/UI.Focused()) — switching focus swallows any buffered keystrokes first, so whatever opened the newly-focused widget doesn’t leak in as its first input. - The heartbeat idles itself. A single
Event.TimerRelativeloop, shared by every widget, only keeps rescheduling whileneedsTick()is true (something focused, something mid-resize-animation, a toast still counting down) — with nothing active, it stops rescheduling entirely rather than ticking forever in the background. A generation counter (the same pattern ForgeMenu already established) guarantees an old loop can’t keep running alongside a freshly-restarted one. - Warm-up repaints.
SetSwfFileloads a movie asynchronously, so the very first paint issued right after creating a widget can be dropped before the movie’s ready. Every widget re-sends its state unconditionally forWARMUP = 8ticks after showing, the same fixForgeMenualready needed for the identical reason.
Read UI.Menu or UI.List for exactly how these pieces click together in one concrete widget.
Deploy
- Copy
uilib.luatoscripts/OnLoad/and register it with a low number so it loads before anything that uses it:[OnLoad] uilib.lua=5 - The six
ui_*.gfxmovies (ui_list,ui_panel,ui_bar,ui_toast,ui_confirm,ui_input) pluschat.gfx/contracts.gfxfor the two richer widgets need to already be injected in the WAD. - Guard any script that uses
UIthe same way every ForgeMenu-based script guards_G.ForgeMenu:if not (_G.UI and UI.Menu) then Loader.Printf("load uilib first"); return end
Scripts: see UI Kit Scripts for complete, ready-to-drop-in scripts built on this kit — the two showcase scripts (split by which widgets each covers) plus coopchat.lua, a full co-op text chat and the current implementation of A Basic Co-op Text Chat.
Full source: see uilib.lua for the library’s complete, current source, reproduced in full.
Pick a toggle key that isn’t a navigation key (arrows/Enter/Esc/Backspace by default) — those drive whichever widget currently has focus while it’s open, the same caution ForgeMenu calls out for the same reason.
See also
- Building ForgeMenu — the library this one’s input/heartbeat/warm-up plumbing is directly lifted from, and the “why the world doesn’t pause” reasoning that applies here too (no PDA hijack — a menu, or any of these widgets, only ever needs discrete key events).
- Building Nested Menus with MrxMultiPageMenu — the native-dialog-box way to nest menus, if you specifically want that look over a custom Scaleform one.
- A Basic Co-op Text Chat — coopchat.lua is the current, improved implementation of this deep dive’s problem, built on
UI.Chat. - Contract Framework: The Contract Board —
UI.Boardreuses its exact movie (contracts.gfx); see that page for the movie’s own callback surface. - WaveDefense — a full gamemode whose entire HUD and setup menu is built on
UI.Panel,UI.Bar, and a hand-rolled “cycler” convenience overUI.Menu.