// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { LineEdit, Palette, CheckBox, ComboBox, Button, Slider } from "std-widgets.slint"; import { Api, ColorData, ElementInformation, PropertyInformation, PropertyValue, PropertyValueKind } from "../api.slint"; import { BodyStrongText } from "../components/body-strong-text.slint"; import { BodyText } from "../components/body-text.slint"; import { StateLayer } from "../components/state-layer.slint"; import { EditorSizeSettings, Icons, EditorAnimationSettings, EditorSpaceSettings, EditorFontSettings } from "../components/styling.slint"; component CodeButton inherits Button { in property element-information; in property property-information; text: @tr("Code"); clicked => { Api.show-document-offset-range( element-information.source-uri, Api.property-declaration-ranges(property-information.name).defined-at.expression-range.start, Api.property-declaration-ranges(property-information.name).defined-at.expression-range.start, true, ); } } component ResetButton inherits Button { in property element-information; in property property-information; text: @tr("Reset"); clicked => { Api.set-code-binding( element-information.source-uri, element-information.source-version, element-information.range.start, property-information.name, "", ); } } component NameLabel inherits HorizontalLayout { in property element-information; in property property-information; horizontal-stretch: 0; BodyText { min-width: EditorSizeSettings.min-prefix-text-width; text: root.property-information.name; font-size: 1rem; font-weight: property-information.value.code != "" ? EditorFontSettings.bold-font-weight : EditorFontSettings.light-font-weight; font-italic: property-information.value.code == ""; overflow: elide; } } component ResettingLineEdit { in property default-text; in-out property can-compile: true; in property enabled; in property input-type <=> le.input-type; in property horizontal-alignment <=> le.horizontal-alignment; in property placeholder-text <=> le.placeholder-text; out property has-focus <=> le.has-focus; in-out property text <=> le.text; property border: 3px; callback accepted <=> le.accepted; callback edited <=> le.edited; min-width <=> le.min-width; preferred-width <=> le.preferred-width; max-width <=> le.max-width; min-height <=> le.min-height; preferred-height <=> le.preferred-height; max-height <=> le.max-height; le := LineEdit { enabled: root.enabled; width: 100%; height: 100%; x: 0px; y: 0px; text: root.default-text; font-size: 1rem; // Reset on focus loss: changed has-focus => { if !self.has_focus && text != default-text { if root.can-compile { root.accepted(self.text); } } else { self.text = root.default-text; root.can-compile = true; } } } Rectangle { visible: !root.can-compile; background: Colors.red.transparentize(0.94); x: root.border; y: root.border; width: root.width - 2 * root.border; height: root.height - 2 * root.border; border-radius: root.border; } } export component CodeWidget inherits VerticalLayout { in property enabled; in property element-information; in property property-information; padding-bottom: EditorSpaceSettings.default-spacing; NameLabel { property-information: property-information; element-information: element-information; } if property-information.value.code == "": Rectangle { min-height: 2rem; width: 100%; background: Palette.alternate-background; Text { x: EditorSpaceSettings.default-padding; horizontal-alignment: left; vertical-alignment: center; text: @tr("Not Set"); font-italic: true; } } if property-information.value.code != "": HorizontalLayout { spacing: EditorSpaceSettings.default-spacing; ResetButton { enabled: root.enabled; element-information <=> element-information; property-information: property-information; } CodeButton { enabled: root.enabled; element-information <=> element-information; property-information: property-information; } } } component ChildIndicator { out property open: false; x: -1.0 * EditorSpaceSettings.group-indent * 2; Rectangle { width: 100%; height: 1cm; expand := TouchArea { clicked => { root.open = !root.open; } indicator := Image { colorize: Palette.foreground; visible: expand.has-hover; source: Icons.chevron-down; width: 16px; height: 16px; rotation-angle: root.open ? 0deg : -90deg; } } } } component SecondaryContent inherits Rectangle { in property element-information; in property property-information; in property enabled; in property open: false; callback reset(); background: Palette.background; clip: true; height: open ? content.preferred-height : 0px; animate height { duration: EditorAnimationSettings.rotation-duration; } content := HorizontalLayout { Rectangle { height: 100%; width: 1px; background: Palette.border; } VerticalLayout { padding: EditorSpaceSettings.default-padding; spacing: EditorSpaceSettings.default-padding; @children HorizontalLayout { spacing: EditorSpaceSettings.default-spacing; ResetButton { enabled: root.enabled; element-information <=> element-information; property-information: property-information; clicked() => { root.reset(); } } CodeButton { enabled: root.enabled; element-information <=> element-information; property-information: property-information; } } } } } export component FloatWidget inherits VerticalLayout { in property enabled; in property element-information; in property property-information; private property current-unit: find_current_unit(property-information.value); pure function find_current_unit(value: PropertyValue) -> string { if value.visual-items.length == 0 { return ""; } return value.visual-items[self.find-current-index(value)]; } pure function find_current_index(value: PropertyValue) -> int { return value.code == "" ? value.default-selection : value.value-int; } width: 100%; NameLabel { property-information: root.property-information; element-information: root.element-information; } function set-binding() { Api.set-code-binding( self.element-information.source-uri, self.element-information.source-version, self.element-information.range.start, self.property-information.name, number.text == "" ? "" : number.text + self.current-unit, ); } HorizontalLayout { spacing: EditorSpaceSettings.default-spacing; alignment: stretch; height: 2rem; number := ResettingLineEdit { enabled: root.enabled; horizontal-alignment: right; min-width: EditorSizeSettings.float-size; horizontal-stretch: 0; default-text: property-information.value.value-float; edited(text) => { self.can-compile = Api.test-code-binding( root.element-information.source-uri, root.element-information.source-version, root.element-information.range.start, root.property-information.name, number.text == "" ? "" : number.text + root.current-unit, ); } accepted(text) => { root.set-binding(); } } if property-information.value.visual-items.length > 1: ComboBox { enabled: root.enabled; horizontal-stretch: 0; min-width: EditorSizeSettings.length-combo; model: property-information.value.visual-items; current-index: root.find_current_index(root.property-information.value); selected(unit) => { root.current-unit = unit; set-binding(); } } if property-information.value.visual-items.length == 1: Text { text: property-information.value.visual-items[0]; changed text => { root.current-unit = self.text; } } } } export component StringWidget { in property enabled; in property element-information; in property property-information; property open: false; childIndicator := ChildIndicator { y: content.y + EditorSpaceSettings.default-spacing/2; } VerticalLayout { NameLabel { element-information: element-information; property-information: property-information; } content := HorizontalLayout { spacing: EditorSpaceSettings.default-spacing; height: 2rem; ResettingLineEdit { enabled: root.enabled; default-text: property-information.value.value-string; edited(text) => { self.can-compile = Api.test-string-binding( root.element-information.source-uri, root.element-information.source-version, root.element-information.range.start, property-information.name, text, property-information.value.is-translatable, property-information.value.tr-context, property-information.value.tr-plural, property-information.value.tr-plural-expression, ); } accepted(text) => { Api.set-string-binding( root.element-information.source-uri, root.element-information.source-version, root.element-information.range.start, property-information.name, text, property-information.value.is-translatable, property-information.value.tr-context, property-information.value.tr-plural, property-information.value.tr-plural-expression, ); } } } sub := SecondaryContent { enabled: root.enabled; element-information: element-information; property-information: property-information; open: childIndicator.open; CheckBox { enabled: root.enabled; text: "Translatable"; } CheckBox { enabled: root.enabled; text: "Disambiguate"; } } } } component ColorLineEdit inherits HorizontalLayout { in property enabled; in property channel: "R"; in-out property value; alignment: stretch; spacing: EditorSpaceSettings.default-spacing; Text { text: channel; vertical-alignment: center; color: Palette.foreground; } slide-value := Slider { enabled: root.enabled; minimum: 0; maximum: 255; value: root.value; changed value => { root.value = Math.floor(self.value); } } num-value := ResettingLineEdit { enabled: root.enabled; input-type: number; width: 5rem; default-text: root.value; edited() => { root.value = self.text.to-float(); } } } export component ColorWidget inherits Rectangle { in property enabled; in property element-information; in property property-information; private property open: false; private property current-color: property-information.value.value-brush; private property current-color-data: Api.color-to-data(self.current-color); childIndicator := ChildIndicator { y: content.y + EditorSpaceSettings.default-spacing/2; } all := VerticalLayout { padding-bottom: EditorSpaceSettings.default-spacing; width: 100%; NameLabel { property-information: root.property-information; element-information: root.element-information; } content := HorizontalLayout { spacing: EditorSpaceSettings.default-spacing; alignment: stretch; ResettingLineEdit { enabled: root.enabled; default-text: root.current-color-data.text; edited(text) => { if text == "" { // allow empty text -- which will delete the property self.can-compile = true; color-preview.background = Colors.transparent; } else { self.can-compile = Api.string-is-color(text); if self.can-compile { color-preview.background = Api.string-to-color(text); } } } accepted(text) => { Api.set-code-binding( root.element-information.source-uri, root.element-information.source-version, root.element-information.range.start, root.property-information.name, text, ); } } color-preview := Rectangle { height: 2rem; background: root.current-color; border-width: 1px; border-color: Palette.foreground; } } sub := SecondaryContent { out property slider-color: Api.rgba-to-color(r.value, g.value, b.value, a.value); enabled: root.enabled; changed slider-color => { root.current-color = slider-color; } open: childIndicator.open; r := ColorLineEdit { enabled: root.enabled; value: root.current-color-data.r; channel: "R"; } g := ColorLineEdit { enabled: root.enabled; value: root.current-color-data.g; channel: "G"; } b := ColorLineEdit { enabled: root.enabled; value: root.current-color-data.b; channel: "B"; } a := ColorLineEdit { enabled: root.enabled; value: root.current-color-data.a; channel: "A"; } reset => { root.current-color = root.property-information.value.value-brush; } } } } export component IntegerWidget inherits VerticalLayout { in property enabled; in property element-information; in property property-information; NameLabel { property-information: property-information; element-information: element-information; } HorizontalLayout { height: 2rem; spacing: EditorSpaceSettings.default-spacing; ResettingLineEdit { enabled: root.enabled; horizontal-alignment: right; input-type: number; default-text: property-information.value.value-int; edited(text) => { self.can-compile = Api.test-code-binding( element-information.source-uri, element-information.source-version, element-information.range.start, property-information.name, text, ); } accepted(text) => { Api.set-code-binding( element-information.source-uri, element-information.source-version, element-information.range.start, property-information.name, text, ); } } } } export component EnumWidget inherits VerticalLayout { in property enabled; in property element-information; in property property-information; NameLabel { property-information: property-information; element-information: element-information; } HorizontalLayout { ComboBox { enabled: root.enabled; current-index: property-information.value.value-int; model: property-information.value.visual-items; selected(value) => { Api.set-code-binding( element-information.source-uri, element-information.source-version, element-information.range.start, property-information.name, property-information.value.value_string + "." + value, ) } } } } export component BooleanWidget inherits VerticalLayout { in property enabled; in property element-information; in property property-information; NameLabel { property-information: property-information; element-information: element-information; } HorizontalLayout { alignment: start; height: 2rem; Rectangle { width: 100%; height: 100%; background: Palette.alternate-background; HorizontalLayout { alignment: start; padding-left: EditorSpaceSettings.default-padding; spacing: EditorSpaceSettings.default-spacing; checkbox := CheckBox { enabled: root.enabled; checked: property-information.value.value_bool; text: self.checked ? "True" : "False"; toggled() => { Api.set-code-binding( element-information.source-uri, element-information.source-version, element-information.range.start, property-information.name, self.checked ? "true" : "false", ); } } } } } } export component ExpandableGroup { in property enabled; in property text; in property panel-width; property open: true; group-layer := Rectangle { content-layer := VerticalLayout { if text != "": Rectangle { touch-area := TouchArea { clicked => { root.open = !root.open; } } state-layer := StateLayer { width: panel-width; height: group-layer.height; y: group-layer.y; has-hover: touch-area.has-hover; pressed: touch-area.pressed; } HorizontalLayout { padding-left: -EditorSpaceSettings.group-indent; spacing: EditorSpaceSettings.default-spacing/2; icon-image := Image { width: EditorSizeSettings.default-icon-width; colorize: Palette.alternate-foreground.transparentize(0.7); source: Icons.chevron-down; height: 2rem; rotation-origin-x: self.width / 2; rotation-origin-y: self.height / 2; states [ closed when !root.open: { rotation-angle: -0.25turn; } ] animate rotation-angle { duration: EditorAnimationSettings.rotation-duration; } } BodyStrongText { text: root.text; } } } Rectangle { height: root.open ? self.preferred-height : 0px; clip: true; @children } } } }