[][src]Module sixtyfps::langref

The .60 language reference

This page is work in progress as the language is not yet set in stones. TODO indicates things that are not yet implemented.

.60 files

The basic idea is that the .60 files contains one or several components. These components contain a tree of elements. Each declared component can be given a name and re-used under that name as an an element later.

By default, the SixtyFPS comes with some builtin elements.

Below is an example of components and elements:


MyButton := Text {
    color: black;
    // ...
}

export MyApp := Window {
    width: 200lx;
    height: 100lx;
    Rectangle {
        width: 200lx;
        height: 100lx;
        color: green;
    }
    MyButton {
        text: "hello";
    }
    MyButton {
        x: 50lx;
        text: "world";
    }
}

Here, both MyButton and MyApp are components. Window and Rectangle are built-in elements used by MyApp. MyApp also re-uses the MyButton component.

You can assign a name to the elements using the := syntax in front an element:

//...
MyApp := Window {
    hello := MyButton {
        text: "hello";
    }
    world := MyButton {
        text: "world";
    }
}

The outermost element of a component is always accessible under the name root. The current element can be referred as self. The parent element can be referred as parent. These names are reserved and cannot be used as element names.

Container Components

When creating components, it may sometimes be useful to influence where child elements are placed when they are used. For example, imagine a component that draws label above whatever element the user places inside:

MyApp := Window {

    BoxWithLabel {
        Text {
            // ...
        }
    }

    // ...
}

Such a BoxWithLabel could be implemented using a layout, but by default child elements like the Text element become children of the BoxWithLabel, when they would have to be somewhere else, inside the layout. For this purpose, you can change the default child placement by using the $children expression inside the element hierarchy of a component:

BoxWithLabel := GridLayout {
    Row {
        Text {
            // label text here
        }
    }
    Row {
        $children
    }
}

Comments

C-style comments are supported:

  • line comments: // means everything to the end of the line is commented.
  • block comments: /* .. */. Note that the blocks comments can be nested, so /* this is a /* single */ comment */

Properties

The elements can have properties. Built-in elements come with common properties such as color or dimensional properties. You can assign values or entire expressions to them:

Example := Window {
    // Simple expression: ends with a semi colon
    width: 42lx;
    // or a code block (no semicolon needed)
    height: { 42lx }
}

You can also declare your own properties. The properties declared at the top level of a component are public and can be accessed by the component using it as an element, or using the language bindings:

Example := Rectangle {
    // declare a property of type int with the name `my_property`
    property<int> my_property;

    // declare a property with a default value
    property<int> my_second_property: 42;
}

Bindings

The expression on the right of a binding is automatically re-evaluated when the expression changes.

In the following example, the text of the button is automaticallty changed when the button is pressed, because changing the counter property automatically changes the text.

import { Button } from "sixtyfps_widgets.60";
Example := Button {
    property <int> counter: 3;
    clicked => { counter += 3 }
    text: counter * 2;
}

Two ways bindings

(TODO)

using the <=> syntax, one can create two ways binding between properties. These properties are now linked together. The right hand side of the <=> must be a reference to a property of the same type.

Example := Window {
    property<color> rect_color <=> r.color;
    r:= Rectangle {
        width: parent.width;
        height: parent.height;
        color: blue;
    }
}

Types

All properties in elements have a type. The following types are supported:

TypeDescription
intSigned integral number.
floatSigned, 32-bit floating point number. Numbers with a % suffix are automatically divided by 100, so for example 30% is the same as 0.30.
stringUTF-8 encoded, reference counted string.
colorRGB color with an alpha channel, with 8 bit precision for each channel.
lengthThe type used for x, y, width and height coordinates. This is an amount of physical pixels. To convert from an integer to a length unit, one can simply multiply by 1px. Or to convert from a length to a float, one can divide by 1px.
logical_lengthCorresponds to a literal like 1lx, 1pt, 1in, 1mm, or 1cm. It can be converted to and from length provided the binding is run in a context where there is an access to the device pixel ratio.
durationType for the duration of animations. A suffix like ms (milisecond) or s (second) is used to indicate the precision.
easingProperty animation allow specifying an easing curve. Valid values are linear (values are interpolated linearly) and the four common cubiz-bezier functions known from CSS: ease, ease_in, ease_in_out, ease_out.

