mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-13 05:46:21 +00:00
508 lines
17 KiB
Text
508 lines
17 KiB
Text
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
|
|
|
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 {
|
|
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;
|
|
property <length> open-width: l.preferred-width;
|
|
property <length> open-height: l.preferred-height;
|
|
width: l.preferred-width;
|
|
height: l.preferred-height - hidden.preferred-height;
|
|
|
|
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; } }
|
|
}
|
|
]
|
|
|
|
clip: true;
|
|
|
|
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 {
|
|
width: 30px;
|
|
property <angle> angle: -90deg;
|
|
clicked => { root.is-open = !root.is-open; }
|
|
Path {
|
|
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; }
|
|
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;
|
|
}
|
|
}
|
|
Text {
|
|
text: root.title;
|
|
horizontal-alignment: center;
|
|
color: Palette.text-color;
|
|
}
|
|
close_button := TouchArea {
|
|
width: 30px;
|
|
clicked => { root.visible = false; }
|
|
Path {
|
|
MoveTo { x: -1; y: -1; }
|
|
LineTo { x: 1; y: 1; }
|
|
MoveTo { x: -1; y: 1; }
|
|
LineTo { x: 1; y: -1; }
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
hidden := VerticalLayout {
|
|
visible: root.is-open;
|
|
Rectangle {
|
|
height: window.border-width;
|
|
background: window.border-color;
|
|
}
|
|
@children
|
|
}
|
|
}
|
|
|
|
if root.is-open : resize-handle := TouchArea {
|
|
width: 20px;
|
|
height: self.width;
|
|
x: parent.width - self.width;
|
|
y: parent.height - self.height;
|
|
mouse-cursor: MouseCursor.nwse-resize;
|
|
Path {
|
|
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; }
|
|
stroke-width: window.border-width;
|
|
stroke: Palette.window-border;
|
|
viewbox-height: 1.2; viewbox-width: 1.2;
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------ Widgets ------
|
|
|
|
import {LineEdit, TextEdit, ComboBox, GridBox, VerticalBox, HorizontalBox, StyleMetrics} from "std-widgets.slint";
|
|
|
|
component Label inherits Text {
|
|
color: Palette.text-color;
|
|
}
|
|
|
|
component Button inherits TouchArea {
|
|
min-height: t.min-height;
|
|
min-width: t.min-width + 10px;
|
|
in property text <=> t.text;
|
|
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-out property <bool> checked;
|
|
in property text <=> t.text;
|
|
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-out property <bool> checked;
|
|
in property text <=> t.text;
|
|
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 {
|
|
min-height: t.min-height;
|
|
min-width: t.min-width + 10px;
|
|
in-out property <bool> checked;
|
|
in-out property text <=> t.text;
|
|
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<float> maximum: 100;
|
|
in property<float> minimum: 0;
|
|
in-out property<float> value;
|
|
in property<bool> enabled <=> touch.enabled;
|
|
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 {
|
|
width: parent.width;
|
|
height: parent.height;
|
|
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)));
|
|
root.changed(root.value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component Hyperlink inherits Text {
|
|
color: Palette.hyper-blue;
|
|
TouchArea { mouse-cursor: pointer; }
|
|
in-out property<string> link;
|
|
}
|
|
|
|
component DragValue inherits TouchArea {
|
|
in-out property <float> value;
|
|
min-height: t.min-height;
|
|
min-width: Math.max(t.min-width + 10px, 50px);
|
|
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);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
in-out property <float> _pressed-value;
|
|
mouse-cursor: MouseCursor.ew-resize;
|
|
}
|
|
|
|
component ProgressBar inherits Rectangle {
|
|
in-out property <float> value;
|
|
min-height: 24px;
|
|
min-width: 100px;
|
|
background: StyleMetrics.textedit-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-ui.com"; }
|
|
}
|
|
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 {
|
|
model: ["First", "Second", "Third"];
|
|
selected => {
|
|
r1.checked = self.current-index == 0;
|
|
r2.checked = self.current-index == 1;
|
|
r3.checked = self.current-index == 2;
|
|
}
|
|
}
|
|
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 {
|
|
x:0;
|
|
height: 100%;
|
|
width: 4px;
|
|
mouse-cursor: ew-resize;
|
|
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)));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|