mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-28 22:34:08 +00:00
569 lines
17 KiB
Text
569 lines
17 KiB
Text
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
global Palette {
|
|
out property <color> window-background: #eee;
|
|
out property <color> widget-background: #ddd;
|
|
out property <color> widget-stroke: #888;
|
|
out property <color> window-border: #ccc;
|
|
out property <color> text-color: #666;
|
|
out property <color> hyper-blue: #90d1ff;
|
|
|
|
}
|
|
|
|
//------ MdiWindow ----
|
|
|
|
component MdiWindow inherits Rectangle {
|
|
in property <string> title;
|
|
in-out property <length> window-x <=> window.x;
|
|
in-out property <length> window-y <=> window.y;
|
|
in-out property <bool> is-open: true;
|
|
|
|
width: 100%;
|
|
height: 100%;
|
|
|
|
|
|
window := Rectangle {
|
|
property <length> open-width: l.preferred-width;
|
|
property <length> open-height: l.preferred-height;
|
|
|
|
x:0;
|
|
y:0;
|
|
background: Palette.window-background;
|
|
border-width: 2px;
|
|
border-color: Palette.window-border;
|
|
border-radius: 6px;
|
|
drop-shadow-blur: 25px;
|
|
drop-shadow-color: Palette.window-border;
|
|
width: l.preferred-width;
|
|
height: l.preferred-height - hidden.preferred-height;
|
|
clip: true;
|
|
|
|
states [
|
|
open when root.is-open : {
|
|
width: self.open-width;
|
|
height: self.open-height;
|
|
expand.angle: 0deg;
|
|
|
|
in { animate width, height, expand.angle { duration: 150ms; easing: ease; } }
|
|
out { animate width, height, expand.angle { duration: 150ms; easing: ease; } }
|
|
}
|
|
]
|
|
|
|
TouchArea {}
|
|
|
|
l := VerticalLayout {
|
|
padding: window.border-width;
|
|
alignment: root.is-open ? stretch : start;
|
|
title_bar := TouchArea {
|
|
moved => {
|
|
if (self.pressed) {
|
|
root.window-x += self.mouse-x - self.pressed-x;
|
|
root.window-y += self.mouse-y - self.pressed-y;
|
|
}
|
|
}
|
|
|
|
HorizontalLayout {
|
|
padding: window.border-width;
|
|
spacing: window.border-width * 2;
|
|
|
|
expand := TouchArea {
|
|
clicked => { root.is-open = !root.is-open; }
|
|
|
|
property <angle> angle: -90deg;
|
|
width: 30px;
|
|
|
|
Path {
|
|
stroke-width: window.border-width * (expand.has-hover ? 1.5 : 1);
|
|
stroke: parent.has-hover ? Palette.widget-stroke.darker(100%) : Palette.widget-stroke;
|
|
viewbox-x: -1.5;
|
|
viewbox-y: -1.5;
|
|
viewbox-height: 3;
|
|
viewbox-width: 3;
|
|
|
|
MoveTo { x: cos(expand.angle) * -1 - sin(expand.angle) * -1; y: sin(expand.angle) * -1 + cos(expand.angle) * -1; }
|
|
LineTo { x: cos(expand.angle) * 1 - sin(expand.angle) * -1; y: sin(expand.angle) * 1 + cos(expand.angle) * -1; }
|
|
LineTo { x: cos(expand.angle) * 0 - sin(expand.angle) * 1; y: sin(expand.angle) * 0 + cos(expand.angle) * 1; }
|
|
LineTo { x: cos(expand.angle) * -1 - sin(expand.angle) * -1; y: sin(expand.angle) * -1 + cos(expand.angle) * -1; }
|
|
}
|
|
}
|
|
Text {
|
|
text: root.title;
|
|
horizontal-alignment: center;
|
|
color: Palette.text-color;
|
|
}
|
|
close_button := TouchArea {
|
|
clicked => { root.visible = false; }
|
|
|
|
width: 30px;
|
|
|
|
Path {
|
|
stroke-width: window.border-width * (close-button.has-hover ? 1.5 : 1);
|
|
stroke: parent.has-hover ? Palette.widget-stroke.darker(100%) : Palette.widget-stroke;
|
|
viewbox-x: -1.5;
|
|
viewbox-y: -1.5;
|
|
viewbox-height: 3;
|
|
viewbox-width: 3;
|
|
|
|
MoveTo { x: -1; y: -1; }
|
|
LineTo { x: 1; y: 1; }
|
|
MoveTo { x: -1; y: 1; }
|
|
LineTo { x: 1; y: -1; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
hidden := VerticalLayout {
|
|
visible: root.is-open;
|
|
|
|
Rectangle {
|
|
height: window.border-width;
|
|
background: window.border-color;
|
|
}
|
|
|
|
@children
|
|
}
|
|
}
|
|
|
|
if root.is-open : resize-handle := TouchArea {
|
|
moved => {
|
|
if (self.pressed) {
|
|
window.open-width = max(l.min-width, min(l.max-width, window.open-width + self.mouse-x - self.pressed-x));
|
|
window.open-height = max(l.min-height, min(l.max-height, window.open-height + self.mouse-y - self.pressed-y));
|
|
}
|
|
}
|
|
|
|
width: 20px;
|
|
height: self.width;
|
|
x: parent.width - self.width;
|
|
y: parent.height - self.height;
|
|
mouse-cursor: MouseCursor.nwse-resize;
|
|
|
|
Path {
|
|
stroke-width: window.border-width;
|
|
stroke: Palette.window-border;
|
|
viewbox-height: 1.2; viewbox-width: 1.2;
|
|
|
|
MoveTo { x: 0; y: 1; }
|
|
LineTo { x: 1; y: 0; }
|
|
MoveTo { x: 0.4; y: 1; }
|
|
LineTo { x: 1; y: 0.4; }
|
|
MoveTo { x: 0.8; y: 1; }
|
|
LineTo { x: 1; y: 0.8; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------ Widgets ------
|
|
|
|
import {LineEdit, TextEdit, ComboBox, GridBox, VerticalBox, HorizontalBox} from "std-widgets.slint";
|
|
|
|
component Label inherits Text {
|
|
color: Palette.text-color;
|
|
}
|
|
|
|
component Button inherits TouchArea {
|
|
in property text <=> t.text;
|
|
|
|
min-height: t.min-height;
|
|
min-width: t.min-width + 10px;
|
|
|
|
Rectangle {
|
|
border-width: 1.5px;
|
|
border-color: root.has-hover ? Palette.widget-stroke : transparent;
|
|
border-radius: 3px;
|
|
background: root.pressed ? Palette.widget-background.darker(30%) : Palette.widget-background;
|
|
|
|
t := Label {
|
|
y:0;
|
|
width: 100%;
|
|
horizontal-alignment: center;
|
|
}
|
|
}
|
|
}
|
|
|
|
component CheckBox inherits TouchArea {
|
|
in property text <=> t.text;
|
|
in-out property <bool> checked;
|
|
|
|
clicked => { root.checked = !root.checked; }
|
|
|
|
HorizontalLayout {
|
|
spacing: 5px;
|
|
|
|
VerticalLayout {
|
|
alignment: center;
|
|
|
|
Rectangle {
|
|
width: 20px;
|
|
height: 20px;
|
|
border-width: 1.5px;
|
|
border-color: root.has-hover ? Palette.widget-stroke : transparent;
|
|
border-radius: 3px;
|
|
background: root.pressed ? Palette.widget-background.darker(30%) : Palette.widget-background;
|
|
|
|
if root.checked : Path {
|
|
stroke: root.has-hover ? Palette.widget-stroke.darker(100%) : Palette.widget-stroke;
|
|
stroke-width: root.pressed ? 2.5px : 2px;
|
|
viewbox-height: 1; viewbox-width: 1;
|
|
|
|
MoveTo { x: 0.2; y: 0.5; }
|
|
LineTo { x: 0.5; y: 0.8; }
|
|
LineTo { x: 0.8; y: 0.2; }
|
|
}
|
|
}
|
|
}
|
|
t := Label {
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
component RadioButton inherits TouchArea {
|
|
in property text <=> t.text;
|
|
in-out property <bool> checked;
|
|
|
|
HorizontalLayout {
|
|
spacing: 5px;
|
|
|
|
VerticalLayout {
|
|
alignment: center;
|
|
|
|
Rectangle {
|
|
width: 20px;
|
|
height: 20px;
|
|
border-width: 1.5px;
|
|
border-color: root.has-hover ? Palette.widget-stroke : transparent;
|
|
border-radius: self.width / 2;
|
|
background: root.pressed ? Palette.widget-background.darker(30%) : Palette.widget-background;
|
|
|
|
if root.checked : Rectangle {
|
|
background: root.has-hover ? Palette.widget-stroke.darker(100%) : Palette.widget-stroke;
|
|
border-radius: self.width / 2;
|
|
width: parent.width / 2;
|
|
height: parent.width / 2;
|
|
x: parent.width / 4;
|
|
y: parent.width / 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
t := Label {
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
component SelectableLabel inherits TouchArea {
|
|
in-out property <bool> checked;
|
|
in-out property text <=> t.text;
|
|
|
|
min-height: t.min-height;
|
|
min-width: t.min-width + 10px;
|
|
|
|
Rectangle {
|
|
border-width: 1.5px;
|
|
border-color: root.has-hover ? Palette.widget-stroke : transparent;
|
|
border-radius: 3px;
|
|
background:
|
|
root.checked ? Palette.hyper-blue :
|
|
root.pressed ? Palette.widget-background.darker(30%) :
|
|
root.has-hover ? Palette.widget-background : transparent;
|
|
|
|
t := Label {
|
|
y:0;
|
|
width: 100%;
|
|
horizontal-alignment: center;
|
|
}
|
|
}
|
|
}
|
|
|
|
component Slider inherits Rectangle {
|
|
in property <bool> enabled <=> touch.enabled;
|
|
in property <float> maximum: 100;
|
|
in property <float> minimum: 0;
|
|
in-out property <float> value;
|
|
|
|
callback changed(float);
|
|
|
|
min-height: 24px;
|
|
min-width: 100px;
|
|
horizontal-stretch: 1;
|
|
vertical-stretch: 0;
|
|
|
|
Rectangle {
|
|
width: parent.width;
|
|
height: parent.height / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
border-radius: 2px;
|
|
background: Palette.widget-background;
|
|
}
|
|
|
|
handle := Rectangle {
|
|
width: self.height;
|
|
height: parent.height;
|
|
border-radius: self.height / 2;
|
|
border-color: touch.has-hover ? Palette.widget-stroke.darker(100%) : Palette.widget-stroke;
|
|
border-width: touch.pressed ? 4px : touch.has-hover ? 3px : 2px;
|
|
background: touch.pressed ? Palette.widget-background.darker(30%) : Palette.widget-background;
|
|
x: (root.width - handle.width) * max(0, min(1, (root.value - root.minimum)/(root.maximum - root.minimum)));
|
|
}
|
|
|
|
touch := TouchArea {
|
|
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)));
|
|
root.changed(root.value);
|
|
}
|
|
}
|
|
|
|
property <float> pressed-value;
|
|
|
|
width: parent.width;
|
|
height: parent.height;
|
|
}
|
|
}
|
|
|
|
component Hyperlink inherits Text {
|
|
in-out property <string> link;
|
|
|
|
color: Palette.hyper-blue;
|
|
|
|
TouchArea { mouse-cursor: pointer; }
|
|
}
|
|
|
|
component DragValue inherits TouchArea {
|
|
in-out property <float> value;
|
|
in-out property <float> _pressed-value;
|
|
|
|
moved => {
|
|
if (root.pressed) {
|
|
root.value = root._pressed-value +(root.mouse-x - root.pressed-x) / 2px;
|
|
}
|
|
}
|
|
pointer-event(e) => {
|
|
if (e.kind == PointerEventKind.down) {
|
|
root._pressed-value = root.value;
|
|
}
|
|
}
|
|
|
|
min-height: t.min-height;
|
|
min-width: Math.max(t.min-width + 10px, 50px);
|
|
mouse-cursor: MouseCursor.ew-resize;
|
|
|
|
Rectangle {
|
|
border-width: 1.5px;
|
|
border-color: root.has-hover ? Palette.widget-stroke : transparent;
|
|
border-radius: 3px;
|
|
background: root.pressed ? Palette.widget-background.darker(30%) : Palette.widget-background;
|
|
|
|
t := Label {
|
|
y:0;
|
|
width: 100%;
|
|
horizontal-alignment: center;
|
|
text: round(root.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
component ProgressBar inherits Rectangle {
|
|
in-out property <float> value;
|
|
|
|
min-height: 24px;
|
|
min-width: 100px;
|
|
background: Palette.window-background;
|
|
border-radius: root.height / 2;
|
|
|
|
Rectangle {
|
|
x:0;
|
|
height: 100%;
|
|
width: self.height + (parent.width - self.height) * max(0, min(1, root.value / 100));
|
|
border-radius: self.height / 2;
|
|
background: Palette.hyper-blue;
|
|
}
|
|
|
|
Label {
|
|
height: 100%;
|
|
vertical-alignment: center;
|
|
x: self.height/2;
|
|
text: round(root.value) + "%";
|
|
}
|
|
}
|
|
|
|
|
|
//------ Demo apps -------
|
|
|
|
component Gallery inherits GridBox {
|
|
|
|
function unsel() { r1.checked = false; r2.checked = false; r3.checked = false; }
|
|
|
|
Row {
|
|
Text { text: "Label:"; }
|
|
Label { text: "Welcome to the widget gallery!"; }
|
|
}
|
|
|
|
Row {
|
|
Text { text: "Hyperlink:"; }
|
|
Hyperlink { text: "Slint homepage"; link: "https://slint.dev"; }
|
|
}
|
|
|
|
Row {
|
|
Text { text: "TextEdit:"; }
|
|
LineEdit { placeholder-text: "WriteSomething here";}
|
|
}
|
|
|
|
Row {
|
|
Text { text: "Button:"; }
|
|
HorizontalLayout {
|
|
alignment: start;
|
|
Button { text: "Click me!"; clicked => { cb.checked = !cb.checked; } }
|
|
}
|
|
}
|
|
|
|
Row {
|
|
Text { text: "Checkbox:"; }
|
|
cb := CheckBox { text: "Checkbox"; }
|
|
}
|
|
|
|
Row {
|
|
Text { text: "RadioButton:"; }
|
|
HorizontalBox {
|
|
alignment: start;
|
|
r1 := RadioButton { text: "First"; clicked => { root.unsel(); r1.checked = true; } }
|
|
r2 := RadioButton { text: "Second"; clicked => { root.unsel(); r2.checked = true; } }
|
|
r3 := RadioButton { text: "Third"; clicked => { root.unsel(); r3.checked = true; } }
|
|
}
|
|
}
|
|
|
|
Row {
|
|
Text { text: "SelectableLabel:"; }
|
|
HorizontalBox {
|
|
alignment: start;
|
|
SelectableLabel { text: "First"; checked <=> r1.checked; clicked => { root.unsel(); r1.checked = true; } }
|
|
SelectableLabel { text: "Second"; checked <=> r2.checked; clicked => { root.unsel(); r2.checked = true; } }
|
|
SelectableLabel { text: "Third"; checked <=> r3.checked; clicked => { root.unsel(); r3.checked = true; } }
|
|
}
|
|
}
|
|
|
|
Row {
|
|
Text { text: "ComboBox:"; }
|
|
|
|
HorizontalBox {
|
|
alignment: start;
|
|
ComboBox {
|
|
selected => {
|
|
r1.checked = self.current-index == 0;
|
|
r2.checked = self.current-index == 1;
|
|
r3.checked = self.current-index == 2;
|
|
}
|
|
|
|
model: ["First", "Second", "Third"];
|
|
}
|
|
|
|
Label { text: "Take your pick"; }
|
|
}
|
|
}
|
|
|
|
Row {
|
|
Text { text: "Slider:"; }
|
|
sl := Slider { }
|
|
}
|
|
|
|
Row {
|
|
Text { text: "DragValue:"; }
|
|
|
|
HorizontalLayout {
|
|
alignment: start;
|
|
DragValue { value <=> sl.value; }
|
|
}
|
|
}
|
|
|
|
Row {
|
|
Text { text: "ProgressBar:"; }
|
|
ProgressBar { value <=> sl.value; }
|
|
}
|
|
|
|
Rectangle {}
|
|
}
|
|
|
|
component TextEditDemo inherits VerticalLayout {
|
|
preferred-height: 150px;
|
|
preferred-width: 300px;
|
|
|
|
TextEdit {
|
|
text: "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";
|
|
}
|
|
}
|
|
|
|
export component Demo inherits Window {
|
|
preferred-width: 1024px;
|
|
preferred-height: 800px;
|
|
background: white;
|
|
|
|
w1 := MdiWindow {
|
|
x:0;y:0;
|
|
title: "🗄 Widget Gallery";
|
|
window-x: 30px;
|
|
window-y: 20px;
|
|
|
|
Gallery { }
|
|
}
|
|
w2 := MdiWindow {
|
|
x:0;y:0;
|
|
visible: false;
|
|
title: "🗉 TextEdit";
|
|
window-x: 230px;
|
|
window-y: 520px;
|
|
|
|
TextEditDemo { }
|
|
}
|
|
|
|
side-panel := Rectangle {
|
|
border-color: resize-side-panel.has-hover ? Palette.window-border.darker(100%) : Palette.window-border;
|
|
border-width: 2px;
|
|
background: Palette.window-background;
|
|
x: parent.width - self.width;
|
|
width: side-panel-l.preferred-width;
|
|
height: 100%;
|
|
|
|
side-panel-l := VerticalBox {
|
|
alignment: start;
|
|
|
|
Label {
|
|
font-weight: 500;
|
|
text: "Slint Demos";
|
|
horizontal-alignment: center;
|
|
}
|
|
Rectangle { height: 2px; background: Palette.window-border; }
|
|
Label {
|
|
preferred-width: 0px;
|
|
text: "This is a demo which is based on the demo from the egui framework";
|
|
wrap: word-wrap;
|
|
horizontal-alignment: center;
|
|
}
|
|
Rectangle { height: 2px; background: Palette.window-border; }
|
|
CheckBox { text: w1.title; checked <=> w1.visible; }
|
|
CheckBox { text: w2.title; checked <=> w2.visible; }
|
|
|
|
}
|
|
resize-side-panel := TouchArea {
|
|
moved => {
|
|
if (self.pressed) {
|
|
side-panel.width = max(side-panel-l.min-width, min(root.width, side-panel.width - (self.mouse-x - self.pressed-x)));
|
|
}
|
|
}
|
|
|
|
x:0;
|
|
height: 100%;
|
|
width: 4px;
|
|
mouse-cursor: ew-resize;
|
|
}
|
|
}
|
|
}
|