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 Recipe := 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 Recipe := Window {
width: 200px;
height: 100px;
rect := Rectangle {
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 Recipe := Window {
width: 200px;
height: 100px;
rect := Rectangle {
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";
Circle := Rectangle {
width: 30px;
height: 30px;
border-radius: width / 2;
animate x { duration: 250ms; easing: ease-in; }
animate y { duration: 250ms; easing: ease-in-out; }
animate background { duration: 250ms; }
}
export Recipe := 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: min-height;
b1 := Button {
text: "State 1";
}
b2 := Button {
text: "State 2";
}
}
Rectangle {
background: root.background.darker(20%);
width: 200px;
height: 100px;
circle1 := Circle { background: green; x: 85px; }
circle2 := Circle { background: green; x: 85px; y: 40px; }
}
}
}
Transitionsο
import { HorizontalBox, VerticalBox, Button } from "std-widgets.slint";
Circle := Rectangle {
width: 30px;
height: 30px;
border-radius: width / 2;
}
export Recipe := Window {
states [
left-aligned when b1.pressed: {
circle1.x: 0px; circle1.y: 40px;
circle2.x: 0px; circle2.y: 0px;
}
right-aligned when !b1.pressed: {
circle1.x: 170px; circle1.y: 70px;
circle2.x: 170px; circle2.y: 00px;
}
]
transitions [
in left-aligned: {
animate circle1.x, circle2.x { duration: 250ms; }
}
out left-aligned: {
animate circle1.x, circle2.x { duration: 500ms; }
}
]
VerticalBox {
HorizontalBox {
max-height: min-height;
b1 := Button {
text: "Press and hold to change state";
}
}
Rectangle {
background: root.background.darker(20%);
width: 250px;
height: 100px;
circle1 := Circle { background: green; x: 85px; }
circle2 := Circle { background: blue; x: 85px; y: 40px; }
}
}
}
Layoutsο
Verticalο
import { VerticalBox, Button } from "std-widgets.slint";
export Recipe := Window {
VerticalBox {
Button { text: "First"; }
Button { text: "Second"; }
Button { text: "Third"; }
}
}
Horizontalο
import { HorizontalBox, Button } from "std-widgets.slint";
export Recipe := Window {
HorizontalBox {
Button { text: "First"; }
Button { text: "Second"; }
Button { text: "Third"; }
}
}
Gridο
import { GridBox, Button, Slider } from "std-widgets.slint";
export Recipe := 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 := {
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);
}
}
}
}
}
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);
});
// ...
}
Custom widgetsο
ToggleSwitchο
export ToggleSwitch := Rectangle {
callback toggled;
property <string> text;
property <bool> checked;
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: 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 + 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, 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 Recipe := 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 MySlider := Rectangle {
property<float> maximum: 100;
property<float> minimum: 0;
property<float> value;
min-height: 24px;
min-width: 100px;
horizontal-stretch: 1;
vertical-stretch: 0;
border-radius: height/2;
background: touch.pressed ? #eee: #ddd;
border-width: 1px;
border-color: background.darker(25%);
handle := Rectangle {
width: height;
height: parent.height;
border-width: 3px;
border-radius: height / 2;
background: touch.pressed ? #f8f: touch.has-hover ? #66f : #0000ff;
border-color: background.darker(15%);
x: (root.width - handle.width) * (value - minimum)/(maximum - minimum);
}
touch := TouchArea {
property <float> pressed-value;
pointer-event(event) => {
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
pressed-value = root.value;
}
}
moved => {
if (enabled && pressed) {
value = max(root.minimum, min(root.maximum,
pressed-value + (touch.mouse-x - touch.pressed-x) * (maximum - minimum) / (root.width - handle.width)));
}
}
}
}
export Recipe := 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 MySlider := Rectangle {
property<float> maximum: 100;
property<float> minimum: 0;
property<float> value;
min-height: 24px;
min-width: 100px;
horizontal-stretch: 1;
vertical-stretch: 0;
border-radius: height/2;
background: touch.pressed ? #eee: #ddd;
border-width: 1px;
border-color: background.darker(25%);
handle := Rectangle {
width: height;
height: parent.height;
border-width: 3px;
border-radius: height / 2;
background: touch.pressed ? #f8f: touch.has-hover ? #66f : #0000ff;
border-color: background.darker(15%);
x: (root.width - handle.width) * (value - minimum)/(maximum - minimum);
touch := TouchArea {
moved => {
if (enabled && pressed) {
value = max(root.minimum, min(root.maximum,
value + (mouse-x - pressed-x) * (maximum - minimum) / root.width));
}
}
}
}
}
export Recipe := 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 Recipe := Window {
preferred-height: 200px;
property <int> active-tab;
VerticalLayout {
tab_bar := HorizontalLayout {
spacing: 3px;
Button {
text: "Red";
clicked => { active-tab = 0; }
}
Button {
text: "Blue";
clicked => { active-tab = 1; }
}
Button {
text: "Green";
clicked => { active-tab = 2; }
}
}
Rectangle {
clip: true;
Rectangle {
background: red;
x: active-tab == 0 ? 0 : active-tab < 0 ? - width - 1px : parent.width + 1px;
animate x { duration: 125ms; easing: ease; }
}
Rectangle {
background: blue;
x: active-tab == 1 ? 0 : active-tab < 1 ? - width - 1px : parent.width + 1px;
animate x { duration: 125ms; easing: ease; }
}
Rectangle {
background: green;
x: active-tab == 2 ? 0 : active-tab < 2 ? - width - 1px : parent.width + 1px;
animate x { duration: 125ms; easing: ease; }
}
}
}
}