Skip to content

sagl.ui

UI documents, pause menu pages and SAGL's message box. Documents are RML and RCSS: see Styling and layouts, and the UI guide for screenshots and walkthroughs.

Tip

For a shop or a dialog whose logic is on the server anyway, the gamemode can open the layout itself with player.ui.open(), without a client script. The element methods are the same.


Opening UI

sagl.ui.open(rml, options?)

sagl.ui.open(rml: string, options?: DocumentOptions): UiDocument

Opens a document from RML text and shows it.

Parameter Type Description
rml string A whole RML document (<rml><head>...</head><body>...</body></rml>), up to 256 KB. Relative links resolve under sagl/ui/scripts/; use /sagl/ui/theme/base.rcss for SAGL's theme.
options DocumentOptions Optional.

Returns the UiDocument.

Throws RangeError past 16 documents or 256 KB; SyntaxError if the RML doesn't load; InternalError if the UI isn't ready.

Any document can show the game's own textures, read from the player's copy of the game: <img src="game:<txd>/<texture>"/>, a TXD in models/gta3.img or gta_int.img (or models/<txd>.txd) and a texture in it, e.g. <img src="game:garag3_lawn/ws_Transfender_dirty"/> for TransFender's sign.

const toast = sagl.ui.open(`
  <rml><head><link type="text/rcss" href="/sagl/ui/theme/base.rcss"/>
  <style>body { position: absolute; left: 40%; top: 20dp; width: 20%; }</style></head>
  <body><div class="panel" id="text"></div></body></rml>`, { input: false });
toast.setText("text", "Checkpoint!");
setTimeout(() => toast.close(), 3000);

sagl.ui.openFile(path, options?)

sagl.ui.openFile(path: string, options?: DocumentOptions): UiDocument

Opens an RML client file as a document. Its relative links (images, stylesheets) resolve beside it.

Parameter Type Description
path string The client file, e.g. "ui/shop.rml".
options DocumentOptions Optional.

Returns the UiDocument.

Throws ReferenceError if there's no such client file, and as open.

const shop = sagl.ui.openFile("ui/shop.rml");

DocumentOptions

Field Type Default Description
input boolean true Takes the player's input while shown. false for HUDs.
cursor boolean input Shows the mouse cursor.
keyboard boolean input Keys go to the document.
gamepad boolean input The gamepad moves focus (A activates, B backs out).
movement boolean false Leaves the player's movement to the game.
escapeCloses boolean true Esc (or gamepad B) closes it.

sagl.ui.pauseMenu.addPage(title, rml)

sagl.ui.pauseMenu.addPage(title: string, rml: string): MenuPage

Adds a page to the pause menu: its title goes in the options row, before Quit Game.

