Nodes, Properties and Values
Goal: At the end of this chapter you can read any SML file. You know what a node is, how properties are written, which kinds of values exist, and what happens when you make a typo.
Part II – Structure
In Part I you wrote two files: one for what is there, one for what should happen. Part II is about the first one. SML (the Simple Markup Language) describes the structure of your app: windows, containers, labels, buttons – and how they look.
SML is small on purpose. There are only three ideas in it: nodes, properties and values. This chapter covers all three.
The example
A business card. One window, a few lines of text, a bar, a button that reveals a hidden line.
Create a folder ~/forge/business-card with two files.
app.sml:
// A business card - one window, a few nodes, many kinds of values.
Window {
id: mainWindow
title: "Business Card"
size: 520, 360
PanelContainer {
anchors: left | top | right | bottom
bgColor: "#1B2433"
VBoxContainer {
padding: 28, 32, 28, 32
spacing: 10
Label {
id: name
text: "Ada Example"
fontSize: 30
fontWeight: bold
color: "#F2C14E"
}
Label { id: role text: "Builds apps with \"ForgeIL\"" fontSize: 18 }
HSeparator { }
Label { text: "ada@example.org" }
Label { text: "Harbour Street 7\nPort Example" }
/* Shown only after the button was pressed. */
Label {
id: secret
text: "Favourite language: SML + SMS"
color: "#7FD1AE"
visible: false
}
ProgressBar { id: coffee value: 72.5 showPercentage: true } // a decimal number
Button { id: btnMore text: "More" sizeFlagsHorizontal: shrinkCenter }
}
}
}
app.sms:
on btnMore.clicked() {
secret.visible = true
}
Start it:
./run.sh url file://$HOME/forge/business-card/app.sml