Please see the language specific API references how these types are mapped to the APIs of the different programming languages.

Custom named structures

It is possible to define a new type as a component without a base type. For example:

export Player := {
    property<string> name;
    property<int> score;
}

Example := Window {
    property<Player> player: { name: "Foo", score: 100 };
}

Signal

Components may declare signals, that allow it to communicate changes of state to the outside. Signals are emitted by "calling" them and you can react to signal emissions by declaring a handler using the => arrow syntax. The built-in TouchArea element comes with a clicked signal, that's emitted when the user touches the rectangular area covered by the element, or clicks into it with the mouse. In the example below, the emission of that signal is forwarded to another custom signal (hello) by declaring a handler and emitting our custom signal:

Example := Rectangle {
    // declare a signal
    signal hello;

    area := TouchArea {
        // sets a handler with `=>`
        clicked => {
            // emit the signal
            root.hello()
        }
    }
}

It is also possible to add parameters to the signal.

Example := Rectangle {
    // declares a signal
    signal hello(int, string);
    hello(aa, bb) => { /* ... */ }
}

Expressions

Expressions are a powerful way to declare relationships and connections in your user interface. They are typically used to combine basic arithmetic with access to properties of other elements. When these properties change, the expression is automatically re-evaluated and a new value is assigned to the property the expression is associated with:

Example := Rectangle {
    // declare a property of type int
    property<int> my_property;

    // This accesses the property
    width: root.my_property * 20px;

}

If something changes my_property, the width will be updated automatically.

Arithmetic in expression works like in most programming language with the operators *, +, -, /:

Example := Rectangle {
    property <int> p: 1 * 2 + 3 * 4; // same as (1 * 2) + (3 * 4)
}

You can access properties by addressing the associated element, followed by a . and the property name:

Example := Rectangle {
    foo := Rectangle {
        x: 42lx;
    }
    x: foo.x;
}

Strings

Strings can be used with surrounding quotes: "foo". (TODO: escaping, support using stuff like `hello {foo}`) (TODO: translations: tr!"Hello")

Example := Text {
    text: "hello";
}

Colors

Color literals follow the syntax of CSS:

Example := Rectangle {
    color: blue;
    property<color> c1: #ffaaff;
}

(TODO: currently color name are only limited to a handfull and only supported in color property)

Arrays/Objects

Arrays are currently only supported in for expressions. [1, 2, 3] is an array of integers. All the types in the array have to be of the same type. It is usefull to have arrays of objects. An Object is between curly braces: { a: 12, b: "hello"}.

Statements

Inside signal handlers, more complicated statements are allowed:

Assignment:

clicked => { some_property = 42; }

Self-assignement with += -= *= /=

clicked => { some_property += 42; }

Calling a signal

clicked => { root.some_signal(); }

Conditional expression

clicked => {
    if (condition) {
        foo = 42;
    } else {
        bar = 28;
    }
}

Empty expression

clicked => { }
// or
clicked => { ; }

Repetition

The for syntax

Example := Window {
    height: 100lx;
    width: 300lx;
    for my_color[index] in [ #e11, #1a2, #23d ]: Rectangle {
        height: 100lx;
        width: 60lx;
        x: width * index;
        color: my_color;
    }
}

Animations

Simple animation that animates a property can be declared with animate like so:

Example := Rectangle {
    property<bool> pressed;
    color: pressed ? blue : red;
    animate color {
        duration: 100ms;
    }
}

This will aniate the color property for 100ms when it changes.

