1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4import { HorizontalBox, Palette, ScrollView } from "std-widgets.slint";
5import { ComponentListItem, ComponentItem } from "../api.slint";
6import { StateLayer } from "./state-layer.slint";
7import { EditorSizeSettings, EditorAnimationSettings, Icons } from "./styling.slint";
8import { BodyText } from "./body-text.slint";
9import { BodyStrongText } from "./body-strong-text.slint";
10import { StatusLineApi } from "status-line.slint";
11
12component HeaderItemTemplate {
13 in property <bool> enabled: true;
14 in property <string> text;
15 out property <bool> open: true;
16 out property <length> offset: icon-image.width + content-layer.spacing;
17
18 min-width: content-layer.min-width;
19 min-height: max(EditorSizeSettings.item-height, content-layer.min-height);
20
21 touch-area := TouchArea {
22 clicked => {
23 root.open = !root.open;
24 }
25 }
26
27 state-layer := StateLayer {
28 width: 100%;
29 height: 100%;
30 has-hover: touch-area.has-hover;
31 pressed: touch-area.pressed;
32 }
33
34 content-layer := HorizontalBox {
35 icon-image := Image {
36 width: EditorSizeSettings.default-icon-width;
37 colorize: Palette.foreground;
38 source: Icons.chevron-down;
39 rotation-origin-x: self.width / 2;
40 rotation-origin-y: self.height / 2;
41 states [
42 closed when !root.open: {
43 rotation-angle: -0.25turn;
44 }
45 ]
46
47 animate rotation-angle { duration: EditorAnimationSettings.rotation-duration; }
48 }
49
50 BodyText {
51 text: root.text;
52 opacity: 0.7;
53 }
54 }
55
56 states [
57 disabled when !root.enabled: {
58 root.opacity: 0.5;
59 }
60 ]
61}
62
63component ItemTemplate {
64 in property <bool> enabled: true;
65 in property <string> text;
66 in property <bool> can-drop-here;
67 in property <length> offset;
68 out property <length> absolute-mouse-x: touch-area.mouse-x - touch-area.x + touch-area.absolute-position.x;
69 out property <length> absolute-mouse-y: touch-area.mouse-y - touch-area.y + touch-area.absolute-position.y;
70 out property <bool> pressed <=> touch-area.pressed;
71
72 callback clicked <=> touch-area.clicked;
73 callback pointer-event <=> touch-area.pointer-event;
74
75 min-width: content-layer.min-width;
76 min-height: max(EditorSizeSettings.item-height, content-layer.min-height);
77
78 touch-area := TouchArea {
79 states [
80 dragging-no-drop when self.pressed && !root.can-drop-here: {
81 mouse-cursor: MouseCursor.no-drop;
82 }
83 dragging-can-drop when self.pressed && root.can-drop-here: {
84 mouse-cursor: MouseCursor.copy;
85 }
86 normal when !self.pressed: {
87 mouse-cursor: MouseCursor.default;
88 }
89 ]
90
91 changed has-hover => {
92 if self.has-hover {
93 StatusLineApi.help-text = @tr("Drag onto canvas to add {}", text);
94 }
95 }
96 }
97
98 state-layer := StateLayer {
99 width: 100%;
100 height: 100%;
101 has-hover: touch-area.has-hover;
102 pressed: touch-area.pressed;
103 }
104
105 content-layer := HorizontalBox {
106 padding-left: self.padding + root.offset;
107 BodyText {
108 text: root.text;
109 }
110 }
111
112 states [
113 disabled when !root.enabled: {
114 root.opacity: 0.5;
115 }
116 ]
117}
118
119export component ExpandableListView inherits ScrollView {
120 in property <[ComponentListItem]> known-components;
121
122 in property <bool> preview-is-current;
123 in property <length> preview-area-position-x;
124 in property <length> preview-area-position-y;
125 in property <length> preview-area-width;
126 in property <length> preview-area-height;
127 in-out property <ComponentItem> visible-component: {
128 name: "",
129 defined-at: "",
130 pretty-location: "",
131 is-user-defined: false,
132 is-currently-shown: false,
133 };
134
135 pure callback can-drop(index: int, x: length, y: length, on-drop-area: bool) -> bool;
136 callback drop(index: int, x: length, y: length);
137 callback show-preview-for(name: string, defined-at: string);
138
139 property <bool> preview-visible: preview-area-width > 0px && preview-area-height > 0px;
140 property <length> list-spacing: 10px;
141
142 VerticalLayout {
143 alignment: start;
144 for cli[index] in root.known-components: VerticalLayout {
145 property <int> my-category-index: index;
146 header-item := HeaderItemTemplate {
147 text: cli.category;
148 }
149
150 if header-item.open: VerticalLayout {
151 for ci[index] in cli.components: ItemTemplate {
152 property <length> drop-x: self.absolute-mouse-x - root.preview-area-position-x;
153 property <length> drop-y: self.absolute-mouse-y - root.preview-area-position-y;
154 property <bool> on-drop-area:
155 drop-x >= 0 && drop-x <= root.preview-area-width && drop-y >= 0 && drop-y <= root.preview-area-height;
156 property <ComponentItem> data: ci;
157
158 can-drop-here: root.preview-is-current && !self.data.is-currently-shown && root.can-drop(self.data.index, drop-x, drop-y, on-drop-area);
159 enabled: root.preview-visible;
160 text: ci.name;
161 offset: header-item.offset;
162 height: self.min-height;
163
164 pointer-event(event) => {
165 if self.can-drop-here && event.kind == PointerEventKind.up && event.button == PointerEventButton.left {
166 root.drop(self.data.index, drop-x, drop-y);
167 }
168 }
169
170 init() => {
171 root.visible-component = ci;
172 }
173 }
174 }
175 }
176 }
177}
178