AntOS Library — gui (reference)
The
guilibrary's design and native core — a retained, event-driven UI layer over immediate-modeimgui. The script-facing API is luau_gui.
Design-stage. A proposed convenience layer over the (also design-stage)
imguiwindowing system.
What it is
gui gives scripts and user programs a retained widget model with an event system: create widgets as objects, set their properties, attach handlers (on_click, on_change). Under the hood gui is an ordinary imgui client — each frame it reconciles its retained widget tree into immediate-mode ImGui calls and collects the resulting events. The immediate-mode machinery is hidden; the program sees a conventional event-driven UI.
Why it exists (and why imgui still does)
Immediate mode is the right primitive but an awkward default for a typical app: it asks the author to re-emit the UI every frame, on the render thread, kept fast. gui trades a little of immediate mode's simplicity back for three things that matter to app and user-program authors:
- Familiar model — retained widgets + events, like GTK/Qt/the web, instead of a per-frame draw.
- Off-thread logic — because widgets are retained, thread-safe objects and events are queued, the program's code runs on its own thread (parallel, worker-lane-style). Only
gui's reconcile touches the render thread, and that'sgui's bounded code, not the user's — so the author never meets cooperative scheduling or the "keepdrawfast" rule. - Fault isolation — a slow handler stalls only the program's thread; the render thread keeps drawing the last reconciled state, so the desktop stays live. Raw
imgui's slowdrawfreezes the render thread;gui's doesn't.
imgui stays for what gui is deliberately not for: AntOS's own UI, power users who want direct control, and dynamic/procedural UIs (debuggers, visualizations, data-driven-per-frame views) that are naturally immediate and awkward to express as a retained tree.
The third bridge
gui completes a pattern already in the system: a client contributes UI by feeding the render thread through a thread-safe hand-off. A text tool feeds a text buffer + key queue (terminal); a gui program feeds a retained widget tree + event queue; only a raw imgui client runs on the render thread directly. gui is the console bridge generalized from characters to widgets — which is why a gui program gets the worker lane's parallelism and isolation with a full GUI.
Design notes
- Routing.
require("gui")marks a program as gui-driven: its logic runs on a worker thread andguimaintains the render-thread reconcile on its behalf;require("imgui")marks a direct UI-lane client. Both are caught by the launch scan; off-thread ImGui is still refused by the binding. - Thread-safe tree. Property sets from the program's thread and the reconcile on the render thread meet through a command queue (mutations in) and an event queue (events out) — the classic cross-thread retained-UI model, so neither side blocks the other. Both are in-process, so the mechanism is a
std::mutex+std::condition_variable+ queue: the program thread blocks on the condvar draining events; the render thread drains the command queue at frame start (or swaps a double-buffered tree). Aguiprogram whose loop must also wait on sockets useseventfd+epollto wait on the event queue and I/O together — no cross-process message queue is needed. - Bounded reconcile. The per-frame cost is
guidiffing its tree, not user code, so it stays predictable regardless of what the program's handlers do. - Blocking dialogs are natural here. Because a
guiprogram runs on its own thread,gui.message_box(and kin) can block that thread for an answer while the render thread keeps drawing the modal — the familiar synchronous dialog. The invariant is block the calling thread, never the render thread:gui's thread may block; a rawimguidraw (which is on the render thread) may not, so it polls. - Same substrate. Because
guirenders throughimgui, its windows dock, float, and mirror to netImgui identically — and a native user program can useguitoo, in-process or over netImgui. - Context targeting, made cleaner by retention. A
guiwindow inheritsimgui's targeting —context = "local"/"remote"/"both"(or an app-wide default viagui.context) — andguireconciles its tree into each active context. The retained model tidies the two-context case that immediate mode leaves to the author: one widget with one handler, reconciled into every context, so a click on a"both"window on either display delivers the single event once to the program's thread, rather than each context's frame reporting the button independently.
Surface profiles
A gui window can render on more than one surface — the local display, the keyboard touch panel, a netImgui PC — and those surfaces differ in more than size. Each carries a surface profile:
- scale — from the surface's physical DPI, not its pixel count (a 4″ 480×800 panel at ~230 PPI needs a far larger font and
ScaleAllSizesfactor than a 27″ 1080p monitor of similar pixel count); - input mode — pointer (mouse) or touch, and for touch, how many concurrent points;
- touch-target floor — a physical minimum hit size (~9 mm) enforced on touch surfaces regardless of layout;
- orientation — portrait or landscape.
gui reconciles its one retained tree into each active surface through that surface's profile — so the same widgets and handlers come out monitor-sized on the display and finger-sized, portrait, and target-floored on the panel, with no per-surface code. It is the responsive-UI idea, but resolved once per surface from a declared profile rather than per frame from a viewport.
Multi-touch is a gui capability, not an imgui one — and retention is why. Raw imgui has one mouse (io.MousePos); it cannot express five fingers on five pads. Because gui holds the retained tree with widget bounds, it can hit-test each concurrent touch point against the tree itself and dispatch each to its widget — so the panel's 5-point surface delivers five simultaneous events (chords on pads, a fader per hand), received on the program's thread as ordinary handler calls. Mouse surfaces keep the single-pointer default; a surface whose profile says touch, N points turns on multi-point dispatch. This is the same reason the touch panel's layouts live in gui rather than raw imgui: the panel is another surface reconciled from the shared tree through its own profile.
Native core — libantos_gui
libantos_gui holds the retained widget tree, the reconcile-to-ImGui pass, and the cross-thread command/event queues, built on libantos_imgui. The binding exposes widget objects and property/event access to Lua.
Related
- luau_gui — the script API · antos_imgui (the immediate-mode layer beneath) · antos_terminal (the simplest bridge) · antos_library_design · AntOS Libraries hub