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 { CosmicPalette, CosmicFontSettings, Icons } from "styling.slint";
5import { ListView } from "../common/listview.slint";
6import { StateLayer, StateLayerBase } from "components.slint";
7
8component TableViewColumn inherits Rectangle {
9 in property <SortOrder> sort-order: SortOrder.unsorted;
10
11 callback clicked <=> touch-area.clicked;
12 callback adjust_size(length);
13
14 min-height: max(24px, layout.min-height);
15 background: CosmicPalette.control-background;
16
17 touch-area := TouchArea {
18 width: parent.width - 11px;
19 }
20
21 layout := HorizontalLayout {
22 padding-left: 8px;
23 padding-right: 8px;
24 spacing: 2px;
25
26 HorizontalLayout {
27 @children
28 }
29
30 icon := Image {
31 image-fit: contain;
32 colorize: CosmicPalette.foreground;
33 visible: root.sort-order != SortOrder.unsorted;
34 width: 12px;
35 y: (parent.height - self.height) / 2;
36 source: root.sort-order == SortOrder.ascending ? Icons.arrow-down : Icons.arrow-up;
37 accessible-role: none;
38
39 animate colorize { duration: 150ms; }
40 }
41 }
42
43 StateLayerBase {
44 width: 100%;
45 height: 100%;
46 has-hover: touch-area.has-hover;
47 pressed: touch-area.pressed;
48 }
49
50 // border
51 Rectangle {
52 y: parent.height - self.height;
53 width: 100%;
54 height: 1px;
55 background: CosmicPalette.border;
56 }
57
58 Rectangle {
59 x: parent.width - 1px;
60 width: 1px;
61
62 states [
63 hover when movable-touch-area.has-hover : {
64 background: CosmicPalette.foreground;
65 }
66 ]
67
68 animate background { duration: 150ms; }
69
70 movable-touch-area := TouchArea {
71 width: 10px;
72 mouse-cursor: ew-resize;
73
74 moved => {
75 if (self.pressed) {
76 adjust_size(self.mouse-x - self.pressed-x);
77 }
78 }
79 }
80 }
81}
82
83component TableViewCell inherits Rectangle {
84 clip: true;
85
86 HorizontalLayout {
87 padding: 8px;
88
89 @children
90 }
91}
92
93component TableViewRow inherits Rectangle {
94 in property <bool> selected;
95 in property <bool> even;
96
97 callback clicked <=> touch-area.clicked;
98 callback pointer-event(event: PointerEvent, position: Point);
99
100
101 min-width: layout.min-width;
102 min-height: max(24px, layout.min-height);
103 background: root.even ? CosmicPalette.control-background : transparent;
104
105 layout := HorizontalLayout {
106 @children
107 }
108
109 StateLayerBase {
110 checked: root.selected;
111 focus-boder-margin: 0;
112 border-radius: root.border-radius;
113 pressed: touch-area.pressed;
114 has-hover: touch-area.has-hover;
115 }
116
117 touch-area := TouchArea {
118 pointer-event(pe) => {
119 root.pointer-event(pe, {
120 x: self.absolute-position.x + self.mouse-x,
121 y: self.absolute-position.y + self.mouse-y,
122 });
123 }
124 }
125}
126
127export component StandardTableView {
128 in property <[[StandardListViewItem]]> rows;
129 out property <int> current-sort-column: -1;
130 in-out property <[TableColumn]> columns;
131 in-out property <int> current-row: -1;
132 in property <bool> enabled <=> scroll-view.enabled;
133 out property <length> visible-width <=> scroll-view.visible-width;
134 out property <length> visible-height <=> scroll-view.visible-height;
135 in-out property <bool> has-focus <=> scroll-view.has-focus;
136 in-out property <length> viewport-width <=> scroll-view.viewport-width;
137 in-out property <length> viewport-height <=> scroll-view.viewport-height;
138 in-out property <length> viewport-x <=> scroll-view.viewport-x;
139 in-out property <length> viewport-y <=> scroll-view.viewport-y;
140 in property <ScrollBarPolicy> vertical-scrollbar-policy <=> scroll-view.vertical-scrollbar-policy;
141 in property <ScrollBarPolicy> horizontal-scrollbar-policy <=> scroll-view.horizontal-scrollbar-policy;
142
143 callback sort-ascending(column: int);
144 callback sort-descending(column: int);
145 callback row-pointer-event(row: int, event: PointerEvent, position: Point);
146 callback current-row-changed(current-row: int);
147
148 public function set-current-row(index: int) {
149 if (index < 0 || index >= rows.length) {
150 return;
151 }
152
153 current-row = index;
154 current-row-changed(current-row);
155
156 if (current-item-y < 0) {
157 scroll-view.viewport-y += 0 - current-item-y;
158 }
159
160 if (current-item-y + item-height > scroll-view.visible-height) {
161 scroll-view.viewport-y -= current-item-y + item-height - scroll-view.visible-height;
162 }
163 }
164
165 private property <length> min-header-height: 32px;
166 private property <length> item-height: scroll-view.viewport-height / rows.length;
167 private property <length> current-item-y: scroll-view.viewport-y + current-row * item-height;
168
169 function sort(index: int) {
170 if (root.current-sort-column != index) {
171 root.columns[root.current-sort-column].sort-order = SortOrder.unsorted;
172 }
173
174 if(root.columns[index].sort-order == SortOrder.ascending) {
175 root.columns[index].sort-order = SortOrder.descending;
176 root.sort-descending(index);
177 } else {
178 root.columns[index].sort-order = SortOrder.ascending;
179 root.sort-ascending(index);
180 }
181
182 root.current-sort-column = index;
183 }
184
185 min-width: 400px;
186 min-height: 200px;
187 horizontal-stretch: 1;
188 vertical-stretch: 1;
189 forward-focus: focus-scope;
190 accessible-role: table;
191
192 VerticalLayout {
193 Rectangle {
194 clip: true;
195 vertical-stretch: 0;
196 min-height: root.min-header-height;
197 background: CosmicPalette.control-background;
198
199 header-layout := HorizontalLayout {
200 width: max(self.preferred-width, parent.width);
201 x: scroll-view.viewport-x;
202 padding-right: 6px;
203 min-height: root.min-header-height;
204
205 for column[index] in root.columns : TableViewColumn {
206 sort-order: column.sort-order;
207 horizontal-stretch: column.horizontal-stretch;
208 min-width: max(column.min-width, column.width);
209 preferred-width: self.min-width;
210 max-width: (index < columns.length && column.width >= 1px) ? max(column.min-width, column.width) : 100000px;
211
212 clicked => {
213 root.sort(index);
214 }
215
216 adjust-size(diff) => {
217 column.width = max(1px, self.width + diff);
218 }
219
220 Text {
221 vertical-alignment: center;
222 text: column.title;
223 font-weight: CosmicFontSettings.body.font-weight;
224 font-size: CosmicFontSettings.body.font-size;
225 color: CosmicPalette.foreground;
226 overflow: elide;
227 }
228 }
229 }
230 }
231
232 scroll-view := ListView {
233 for row[idx] in root.rows : TableViewRow {
234 selected: idx == root.current-row;
235 even: mod(idx, 2) == 0;
236
237 pointer-event(pe, pos) => {
238 root.row-pointer-event(idx, pe, {
239 x: pos.x - root.absolute-position.x,
240 y: pos.y - root.absolute-position.y,
241 });
242 }
243
244 clicked => {
245 root.focus();
246 root.set-current-row(idx);
247 }
248
249 for cell[index] in row : TableViewCell {
250 private property <bool> has_inner_focus;
251
252 horizontal-stretch: root.columns[index].horizontal-stretch;
253 min-width: max(columns[index].min-width, columns[index].width);
254 preferred-width: self.min-width;
255 max-width: (index < columns.length && columns[index].width >= 1px) ? max(columns[index].min-width, columns[index].width) : 100000px;
256
257 Rectangle {
258 cell-text := Text {
259 width: 100%;
260 height: 100%;
261 overflow: elide;
262 vertical-alignment: center;
263 text: cell.text;
264 font-weight: CosmicFontSettings.body.font-weight;
265 font-size: CosmicFontSettings.body.font-size;
266 color: mod(idx, 2) == 0 ? CosmicPalette.control-foreground : CosmicPalette.foreground;
267
268 states [
269 selected when idx == root.current-row : {
270 cell-text.color: CosmicPalette.accent-background;
271 }
272 ]
273 }
274 }
275 }
276 }
277 }
278 }
279
280 focus-scope := FocusScope {
281 x: 0;
282 width: 0; // Do not react on clicks
283
284 key-pressed(event) => {
285 if (event.text == Key.UpArrow) {
286 root.set-current-row(root.current-row - 1);
287 return accept;
288 } else if (event.text == Key.DownArrow) {
289 root.set-current-row(root.current-row + 1);
290 return accept;
291 }
292 reject
293 }
294 }
295}
296