Luau Bindings — imgui

The require("imgui") script API — register a window into AntOS's shared ImGui desktop and maintain it with a per-frame draw callback. This is how any script puts a real GUI window on screen, alongside the system panels. Every function is [antos]. Design & native core: antos_imgui.

Design-stage. The ImGui context and overlay already exist (sys_imgui); this library is the proposed scripting interface that opens them to multiple clients. The shape below is the intended API, not yet a shipped one.

local imgui = require("imgui")
local count = 0
imgui.window("Counter", function()          -- draw callback: runs every frame
  imgui.text("count: " .. count)
  if imgui.button("Add") then count = count + 1 end
end)
imgui.run()                                  -- stay alive; yield to the UI until closed

Where windows live. A normal script runs in the main terminal window. Graphical windows belong to parallel VM tasks — start one (each is its own Lua VM) and it gets its own window(s). The terminal is itself just the main VM's window, so it's uniform: every window, console included, is an ImGui client.

Windows

Function Behaviour
imgui.window(title, draw [, opts]) Register a window with a per-frame draw callback → a window handle. opts: { x, y, w, h, no_close, no_resize, no_collapse, context, dock, menu_bar }.
imgui.focus(handle) Raise a window to the front (this is how you change z-order — see the note below).
imgui.contexts() Which contexts are live — { local = true, remote = <connected?> }.
imgui.current_context() Which context the running draw is rendering into — "local" or "remote". Branch on it to vary a window per display.
imgui.close(handle) · imgui.is_open(handle) Close / query a window.
imgui.run() Keep the client alive, yielding to the UI, until all its windows close.

The draw callback is called once per frame, on the UI thread, between the desktop's frame begin and render. Emit the window's contents fresh each frame — this is immediate mode; there are no persistent widget objects, just state in your script.

