ForgeCrowdBookWe don't sell the fish – we give you the rod.

Your First App

Goal: At the end of this chapter you have a running Forge app with a window, a label and a button – and you know what every line does.


What you need

  • A Mac with Apple Silicon, or a Linux PC (x86-64).
  • git.
  • A text editor. Any editor works. SML and SMS are plain text.

Get Forge4D:

git clone https://codeberg.org/CrowdWare/Forge4D.git
cd Forge4D

That is the whole installation. The first time you start an app, run.sh downloads the ready-made Forge Runner for your system into the folder .runtime/. You do not need to compile anything.


The example

Create a new folder for your project, for example ~/forge/first-app, and put two files into it.

app.sml – what is there:

Window {
    id: mainWindow
    title: "My First Forge App"
    size: 480, 320

    VBoxContainer {
        anchors: left | top | right | bottom
        padding: 32, 32, 32, 32
        spacing: 16

        Label {
            id: greeting
            text: "Hello, Forge!"
            fontSize: 24
        }

        Button {
            id: btnHello
            text: "Say hello"
        }
    }
}

app.sms – what should happen:

var clicks = 0

on mainWindow.ready() {
    log.info("My first app is ready")
}

on btnHello.clicked() {
    clicks = clicks + 1
    greeting.text = "Hello for the ${clicks}. time!"
}

Start it from inside the Forge4D folder:

./run.sh url file://$HOME/forge/first-app/app.sml

A window opens. Press the button three times.

The first app: at start, and after three clicks

Congratulations. That is a native app, and your script was compiled to machine code before the window appeared.


The structure, line by line

Window {

Every SML file is a tree of nodes. A node has a name, curly braces, and inside them properties and child nodes. Window is the root of this screen.

    id: mainWindow
    title: "My First Forge App"
    size: 480, 320

Properties are written name: value. No equals sign, no semicolon.

  • id gives the node a name your script can use. It is written without quotes and must be unique in the file.
  • title is a string, so it is in double quotes.
  • size is a pair of whole numbers: width and height in pixels.
    VBoxContainer {
        anchors: left | top | right | bottom
        padding: 32, 32, 32, 32
        spacing: 16

A VBoxContainer stacks its children vertically – one below the other. You almost never position things by hand in Forge. You put them into containers and let the containers do the layout.

  • anchors: left | top | right | bottom – stretch the container to all four edges of the window.
  • padding – space inside the container: left, top, right, bottom.
  • spacing – space between the children.
        Label {
            id: greeting
            text: "Hello, Forge!"
            fontSize: 24
        }

        Button {
            id: btnHello
            text: "Say hello"
        }

A Label shows text. A Button can be pressed. Both have an id, because the script wants to talk to them.


The behaviour, line by line

var clicks = 0

A variable at the top of the script. It lives as long as the screen lives and counts how often the button was pressed.

on mainWindow.ready() {
    log.info("My first app is ready")
}

This is an event handler. Read it aloud: on mainWindow ready, do this. The ready event fires once, when the window has been built.

log.info writes a line into the terminal you started the app from. It is your first debugging tool.

on btnHello.clicked() {
    clicks = clicks + 1
    greeting.text = "Hello for the ${clicks}. time!"
}

On btnHello clicked, do this. Count up, then change the text of the label.

greeting is not a variable – it is the id of the label in app.sml. Every element with an id can be used in the script by that name: greeting.text = … changes it, greeting.text reads it back. Nothing to declare, nothing to look up.

${clicks} inside a string is replaced by the value of clicks. This is called string interpolation.

Notice what is not there: no main function, no event loop, no listener registration, no thread handling. You wrote down two intentions – when the window is ready and when the button is clicked – and the Runner connected them.


How the two files find each other

You started app.sml. The Runner looked for a script with the same name next to it – app.sms – and loaded it. That is the whole convention:

You start The Runner also loads
app.sml app.sms
main.sml main.sms

The connection between them is the id. The id you give a node in SML is the name you use in SMS: btnHello in the SML file, on btnHello.clicked() in the script; greeting in the SML file, greeting.text in the script.


Pitfalls

The handler never fires, or the label does not change. Check the id. on btnhello.clicked() and id: btnHello are not the same name. Copy ids, do not retype them.

The app does not start and the terminal says SMS AOT compile failed. Your script was compiled before the window opened, and the compiler found something it could not translate. The message names the file and usually the variable. Fix it and start again.

Everything is squeezed into the top-left corner. The container has no anchors. Without them it only takes the space its children need.

text: Hello without quotes is not a string. Strings always need double quotes in SML.

size: 480 x 320 does not work. Pairs of numbers are written with a comma: size: 480, 320.


Exercise

  1. Add a second button, btnReset, with the text "Reset".
  2. Write a handler for it that sets clicks back to 0 and the label back to "Hello, Forge!".
  3. Change the window title to your own name.

The solution is in the appendix – but try it first. You already know everything you need.


What you learned

  • A Forge project can be as small as two files: app.sml and app.sms.
  • ./run.sh url file://…/app.sml starts it; the Runner is downloaded on first use.
  • SML nodes have properties (name: value) and children; containers do the layout.
  • id connects structure and behaviour.
  • on id.event() { … } is an event handler; ready fires once, clicked on every press.
  • An element's id is its name in the script: greeting.text = "…" changes it.
  • ${…} puts a value into a string.

Next: Part II – Structure