HTML/CSS overlays and panels
Status: Implemented (API 1.22). RmlUi documents (HTML-like RML and CSS-like RCSS) from the mod folder shown as HUD overlays, as panels inside the mod’s own screens, and as screens (GUI Screens (RmlUi)), with a data model bound to the mod, actions called from markup, the player’s Appearance through a theme style sheet, and a sandbox that refuses everything outside the mod folder.
Function table: rml. Related: GUI Screens (RmlUi), HUD Elements, Components, guide An HTML/CSS HUD Overlay. Example mod: sdk/examples/html_hud_lua. Client only.
Three ways to show a document
rml_desc |
Shown as | Input |
|---|---|---|
as_screen = true (the default) |
A screen (mouse released, Esc closes) | Yes |
parent = <one of your open screens> + x, y, width, height |
A panel over that screen, in that box | Yes, while the screen is open; it closes with the screen |
as_screen = false + width, height (+ anchor, x, y, id, label) |
A HUD overlay: a HUD element of your mod in a box of that size | Never |
A HUD overlay is a HUD element: the player sees it in the HUD editor and Appearance > HUD under your mod’s name, and moves, scales or hides it there; F1 hides it. It draws inside its box only and under every screen, so it can’t cover menus, the chat input or server screens. id names it for the saved placement (default: the file name), label in the editor.
Overlays can be opened at lifecycle.client_setup (they show once the HUD draws). Panels need an open screen of your own mod (CL_E_PERMISSION_DENIED for another mod’s screen).
local hud = cl.rml.open{
path = "assets/mana_hud/gui/mana.rml",
as_screen = false,
id = "mana", label = "Mana bar",
anchor = "bottom_left", x = 4, y = 4, -- the default place (HUD element anchors)
width = 110, height = 34,
model = { "mana", "max", "spells[]" },
}
hud:set("mana", 12)
The data model
Name the variables your document uses in model ("rubies, name, items[]" in C; a list in Lua). A [] suffix makes a list, for data-for. Put data-model="mod" on the element whose content uses them (usually body); every document has its own model, so a document can’t read another one’s.
<body data-model="mod">
<p>Rubies: {{ rubies }}</p>
<div data-for="item : items" data-class-cheap="item.price < 5">{{ item.name }} - {{ item.price }}</div>
<p data-if="items.size == 0">Nothing for sale</p>
<input type="range" min="1" max="16" data-value="amount"/>
<button data-event-click="buy(item.id)">Buy</button>
</body>
| C | Lua | |
|---|---|---|
cl_rml_data_set_num(doc, name, v) |
doc:set(name, 3) |
A number |
cl_rml_data_set_str(doc, name, s) |
doc:set(name, "Steve") |
A string |
cl_rml_data_set_bool(doc, name, b) |
doc:set(name, true) |
A boolean |
cl_rml_data_set_json(doc, name, json) |
doc:set(name, { { name = "Ruby", price = 3 } }) |
Lists and objects (JSON in C; tables in Lua) |
cl_rml_data_get_json(doc, name, buf, cap, &len) |
doc:get(name) |
The current value |
Only the views of a changed variable update, on the next frame. Setting a name that is not in model fails with CL_E_NOT_FOUND; a list variable takes only lists (CL_E_WRONG_TYPE).
Two-way bindings (data-value, data-checked) write the player’s input into the variable; your on_event gets (doc, "<variable>", "data", "<new value>").
Actions: list the names data-event-* may call in actions ("buy, sell"); they reach on_action(doc, action, args) with the arguments as a JSON array in C ("[3]") and as Lua values in Lua. Names that are not listed are ignored.
local shop = cl.rml.open{
path = "assets/my_mod/gui/shop.rml",
model = { "rubies", "items[]" },
actions = { "buy" },
on_action = function(doc, action, id) cl.net.send_to_server("my_mod:buy", cl.buf.create(0)) end,
}
The player’s Appearance
Link the theme style sheet and use its classes; they look right in both styles and every theme:
<link type="text/rcss" href="celestial:theme.rcss"/>
<body data-model="mod" class="cl-root">
<div class="cl-panel"><span class="cl-h2">Shop</span> <span class="cl-small cl-accent">3 new</span></div>
</body>
| Class | Fancy | Vanilla |
|---|---|---|
cl-panel, cl-hud-panel, cl-raised, cl-sunken, cl-selected |
The theme’s rounded shapes | The popup background, the translucent HUD box |
cl-button, cl-primary (+ :hover, :focus, :disabled) |
The glass plate | The vanilla button sprites |
cl-h1, cl-h2, cl-h3, cl-body, cl-small, cl-button-label, cl-chip-label |
The UI font in the type scale | The game font with its shadow |
cl-c-<colour> (cl-c-on-surface-2, cl-c-accent, …), cl-accent |
The theme’s colours |
The document’s body also gets cl-fancy or cl-vanilla and cl-theme-<id>, so your own rules can follow the style: .cl-fancy .bar { ... }. When the player changes the style or theme, the sheet reloads by itself. Screens and panels follow your mod’s scope (Appearance > Menus > Mods); overlays follow the HUD.
The sandbox
A document is a file of your mod and stays inside it. When a document opens, when a style sheet or template it links loads, and when your code sets markup, attributes or properties, the engine checks it and refuses (CL_E_PERMISSION_DENIED, with the reason in cl_last_error_message and the log):
| Refused | Example |
|---|---|
| URLs of any kind (no network) | src="https://...", href="//host/x.rcss", url(http://...), data:, javascript:, file: |
| Paths outside the mod folder | ../other_mod/x.rcss, /etc/passwd, C:/Windows/..., backslashes, percent-encoding |
| Scripts | <script>, event attributes (onclick=...), <iframe>, <object>, <embed> |
| Other models | data-model="..." other than "mod" |
| Style sheet imports | @import (link the sheets from the document) |
Allowed references: files of your mod relative to the document (style.rcss, parts/row.rml), resource ids for images (my_mod:textures/gui/bar.png, minecraft:textures/...: they go through the resource packs) and celestial:theme.rcss. While a document loads, the file layer reads only mods/<your id>/ (never another mod’s files or the game’s).
Documents never run code: everything happens in your mod through events, the data model and actions. No capability is needed (like screens and HUD elements); a planned ui.inject capability will cover changing other screens’ documents.
Marked as mod UI. A mod document shown as a screen shows your mod’s name in its corner, drawn after the document, so a document can’t pass for a vanilla or server screen. Overlays can’t take input and draw under every screen.
Cost
| Situation | Work |
|---|---|
| No document open | Nothing |
| An overlay | RmlUi’s update and draw of that small context once per frame; layout runs only when something changed |
| A data change | The views bound to that variable, on the next frame |
| A theme change | One style sheet reload per open document |
Versions
Overlays, panels, the data model, actions and the theme sheet came with API 1.22 (cl.has("rml.overlay") is true from then on). The fields are appended to cl_rml_desc, so code built against API 1.4 keeps working unchanged.