Authoring RON UI files
The engine’s UI documents are hand-written RON deserialized into the real Rust
types in manifold-ui-protocol. Validate files with ui-validate; optional
editor support is described below.
1. Validate before you run — ui-validate
# validate a single file (or a directory of them):
cargo run -p manifold-ui-validate --bin ui-validate -- path/to/your.ui.ron
# validate the whole engine asset tree (default when no path is given):
cargo run -p manifold-ui-validate --bin ui-validate
It runs two passes:
- Parse (structural): wrong field names, unknown enum variants, type
mismatches, reported with
file:line:col. - Reference (strict): every binding, slot schema, and typed action is checked
against the live registries. A dangling binding is an error, even in a
Label, HUD predicate, or template, where the renderer would otherwise silently degrade the value (empty string /false/"??") and hide the mistake.
References intentionally written ahead of their gameplay backing live in
assets/ui/.validate-allow.ron and are reported as non-failing info lines.
Remove an entry there once its backing exists. A CI test fails if an allowlist
row no longer matches any asset (no stale rows), and another fails if a new
dangling reference appears without an allowlist entry.
The same checks run in CI: manifold-ui-runtime’s parse gate covers every
assets/ui/** file structurally, and manifold-ui-validate’s asset gate
resolves every reference.
It also checks text and direction (see text and localization):
- Hardcoded text (warning):
LiteralorTemplatetext with words, on a node withouttext_options: (untranslated: true). - Unknown message keys (error): a
Localizedkey that the engine’s or the owning mod’s catalogs (the nearestmod.toml) lack. - Mirroring (warning): a physical-side anchor (
TopLeft, …) or unequalleft/rightmargins withoutmirror: Never. - Catalog lints (warning): messages without a translator comment.
A developer-only document (a debug panel, an editor fixture) opts out of the
hardcoded-text warning with a // ui-validate: untranslated line.
Validate a translation pack against the mods it translates with
ui-validate --translation-pack PACK_DIR --source MOD_DIR....
2. Text players read
Write text as message keys resolved in the player’s language:
Label(text: Localized(key: "mymod.shop.title")),
Button(
label: Localized(key: "mymod.shop.buy", args: {"price": Bind("Shop.price")}),
action: Typed("Buy", ()),
),
The key mymod.shop.buy is the message shop-buy in
locales/<language>/*.ftl of the mod whose id is mymod. Numbers passed as
arguments are formatted for the player’s locale and drive plural selectors.
Literal(..) is for names and symbols. Template(..) is for
language-independent formatting like {0} / {1}.
text_options controls how a text node lays out:
dir: Some(Auto | Ltr | Rtl | Locale). The default isLocaleforLocalizedtext andAuto(first strong letter) otherwise.overflow_wrap: Normal | BreakWord | Anywhere.max_lines: Some(n)andellipsis: End.untranslated: true.
Place things logically. Anchors TopStart, CenterEnd, BottomStart, …
and Margin(start: Some(..), end: Some(..)) follow the reading direction. Rows, grids,
anchors and margins mirror in a right-to-left UI unless the node sets
mirror: Never. Icons flip only when icons.ron marks them mirror_in_rtl.
A Dropdown with append: Languages lists every installed language after its
authored options, each named in its own language.
3. Editor autocomplete — ron-lsp (optional)
For RON autocomplete, try the third-party
JasonMcGhee.ron-lsp extension, which derives completions / diagnostics / hover
from Rust types rather than a schema.
- Install RON (Rusty Object Notation) LSP and CLI (
JasonMcGhee.ron-lsp) in VS Code. - Annotate the document’s root Rust type so the LSP knows what to complete
against (see the extension’s docs for the annotation syntax, e.g. the root
Nodefor*.ui.ron,HudLayerDocumentforhud/*,Themefor*.theme.ron). - Open a
.ui.ronfile and confirm completions for theNodevariants/fields.
Workspace support is unverified. Test whether
ron-lspresolvesmanifold_ui_protocol’s cross-crate types in your editor, then record the result and any working root-type annotation here. Useui-validatefor validation regardless of editor support.