AntOS Library — gui (reference)

The gui library's design and native core — a retained, event-driven UI layer over immediate-mode imgui. The script-facing API is luau_gui.

Design-stage. A proposed convenience layer over the (also design-stage) imgui windowing 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's gui's bounded code, not the user's — so the author never meets cooperative scheduling or the "keep draw fast" 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 slow draw freezes 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 and gui maintains 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). A gui program whose loop must also wait on sockets uses eventfd + epoll to wait on the event queue and I/O together — no cross-process message queue is needed.
  • Bounded reconcile. The per-frame cost is gui diffing its tree, not user code, so it stays predictable regardless of what the program's handlers do.
  • Blocking dialogs are natural here. Because a gui program 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 raw imgui draw (which is on the render thread) may not, so it polls.
  • Same substrate. Because gui renders through imgui, its windows dock, float, and mirror to netImgui identically — and a native user program can use gui too, in-process or over netImgui.
  • Context targeting, made cleaner by retention. A gui window inherits imgui's targeting — context = "local" / "remote" / "both" (or an app-wide default via gui.context) — and gui reconciles 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 ScaleAllSizes factor 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

Important: The Ant64 family of home computers are at early design/prototype stage, everything you see here is subject to change.