Parameter Type Description
title string 1-32 characters.
rml string The page's content (no <rml>/<head>), styled by the pause menu's theme. <style> blocks in it add styles of its own (scope them with a class on the page's outer element).

Returns the MenuPage.

Throws RangeError past 8 pages, or for a bad title.

const help = sagl.ui.pauseMenu.addPage("Help", `
  <h2 class="first">How to play</h2>
  <span class="subtitle">Cops catch robbers. Robbers get rich.</span>`);

sagl.ui.showDocument(source, options?)

sagl.ui.showDocument(source: { file: string } | { markdown: string }, options?: { title?, buttons?, requireEnd?, closable? }): Promise<number>

As the server's player.ui.showDocument: a markdown client file (or markdown text) in the document viewer, resolving with the button pressed, or -1.

sagl.ui.messageBox(options)

sagl.ui.messageBox(options: { title?: string; text?: string; buttons?: string[] }): Promise<string | null>

Shows SAGL's message box. Boxes queue: one shows at a time.

Parameter Type Description
options.title string
options.text string
options.buttons string[] Left to right; the last is the default. Default ["OK"].

Returns a promise of the chosen button's label, or null if dismissed (Esc).

const choice = await sagl.ui.messageBox({
  title: "Join a team",
  text: "Which side are you on?",
  buttons: ["Robbers", "Cops"],
});
if (choice) sagl.emit("team", choice.toLowerCase());

sagl.ui.onMissionResult(fn)

sagl.ui.onMissionResult(fn: ((result: { kind: string; title: string; text: string }) => boolean) | null): void

Shows the script's own result screen instead of SAGL's ("MISSION PASSED!"). fn gets each result the gamemode shows (player.hud.missionPassed(), missionFailed(), missionResult(); kind is "passed", "failed" or "custom"): return true if you showed it, and SAGL's isn't shown. Return anything else to leave it to SAGL. null removes it.

sagl.ui.onMissionResult(({ kind, title, text }) => {
  if (kind !== "custom") return false;          // SAGL's for the rest
  const doc = sagl.ui.openFile("ui/race-result.rml", { input: false });
  doc.setText("title", title).setText("time", text);
  setTimeout(() => doc.close(), 5000);
  return true;
});

Element methods

UiDocument and MenuPage share these. They work on the document's or page's own elements, by id, and return it so calls chain.

setText(id, text)

setText(id: string, text: any): this

Sets an element's text (escaped: no markup).

Parameter Type Description
id string The element's id.
text any Converted to a string.
hud.setText("cash", `$${cash.toLocaleString()}`);

setMarkdown(id, markdown, base?)

setMarkdown(id: string, markdown: string, base?: string): this

Replaces an element's contents with markdown, {key:ID} placeholders and all. base is the client file it came from, for relative images. Link /sagl/ui/theme/markdown.rcss to style it. The <sagl-markdown src="docs/help.md"/> element does the same for a client file, right in your RML.

help.setMarkdown("text", sagl.files.read("docs/help.md"), "docs/help.md");

setHtml(id, rml)

setHtml(id: string, rml: string): this

Replaces an element's contents with RML.

list.setHtml("items", items.map((i) => `<div class="item" id="${i.id}">${i.name}</div>`).join(""));

setValue(id, value), getValue(id)

setValue(id: string, value: any): this
getValue(id: string): string

A form control's value (<input>, <select>, <textarea>).

form.setValue("name", "");
const name = form.getValue("name").trim();

isChecked(id)

isChecked(id: string): boolean

Whether a checkbox or radio button is checked.

setClass(id, name, on?)

setClass(id: string, name: string, on?: boolean): this

Adds (on, the default) or removes a class.

hud.setClass("health", "low", sagl.player.health() < 25);

setAttribute(id, name, value)

setAttribute(id: string, name: string, value: string | null): this

Sets an attribute, or removes it with null.

doc.setAttribute("avatar", "src", sagl.files.url("ui/avatars/cop.png"));
doc.setAttribute("bar", "style", `width: ${pct}%`);

focus(id)

focus(id: string): boolean

Gives an element keyboard and gamepad focus. Returns whether it could.

on(type, id?, handler)

on(type: string, handler: (e: UiEvent) => void): this
on(type: string, id: string, handler: (e: UiEvent) => void): this

Handles events from the whole document or page, or from one element (and what's inside it).

Parameter Type Description
type string click, change, submit, keydown, focus, blur, mouseover or mouseout.
id string Optional: only events from this element.
handler function Gets a UiEvent.
shop.on("click", "buy", () => sagl.emit("shop", { buy: shop.getValue("item") }));
shop.on("change", "quantity", (e) => shop.setText("total", `$${200 * Number(e.value)}`));
shop.on("change", "search", (e) => { if (e.enter) sagl.emit("shop:search", e.value); });

off(type, handler?)

off(type: string, handler?: (e: UiEvent) => void): this

Removes a handler, or every handler of that type.

UiEvent

Field Type Description
type string The event type.
id string The id of the element it came from (the nearest one with an id).
value string A form control's value, for change and submit.
key number | undefined For keydown: RmlUi's key identifier (KI_A 12 ... KI_Z 37, KI_0 2 ... KI_9 11).
checked boolean For checkboxes.
enter boolean For change from a text input: true when the player pressed Enter in it.

Note

Text inputs keep the keys they use (Enter, arrows, Backspace, ...), so keydown doesn't see those from a focused input. For "Enter sends", handle change and check e.enter:

form.on("change", "message", (e) => { if (e.enter) send(form.getValue("message")); });

UiDocument

From open and openFile. The element methods above, plus:

Member Description
open boolean: false once it's closed.
show() Shows it again (and takes input again, if it does). Returns it.
hide() Hides it (and gives input back) without closing it. Returns it.
close() Closes it for good.
onClose(fn) Calls fn when it closes, by close() or Esc (not when scripts reload). Returns it.
const menu = sagl.ui.openFile("ui/garage.rml");
menu.onClose(() => sagl.emit("garage", { closed: true }));
sagl.on("garage:hide", () => menu.hide());

From pauseMenu.addPage. The element methods above, plus:

Member Description
remove() Takes the page out of the pause menu.
onShow(fn) Calls fn each time the player opens the page. Returns it.
const stats = sagl.ui.pauseMenu.addPage("Stats", `<h2 class="first">Stats</h2><div id="list"></div>`);
stats.onShow(() => sagl.emit("stats:request"));
sagl.on("stats", (rows) => stats.setHtml("list", rows.map((r) => `<div>${r}</div>`).join("")));