Windows and responsive layouts
window(player, spec) opens or replaces one window for that player.
window(player, spec) opens or replaces one window for that player. close(player) removes it. Welcome windows, HUDs and scoreboards are retained and replayed after scene entry or reconnect; scripts do not need an arrival-delay timer to display them.
Spec fields: id, title, text, skin, layout, items. id identifies the window in window_closed; it is optional, then nil. Window text preserves line breaks. A long window scrolls on small screens.
| Item kind | Fields and interaction |
|---|---|
button | id, label; sends ui(player, id, nil) |
card | id, label, image (room asset name/id), text, badge, accent (#rrggbb), disabled; illustrated action tile |
input | id, label, placeholder; submits trimmed text, up to 40 characters |
text | text; preserves paragraphs and newlines |
friends | id, optional label; selects an online friend, sends their id as value |
list | rows = {{"name", "value"}, ...}; display only |
Action ids must be present and unique in a window. Player actions are input to your rules: validate them against the current game state, including UI events that arrive after a different screen has opened.
Default windows fit phone and desktop screens. layout supports:
width: integer from 240 to 960, in layout pixels.dock:center(default) orright. Right-docked flow windows have a draggable desktop header and a scrolling two-column phone panel above combat controls.columns: 1 or 2 for buttons and cards. Text, lists and fields span all columns.skin:auto,fit, orflow.phoneanddesktop: tables overriding these same settings.
Phone layout is selected below 700 CSS pixels wide or 500 high. A small desktop window can use it too. Players interact with the creator's controls; they do not edit the creator's UI. Docked desktop windows can be moved by dragging their header.
skin at the spec's top level names a thing from the room, including stock. Its flat voxel view supplies the window artwork. Items can use x, y, w, h in that skin's voxel pixels, with item-level phone and desktop overrides. Use layout.phone.skin = "flow" to replace precise skin positioning with a scrolling phone layout. Start with flow layouts for dependable touch controls.
on("enter", function(p)
window(p, {
id = "signals", title = "SIGNALS",
layout = {width = 600, columns = 2,
phone = {width = 320, columns = 1, skin = "flow"}},
items = {
{kind = "text", text = "Watch the light.\nChoose its colour."},
{kind = "button", id = "red", label = "RED"},
{kind = "button", id = "blue", label = "BLUE"}
}
})
every(1, "signals:" .. p.id)
end)
on("window_closed", function(p, id)
if id == "signals" then stop_timer("signals:" .. p.id) end
end)
on("leave", function(p)
stop_timer("signals:" .. p.id)
end)
The X reports window_closed once for the window the player actually dismissed. Calling close(player) from Lua does not emit it. Closing does not automatically cancel all of a game's timers: stop the relevant timer or mark the screen closed in your handler, so a subsequent timer does not reopen it.
Window validation refuses invalid fields with an actionable error, for example window.items[1].text allows at most 4096 characters. It does not silently shorten the window. Limits per spec:
| Field | Maximum |
|---|---|
title, item label, item placeholder, list cell | 80 characters |
Top-level text, each text item's text | 4096 characters |
Window id, item id | 64 characters |
| Items | 12 |
| Rows per list, cells per row | 12 rows, 3 cells |
| Skin coordinates | Whole numbers from -4096 to 4096; width/height positive |
This page as markdown: /api/windows-and-responsive-layouts.md