slint/tools/lsp/ui/components/spreadsheet.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

367 lines
13 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, PreviewData, PreviewDataKind, PropertyValue, PropertyValueKind, PropertyValueTable } from "../api.slint";
import { StatusLineApi } from "../components/status-line.slint";
import { EditorSizeSettings, Icons, EditorSpaceSettings } from "../components/styling.slint";
import { PropertyValueWidget } from "../components/property-widgets.slint";
import { TableData } from "../windowglobal.slint";
import { NameLabel } from "widgets/basics.slint";
export struct CellData {
property-group-id: string,
property-name: string,
row: int,
col: int,
x: length,
y: length,
width: length,
height: length}
export component EditWindow inherits PopupWindow {
close-policy: close-on-click-outside;
callback test(string) -> bool;
callback save(string);
callback close-editor();
in property <CellData> current-cell-data;
in-out property <PropertyValueTable> current-table;
in property <PreviewData> preview-data;
in property <string> property-container-id;
function is-brush(property-value: PropertyValue) -> bool {
return property-value.kind == PropertyValueKind.brush || property-value.kind == PropertyValueKind.color;
}
content := Rectangle {
changed height => {
parent.height = self.height;
}
drop-shadow-blur: 10px;
drop-shadow-color: black.transparentize(0.5);
drop-shadow-offset-x: 0;
drop-shadow-offset-y: 0;
border-radius: EditorSizeSettings.radius;
width: is-brush(root.current-table.values[root.current-cell-data.row][root.current-cell-data.col]) ? 260px : self.preferred-width;
height: self.preferred-height;
background: Palette.alternate-background;
hl := HorizontalLayout {
padding: EditorSpaceSettings.default-padding;
padding-left: EditorSpaceSettings.default-padding;
width: is-brush(root.current-table.values[root.current-cell-data.row][root.current-cell-data.col]) ? 220px : self.preferred-width;
NameLabel {
property-name: pvw.property-name;
property-value: pvw.property-value;
}
pvw := PropertyValueWidget {
property-value: root.current-table.values[root.current-cell-data.row][root.current-cell-data.col];
property-name: root.current-table.headers[root.current-cell-data.col];
enabled: true;
changed property-value => {
root.current-table.values[root.current-cell-data.row][root.current-cell-data.col] = self.property-value;
}
set-current-item() => {
TableData.show-brush-editor(root.current-cell-data.row, root.current-cell-data.col);
close-editor();
}
update-display-string(value) => {
root.current-table.values[root.current-cell-data.row][root.current-cell-data.col].display-string = value;
}
test-color-binding(text) => {
return root.test("\"\{text}\"");
}
test-brush-binding(kind, angle, color, stops) => {
return root.test(Api.as-json-brush(kind, angle, color, stops));
}
test-float-binding(text, unit) => {
return root.test(text);
}
test-code-binding(text) => {
return root.test(text);
}
test-string-binding(text, is_translated) => {
return root.test(is_translated ? "\"\{text}\"" : text);
}
set-bool-binding(value) => {
root.save(value ? "true" : "false");
}
set-color-binding(text) => {
root.save("\"\{text}\"");
}
set-brush-binding(kind, angle, color, stops) => {
root.save(Api.as-json-brush(kind, angle, color, stops));
}
set-float-binding(text, _unit) => {
root.save(text);
}
set-code-binding(text) => {
root.save(text);
}
set-string-binding(text, is_translated) => {
root.save(is_translated ? "\"\{text}\"" : text);
}
set-enum-binding(text) => {
root.save("\"\{text}\"");
}
}
}
}
}
component RowMarker inherits Rectangle {
in property <int> value;
in property <bool> header: false;
in property <bool> hovered;
in property <bool> show-menu: true;
width: 30px;
if (value > 0): Rectangle {
height: 30px;
background: header ? transparent : Palette.alternate-background;
VerticalLayout {
HorizontalLayout {
Text {
vertical-alignment: center;
horizontal-alignment: right;
text: value;
}
VerticalLayout {
alignment: LayoutAlignment.center;
padding: EditorSpaceSettings.default-padding / 4;
indicator := Image {
visible: hovered && show-menu;
colorize: Palette.foreground;
source: Icons.chevron-down;
width: 10px;
height: 10px;
}
}
}
Rectangle {
height: 1px;
background: Palette.border;
width: 100%;
}
}
}
}
component Cell inherits Rectangle {
in property <PropertyValue> property-value;
in property <bool> is-header: false;
in property <bool> is-writeable: false;
in property <string> text;
in-out property <color> bg-color: cell-clicked ? Palette.accent-background.transparentize(0.8) : transparent;
private property <bool> cell-clicked: false;
callback edit-clicked();
width: 100px;
height: 30px;
border-width: 1px;
border-color: Palette.border;
background: is-header ? Palette.foreground.transparentize(0.9) : bg-color;
HorizontalLayout {
Rectangle {
width: EditorSpaceSettings.default-padding / 2;
}
Text {
font-weight: is-header ? 700 : 400;
width: root.width - EditorSpaceSettings.default-padding;
height: root.height;
horizontal-alignment: TextHorizontalAlignment.left;
vertical-alignment: center;
overflow: TextOverflow.elide;
text: root.text;
}
}
if is-writeable: TouchArea {
clicked => {
root.edit-clicked();
}
}
}
export component Spreadsheet {
in property <string> property-group-id;
in property <PreviewData> preview-data: {
name: "Addresses",
has-getter: true,
has-setter: true,
kind: PreviewDataKind.Table,
};
in-out property <PropertyValueTable> current-table: {
is-array: true,
headers: ["type", "street", "city", "state", "zip", "favorite", "color"],
values: [
[
{ kind: PropertyValueKind.string, display-string: "home" },
{ kind: PropertyValueKind.string, display-string: "123 Oak Lane" },
{ kind: PropertyValueKind.string, display-string: "Richmond" },
{ kind: PropertyValueKind.string, display-string: "VA" },
{ kind: PropertyValueKind.string, display-string: "23226" },
{ kind: PropertyValueKind.boolean, value-bool: true, display-string: "true" },
{ kind: PropertyValueKind.color, display-string: "#ff0000" }
],
[
{ kind: PropertyValueKind.string, display-string: "work" },
{ kind: PropertyValueKind.string, display-string: "456 Corporate Blvd" },
{ kind: PropertyValueKind.string, display-string: "Richmond" },
{ kind: PropertyValueKind.string, display-string: "VA" },
{ kind: PropertyValueKind.string, display-string: "23219" },
{ kind: PropertyValueKind.boolean, value-bool: false, display-string: "false" }
],
[
{ kind: PropertyValueKind.string, display-string: "world" },
{ kind: PropertyValueKind.string, display-string: "456 Corporate Blvd" },
{ kind: PropertyValueKind.string, display-string: "Richmond" },
{ kind: PropertyValueKind.string, display-string: "VA" },
{ kind: PropertyValueKind.string, display-string: "23219" },
{ kind: PropertyValueKind.boolean, value-bool: false, display-string: "false" },
{ kind: PropertyValueKind.color, display-string: "#ff0000" }
]
],
};
private property <length> selection-x;
private property <length> selection-y;
private property <CellData> current-cell-data;
VerticalLayout {
HorizontalLayout {
RowMarker { }
for header in root.current-table.headers: Cell {
is-header: true;
text: header;
}
}
for data-row[row] in root.current-table.values: HorizontalLayout {
RowMarker {
value: row + 1;
hovered: ta.has-hover;
show-menu: root.current-table.is-array;
ta := TouchArea {
visible: root.current-table.is-array;
clicked => {
root.selection-x = self.x;
root.selection-y = self.y;
row_menu.row-number = row;
row_menu.show({
x:self.mouse-x + ta.absolute-position.x - row_menu.absolute-position.x,
y:self.mouse-y + ta.absolute-position.y - row_menu.absolute-position.y
});
}
}
}
for value[col] in data-row: Cell {
text: value.display-string;
is-writeable: true;
edit-clicked() => {
root.current-cell-data = {
property-group-id: root.property-group-id,
property-name: preview-data.name,
property-value: root.current-table.values[row][col],
row: row,
col: col,
x: self.x,
y: parent.y,
width: self.width,
height: self.height
};
ew.show();
}
}
}
}
ew := EditWindow {
x: self.current-cell-data.x > 15px ? self.current-cell-data.x - EditorSpaceSettings.default-padding - 15px : (self.current-cell-data.x + self.current-cell-data.width > root.width ? root.width - ew.width : 0); // TODO the end needs to be windowWidth - current-cell.x < current.cell.width * 2;
y: self.current-cell-data.y - EditorSpaceSettings.default-padding;
current-cell-data <=> root.current-cell-data;
current-table: root.current-table;
preview-data <=> root.preview-data;
property-container-id <=> property-group-id;
private property <string> possible-error;
function edit(new-value: string) -> bool {
self.current-table.values[self.current-cell-data.row][self.current-cell-data.col].was-edited = true;
self.current-table.values[self.current-cell-data.row][self.current-cell-data.col].edited-value = new-value;
self.possible_error = Api.set-property-value-table(root.property-group-id, root.preview-data.name, root.current-table.values, root.current-table.is-array);
StatusLineApi.help-text = self.possible-error;
if self.possible-error == "" {
TableData.populate-table(root.property-group-id, root.preview-data);
}
return self.possible-error == "";
}
test(new-value) => {
return edit(new-value);
}
save(new-value) => {
edit(new-value);
self.close-editor();
}
close-editor => {
self.close();
}
}
row_menu := ContextMenuArea {
in-out property <int> row-number;
Menu {
MenuItem {
title: "Add Row Above";
activated() => {
Api.insert-row-into-value-table(root.current_table, row_menu.row-number);
}
}
MenuItem {
title: "Add Row Below";
activated() => {
Api.insert-row-into-value-table(root.current_table, row_menu.row-number + 1);
}
}
MenuItem {
title: "Remove Row";
activated() => {
Api.remove-row-from-value-table(root.current_table, row_menu.row-number);
}
}
}
}
}