GUI Library (gui)
gui is a component-based 2D GUI framework built on top of gfx2d.
It handles layout, mouse hit-testing, resize adaptation, and event-driven rendering so scripts can build interactive
terminal UIs without managing low-level drawing or event loops.
local GUI = require("gui")
Quick Start
local GUI = require("gui")
local mgr = GUI.GUIManager.new()
local panel = GUI.Panel.new(10, 10, 180, 80, "My Panel")
mgr:addComponent(panel)
local btn = GUI.Button.new(20, 40, 60, 14, "Click Me", function()
console.print("clicked!")
end)
panel:addChild(btn)
mgr:setLayoutCallback(function(m, w, h)
panel:setSize(w - 20, h - 20)
end)
mgr:run()
GUIManager
Main controller. Owns the event loop, canvas dimensions, and component list.
Constructor
local mgr = GUI.GUIManager.new()
Methods
Method |
Description |
|---|---|
|
Add a top-level component. |
|
Remove a previously added component. |
|
Render all visible components once. Usually called by |
|
Propagate update tick to all components (seconds as float). |
|
Start the event-driven loop. Blocks until |
|
Signal |
|
Idle redraw interval in ms (default |
|
Global pixel offset applied to mouse |
|
Background fill color for the full canvas (default dark navy). |
|
2px inner border color (default blue). |
|
Callback |
Fields (read)
Field |
Description |
|---|---|
|
Current canvas width in pixels. |
|
Current canvas height in pixels. |
|
Number of frames rendered since |
|
|
|
Latest mouse state table (see below). |
mouseState fields:
{
hasPosition = bool, -- true once any mouse move was received
insideCanvas = bool,
uiX = number, -- logical canvas X of last cursor position
uiY = number, -- logical canvas Y of last cursor position
rawX = number, -- uiX before mouseOffset is applied
rawY = number,
leftDown = bool, -- true while left button is held
}
Built-in layers
GUIManager creates these gfx2d layers automatically (lower order renders first):
Layer name |
Order |
|---|---|
|
0 |
|
2 |
|
4 |
|
6 |
|
8 |
|
10 |
Component (base class)
All GUI widgets extend Component. You can subclass it for custom widgets.
Constructor
local comp = GUI.Component.new(x, y, width, height)
Common methods (available on every widget)
Method |
Description |
|---|---|
|
Move to absolute canvas pixel coordinates. |
|
Resize. |
|
Returns |
|
Returns |
|
Show or hide without removing. |
|
Returns current visibility. |
|
Which |
|
Responsive layout in [0, 1] fractions of canvas size. Applied every frame before drawing. Overrides absolute position/size. |
|
Per-component callback |
|
Returns |
Overrideable methods (for custom components)
function MyComp:draw() -- called each frame by manager
function MyComp:update(dt) -- called each frame with delta time (seconds)
function MyComp:onMouseEvent(e) -- return true to consume (stop propagation)
Panel
Rectangular container with optional title. Renders its children on top of itself.
Constructor
local panel = GUI.Panel.new(x, y, width, height, title)
title is optional (defaults to "").
Methods
Method |
Description |
|---|---|
|
Fill color (default semi-transparent dark blue). |
|
Outline color (default bright blue). |
|
Integer text scale for the title (default |
|
Add a child component. Inherits manager automatically. |
|
Remove a child. |
Children’s draw(), update(), and onMouseEvent() are forwarded automatically.
Mouse events are dispatched back-to-front (topmost/last-added child gets priority).
Example
local panel = GUI.Panel.new(8, 8, 200, 100, "Status")
panel:setBorderColor(0.3, 0.8, 0.4, 1.0)
local label = GUI.Text.new(12, 26, "All systems nominal")
panel:addChild(label)
mgr:addComponent(panel)
Text
Static text label. Supports multi-line, word-wrap, alignment, and clipping.
Constructor
local lbl = GUI.Text.new(x, y, content)
content may contain \n for line breaks.
Methods
Method |
Description |
|---|---|
|
Update the displayed string. |
|
Text color (default white). |
|
Integer glyph scale (1–16, default |
|
Optional clipping/wrapping. |
Example
local status = GUI.Text.new(10, 30, "Ready")
status:setColor(0.4, 1.0, 0.5, 1.0)
status:setScale(2)
panel:addChild(status)
-- later:
status:setText("Charging...")
HorizontalLayout
Arranges children left-to-right, separated by spacing pixels. Automatically repositions children when items are
added/removed.
Constructor
local row = GUI.HorizontalLayout.new(x, y, height, spacing)
height sets the common height for children placed inside. spacing defaults to 2.
Methods
Method |
Description |
|---|---|
|
Add a child; triggers |
|
Remove a child; triggers |
|
Re-position all children. Called automatically. |
|
Move the layout origin; triggers |
After all children are added, row.width reflects the total occupied width.
Example
local btnRow = GUI.HorizontalLayout.new(10, 80, 14, 4)
btnRow:addChild(GUI.Button.new(0, 0, 50, 14, "OK", function() end))
btnRow:addChild(GUI.Button.new(0, 0, 50, 14, "Cancel", function() end))
panel:addChild(btnRow)
VerticalLayout
Arranges children top-to-bottom, separated by spacing pixels.
Constructor
local col = GUI.VerticalLayout.new(x, y, width, spacing)
spacing defaults to 1.
Methods
Method |
Description |
|---|---|
|
Add a child; triggers |
|
Remove a child; triggers |
|
Re-position all children. Called automatically. |
|
Move the layout origin; triggers |
After all children are added, col.height reflects the total occupied height.
Example
local col = GUI.VerticalLayout.new(10, 20, 120, 3)
for i = 1, 5 do
col:addChild(GUI.Text.new(0, 0, "Row " .. i))
end
panel:addChild(col)
ModalDialog
Centered overlay dialog with a message, configurable buttons, and automatic layout.
Renders on the "overlay" layer so it appears above everything else.
Always consumes mouse events (modal behavior) while visible.
Constructor
local dialog = GUI.ModalDialog.new(title, message)
Methods
Method |
Description |
|---|---|
|
Update the body text. Supports |
|
Add a button to the dialog footer. Returns the |
Buttons are arranged automatically in a HorizontalLayout. The dialog positions and sizes itself relative to the canvas
via applyLayout — no manual sizing needed.
Example
local dialog = GUI.ModalDialog.new("Confirm", "Delete this route?")
dialog:addButton("Yes", function()
deleteRoute()
dialog:setVisible(false)
end)
dialog:addButton("No", function()
dialog:setVisible(false)
end)
dialog:setVisible(false) -- hidden initially
mgr:addComponent(dialog)
-- show it later:
dialog:setVisible(true)
Responsive layouts
Two mechanisms work together for resize-aware UIs:
1. setRelativeRect — fractional positioning on the component itself:
-- Panel fills 80% of canvas width, 60% of height, centered
panel:setRelativeRect(0.1, 0.2, 0.8, 0.6)
2. setLayoutCallback — arbitrary Lua logic on resize:
-- on GUIManager (called for every component too)
mgr:setLayoutCallback(function(m, w, h)
titleLabel:setPosition(math.floor(w * 0.5) - 40, 6)
btnRow:setPosition(8, h - 20)
end)
-- on a single component
panel:setLayoutCallback(function(self, w, h)
self:setSize(w - 20, h - 40)
end)
Both are applied every frame before drawing, so resizing the terminal window automatically reflows the UI.
Full example
local GUI = require("gui")
local mgr = GUI.GUIManager.new()
local panel = GUI.Panel.new(0, 0, 0, 0, "Control Panel")
panel:setRelativeRect(0.05, 0.05, 0.9, 0.9)
mgr:addComponent(panel)
local header = GUI.Text.new(8, 8, "Ship Status")
header:setColor(0.4, 0.85, 1.0, 1.0)
header:setScale(2)
panel:addChild(header)
local statusText = GUI.Text.new(8, 30, "Idle")
panel:addChild(statusText)
local row = GUI.HorizontalLayout.new(8, 0, 14, 6)
local startBtn = GUI.Button.new(0, 0, 60, 14, "Start", function()
statusText:setText("Running")
startBtn:setEnabled(false)
stopBtn:setEnabled(true)
end)
stopBtn = GUI.Button.new(0, 0, 60, 14, "Stop", function()
statusText:setText("Idle")
startBtn:setEnabled(true)
stopBtn:setEnabled(false)
end)
stopBtn:setEnabled(false)
row:addChild(startBtn)
row:addChild(stopBtn)
panel:addChild(row)
mgr:setLayoutCallback(function(m, w, h)
row:setPosition(8, h - 30)
end)
mgr:run()