slint/tools/lsp/ui/components/property-widgets.slint
Olivier Goffart 0796bb1db4 live-preview: Simplify the code of the property editor
Put the NameLabel outside of the typed property widget
2025-10-02 10:38:59 +02:00

517 lines
18 KiB
Text

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
import { Palette } from "std-widgets.slint";
import { Api, BrushKind, ElementInformation, GradientStop, PreviewData, PreviewDataKind, PropertyContainer, PropertyInformation, PropertyValue, PropertyValueKind } from "../api.slint";
import { StatusLineApi } from "../components/status-line.slint";
import { EditorSpaceSettings } from "../components/styling.slint";
import { BooleanWidget } from "./widgets/boolean-widget.slint";
import { ConsoleStyles } from "./styling.slint";
import { CodeWidget } from "./widgets/code-widget.slint";
import { EnumWidget } from "./widgets/enum-widget.slint";
import { FloatWidget } from "./widgets/float-widget.slint";
import { IntegerWidget } from "./widgets/integer-widget.slint";
import { JsonWidget } from "./widgets/json-widget.slint";
import { MultiValueWidget } from "./widgets/multi-value-widget.slint";
import { StringWidget } from "./widgets/string-widget.slint";
import { BrushPropertyType, WindowGlobal, WindowManager } from "../windowglobal.slint";
import { InlineBrushWidget } from "widgets/inline-brush-widget.slint";
import { NameLabel, ResettingLineEdit } from "widgets/basics.slint";
export component PropertyValueWidget inherits VerticalLayout {
in-out property <PropertyValue> property-value;
in property <PreviewData> preview-data;
in property <string> property-name;
in property <bool> enabled;
in property <bool> has-code-action: true;
in property <bool> has-reset-action: true;
in property <bool> strings-are-translatable: true;
in property <string> property-container-id;
callback update-display-string(value: string);
callback set-bool-binding(value: bool);
callback set-color-binding(text: string);
callback test-color-binding(text: string) -> bool;
callback set-brush-binding(kind: BrushKind, angle: float, color: color, stops: [GradientStop]);
callback test-brush-binding(kind: BrushKind, angle: float, color: color, stops: [GradientStop]) -> bool;
callback set-float-binding(text: string, unit: string);
callback test-float-binding(text: string, unit: string) -> bool;
callback set-code-binding(text: string);
callback test-code-binding(text: string) -> bool;
callback set-string-binding(text: string, is_translated: bool);
callback test-string-binding(text: string, is_translated: bool) -> bool;
callback set-enum-binding(text: string);
callback set-current-item();
callback reset-action();
callback code-action();
function update-display-string-impl(value: string) {
self.property-value.display-string = value;
self.update-display-string(value);
}
if root.property-value.kind == PropertyValueKind.boolean: BooleanWidget {
enabled <=> root.enabled;
property-value <=> root.property-value;
set-bool-binding(value) => {
root.set-bool-binding(value);
}
update-display-string(value) => {
root.update-display-string-impl(value);
}
}
if root.property-value.kind == PropertyValueKind.color || root.property-value.kind == PropertyValueKind.brush: InlineBrushWidget {
enabled <=> root.enabled;
property-name <=> root.property-name;
property-value <=> root.property-value;
brush-property-type: root.property-value.kind == PropertyValueKind.color ? BrushPropertyType.color : BrushPropertyType.brush;
preview-data <=> root.preview-data;
update-floating-editor() => {
return root.set-current-item();
}
test-color-binding(text) => {
return (root.test-color-binding(text));
}
set-color-binding(text) => {
root.set-color-binding(text);
}
test-brush-binding(kind, angle, color, stops) => {
return root.test-brush-binding(kind, angle, color, stops);
}
set-brush-binding(kind, angle, color, stops) => {
root.set-brush-binding(kind, angle, color, stops);
}
update-display-string(value) => {
root.update-display-string-impl(value);
}
init => {
// Floating editors need to know the current property details. Call the update if something changes.
if WindowManager.showing-color-picker && WindowManager.current-property-information.name == root.property-name {
self.update-floating-editor();
}
}
}
if root.property-value.kind == PropertyValueKind.code: CodeWidget {
enabled <=> root.enabled;
property-value <=> root.property-value;
update-display-string(value) => {
root.update-display-string-impl(value);
}
reset-action() => {
root.reset-action();
}
code-action() => {
root.code-action();
}
}
if root.property-value.kind == PropertyValueKind.enum: EnumWidget {
enabled <=> root.enabled;
property-value <=> root.property-value;
set-enum-binding(text) => {
root.set-enum-binding(text);
}
update-display-string(value) => {
root.update-display-string-impl(value);
}
}
if root.property-value.kind == PropertyValueKind.float: FloatWidget {
enabled <=> root.enabled;
property-value <=> root.property-value;
test-float-binding(text, unit) => {
return (root.test-float-binding(text, unit));
}
set-float-binding(text, unit) => {
root.set-float-binding(text, unit);
}
update-display-string(value) => {
root.update-display-string-impl(value);
}
}
if root.property-value.kind == PropertyValueKind.integer: IntegerWidget {
enabled <=> root.enabled;
property-value <=> root.property-value;
test-integer-binding(text) => {
return (root.test-code-binding(text));
}
set-integer-binding(text) => {
root.set-code-binding(text);
}
update-display-string(value) => {
root.update-display-string-impl(value);
}
}
if root.property-value.kind == PropertyValueKind.string: StringWidget {
enabled <=> root.enabled;
property-value: root.property-value;
has-code-action: root.has-code-action;
has-reset-action: root.has-reset-action;
is-translatable <=> root.strings-are-translatable;
test-string-binding(text, is_translated) => {
return root.test-string-binding(text, is_translated);
}
set-string-binding(text, is_translated) => {
root.set-string-binding(text, is_translated);
}
update-display-string(value) => {
root.update-display-string-impl(value);
}
}
}
component IconButton inherits Rectangle {
in property <image> icon;
in property <bool> checked: false;
in property <bool> enabled: true;
ta := TouchArea {
clicked => root.clicked();
mouse-cursor: pointer;
enabled: root.enabled;
}
callback clicked();
Image {
height: 1rem;
colorize: !enabled ? Palette.foreground.transparentize(0.8) :
checked ? Palette.accent-background :
ta.has-hover ? Palette.accent-background.transparentize(0.2) :
Palette.foreground.transparentize(0.5);
source: root.icon;
}
}
enum PropertyWidgetMode { value, expression, bind }
export component PropertyInformationWidget inherits TouchArea {
in property <PropertyInformation> property-information;
in property <ElementInformation> element-information;
private property <PropertyValueKind> kind: property-information.value.kind;
changed kind => {
if kind == PropertyValueKind.code {
mode = PropertyWidgetMode.expression;
} else {
mode = PropertyWidgetMode.value;
}
}
private property <PropertyWidgetMode> mode: kind == PropertyValueKind.code ? PropertyWidgetMode.expression : PropertyWidgetMode.value;
function code-action() {
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,
);
}
function reset-action() {
Api.set-code-binding(
element-information.source-uri,
element-information.source-version,
element-information.offset,
property-information.name,
"",
);
}
VerticalLayout {
padding-bottom: EditorSpaceSettings.default-padding;
padding-right: EditorSpaceSettings.default-padding * 2;
spacing: EditorSpaceSettings.default-spacing / 2;
HorizontalLayout {
alignment: LayoutAlignment.space-between;
NameLabel {
property-name: property-information.name;
property-value: property-information.value;
TouchArea {
double-clicked => { root.code-action(); }
}
}
HorizontalLayout {
opacity: root.has-hover ? 1 : 0;
animate opacity {
duration: 200ms;
easing: ease-in-out-quad;
}
alignment: end;
spacing: 4px;
IconButton {
icon: @image-url("../assets/constant.svg");
checked: mode == PropertyWidgetMode.value;
enabled: kind != PropertyValueKind.code;
clicked => {
mode = PropertyWidgetMode.value;
}
}
IconButton {
checked: mode == PropertyWidgetMode.expression;
icon: @image-url("../assets/code.svg");
clicked => {
mode = PropertyWidgetMode.expression;
}
}
IconButton {
icon: @image-url("../assets/revert.svg");
enabled: property-information.value.code != "";
clicked => {
root.reset-action();
}
}
}
}
if mode == PropertyWidgetMode.expression : HorizontalLayout {
alignment: LayoutAlignment.stretch;
spacing: 5px;
IconButton {
icon: @image-url("../assets/code.svg");
clicked => { root.code-action(); }
}
ResettingLineEdit {
enabled: root.enabled;
default-text: property-information.value.code;
edited(text) => {
self.can-compile = Api.test-code-binding(
root.element-information.source-uri,
root.element-information.source-version,
root.element-information.offset,
root.property-information.name,
text,
);
}
accepted(text) => {
Api.set-code-binding(
root.element-information.source-uri,
root.element-information.source-version,
root.element-information.offset,
root.property-information.name,
text,
);
}
}
}
if mode == PropertyWidgetMode.value : PropertyValueWidget {
property-value: root.property-information.value;
property-name: root.property-information.name;
enabled: root.enabled;
set-current-item() => {
WindowManager.show-floating-widget(property-information, element-information);
}
set-bool-binding(value) => {
self.set-code-binding(value ? "true" : "false");
}
set-brush-binding(kind, angle, color, stops) => {
self.set-code-binding(Api.as-slint-brush(kind, angle, color, stops));
}
test-brush-binding(kind, angle, color, stops) => {
return self.test-code-binding(Api.as-slint-brush(kind, angle, color, stops));
}
set-color-binding(text) => {
self.set-code-binding(text);
}
test-color-binding(text) => {
return (Api.string-is-color(text));
}
set-enum-binding(text) => {
self.set-code-binding(text);
}
test-float-binding(text, unit) => {
return (self.test-code-binding(text + unit));
}
set-float-binding(text, unit) => {
self.set-code-binding(text + unit);
}
set-code-binding(text) => {
Api.set-code-binding(
element-information.source-uri,
element-information.source-version,
element-information.offset,
property-information.name,
text,
);
}
test-code-binding(text) => {
return (Api.test-code-binding(
root.element-information.source-uri,
root.element-information.source-version,
root.element-information.offset,
root.property-information.name,
text,
));
}
set-string-binding(text, is-translated) => {
Api.set-code-binding(
element-information.source-uri,
element-information.source-version,
element-information.offset,
property-information.name,
text);
}
test-string-binding(text, is-translated) => {
return (Api.test-code-binding(
root.element-information.source-uri,
root.element-information.source-version,
root.element-information.offset,
root.property-information.name,
text));
}
reset-action() => {
root.reset-action();
}
code-action() => {
root.code-action();
}
}
}
}
export component PreviewDataPropertyValueWidget inherits VerticalLayout {
in property <PreviewData> preview-data;
in property <string> property-container-id;
private property <PropertyValue> value: Api.get-property-value(root.property-container-id, root.preview-data.name);
private property <string> possible-error;
callback edit-in-table-editor(property-group-id: string, data: PreviewData);
function reset-action() {
self.set-code-binding(self.value.code);
}
function set-code-binding(text: string) -> bool {
self.possible_error = Api.set-json-preview-data(root.property-container-id, root.preview-data.name, text, check-new-value(root.property-container-id + root.preview-data.name));
StatusLineApi.help-text = self.possible-error;
return (self.possible-error == "");
}
property <string> container-property-name;
function check-new-value(value: string) -> bool {
if container-property-name == value {
return false;
} else {
container-property-name = value;
return true;
}
}
padding-bottom: EditorSpaceSettings.default-padding;
padding-right: EditorSpaceSettings.default-padding * 2;
spacing: EditorSpaceSettings.default-spacing / 2;
NameLabel {
property-name: root.preview-data.name;
property-value: root.value;
}
if root.preview-data.kind == PreviewDataKind.Value: PropertyValueWidget {
property-value <=> root.value;
property-name: root.preview-data.name;
enabled: root.preview-data.has-setter;
property-container-id <=> root.property-container-id;
preview-data <=> root.preview-data;
strings-are-translatable: false;
has-code-action: false;
has-reset-action: false;
set-current-item() => {
WindowManager.show-floating-preview-widget(property-container-id, preview-data, root.value);
}
set-bool-binding(value) => {
self.set-code-binding(value ? "true" : "false");
}
set-brush-binding(kind, angle, color, stops) => {
self.set-code-binding(Api.as-json-brush(kind, angle, color, stops));
}
test-brush-binding(kind, angle, color, stops) => {
return self.test-code-binding(Api.as-json-brush(kind, angle, color, stops));
}
set-color-binding(text) => {
self.set-code-binding("\"\{text}\"");
}
test-color-binding(text) => {
return (root.set-code-binding("\"\{text}\""));
}
set-float-binding(text, _unit) => {
self.set-code-binding("\{text}");
}
test-float-binding(text, _unit) => {
return (root.set-code-binding("\{text}"));
}
set-enum-binding(text) => {
self.set-code-binding("\"\{text}\"");
}
set-code-binding(text) => {
root.set-code-binding(text);
}
test-code-binding(text) => {
return (root.set-code-binding(text));
}
set-string-binding(text, is_translated) => {
self.test-string-binding(text, is_translated);
}
test-string-binding(text, is_translated) => {
return (root.set-code-binding(is_translated ? "\"\{text}\"" : text));
}
}
if root.preview-data.kind == PreviewDataKind.Table: MultiValueWidget {
property-value <=> root.value;
preview-data: root.preview-data;
property-group-id: root.property-container-id;
edit-in-table-editor(property-group-id, data) => {
root.edit-in-table-editor(property-group-id, data);
}
}
if root.preview-data.kind == PreviewDataKind.Json: JsonWidget {
enabled: root.preview-data.has-setter;
property-value <=> root.value;
set-code-binding(text) => {
return (root.set-code-binding(text));
}
}
}