mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-20 15:22:13 +00:00
560 lines
24 KiB
Text
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;
|
|
}
|
|
}
|
|
}
|
|
}
|