slint/tools/lsp/ui/components/selection-popup.slint
Olivier Goffart 3e94bd2167 Janitor: Remove trailing whitespaces from all files
`git grep -I -l -O'sed -i "s/[[:space:]]*$//"' -e ''`
2025-01-10 13:23:22 +01:00

560 lines
24 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 { Button, CheckBox, ListView, Palette, ScrollView, LineEdit } from "std-widgets.slint";
import { EditorFontSettings, EditorSizeSettings, EditorSpaceSettings, EditorPalette } from "./styling.slint";
import { Api, SelectionStackFrame, SelectionStackFilter } from "../api.slint";
import { Icons } from "styling.slint";
component FilterList {
public function show() { pop.show(); }
public function close() { pop.close(); }
out property <SelectionStackFilter> filter: {
if (!self.filter-layout && !self.filter-interactive && self.filter-other) {
return SelectionStackFilter.Others;
} else if (!self.filter-layout && self.filter-interactive && !self.filter-other) {
return SelectionStackFilter.Interactive;
} else if (!self.filter-layout && self.filter-interactive && self.filter-other) {
return SelectionStackFilter.InteractiveAndOthers;
} else if (self.filter-layout && !self.filter-interactive && !self.filter-other) {
return SelectionStackFilter.Layouts;
} else if (self.filter-layout && !self.filter-interactive && self.filter-other) {
return SelectionStackFilter.LayoutsAndOthers;
} else if (self.filter-layout && self.filter-interactive && !self.filter-other) {
return SelectionStackFilter.LayoutsAndInteractive;
} else if (self.filter-layout && self.filter-interactive && self.filter-other) {
return SelectionStackFilter.Everything;
}
return SelectionStackFilter.Nothing;
}
out property <bool> has-filter: !self.filter-layout || !self.filter-interactive || !self.filter-other;
in-out property <bool> filter-interactive: true;
in-out property <bool> filter-layout: true;
in-out property <bool> filter-other: true;
width: 0px;
height: 0px;
pop := PopupWindow {
width: self.preferred-width;
height: self.preferred-height;
close-policy: PopupClosePolicy.close-on-click-outside;
Rectangle {
border-color: Palette.border;
border-width: 1px;
border-radius: EditorSizeSettings.radius;
drop-shadow-blur: EditorSpaceSettings.default-padding;
drop-shadow-color: Palette.foreground.transparentize(0.9);
background: Palette.alternate-background;
TouchArea {
// Just block events from reaching other TouchAreas!
}
VerticalLayout {
padding: EditorSpaceSettings.default-padding;
spacing: EditorSpaceSettings.default-spacing;
lcb := CheckBox {
text: @tr("Layouts");
checked <=> root.filter-layout;
}
icb := CheckBox {
text: @tr("Interactive");
checked <=> root.filter-interactive;
}
ocb := CheckBox {
text: @tr("Other");
checked <=> root.filter-other;
}
}
}
}
}
component PopupInner inherits Rectangle {
in property <length> preview-width: 500px;
in property <length> preview-height: 900px;
callback close();
in-out property <bool> filter-interactive: true;
in-out property <bool> filter-layout: true;
in-out property <bool> filter-other: true;
in-out property <string> filter-text: "";
in property <length> max-popup-height: 900px;
in-out property <length> selection-x: 0px;
in-out property <length> selection-y: 0px;
in-out property <[SelectionStackFrame]> selection-stack: [
{
width: 100%,
height: 40%,
x: 10%,
y: 0%,
is-in-root-component: true,
is-layout: false,
is-interactive: true,
is-selected: false,
type-name: "TypeA",
file-name: "thumb.slint",
id: "some_a",
},
{
width: 50%,
height: 50%,
x: 0%,
y: 0%,
is-in-root-component: false,
is-layout: true,
is-interactive: false,
is-selected: false,
file-name: "thumb.slint",
type-name: "HorizontalLayout",
},
{
width: 50%,
height: 10%,
x: 20%,
y: 40%,
is-in-root-component: false,
is-layout: false,
is-interactive: false,
is-selected: false,
type-name: "Rectangle",
file-name: "tiling.slint",
id: "",
},
{
width: 50%,
height: 50%,
x: 50%,
y: 50%,
is-in-root-component: false,
is-interactive: false,
is-selected: true,
type-name: "TypeA",
file-name: "finger.slint",
id: "some_a",
},
{
width: 250%,
height: 50%,
x: 50%,
y: 50%,
is-in-root-component: false,
is-layout: false,
is-interactive: true,
is-selected: false,
type-name: "Button",
file-name: "finger.slint",
id: "alsoInteractive",
},
{
width: 50%,
height: 50%,
x: 0%,
y: 50%,
is-in-root-component: false,
is-layout: true,
is-interactive: false,
is-selected: false,
file-name: "finger.slint",
type-name: "VerticalLayout",
},
{
width: 5%,
height: 5%,
x: 25%,
y: 25%,
is-in-root-component: true,
is-layout: false,
is-interactive: false,
is-selected: false,
id: "words",
type-name: "Text",
},
{
width: 50%,
height: 100%,
x: 0%,
y: 0%,
is-in-root-component: true,
is-layout: false,
is-interactive: false,
is-selected: false,
type-name: "Window",
id: "",
}
];
private property <SelectionStackFrame> select-on-close: Api.find-selected-selection-stack-frame(self.selection-stack);
private property <float> aspect-ratio: preview-width / preview-height;
private property <length> max-rect-size: 40px;
private property <length> frame-height: self.max-rect-size + EditorSpaceSettings.default-padding * 2 + 1rem;
private property <int> max-visible-frames: Math.floor(((self.max-popup-height / 1px) * 0.8) / (self.frame-height / 1px));
private property <int> visible-frames: Math.min(self.max-visible-frames, root.selection-stack.length);
function unselect-previewed-selection() {
if self.select-on-close.element-path != "" {
Api.select-element(self.select-on-close.element-path, self.select-on-close.element-offset, self.select-on-close.x * root.preview-width, self.select-on-close.y * root.preview-height);
}
}
private property <[SelectionStackFrame]> filtered-model: Api.filter-sort-selection-stack(root.selection-stack, filter-edit.text, filter-list.filter);
border-radius: EditorSizeSettings.radius;
border-width: 0.5px;
background: Palette.background;
drop-shadow-blur: 10px;
drop-shadow-color: black;
border-color: lightgray;
width: 250px;
TouchArea {
changed has-hover => {
if self.has-hover {
root.unselect-previewed-selection();
}
}
}
VerticalLayout {
spacing: EditorSpaceSettings.default-spacing;
header := HorizontalLayout {
padding: EditorSpaceSettings.default-padding;
padding-bottom: 0px;
spacing: EditorSpaceSettings.default-spacing;
filter-button := Button {
private property <bool> filter-state: filter-list.filter != SelectionStackFilter.Everything || filter-edit.text != "";
changed filter-state => {
self.checked = filter-state;
}
icon: Icons.filter;
checkable: true;
colorize-icon: true;
clicked => {
filter-list.show();
filter-edit.clear-focus();
self.checked = filter-state;
}
init => {
self.checked = filter-state;
}
}
filter-edit := LineEdit {
placeholder-text: "Filter";
min-width: 50px;
text <=> root.filter-text;
init => {
self.focus();
}
changed has-focus => {
if !self.has-focus {
self.focus();
}
}
}
}
Rectangle {
VerticalLayout {
if filtered-model.length == 0: Rectangle {
width: 100%;
height: (root.visible-frames * root.frame-height);
Text {
text: @tr("No match");
}
}
if filtered-model.length >= 1: ScrollView {
height: (root.visible-frames * root.frame-height);
list-view := VerticalLayout {
for frame[index] in root.filtered-model: frame-rect := Rectangle {
height: root.frame-height;
function frame-color(frame: SelectionStackFrame) -> brush {
return EditorPalette.interactive-element-selection-secondary;
}
function frame-background(frame: SelectionStackFrame) -> brush {
if frame.is-interactive {
return EditorPalette.interactive-element-selection-primary;
} else if frame.is-layout {
return transparent;
} else if frame.is-selected {
EditorPalette.general-element-selection-selected;
} else {
return EditorPalette.general-element-selection-primary;
}
}
function calculate_pos(p: length, percent: float) -> length {
return Math.round((p / 1px) * percent) * 1px;
}
function calculate_size(p: length, percent: float) -> length {
return Math.max(Math.round((p / 1px) * percent), 2) * 1px;
}
VerticalLayout {
padding-top: EditorSpaceSettings.default-padding / 2;
if !frame.is-in-root-component: Text {
x: EditorSpaceSettings.default-padding;
text: frame.file-name;
overflow: elide;
color: frame.is-selected ? Palette.accent-foreground : Palette.foreground;
font-size: EditorFontSettings.label-sub.font-size - 3px;
font-italic: true;
}
HorizontalLayout {
visible: (frame.type-name == "Window") ? false : true;
padding-left: EditorSpaceSettings.default-padding;
spacing: EditorSpaceSettings.default-spacing / 2.0;
alignment: start;
VerticalLayout {
alignment: center;
padding-bottom: EditorSpaceSettings.default-padding / 2;
Rectangle {
function calculate_aspect_ratio_box(aspect-ratio: float, max-rect-length: length) -> length {
return Math.min(aspect-ratio, 1.0) * max-rect-length;
}
clip: true;
width: calculate_aspect_ratio_box(root.aspect-ratio, root.max-rect-size) + EditorSpaceSettings.default-padding;
height: calculate_aspect_ratio_box(1.0 / root.aspect-ratio, root.max-rect-size) + EditorSpaceSettings.default-padding / 2;
measure-rect := Rectangle {
width: calculate_aspect_ratio_box(root.aspect-ratio, root.max-rect-size);
height: calculate_aspect_ratio_box(1.0 / root.aspect-ratio, root.max-rect-size);
border-color: frame.is-selected ? EditorPalette.general-element-selection-selected.transparentize(0.5) : Palette.foreground.transparentize(0.65);
border-width: 1px;
placeholder-rect := Rectangle {
x: calculate_pos(measure-rect.width, frame.x);
y: calculate_pos(measure-rect.height, frame.y);
width: calculate_size(measure-rect.width, frame.width);
height: calculate_size(measure-rect.height, frame.height);
border-color: frame-rect.frame-color(frame);
border-width: 0.5px;
background: frame-rect.frame-background(frame);
if frame.is-layout: Rectangle {
border-color: EditorPalette.layout-element-selection-secondary;
border-width: 0.5px;
if (frame.type-name == "HorizontalLayout" || frame.type-name == "HorizontalBox"): HorizontalLayout {
spacing: 1px;
padding: 1px;
Rectangle {
background: EditorPalette.layout-element-selection-primary;
}
Rectangle {
background: EditorPalette.layout-element-selection-primary;
}
}
if (frame.type-name == "VerticalLayout" || frame.type-name == "VerticalBox"): VerticalLayout {
spacing: 1.5px;
padding: 1.5px;
Rectangle {
background: EditorPalette.layout-element-selection-primary;
}
Rectangle {
background: EditorPalette.layout-element-selection-primary;
}
}
if (frame.type-name == "GridLayout" || frame.type-name == "GridBox"): VerticalLayout {
spacing: 1px;
HorizontalLayout {
spacing: 1.5px;
padding: 1.5px;
Rectangle {
background: EditorPalette.layout-element-selection-primary;
}
Rectangle {
background: EditorPalette.layout-element-selection-primary;
}
}
HorizontalLayout {
spacing: 1.5px;
padding: 1.5px;
Rectangle {
background: EditorPalette.layout-element-selection-primary;
}
Rectangle {
background: EditorPalette.layout-element-selection-primary;
}
}
}
}
}
}
}
}
VerticalLayout {
spacing: EditorSpaceSettings.default-spacing / 2;
alignment: center;
if frame.id != "": Text {
text: frame.id;
overflow: elide;
color: frame.is-selected ? Palette.accent-foreground : Palette.foreground;
font-weight: EditorFontSettings.bold-font-weight;
font-size: EditorFontSettings.label.font-size;
}
Text {
text: frame.type-name;
overflow: elide;
color: frame.is-selected ? Palette.accent-foreground : Palette.foreground;
font-size: EditorFontSettings.label-sub.font-size;
font-italic: true;
}
}
}
Rectangle {
height: 1px;
background: Palette.border;
}
}
x: -EditorSpaceSettings.default-padding / 2;
border-width: 0px;
states [
hover when ta.has-hover && !frame.is-selected: {
background: Palette.accent-background.transparentize(0.9);
}
unhover when !ta.has-hover && !frame.is-selected: {
background: Palette.background;
}
selected when frame.is-selected: {
background: Palette.accent-background;
}
]
ta := TouchArea {
clicked() => {
Api.select-element(frame.element-path, frame.element-offset, frame.x * root.preview-width, frame.y * root.preview-height);
root.select-on-close = { };
root.close();
}
changed has-hover => {
if self.has-hover {
Api.select-element(frame.element-path, frame.element-offset, frame.x * root.preview-width, frame.y * root.preview-height);
}
}
}
}
}
}
}
Rectangle {
height: 3px;
y: 0px;
width: 100%;
background: EditorPalette.shadow-gradient;
}
}
}
filter-list := FilterList {
x: filter-button.x + EditorSpaceSettings.default-spacing;
y: filter-button.y + filter-button.height + EditorSpaceSettings.default-spacing;
filter-layout <=> root.filter-layout;
filter-interactive <=> root.filter-interactive;
filter-other <=> root.filter-other;
}
}
export component SelectionPopup {
public function show-selection-stack(x: length, y: length) {
self.selection-x = x;
self.selection-y = y;
self.selection-stack = Api.selection-stack-at(self.selection-x, self.selection-y);
popup.show();
}
in property <length> preview-width;
in property <length> preview-height;
in property <length> max-popup-height;
private property <[SelectionStackFrame]> selection-stack;
private property <length> selection-x;
private property <length> selection-y;
private property <bool> filter-interactive: true;
private property <bool> filter-layout: true;
private property <bool> filter-other: true;
private property <string> filter-text: "";
popup := PopupWindow {
in property <length> preview-width: root.preview-width;
in property <length> preview-height: root.preview-height;
in-out property <[SelectionStackFrame]> selection-stack: root.selection-stack;
in-out property <length> selection-x: root.selection-x;
in-out property <length> selection-y: root.selection-y;
in property <length> max-popup-height: root.max-popup-height;
in-out property <bool> filter-interactive <=> root.filter-interactive;
in-out property <bool> filter-layout <=> root.filter-layout;
in-out property <bool> filter-other <=> root.filter-other;
in-out property <string> filter-text <=> root.filter-text;
close-policy: PopupClosePolicy.close-on-click-outside;
max-height: root.max-popup-height;
x: root.selection-x + 10px;
y: root.selection-y + 10px;
VerticalLayout {
inner := PopupInner {
close() => {
popup.close();
}
filter-text <=> popup.filter-text;
filter-layout <=> popup.filter-layout;
filter-interactive <=> popup.filter-interactive;
filter-other <=> popup.filter-other;
preview-width <=> popup.preview-width;
preview-height <=> popup.preview-height;
max-popup-height <=> popup.max-popup-height;
selection-stack <=> popup.selection-stack;
selection-x <=> popup.selection-x;
selection-y <=> popup.selection-y;
}
}
}
}