Upcoming Changes to the Slint Language - Part 1

November 4, 2022
Olivier Goffart

Update: Slint 0.3.4 is the first version to include all these improvements.

Our design for the Slint language combines the old with the new: our extensive experience with QML and a fresh canvas of possibilities. We also recognize that modern HTML/CSS provides features that make certain aspects of UI design very convenient.

When we develop new features, we follow these guiding principles:

In the past year we've added various small features to the language, while maintaining stability and backwards compatibility. Meanwhile, we've collected feedback from our users and gained experience by implementing some designs ourselves. We gathered various ideas to improve the language itself and we think that now is a good time to implement these: Since we're still at version 0.x and need to make these changes before we reach version 1.0.

In the spirit of supporting existing users:

For any of the changes that we're planning we'd like to include you: If you have ideas, suggestions, or feedback, please add a comment in our main issue #1750 on GitHub, or chat with us in our Mattermost instance. With your help we can make Slint even better.

Now let's look at the changes we're working on:

Declaring Components

Currently, this is how you declare a component:

struct MyStruct := { foo: string, bar: int }
global MyGlobal := { property<string> foo; }
MyComponent := Rectangle { /*...*/ }

We used the := character sequence to consistently declare named elements. In this example:

We find that the readability of this snippet improves if we emphasize the declaration, instead of the naming. Therefore, in the new syntax we propose keywords¹ to declare data structures and components:

struct MyStruct { foo: string, bar: int }
global MyGlobal { property<string> foo; }
component MyComponent { /*...*/ }
component AnotherComponent inherits MyComponent { /*...*/ }

The parser detects use of the new syntax and supports using both in the same file simultaneously.

Another change is: Making component inheritance optional. This is important because sometimes we've noticed that, the properties of the base component were unintentionally exposed in the public API. Now, the recommended way is to avoid inheritance, unless you need it.

See issue #1682 for more details.

Input or Output Properties

There's a barely visible difference between the Rectangle::background, TouchArea::has-mouse, and LineEdit::text properties. Can you tell?

The answer is tricky: They differ in who can modify them.

The new syntax to declare properties makes these differences visible, so you can use them when writing your own components:

component FooWidget {
  in property<color> background;
  out property<bool> has-mouse;
  in-out property<string> text;
  property<int> internal-state; //Private by default
  /* ... */
}

See issue #191 for more details.

Update: The original proposal used input and output keywords, but they have been shortened to in and out and this paragraph has been updated accordingly.

Changes in Lookup Order in Expressions

When looking up an identifier in expressions, we try to find out what it refers to in the following order:

  1. Find an element with the same name.
  2. Else find a property in self.
  3. Else find a property in any models that are in scope.
  4. Else find a property in the root element.

This order has a downside: Adding properties to an element might break existing code that uses this element, because in step 2 new properties get injected.

Within components declared with the new syntax, we apply a new order:

  1. Find an element with the same name.
  2. Else find a property in self if it's declared in the current component.
  3. Else find a property in any models that are in scope.

If this fails, you need to explicitly qualify your properties with self or root.

component MyWidget inherits Button {
  property <int> hello;
  Rectangle {
    property <int> world;
    Text {
      property <int> hi : hello;
      text: world; // works now (did not work before)
      text: width / 1px; // ERROR: we don't find self.width anymore,
    }
  }
}

See issue #273 for more details.

Conclusion

All of these changes are already implemented in our development branch in git. Support for the new syntax is not enabled by default, but you can enable it by setting the SLINT_EXPERIMENTAL_SYNTAX environment variable:

export SLINT_EXPERIMENTAL_SYNTAX=1

We also have an updater tool in our repository, which can upgrade all your files to the new syntax:

cargo run -p slint-updater -- -i /path/to/my/app/ui/**/*.slint

We're planning more changes in the future, so keep an eye out for future blog posts. For example, we're working on the following issues:

We'd love to get your feedback. What do you think about these changes? Let us know by commenting in the issue #1750 or any of its linked issues, post in our GitHub Discussion Forum, or chat with us directly in our Mattermost instance.

Update: Continuation in Part 2.


¹ Note that the Slint language only has context sensitive keywords. There are, in fact, no global keywords. That means that you can write the following 🤪 (not recommended!):

struct struct { property: string, int: int }
global global { property<struct> property; }
component inherits { /*...*/ }
component component inherits inherits { /*...*/ }