Nodes
Window {
...
PanelContainer {
...
VBoxContainer {
...
Label { ... }
A node is a name followed by curly braces. Everything between the braces belongs to the node: its properties and its child nodes. Children sit inside their parent, so every SML file is a tree:
Window
└── PanelContainer
└── VBoxContainer
├── Label (name)
├── Label (role)
├── HSeparator
├── Label
├── Label
├── Label (secret)
├── ProgressBar
└── Button
The tree is the app. The Runner reads it from top to bottom and builds exactly these elements, in exactly this order. A VBoxContainer shows its children in the order you wrote them – move a Label up three lines, and it moves up on the screen.
The node names are not invented for Forge. They are the names of the user interface classes of Godot, the open source engine the Forge Runner is built on: Label, Button, ProgressBar, HSeparator, PanelContainer. If you ever read Godot documentation, you already know the names. Node names start with a capital letter and are written exactly as shown – Label, not label.
A node without properties and without children is still a node: HSeparator { } draws a horizontal line, nothing else is needed.
Properties
id: mainWindow
title: "Business Card"
size: 520, 360
A property is a name, a colon, and a value. No equals sign, no semicolon, no comma at the end of the line.
Property names start with a lowercase letter and use camelCase: fontSize, bgColor, showPercentage. Many of them are Godot properties under a slightly different spelling – Godot writes show_percentage, SML writes showPercentage. Others were added by Forge because they make life simpler: padding, spacing, bgColor, anchors.
You can write one property per line, or several on one line. Both are the same to the Runner:
Label { id: role text: "Builds apps with \"ForgeIL\"" fontSize: 18 }
Label {
id: role
text: "Builds apps with \"ForgeIL\""
fontSize: 18
}
Use the short form for small nodes that fit on one line, the long form for everything else. The rest of this book does the same.
Properties come first, child nodes after them. That is not a rule of the parser, but it makes a file much easier to read: you see what a node is before you see what it contains.
Values
Every value in SML is one of a handful of kinds. The business card uses almost all of them.
| Kind | Example | Used for |
|---|---|---|
| String | "Ada Example" |
texts, titles, colours, file paths |
| Whole number | 30, -10 |
sizes, font sizes, counts |
| Decimal number | 72.5 |
values with a fraction |
| Boolean | true, false |
switches: visible, showPercentage |
| Keyword | bold, shrinkCenter |
a choice from a fixed list |
| Pair or list | 520, 360 / 28, 32, 28, 32 |
sizes, positions, paddings |
| Flags | left | top | right | bottom |
several choices at once |
| Identifier | mainWindow |
the id of a node |
A few details are worth knowing.
Strings are always in double quotes. Inside a string, \" is a double quote and \n starts a new line – that is how the address gets its two lines:
Label { text: "Harbour Street 7\nPort Example" }
Colours are strings, written the way web designers write them: "#F2C14E" – a hash and three pairs of hexadecimal digits for red, green and blue.
Keywords look like identifiers, but they are not ids of other nodes. fontWeight: bold picks bold from the list of weights; sizeFlagsHorizontal: shrinkCenter tells the button to be only as wide as its text and sit in the middle. Each property that takes a keyword has its own list. You will meet the important ones in the next chapters.
Pairs and lists are values separated by commas. size: 520, 360 is width and height. padding: 28, 32, 28, 32 is left, top, right, bottom. Two values are a shortcut: padding: 28, 32 means 28 left and right, 32 top and bottom. One value is the same on all four sides.
Flags combine several keywords with |. anchors: left | top | right | bottom means: hold on to all four edges. You will see what anchors do in the chapter on layout.
The id
Label {
id: secret
...
visible: false
}
The id is a special property. It gives a node a name, and that name is how the script reaches it:
on btnMore.clicked() {
secret.visible = true
}
Rules for ids:
- Write them without quotes:
id: secret, notid: "secret". - Start with a letter, use letters and digits, no spaces and no dashes:
btnMore,label2. - Each id must be unique in its file.
- Give an id only to nodes the script talks to. The e-mail label has none, because nothing ever changes it. Fewer ids make a file calmer to read.
visible: false builds the label but does not show it. It still exists, it is still part of the tree, and the script can switch it on at any time. When it appears, the VBoxContainer makes room for it and pushes the elements below it down – you can see that in the screenshot.
Comments
// A business card - one window, a few nodes, many kinds of values.
/* Shown only after the button was pressed. */
ProgressBar { id: coffee value: 72.5 showPercentage: true } // a decimal number
// starts a comment that runs to the end of the line. /* … */ can span several lines. Comments are for people; the Runner skips them. A comment is also the quickest way to switch something off while you experiment: put /* before a node and */ after it, and it is gone.
Pitfalls
A misspelled property is silently ignored. Write colour: "#F2C14E" instead of color, and nothing happens – no error, no warning, the label just stays white. When a property seems to have no effect, check its spelling first, letter by letter.
A misspelled node becomes an empty box. Lable { text: "Hello" } does not show anything. The Runner does not know a Lable, so it puts an invisible placeholder into the tree instead. The rest of the app works, which makes this easy to miss.
A missing } stops the app. The Runner reports a parse error with a line number, for example SML parse error: Expected '}' at end of element at line 9. That line is where the parser noticed the problem – usually the end of the file. The brace is missing somewhere above. Good indentation makes it easy to find: every } sits exactly below the first letter of its node name.
id: "secret" with quotes is a string, not an id. The script will not find the node.
size: 520 360 without a comma is not a pair. Lists always need commas.
padding starts on the left. If you know CSS, you expect the top first. In SML the order is left, top, right, bottom.
Exercise
- Add a second hidden label below
secret, with your own favourite thing and a colour of your choice. - Show both hidden labels when More is pressed.
- Change the
ProgressBarto100and switch off its percentage text. - Add a comment above the
ProgressBarthat says what it shows.
The solution is in the appendix.
What you learned
- An SML file is a tree of nodes. Node names are Godot class names:
Label,Button,PanelContainer. - Properties are written
name: value, one per line or several on one line. - Values are strings, whole and decimal numbers, booleans, keywords, lists and flags. Colours are strings like
"#F2C14E". \nand\"work inside strings.- The
idconnects a node to the script; give one only to nodes the script uses. visible: falsebuilds a node but hides it.//and/* … */are comments.- Misspelled properties and node names fail silently – check spelling first.
Next: Layout