Two contexts. opts.context picks where the window appears: "local" (the desktop, the default), "remote" (a connected PC over netImgui), or "both". Defaulting to "local" is conservative — nothing leaves the machine unless you ask. The remote context runs its frame loop only while a PC is connected. The library doesn't pick a context for a window — each frame it invokes the draw in every targeted context that's currently active (a "both" window's draw runs once for local and once for remote), and both share the same script state, so the window is consistent and input from either display drives it. If a window needs to render differently per display, branch on imgui.current_context().

Z-order is focus, not list order. Windows stack by focus, not by the order they're registered or drawn — so moving a window in the callback list won't restack it. Raise a window with imgui.focus(handle); "always on top" is a window flag in opts. (Registration order only affects keyboard nav/tab order.)

Docking. AntOS uses ImGui's docking branch, so the desktop is a dockspace: the user can dock your window to an edge, tab it with others, or split — or leave it floating. opts.dock ("left"/"right"/"top"/"bottom"/"center"/"float") sets where it first appears; after that the user arranges it, and the layout persists per context.

Frame hooks & require. imgui.window is a convenience over a lower-level frame hook: imgui.on_draw(context, fn) registers fn to run each frame in a given context, where you drive your own imgui.window/begin calls.

Call Effect
imgui.on_draw("local", fn) Run fn in the local imgui loop each frame.
imgui.on_draw("remote", fn) Run fn in the netImgui loop — only while a PC is connected.
imgui.on_draw("both", fn) Both.

Bind the callback to a context, not to a separate module: require("imgui") is cached, so calling it twice returns the same table — you can't make "two imgui instances" for two contexts (the second on_draw = … would just overwrite the first). Register one on_draw per context on the one module instead. And starting/stopping the remote context is the netimgui library's job (netimgui.start()), not imgui's — a "remote" hook simply doesn't fire until a PC connects.

Two surfaces, not a mirror. Because the local and remote hooks are independent, one program can show completely different UIs on each — a compact status view on the device and a full control panel on the PC, or the normal UI locally with a developer/inspector window only on "remote". It's one program over one shared state, so the two surfaces stay in sync automatically: a value the local view shows and the remote view edits is the same value — no messaging, no serialisation.

Keep draw fast and non-blocking. It runs inside the compositor's frame, so no network calls, file reads, or heavy loops inside it. Do that work on your own task and have draw read the result from a shared table.

Widgets (inside a draw callback)

Call Returns / effect
imgui.text(s) · imgui.text_colored(rgb, s) · imgui.bullet(s) Draw text.
imgui.button(label) · imgui.small_button(label) clicked (bool).
imgui.checkbox(label, value) changed, value.
imgui.slider(label, value, min, max) · imgui.input_int(label, v) changed, value.
imgui.input_text(label, s [, maxlen]) changed, s.
imgui.combo(label, items, current) · imgui.selectable(label, sel) changed, index / clicked.
imgui.same_line() · imgui.separator() · imgui.spacing() Layout.
imgui.tree_node(label) → imgui.tree_pop() · imgui.collapsing_header(label) Collapsible sections.
imgui.begin_child(id, w, h) → imgui.end_child() A scrollable sub-region.
imgui.begin_table(id, cols) → imgui.end_table() (+ imgui.next_column) Tables.
imgui.image(tex, w, h) Draw an image texture (see Images below).

Widgets that change a value follow one shape — changed, newValue — so the idiom is uniform:

local on
imgui.window("Settings", function()
  local changed
  changed, on = imgui.checkbox("Enabled", on)
  if changed then apply(on) end
end)

Menus

A window can carry a menu bar (enable it with opts.menu_bar = true). The main console uses one — its menu opens Settings, the wiki reader, and more.

Call Returns / effect
imgui.begin_menu_bar() → imgui.end_menu_bar() The window's menu bar (requires opts.menu_bar).
imgui.begin_menu(label) → imgui.end_menu() A drop-down; begin_menu returns whether it's open.
imgui.menu_item(label [, shortcut [, selected]]) clicked (bool).

Opening another window is just launching a windowed task — the console's "Settings" item is os.spawn("settings"):

imgui.window("Console", function()
  if imgui.begin_menu_bar() then
    if imgui.begin_menu("System") then
      if imgui.menu_item("Settings") then os.spawn("settings") end
      if imgui.menu_item("Wiki")     then os.spawn("wiki") end
      imgui.end_menu()
    end
    imgui.end_menu_bar()
  end
  -- ... console contents ...
end, { menu_bar = true })

Because a windowed task is just a parallel VM (above), a menu item opening Settings and a script opening its own window are the same mechanism — system panels are imgui clients too.

Images

imgui.image draws a texture — a GPU resource. You keep your own pixel buffer and push a snapshot into the texture when you want the display to change; between pushes you can edit your buffer freely without touching the GPU:

Function Behaviour
imgui.texture(w, h) Allocate a GPU texture → a handle.
imgui.set_texture(tex, pixels) Copy pixels into the texture — a snapshot. Your buffer stays yours; the texture doesn't track later edits until the next set_texture.
imgui.texture_free(tex) Release it (also freed automatically when your task exits).
imgui.image(tex, w, h) Draw the texture's current snapshot.

Because set_texture copies, drawing to your buffer and updating the display are decoupled: edit at will, set_texture when a frame is ready (classic double-buffering). Pixels come from the image library; the copy happens on the render thread, and a "both" window's texture is pushed to the PC over netImgui automatically.

local tex = imgui.texture(w, h)
-- ... draw into your own `buf` however you like ...
imgui.set_texture(tex, buf)          -- push a snapshot; keep editing buf freely
imgui.window("Canvas", function() imgui.image(tex, w, h) end)

Popups & the non-blocking message box

Popups and modals are drawn in the frame, so they never block — you open one and keep running; the result comes back on a later frame, not by stalling.

Call Behaviour
imgui.open_popup(id) Mark a popup to open.
imgui.begin_popup(id) → imgui.end_popup() A lightweight popup.
imgui.begin_popup_modal(id) → imgui.end_popup() A modal — dims its window/context until dismissed, not the whole desktop.
imgui.close_current_popup() Dismiss.
imgui.message_box(id, title, text [, buttons]) Convenience modal → the pressed button label, or nil while still open.

Non-blocking here — because the draw is on the render thread. In raw imgui, message_box is called from your draw, which runs on the render thread and must finish a balanced frame each tick — so it can't block (that would freeze the desktop) and returns the pressed button or nil to poll. If you want the natural blocking if message_box(...) == "Save" form, use gui: a gui program runs on its own thread, so gui.message_box blocks that thread while the render thread keeps drawing the modal. The rule is simply: block the calling thread if you like, never the render thread.

if imgui.message_box("quit", "Quit?", "Save first?", {"Save","Discard","Cancel"}) == "Save" then save() end

Widget set (v1)

The native side has all of Dear ImGui; the binding exposes a curated set, grown as needed. v1, in snake_case:

  • Text — text, text_colored, text_wrapped, bullet_text
  • Buttons/toggles — button, small_button, checkbox, radio_button, image_button
  • Input — input_text, input_text_multiline, input_int, input_float
  • Sliders/drags — slider_int, slider_float, drag_int, drag_float
  • Selection — combo, list_box, selectable
  • Trees/tabs — tree_node/tree_pop, collapsing_header, begin_tab_bar/begin_tab_item
  • Layout — same_line, separator, spacing, indent/unindent, group, begin_child/end_child, begin_table/end_table
  • Menus — begin_menu_bar, begin_menu, menu_item
  • Popups — as above, incl. message_box
  • Display — progress_bar, image, color_edit, plot_lines/plot_histogram

Text obeys the console glyph set and the desktop theme; input is routed by the desktop (focus, z-order, docking, the slide-down top menu) — a script window behaves like a system panel because it is one.

Related

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