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:
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 them — group: 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.
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
- ui-styles.md — tokens, styles, states, transitions
- ui-tab.md — the ◫ authoring canvas
- ui-navigation.md — focus, fields, drag & drop, tooltips
- ui-make.md — building a screen from data
- scripting.md —
color,ui.bind, repeaters, the hooks