// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { Api, BrushKind, ElementInformation, GradientStop, PreviewData, PropertyInformation, PropertyValue, PropertyValueKind, PropertyValueTable, } from "./api.slint"; import { StatusLineApi } from "components/status-line.slint"; export enum WidgetMode { edit, preview, color-stop } export enum PickerTab { color, gradient, css-color, globals } // Color and Brush properties are related, but while a can be a color, // a property cannot be a brush. This property type is used to ensure // properties do not show the more complex brush editor. export enum BrushPropertyType { color, brush, } export enum BrushMode { color, conic, linear, radial, css-color, code, } export enum GradientType { conic, linear, radial, } export enum ColorCodeType { other, css-color, global } export global WindowGlobal { in-out property window-width; in-out property window-height; } export global TableData { callback populate-table(property-group-id: string, preview-data: PreviewData); callback show-brush-editor(selected-row: int, selected-col: int); callback set-color-preview(value: string); out property property-group-id; out property preview-data; out property current-table; property possible-error; property selected-row; property selected-col; show-brush-editor(selected-row, selected-col) => { self.selected-row = selected-row; self.selected-col = selected-col; WindowManager.show-floating-preview-widget(self.property-group-id, self.preview-data, self.current-table.values[selected-row][selected-col]); } populate-table(property-group-id, preview-data) => { self.property-group-id = property-group-id; self.preview-data = preview-data; self.current-table = Api.get-property-value-table(property-group-id, preview-data.name); } set-color-preview(value) => { self.current-table.values[self.selected-row][self.selected-col].was-edited = true; self.current-table.values[self.selected-row][self.selected-col].edited-value = 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 == "" { self.populate-table(root.property-group-id, root.preview-data); } } } export global PickerData { out property active-tab: PickerTab.color; out property picker-mode: color; out property brush-mode: color; // Used to remember the last used gradient type so it won't change if the user // switches Picker tab in-out property last-used-gradient-type: linear; in-out property hue; in-out property saturation; in-out property value; // alpha is an int, instead of float to help snap values to whole percentage numbers in-out property alpha; out property current-color: hsv(hue, saturation, value, alpha / 100); in-out property current-brush-kind; // As the brush can be updated via the Api.move-gradient-stop function it won't trigger a reactive update. // So we need to keep track of the current brush ourselves and update it with update-brush(); out property current-brush; in-out property <[GradientStop]> current-gradient-stops; in-out property current-angle; in-out property current-stop-index: 0; property current-stop-position: current-gradient-stops[current-stop-index].position; property current-stop-color: current-gradient-stops[current-stop-index].color; callback rebuild-gradient-stops(); callback set-active-tab(picker-tab: PickerTab); callback set-gradient-type(gradient-type: GradientType); callback reset(); changed current-stop-index => { update-brush(); if WindowManager.showing-color-stop-picker { set-current-stop-as-color(); } } changed current-color => { update-brush(); if WindowManager.showing-color-stop-picker { current-gradient-stops[current-stop-index].color = current-color; } else if WindowManager.widget-mode == WidgetMode.preview && current-brush-kind == BrushKind.solid { WindowManager.update-preview-value(Api.color-to-data(root.current-color).text); } } changed current-brush-kind => { update-brush(); if WindowManager.widget-mode == WidgetMode.preview { WindowManager.update-brush-preview(); } } changed current-angle => { if WindowManager.widget-mode == WidgetMode.preview { update-brush(); WindowManager.update-brush-preview(); } } changed current-stop-position => { update-brush(); WindowManager.update-brush-preview(); } changed current-stop-color => { update-brush(); WindowManager.update-brush-preview(); } changed active-tab => { WindowManager.hide-color-stop-picker(); } rebuild-gradient-stops => { current-gradient-stops = Api.clone-gradient-stops(current-gradient-stops); update-brush(); WindowManager.update-brush-preview(); } set-active-tab(picker-tab) => { active-tab = picker-tab; if picker-tab == PickerTab.color { current-brush-kind = BrushKind.solid; brush-mode = BrushMode.color; } if picker-tab == PickerTab.gradient { if last-used-gradient-type == GradientType.radial { current-brush-kind = BrushKind.radial; brush-mode = BrushMode.radial; } else if last-used-gradient-type == GradientType.conic { current-brush-kind = BrushKind.conic; brush-mode = BrushMode.conic; } else { current-brush-kind = BrushKind.linear; brush-mode = BrushMode.linear; } } } set-gradient-type(gradient-type) => { last-used-gradient-type = gradient-type; if gradient-type == GradientType.linear { current-brush-kind = BrushKind.linear; brush-mode = BrushMode.linear; } else if gradient-type == GradientType.conic { current-brush-kind = BrushKind.conic; brush-mode = BrushMode.conic; } else { current-brush-kind = BrushKind.radial; brush-mode = BrushMode.radial; } } reset => { brush-mode = BrushMode.color; active-tab = PickerTab.color; picker-mode = BrushPropertyType.color; last-used-gradient-type = GradientType.linear; } public function set-current-stop-as-color() { set-color(current-gradient-stops[current-stop-index].color); } public function update-brush() { self.current-brush = Api.create-brush(current-brush-kind, current-angle, current-color, current-gradient-stops); } function set-svg-or-global-tab(property-value: PropertyValue) { if PickerData.get-color-code-type(property-value) == ColorCodeType.css-color { set-active-tab(PickerTab.css-color); } else { set-active-tab(PickerTab.globals); } } public function init-with-property-value(property-value: PropertyValue) { PickerData.current-brush-kind = property-value.brush-kind; current-gradient-stops = Api.clone-gradient-stops(property-value.gradient-stops); current-angle = property-value.value-float; brush-mode = property-value.brush-kind == BrushKind.solid ? BrushMode.color : property-value.brush-kind == BrushKind.linear ? BrushMode.linear : BrushMode.radial; if property-value.kind == PropertyValueKind.color { picker-mode = BrushPropertyType.color; if PickerData.get-color-code-type(property-value) == ColorCodeType.other { set-active-tab(PickerTab.color); } else { set-svg-or-global-tab(property-value); } } if property-value.kind == PropertyValueKind.brush { picker-mode = BrushPropertyType.brush; if PickerData.get-color-code-type(property-value) == ColorCodeType.other { if property-value.brush-kind == BrushKind.solid { set-active-tab(PickerTab.color); } else { set-active-tab(PickerTab.gradient); } } else { set-svg-or-global-tab(property-value); } } if property-value.brush-kind == BrushKind.solid { if property-value.code == "" { PickerData.set-default-color(); } else { PickerData.set-color(property-value.value-brush); } } else { PickerData.set-color(PickerData.current-gradient-stops[PickerData.current-stop-index].color); if property-value.brush-kind == BrushKind.radial { set-gradient-type(GradientType.radial) } if property-value.brush-kind == BrushKind.linear { set-gradient-type(GradientType.linear) } if property-value.brush-kind == BrushKind.conic { set-gradient-type(GradientType.conic) } } update-brush(); } public function set-color(color: color) { PickerData.hue = color.to-hsv().hue; PickerData.saturation = color.to-hsv().saturation; PickerData.value = color.to-hsv().value; PickerData.alpha = color.to-hsv().alpha * 100; } public function set-default-color() { if Api.recent-colors.length > 0 { set-color(Api.recent-colors[0]); } else { set-color(#2479f4); } } pure public function get-color-code-type(property-value: PropertyValue) -> ColorCodeType { if property-value.value-string == "" { return ColorCodeType.other; } if Api.is-css-color(property-value.code) { return ColorCodeType.css-color; } else { return ColorCodeType.global; } } } export global WindowManager { out property showing-color-picker: false; out property showing-table-editor: false; out property showing-color-stop-picker: false; out property widget-mode: edit; property current-element-information; out property current-property-information; in-out property current-property-container-id; in-out property current-preview-data; property component-factory <=> Api.preview-data; property possible_error; property brush-string; callback show-floating-widget(property-information: PropertyInformation, element-information: ElementInformation); callback show-floating-preview-widget(property-container-id: string, preview-data: PreviewData, property-value: PropertyValue); callback show-floating-table-editor(property-group-id: string, preview-data: PreviewData); callback hide-floating-widget(); callback hide-floating-color-widget(); callback apply-current-value(value: string); callback update-preview-value(value: string); callback update-brush-preview(); callback show-color-stop-picker(); callback hide-color-stop-picker(); show-floating-widget(property-information, element-information) => { widget-mode = WidgetMode.edit; current-property-information = property-information; current-element-information = element-information; PickerData.init-with-property-value(current-property-information.value); showing-color-picker = true; } hide-floating-widget => { showing-color-picker = false; showing-table-editor = false; showing-color-stop-picker = false; current-element-information = { }; current-property-information = { }; widget-mode = WidgetMode.edit; PickerData.reset() } show-floating-preview-widget(property-container-id, preview-data, property-value) => { current-property-container-id = property-container-id; current-preview-data = preview-data; widget-mode = WidgetMode.preview; PickerData.init-with-property-value(property-value); showing-color-picker = true; } show-floating-table-editor(property-group-id, preview-data) => { TableData.populate-table(property-group-id, preview-data); showing-table-editor = true; } show-color-stop-picker => { PickerData.set-current-stop-as-color(); showing-color-stop-picker = true; } hide-color-stop-picker => { showing-color-stop-picker = false; } apply-current-value(text) => { Api.set-code-binding( current-element-information.source-uri, current-element-information.source-version, current-element-information.offset, current-property-information.name, text); } update-preview-value(text) => { if WindowManager.showing-table-editor { TableData.set-color-preview("\"\{text}\""); } else { self.possible_error = Api.set-json-preview-data(current-property-container-id, current-preview-data.name, "\"\{text}\"", check-new-value(current-property-container-id + current-preview-data.name)); } } update-brush-preview() => { if widget-mode == WidgetMode.preview { brush-string = Api.as-slint-brush(PickerData.current-brush-kind, PickerData.current-angle, PickerData.current-brush, PickerData.current-gradient-stops); if WindowManager.showing-table-editor { TableData.set-color-preview("\"\{brush-string}\""); } else { self.possible_error = Api.set-json-preview-data(current-property-container-id, current-preview-data.name, "\"\{brush-string}\"", check-new-value(current-property-container-id + current-preview-data.name)); } } } hide-floating-color-widget() => { if showing-table-editor { showing-color-picker = false; showing-color-stop-picker = false; } else { hide-floating-widget(); } } property container-property-name; function check-new-value(value: string) -> bool { if container-property-name == value { return false; } else { container-property-name = value; return true; } } }