Animation can be configured with the following parameter:

  • duration: the amount of time it takes for the animation to complete
  • loop_count: FIXME
  • easing: can be linear, ease, ease_in, ease_out, ease_in_out, cubic_bezier(a, b, c, d) as in CSS

It is also possible to animate sevaral properties with the same animation:

animate x, y { duration: 100ms; }

is the same as

animate x { duration: 100ms; }
animate y { duration: 100ms; }

States

The states statement alow to declare states like so:

Example := Rectangle {
    text := Text { text: "hello"; }
    property<bool> pressed;
    property<bool> is_enabled;

    states [
        disabled when !is_enabled : {
            color: gray; // same as root.color: gray;
            text.color: white;
        }
        down when pressed : {
            color: blue;
        }
    ]
}

In that example, when the is_enabled property is set to false, the disabled state will be entered This will change the color of the Rectangle and of the Text.

Transitions (TODO)

Complex animations can be declared on state transitions:

Example := Rectangle {
    text := Text { text: "hello"; }
    property<bool> pressed;
    property<bool> is_enabled;

    states [
        disabled when !is_enabled : {
            color: gray; // same as root.color: gray;
            text.color: white;
        }
        down when pressed : {
            color: blue;
        }
    ]

    transitions [
        to down : {
            animate color { duration: 300ms; }
        }
        out disabled : {
            animate * { duration: 800ms; }
        }
    ]
}

Modules

Components declared in a .60 file can be shared with components in other .60 files, by means of exporting and importing them. By default, everything declared in a .60 file is private, but it can be made accessible from the outside using the export keyword:

ButtonHelper := Rectangle {
    // ...
}

Button := Rectangle {
    // ...
    ButtonHelper {
        // ...
    }
}

export { Button }

In the above example, Button is usable from other .60 files, but ButtonHelper isn't.

It's also possible to change the name just for the purpose of exporting, without affecting its internal use:

Button := Rectangle {
    // ...
}

export { Button as ColorButton }

In the above example, Button is not accessible from the outside, but instead it is available under the name ColorButton.

For convenience, a third way of exporting a component is to declare it exported right away:

export Button := Rectangle {
    // ...
}

Similarly, components exported from other files can be accessed by importing them:

import { Button } from "./button.60";

App := Rectangle {
    // ...
    Button {
        // ...
    }
}

In the event that two files export a type under the same then, then you have the option of assigning a different name at import type:

import { Button } from "./button.60";
import { Button as CoolButton } from "../other_theme/button.60";

App := Rectangle {
    // ...
    CoolButton {} // from cool_button.60
    Button {} // from button.60
}

Focus Handling

Certain elements such as TextInput accept not only input from the mouse/finger but also key events originating from (virtual) keyboards. In order for an item to receive these events, it must have the focus. This is visible through the has_focus property.

Items themselves may acquire the focus as a result of user input such as a mouse click. In addition, developers can specify which items shall receive the focus when the application window initially receives the focus. This is specified through the initial_focus property, for which bindings may be declared at the root of components. For example in the following scene with two TextInput elements, it is the second one that'll have the initial keyboard focus when they're shown:

App := Window {
    initial_focus: second_input_field;

    GridLayout {
        Row {
            first_input_field := TextInput {}
        }
        Row {
            second_input_field := TextInput {}
        }
    }
}

The initial focus may also be propagated through reusable components:

LabeledInput := GridLayout {
    initial_focus: input;
    Row {
        Text {
            text: "Input Label:";
        }
        input := TextInput {}
    }
}

App := Window {
    initial_focus: label2;

    GridLayout {
        label1 := LabeledInput {}
        label2 := LabeledInput {}
    }
}

It's also possible to manually activate the focus on elements such as TextInput:

import { Button } from "sixtyfps_widgets.60";

App := Window {
    GridLayout {
        Button {
            text: "press me";
            clicked => { input.focus(); }
        }
        input := TextInput {
            text: "I am a text input field";
        }
    }
}