Recipes and Examples๏
This page provides a collection of common use-cases and how to implement them using Slint.
Get Started๏
Use property bindings to synchronize controls๏
import { VerticalBox, Slider } from "std-widgets.slint";
export component Recipe inherits Window {
VerticalBox {
slider := Slider {
maximum: 100;
}
Text {
text: "Value: \{round(slider.value)}";
}
}
}
This example introduces the Slider
widget.
It also introduces interpolation in string literal: Use \{...}
in strings to render
code between the curly braces to a string.
Animations๏
Animate the position of an element๏
import { CheckBox } from "std-widgets.slint";
export component Recipe inherits Window {
width: 200px;
height: 100px;
rect := Rectangle {
x:0;
y: 5px;
width: 40px;
height: 40px;
background: blue;
animate x {
duration: 500ms;
easing: ease-in-out;
}
}
CheckBox {
y: 25px;
text: "Align rect to the right";
toggled => {
if (self.checked) {
rect.x = parent.width - rect.width;
} else {
rect.x = 0px;
}
}
}
}
Layouts are typically used to position elements automatically. In this example they are positioned
manually using the x
, y
, width
, height
properties.
Notice the animate x
block that specifies an animation. It is run when the property
changes: Either because the property is set in a callback, or if its binding value changes.
Animation Sequence๏
import { CheckBox } from "std-widgets.slint";
export component Recipe inherits Window {
width: 200px;
height: 100px;
rect := Rectangle {
x:0;
y: 5px;
width: 40px;
height: 40px;
background: blue;
animate x {
duration: 500ms;
easing: ease-in-out;
}
animate y {
duration: 250ms;
delay: 500ms;
easing: ease-in;
}
}
CheckBox {
y: 25px;
text: "Align rect bottom right";
toggled => {
if (self.checked) {
rect.x = parent.width - rect.width;
rect.y = parent.height - rect.height;
} else {
rect.x = 0px;
rect.y = 0px;
}
}
}
}
This example uses the delay
property to make one animation run after another.
States๏
Associate multiple property values with states๏
import { HorizontalBox, VerticalBox, Button } from "std-widgets.slint";
component Circle inherits Rectangle {
width: 30px;
height: 30px;
border-radius: root.width / 2;
animate x { duration: 250ms; easing: ease-in; }
animate y { duration: 250ms; easing: ease-in-out; }
animate background { duration: 250ms; }
}
export component Recipe inherits Window {
states [
left-aligned when b1.pressed: {
circle1.x: 0px; circle1.y: 40px; circle1.background: green;
circle2.x: 0px; circle2.y: 0px; circle2.background: blue;
}
right-aligned when b2.pressed: {
circle1.x: 170px; circle1.y: 70px; circle1.background: green;
circle2.x: 170px; circle2.y: 00px; circle2.background: blue;
}
]
VerticalBox {
HorizontalBox {
max-height: self.min-height;
b1 := Button {
text: "State 1";
}
b2 := Button {
text: "State 2";
}
}
Rectangle {
background: root.background.darker(20%);
width: 200px;
height: 100px;
circle1 := Circle { y:0; background: green; x: 85px; }
circle2 := Circle { background: green; x: 85px; y: 40px; }
}
}
}
Transitions๏
import { HorizontalBox, VerticalBox, Button } from "std-widgets.slint";
component Circle inherits Rectangle {
width: 30px;
height: 30px;
border-radius: root.width / 2;
}
export component Recipe inherits Window {
states [
left-aligned when b1.pressed: {
circle1.x: 0px; circle1.y: 40px;
circle2.x: 0px; circle2.y: 0px;
in {
animate circle1.x, circle2.x { duration: 250ms; }
}
out {
animate circle1.x, circle2.x { duration: 500ms; }
}
}
right-aligned when !b1.pressed: {
circle1.x: 170px; circle1.y: 70px;
circle2.x: 170px; circle2.y: 00px;
}
]
VerticalBox {
HorizontalBox {
max-height: self.min-height;
b1 := Button {
text: "Press and hold to change state";
}
}
Rectangle {
background: root.background.darker(20%);
width: 250px;
height: 100px;
circle1 := Circle { y:0; background: green; x: 85px; }
circle2 := Circle { background: blue; x: 85px; y: 40px; }
}
}
}
Layouts๏
Vertical๏
import { VerticalBox, Button } from "std-widgets.slint";
export component Recipe inherits Window {
VerticalBox {
Button { text: "First"; }
Button { text: "Second"; }
Button { text: "Third"; }
}
}
Horizontal๏
import { HorizontalBox, Button } from "std-widgets.slint";
export component Recipe inherits Window {
HorizontalBox {
Button { text: "First"; }
Button { text: "Second"; }
Button { text: "Third"; }
}
}
Grid๏
import { GridBox, Button, Slider } from "std-widgets.slint";
export component Recipe inherits Window {
GridBox {
Row {
Button { text: "First"; }
Button { text: "Second"; }
}
Row {
Button { text: "Third"; }
Button { text: "Fourth"; }
}
Row {
Slider {
colspan: 2;
}
}
}
}
Global Callbacks๏
Invoke a globally registered native callback from Slint๏
This example uses a global singleton to implement common logic in native code. It can also be used to store properties and they can be set by native code.
Please note that in the preview only visualize the slint code, but is not connected to the native code.
import { HorizontalBox, VerticalBox, LineEdit } from "std-widgets.slint";
export global Logic {
pure callback to-upper-case(string) -> string;
// You can collect other global properties here
}
export component Recipe inherits Window {
VerticalBox {
input := LineEdit {
text: "Text to be transformed";
}
HorizontalBox {
Text { text: "Transformed:"; }
// Callback invoked in binding expression
Text {
text: {
Logic.to-upper-case(input.text);
}
}
}
}
}
Rust code
In Rust you can set the callback like this:slint::slint!{
import { HorizontalBox, VerticalBox, LineEdit } from "std-widgets.slint";
export global Logic := {
callback to-upper-case(string) -> string;
// You can collect other global properties here
}
export Recipe := Window {
VerticalBox {
input := LineEdit {
text: "Text to be transformed";
}
HorizontalBox {
Text { text: "Transformed:"; }
// Callback invoked in binding expression
Text {
text: {
Logic.to-upper-case(input.text);
}
}
}
}
}
}
fn main() {
let recipe = Recipe::new();
recipe.global::<Logic>().on_to_upper_case(|string| {
string.as_str().to_uppercase().into()
});
// ...
}
C++ code
In C++ you can set the callback like this:int main(int argc, char **argv)
{
auto recipe = Recipe::create();
recipe->global<Logic>().on_to_upper_case([](slint::SharedString str) -> slint::SharedString {
std::string arg(str);
std::transform(arg.begin(), arg.end(), arg.begin(), toupper);
return slint::SharedString(arg);
});
// ...
}
Translations๏
Slint doesnโt currently provide built-in support for translations. Weโre tracking this feature in our issue tracker. Meanwhile, you can make your design translatable with a global callback, some native code, and a translation library such as gettext or fluent.
Example using gettext:
import { HorizontalBox, Button } from "std-widgets.slint";
export global Tr {
// Do the translation of the first argument, with an array of string as supstitution
pure callback gettext(string, [string]) -> string;
// A default implementation that returns the original string for preview purposes.
gettext(text, _) => { return text; }
}
export component Example inherits Window {
in-out property <int> count;
HorizontalBox {
Button {
text: Tr.gettext("Button pressed {0} times", [root.count]);
}
}
}
Then the translatable text can be extracted with xgettext
and tranlated using gettext tools:
xgettext -o example.pot example.slint
Set this callback in your native code and call gettext behind the scenes. A similar approach should be used for ngettext
,dgettext
, and other gettext functions.
Rust code
This example uses gettext-rs:
slint::slint!{
import { HorizontalBox , Button } from "std-widgets.slint";
export global Tr := {
// Do the translation of the first argument, with an array of string as supstitution
callback gettext(string, [string]) -> string;
}
Example := Window {
property <int> count;
HorizontalBox {
Button {
text: Tr.gettext("Button pressed {0} times", [count]);
}
}
}
}
fn main() {
let app = Example::new();
app.global::<Tr>().on_gettext(|string, model| {
use crate::slint::Model;
# let mut str = String::from(string.as_str()); /* (we don't depend on gettext-rs just for that)
let mut str = gettextrs::gettext(string.as_str());
# */
for (idx, to) in model.iter().enumerate() {
str = str.replace(&format!("%{}", idx + 1), to.as_str());
}
str.into()
});
//...
# return;
app.run()
}
Custom widgets๏
ToggleSwitch๏
export component ToggleSwitch inherits Rectangle {
callback toggled;
in-out property <string> text;
in-out property <bool> checked;
in-out property<bool> enabled <=> touch-area.enabled;
height: 20px;
horizontal-stretch: 0;
vertical-stretch: 0;
HorizontalLayout {
spacing: 8px;
indicator := Rectangle {
width: 40px;
border-width: 1px;
border-radius: root.height / 2;
border-color: self.background.darker(25%);
background: root.enabled ? (root.checked ? blue: white) : white;
animate background { duration: 100ms; }
bubble := Rectangle {
width: root.height - 8px;
height: bubble.width;
border-radius: bubble.height / 2;
y: 4px;
x: 4px + self.a * (indicator.width - bubble.width - 8px);
property <float> a: root.checked ? 1 : 0;
background: root.checked ? white : (root.enabled ? blue : gray);
animate a, background { duration: 200ms; easing: ease;}
}
}
Text {
min-width: max(100px, self.preferred-width);
text: root.text;
vertical-alignment: center;
color: root.enabled ? black : gray;
}
}
touch-area := TouchArea {
width: root.width;
height: root.height;
clicked => {
if (root.enabled) {
root.checked = !root.checked;
root.toggled();
}
}
}
}
export component Recipe inherits Window {
VerticalLayout {
alignment: start;
ToggleSwitch { text: "Toggle me"; }
ToggleSwitch { text: "Disabled"; enabled: false; }
}
}
CustomSlider๏
This slider can be dragged from any point within itself, because the TouchArea is covering the whole widget.
import { VerticalBox } from "std-widgets.slint";
export component MySlider inherits Rectangle {
in-out property<float> maximum: 100;
in-out property<float> minimum: 0;
in-out property<float> value;
min-height: 24px;
min-width: 100px;
horizontal-stretch: 1;
vertical-stretch: 0;
border-radius: root.height/2;
background: touch.pressed ? #eee: #ddd;
border-width: 1px;
border-color: root.background.darker(25%);
handle := Rectangle {
width: self.height;
height: parent.height;
border-width: 3px;
border-radius: self.height / 2;
background: touch.pressed ? #f8f: touch.has-hover ? #66f : #0000ff;
border-color: self.background.darker(15%);
x: (root.width - handle.width) * (root.value - root.minimum)/(root.maximum - root.minimum);
}
touch := TouchArea {
property <float> pressed-value;
pointer-event(event) => {
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
self.pressed-value = root.value;
}
}
moved => {
if (self.enabled && self.pressed) {
root.value = max(root.minimum, min(root.maximum,
self.pressed-value + (touch.mouse-x - touch.pressed-x) * (root.maximum - root.minimum) / (root.width - handle.width)));
}
}
}
}
export component Recipe inherits Window {
VerticalBox {
alignment: start;
slider := MySlider {
maximum: 100;
}
Text {
text: "Value: \{round(slider.value)}";
}
}
}
This example show another implementation that has a draggable handle: The handle only moves when we click on that handle. The TouchArea is within the handle and moves with the handle.
import { VerticalBox } from "std-widgets.slint";
export component MySlider inherits Rectangle {
in-out property<float> maximum: 100;
in-out property<float> minimum: 0;
in-out property<float> value;
min-height: 24px;
min-width: 100px;
horizontal-stretch: 1;
vertical-stretch: 0;
border-radius: root.height/2;
background: touch.pressed ? #eee: #ddd;
border-width: 1px;
border-color: root.background.darker(25%);
handle := Rectangle {
width: self.height;
height: parent.height;
border-width: 3px;
border-radius: self.height / 2;
background: touch.pressed ? #f8f: touch.has-hover ? #66f : #0000ff;
border-color: self.background.darker(15%);
x: (root.width - handle.width) * (root.value - root.minimum)/(root.maximum - root.minimum);
touch := TouchArea {
moved => {
if (self.enabled && self.pressed) {
root.value = max(root.minimum, min(root.maximum,
root.value + (self.mouse-x - self.pressed-x) * (root.maximum - root.minimum) / root.width));
}
}
}
}
}
export component Recipe inherits Window {
VerticalBox {
alignment: start;
slider := MySlider {
maximum: 100;
}
Text {
text: "Value: \{round(slider.value)}";
}
}
}
Custom Tabs๏
Use this recipe as a basis to when you want to create your own custom tab widget.
import { Button } from "std-widgets.slint";
export component Recipe inherits Window {
preferred-height: 200px;
in-out property <int> active-tab;
VerticalLayout {
tab_bar := HorizontalLayout {
spacing: 3px;
Button {
text: "Red";
clicked => { root.active-tab = 0; }
}
Button {
text: "Blue";
clicked => { root.active-tab = 1; }
}
Button {
text: "Green";
clicked => { root.active-tab = 2; }
}
}
Rectangle {
clip: true;
Rectangle {
background: red;
x: root.active-tab == 0 ? 0 : root.active-tab < 0 ? - self.width - 1px : parent.width + 1px;
animate x { duration: 125ms; easing: ease; }
}
Rectangle {
background: blue;
x: root.active-tab == 1 ? 0 : root.active-tab < 1 ? - self.width - 1px : parent.width + 1px;
animate x { duration: 125ms; easing: ease; }
}
Rectangle {
background: green;
x: root.active-tab == 2 ? 0 : root.active-tab < 2 ? - self.width - 1px : parent.width + 1px;
animate x { duration: 125ms; easing: ease; }
}
}
}
}
Table view๏
Slint doesnโt currently provide a table widget. Itโs possible to emulate it with a ListView:
import { VerticalBox, ListView } from "std-widgets.slint";
component TableView inherits Rectangle {
in property <[string]> columns;
in property <[[string]]> values;
private property <length> e: self.width / root.columns.length;
private property <[length]> column_sizes: [
root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e,
root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e,
root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e, root.e,
];
VerticalBox {
padding: 5px;
HorizontalLayout {
padding: 5px; spacing: 5px;
vertical-stretch: 0;
for title[idx] in root.columns : HorizontalLayout {
width: root.column_sizes[idx];
Text { overflow: elide; text: title; }
Rectangle {
width: 1px;
background: gray;
TouchArea {
width: 10px;
x: (parent.width - self.width) / 2;
property <length> cached;
pointer-event(event) => {
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
self.cached = root.column_sizes[idx];
}
}
moved => {
if (self.pressed) {
root.column_sizes[idx] += (self.mouse-x - self.pressed-x);
if (root.column_sizes[idx] < 0) {
root.column_sizes[idx] = 0;
}
}
}
mouse-cursor: ew-resize;
}
}
}
}
ListView {
for r in root.values : HorizontalLayout {
padding: 5px;
spacing: 5px;
for t[idx] in r : HorizontalLayout {
width: root.column_sizes[idx];
Text { overflow: elide; text: t; }
}
}
}
}
}
export component Example inherits Window {
TableView {
columns: ["Device", "Mount Point", "Total", "Free"];
values: [
["/dev/sda1", "/", "255GB", "82.2GB"] ,
["/dev/sda2", "/tmp", "60.5GB", "44.5GB"] ,
["/dev/sdb1", "/home", "255GB", "32.2GB"] ,
];
}
}
Breakpoints for responsive ui๏
Use this recipe to implement a responsive SideBar that collapses when the parent width is smaller than the given break-point. When the button is clicked, the SideBar expands if collapsed on break-point.
import { Button, StyleMetrics } from "std-widgets.slint";
export component SideBar inherits Rectangle {
private property <bool> collapsed: root.reference-width < root.break-point;
/// Defines the reference width to check `break-point`.
in-out property <length> reference-width;
/// If `reference-width` is less `break-point` the `SideBar` collapses.
in-out property <length> break-point: 600px;
/// Set the text of the expand button.
in-out property <string> expand-button-text;
width: 160px;
container := Rectangle {
private property <bool> expaned;
width: parent.width;
background: StyleMetrics.window-background.darker(0.2);
VerticalLayout {
padding: 2px;
alignment: start;
HorizontalLayout {
alignment: start;
if (root.collapsed) : Button {
checked: container.expaned;
text: root.expand-button-text;
clicked => {
container.expaned = !container.expaned;
}
}
}
@children
}
states [
expaned when container.expaned && root.collapsed : {
width: 160px;
in {
animate width { duration: 200ms; }
}
out {
animate width { duration: 200ms; }
}
in {
animate width { duration: 200ms; }
}
out {
animate width { duration: 200ms; }
}
}
]
}
states [
collapsed when root.collapsed : {
width: 62px;
}
]
}
component SideBarTest inherits Window {
preferred-width: 700px;
min-height: 400px;
GridLayout {
Rectangle {
height: 100%;
col: 1;
background: white;
HorizontalLayout {
padding: 8px;
Text {
color: black;
text: "Content";
}
}
}
SideBar {
col: 0;
reference-width: parent.width;
expand-button-text: "E";
}
}
}