// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial global Palette { out property window-background: #eee; out property widget-background: #ddd; out property widget-stroke: #888; out property window-border: #ccc; out property text-color: #666; out property hyper-blue: #90d1ff; } //------ MdiWindow ---- component MdiWindow inherits Rectangle { in property title; in-out property window-x <=> window.x; in-out property window-y <=> window.y; in-out property 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 open-width: l.preferred-width; property 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: -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 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 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 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 maximum: 100; in property minimum: 0; in-out property value; in property 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 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 link; } component DragValue inherits TouchArea { in-out property 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 _pressed-value; mouse-cursor: MouseCursor.ew-resize; } component ProgressBar inherits Rectangle { in-out property 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))); } } } } }