XML templates
An XML template describes a tree of widgets in markup. willowui compiles it once, then builds live widgets from it as often as you like. Templates are a good fit for screens with many controls (forms, settings, inventories, editors), where code would be mostly new, setSize and addChild.
xml
<Panel layout="vbox" spacing="12" padding="20">
<Label bind:text="'Clicks: ' + clicks" fontSize="20" />
<Button text="Click me" on:click="add" />
<Label text="That is a lot of clicks." ui:show="clicks > 4" />
</Panel>Every element is a widget. Plain attributes configure it. Three prefixes connect it to your code:
| Prefix | Example | Meaning |
|---|---|---|
bind: | bind:text="'Clicks: ' + clicks" | Evaluate an expression and keep the attribute in step with it. |
on: | on:click="add", on:click="select(item)" | Call a method on the controller when the widget fires the event, with optional arguments. |
ui: | ui:show="clicks > 4" | A directive: show, hide or repeat the element. |
The pieces
| Piece | What it is |
|---|---|
TemplateRegistry | Compiles templates, stores them by name and instantiates them. |
| The controller | Your object. Its lookupRef(path) answers names used in expressions, and its callMethod(name, args?) runs event handlers. |
instantiate(name, controller, screen) | Builds the widget tree and returns its root component. |
A complete app
ts
import { Component, Controller, DEFAULT_STAGE, Ref, Screen, TemplateRegistry } from "willowui";
const TEMPLATE: string = `
<Panel layout="vbox" spacing="12" padding="20">
<Label bind:text="'Clicks: ' + clicks" fontSize="20" />
<Button text="Click me" on:click="add" />
<Label text="That is a lot of clicks." ui:show="clicks > 4" />
</Panel>`;
class CounterScreen implements Controller {
clicks: Ref<int> = new Ref<int>(0);
onCreate(screen: Screen): void {
const reg: TemplateRegistry = new TemplateRegistry();
reg.registerXML("counter", TEMPLATE);
const root: Component = reg.instantiate("counter", this, screen);
root.setPosition(24.0, 24.0);
screen.add(root);
}
onUpdate(dt: float): void {}
onDispose(): void {}
// Names used in bind: and ui: expressions.
lookupRef(path: string): any {
if (path == "clicks") { return this.clicks; }
return null;
}
// Names used in on: attributes. Return true when the name was handled.
callMethod(name: string): boolean {
if (name == "add") {
this.clicks.set(this.clicks.peek() + 1);
return true;
}
return false;
}
}
DEFAULT_STAGE.setScene(engine.root2d);
DEFAULT_STAGE.add(new Screen().withController(new CounterScreen()));
App.onFrame((dt: float): void => { DEFAULT_STAGE.update(dt); });How it works:
registerXMLparses and compiles the text. A mistake (an unknown tag, a bad expression) throws aTemplateErrorwith the line and column.instantiatebuilds the widgets. For eachbind:it starts a reactive effect, so whenclickschanges, the label text and theui:showvisibility update on the next frame.- Clicking the button calls
callMethod("add").
Because the screen is passed in, its bindings are stopped when the screen is disposed.
Loading templates from files
Keep templates in .xml files and read them with the fs system (add "fs" to systems and permissions: { fs: true } to thicket.json):
ts
import { Files } from "fs";
const templateXML: string = await Files.readText(moduleDir + "/template.xml");To ship a template without parsing XML at start-up, compile it to the binary form. To edit a template while the app runs, use hot reload.
Registry settings
| Method | Use |
|---|---|
setFonts(fonts, defaultFamily) | A map from the names used in font="..." to font families, and the default family. Pass null for no map. |
setFontSize(size) | The default font size for text widgets (14 when not set). Each element can override it with fontSize. |
setTheme(theme) | Register a compiled theme's custom variant names, so variant="card" resolves. |
setThemeJSON(json) | Compile a theme from JSON text, register its variants, and keep the text so templates can patch it with an inline <Theme>. |
putAsset(path, bytes) | Hand over image bytes for Image src="..." and AnimatedImage gif="...". Setters cannot read files, so you load the bytes first. |
registerWidget(tag, factory, setters) | Add your own tag. See Custom widgets. |
registerBinary(name, bytes) | Register a precompiled .xmlui template. |
instantiateStatic(name, screen) | Instantiate with no controller. Bindings and events then do nothing. |
get(name) | The compiled tree (IRNode), or null. |
newTemplateRegistryWithFont(path, size) makes a registry whose default font is the TTF or OTF file at path.
In this section
- Syntax and attributes: the XML rules, value formats and the attributes every tag takes.
- Expressions: the small language inside
bind:andui:. - Bindings and events:
bind:andon:in depth. - Directives:
ui:show,ui:if,ui:for. - Layout from XML
- Variants and inline themes
- Custom widgets
- Binary templates
- Hot reload
- Tag reference: every tag with its attributes and events.