Docs / Menus and HUDs · v0.95.0

The UI demo

The UI demo

assets/scenes/ui_demo.ron — open it and press Play.

It is one screen that uses everything the UI system grew: styles and tokens, the five interaction states, keyboard and gamepad focus, a text field, a radio-group tab strip, toggle chips, a slider, a scroll view with a scrollbar, a list built by a repeater, drag & drop, tooltips, and a panel whose entire contents are described in Lua and built from a table.

(The screenshot lives beside this page in the repo as ui-demo.png; the docs site carries text only. Run the probe below to regenerate it, or just open the demo scene — it is the same thing, live.)

What to try

Arrows / d-pad / left stick move the focus. Nothing is bound in input.ron — the fallback works before anyone opens the Input settings
Enter / A submit. It fires the same clicked a mouse click fires
Hover anything for a moment the tooltip element follows the pointer
Click into CALL SIGN and type caret, selection, Ctrl-A/C/X/V, key repeat. It shouts, because upper is on, and stops at 12 characters
Drag the THRUST handle an ordinary slider: track, Fill, Handle
Click the tabs, click the chips a radio group and three toggles, with no script on them at all — the tabs report what you picked because ui_demo.lua listens from the panel with ui.on
Wheel over the manifest it scrolls, and the bar's thumb is the visible fraction of the list
Click a manifest row it is removed — watch the others keep their hover and the view keep its scroll
Drag ORE or FUEL onto an empty slot the engine reports the drag; ui_demo_slot.lua decides what it means
Click a crew member the whole right-hand panel is ui.make — one table, one call. The row goes, the others keep their hover and their transitions, and a RECALL button appears because a function child returned something this time

What it is made of

assets/scenes/ui_demo.ron the screen (generated by scripts/gen_ui_demo.py — see below)
assets/ui/demo.tokens.ron the palette, spacing scale, radii and type scale
assets/ui/demo.uistyle.ron fifteen styles, every colour a token name
assets/prefabs/DemoRow.prefab.ron one manifest row
assets/scripts/ui_demo*.lua ~215 lines, total — 70 of which are the crew panel: its contents, its look and what its rows do

Delete the two files in assets/ui/ and the demo still runs, grey and flat. That is the whole point of the arc: nothing in the engine knows what this screen looks like. There is no theme to opt out of, because there is no theme.

The Lua is the short part

There is no per-frame layout arithmetic, no hover function, no list rebuilding and no key polling. What is left is state and the things that depend on it:

lua
ui.bind(find("Manifest List"), "count",  function() return #manifest end)
ui.bind(find("Status"),        "text",   function() return status end)
ui.bind(find("Status"), "textColor", function()
    return good and color.hex("#66de8f") or color(0.56, 0.60, 0.68)
end)
ui.focus(find("Play"))

for _, tab in ipairs({ "Tab Loadout", "Tab Crew", "Tab Log" }) do
    ui.on(find(tab), "clicked", function(el) status = el.text .. " selected" end)
end

Each row asks node.index who it is. The tab strip and the toggle chips still have no script on themgroup: and toggle: flip the selected state, and the style says what selected looks like. What the last three lines add is ui.on: this one file answers for buttons it doesn't live on, so a menu stays one script instead of becoming one three-line script per button.

The crew panel is the other half of that story: the scene holds one empty node saying where it sits, and everything inside it — the heading, five rows, each row's badge, name, role and click handler — is one ui.make call over a table of five crew members. Add a sixth to the table and a sixth row appears.

lua
ui.make(find("Crew Panel"), {
    "col", inset = 0, style = "panel", gap = 10, pad = 16,
    { "text", text = "CREW · " .. #crew .. " on duty", style = "caption" },
    { "col", w = "100%", gap = 6, items = crew, crewRow },
    function() if #offDuty > 0 then return recallButton() end end,
})

Rendering it without the editor

cargo run --release -p floptle-render --example ui_demo_probe
cargo run --release -p floptle-render --example ui_demo_probe -- 2560 1080

Writes docs/ui-demo.png (hover + focus — the image above, so it is never stale), ui_demo_states.png (a pressed button and a field mid-edit) and ui_demo_bare.png (nothing interacting). It loads the real scene, the real tokens and the real style sheet and pushes them through the shipping path, so it is checking the demo rather than a Rust rebuild of it — and it asserts that the list clips and that the focused button is actually lit by its focus block, which is the check that would have caught "focus resolves but nothing ever sets it".

Editing it

The scene is generated, because parent in a scene file is a positional index into the node list: insert one node near the top by hand and every parent below it silently points somewhere else. Edit it in the editor (which rewrites the indices correctly) or edit scripts/gen_ui_demo.py and re-run it. Don't hand-splice nodes into the .ron.

Where the rest is written down