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