Evan's Blog
EvanZhu0721 / 公开仓库

SBMS

镜像已同步

SBMS complete the calculation rules for the default logical desktop topology relationship and the physical dimensions of the displays in the Windows multi-screen collaboration system.

上游 GitHub
READ-ONLY MIRROR

README、源码与 Release 已保存到本站;源码视图固定到 commit a37d6c4b。

main/ root / ui
a37d6c4b· 固定源码快照
ui/controls.slint16.0 KB
1import { Button, LineEdit } from "std-widgets.slint";
2import { MdTokens } from "theme.slint";
3
4export component ResolutionLineEdit inherits Rectangle {
5 in-out property <string> text;
6 in property <string> placeholder-text;
7 in property <string> field-label;
8 in property <string> suggestion;
9 in property <string> suggestion-suffix;
10 callback edited();
11
12 height: MdTokens.target;
13 background: transparent;
14 clip: true;
15
16 editor := LineEdit {
17 width: parent.width;
18 height: parent.height;
19 text <=> root.text;
20 placeholder-text: root.placeholder-text;
21 input-type: InputType.number;
22 font-size: MdTokens.type-body-large;
23 accessible-label: root.field-label;
24 accessible-description: root.suggestion == ""
25 ? ""
26 : "Suggested " + root.suggestion + ". Press Tab to accept.";
27 edited => { root.edited(); }
28 key-pressed(event) => {
29 if event.text == Key.Tab && root.suggestion != "" {
30 root.text = root.suggestion;
31 root.edited();
32 }
33 // Let Tab keep its normal focus-navigation behavior.
34 return reject;
35 }
36 }
37
38 prefix-measure := Text {
39 x: 16px;
40 width: self.preferred-width;
41 height: parent.height;
42 text: root.text;
43 color: transparent;
44 font-size: MdTokens.type-body-large;
45 vertical-alignment: center;
46 accessible-role: none;
47 }
48
49 if editor.has-focus && root.suggestion-suffix != "" : Text {
50 x: 16px + prefix-measure.preferred-width;
51 width: parent.width - self.x - 40px;
52 height: parent.height;
53 text: root.suggestion-suffix;
54 color: MdTokens.on-surface-variant;
55 opacity: 0.55;
56 font-size: MdTokens.type-body-large;
57 vertical-alignment: center;
58 overflow: clip;
59 accessible-role: none;
60 }
61}
62
63export component CompactComboBox inherits Rectangle {
64 in property <[string]> model;
65 in property <[string]> collapsed-model: root.model;
66 in-out property <int> current-index: -1;
67 in property <bool> control-enabled: true;
68 in property <string> field-label;
69 in property <length> popup-width: root.width;
70 callback selected();
71
72 property <string> current-value: root.current-index >= 0
73 && root.current-index < root.collapsed-model.length
74 ? root.collapsed-model[root.current-index]
75 : "";
76 property <length> popup-height: root.model.length * MdTokens.target + 8px;
77
78 height: MdTokens.target;
79 background: selector-touch.pressed
80 ? MdTokens.surface-container
81 : (selector-touch.has-hover ? MdTokens.surface-container : MdTokens.surface);
82 border-width: combo-focus.has-focus || popup.is-open ? 2px : 1px;
83 border-color: combo-focus.has-focus || popup.is-open
84 ? MdTokens.primary
85 : MdTokens.outline-variant;
86 border-radius: MdTokens.space-1;
87 opacity: root.control-enabled ? 1 : 0.38;
88 forward-focus: combo-focus;
89
90 accessible-role: combobox;
91 accessible-label: root.field-label;
92 accessible-value: root.current-value;
93 accessible-enabled: root.control-enabled;
94 accessible-expandable: true;
95 accessible-expanded: popup.is-open;
96 accessible-action-expand => {
97 if root.control-enabled && root.model.length > 0 {
98 popup.show();
99 }
100 }
101
102 Text {
103 x: MdTokens.space-2;
104 width: parent.width - 52px;
105 height: parent.height;
106 text: root.current-value;
107 color: MdTokens.on-surface;
108 font-size: MdTokens.type-body-large;
109 vertical-alignment: center;
110 overflow: clip;
111 accessible-role: none;
112 }
113
114 Path {
115 x: parent.width - 36px;
116 y: 12px;
117 width: 24px;
118 height: 24px;
119 viewbox-width: 24;
120 viewbox-height: 24;
121 commands: "M7 9.5L12 14.5L17 9.5";
122 fill: transparent;
123 stroke: MdTokens.on-surface;
124 stroke-width: 2.5px;
125 accessible-role: none;
126 }
127
128 combo-focus := FocusScope {
129 key-pressed(event) => {
130 if !root.control-enabled || root.model.length == 0 {
131 return reject;
132 }
133 if event.text == Key.UpArrow {
134 root.current-index = Math.max(root.current-index - 1, 0);
135 root.selected();
136 return accept;
137 } else if event.text == Key.DownArrow {
138 root.current-index = Math.min(root.current-index + 1, root.model.length - 1);
139 root.selected();
140 return accept;
141 } else if event.text == Key.Return {
142 popup.show();
143 return accept;
144 }
145 return reject;
146 }
147
148 selector-touch := TouchArea {
149 enabled: root.control-enabled && root.model.length > 0;
150 clicked => {
151 root.focus();
152 popup.show();
153 }
154 }
155 }
156
157 popup := PopupWindow {
158 x: 0;
159 y: -root.popup-height - 4px;
160 width: root.popup-width;
161 height: root.popup-height;
162 close-policy: close-on-click-outside;
163 forward-focus: popup-focus;
164
165 popup-focus := FocusScope {
166 key-pressed(event) => {
167 if event.text == Key.UpArrow {
168 root.current-index = Math.max(root.current-index - 1, 0);
169 root.selected();
170 return accept;
171 } else if event.text == Key.DownArrow {
172 root.current-index = Math.min(root.current-index + 1, root.model.length - 1);
173 root.selected();
174 return accept;
175 } else if event.text == Key.Return || event.text == Key.Escape {
176 popup.close();
177 return accept;
178 }
179 return reject;
180 }
181
182 Rectangle {
183 background: MdTokens.surface;
184 border-width: 1px;
185 border-color: MdTokens.outline-variant;
186 border-radius: MdTokens.shape-medium;
187 clip: true;
188
189 VerticalLayout {
190 padding: 4px;
191 spacing: 0px;
192
193 for option[index] in root.model : Rectangle {
194 height: MdTokens.target;
195 border-radius: MdTokens.space-1;
196 background: index == root.current-index
197 ? MdTokens.primary-container
198 : (option-touch.has-hover ? MdTokens.surface-container : transparent);
199
200 Text {
201 x: MdTokens.space-2;
202 width: parent.width - 2 * MdTokens.space-2;
203 height: parent.height;
204 text: option;
205 color: index == root.current-index
206 ? MdTokens.on-primary-container
207 : MdTokens.on-surface;
208 font-size: MdTokens.type-body-large;
209 vertical-alignment: center;
210 overflow: clip;
211 accessible-role: none;
212 }
213
214 option-touch := TouchArea {
215 clicked => {
216 root.current-index = index;
217 root.selected();
218 popup.close();
219 }
220 }
221 }
222 }
223 }
224 }
225 }
226
227 animate background { duration: 120ms; easing: ease-in-out; }
228}
229
230// Text buttons use three deliberate width tiers. This keeps their geometry
231// stable when labels or translations change.
232export component BackButton inherits Button {
233 text: "Back";
234 width: 80px;
235 height: MdTokens.target;
236}
237
238export component PageActionButton inherits Button {
239 width: 96px;
240 height: MdTokens.target;
241}
242
243export component FullWidthPrimaryButton inherits Button {
244 height: MdTokens.target;
245 primary: true;
246}
247
248// A full-width icon action shares the primary action's pill geometry while
249// retaining the lower-emphasis surface treatment of an auxiliary action.
250export component FullWidthSettingsButton inherits Rectangle {
251 in property <bool> control-enabled: true;
252 in property <string> field-label;
253 callback clicked();
254
255 height: MdTokens.target;
256 background: MdTokens.surface-container;
257 border-width: 1px;
258 border-color: MdTokens.outline-variant;
259 border-radius: MdTokens.button-radius;
260 opacity: root.control-enabled ? 1 : 0.38;
261
262 Rectangle {
263 width: parent.width;
264 height: parent.height;
265 border-radius: root.border-radius;
266 background: MdTokens.primary;
267 opacity: settings-touch.pressed ? 0.12 : (settings-touch.has-hover ? 0.08 : 0);
268 animate opacity { duration: 100ms; easing: ease-in-out; }
269 }
270
271 Path {
272 x: (parent.width - self.width) / 2;
273 y: (parent.height - self.height) / 2;
274 width: 22px;
275 height: 22px;
276 viewbox-width: 24;
277 viewbox-height: 24;
278 commands: "M19.43 12.98C19.47 12.66 19.5 12.34 19.5 12C19.5 11.66 19.47 11.34 19.42 11.02L21.54 9.37L19.54 5.91L17.04 6.92C16.54 6.52 16 6.18 15.4 5.93L15 3.27H11L10.6 5.93C10 6.18 9.45 6.53 8.96 6.92L6.46 5.91L4.46 9.37L6.58 11.02C6.53 11.34 6.5 11.67 6.5 12C6.5 12.33 6.53 12.66 6.58 12.98L4.46 14.63L6.46 18.09L8.96 17.08C9.45 17.47 10 17.82 10.6 18.07L11 20.73H15L15.4 18.07C16 17.82 16.54 17.48 17.04 17.08L19.54 18.09L21.54 14.63L19.43 12.98ZM13 15.5C11.07 15.5 9.5 13.93 9.5 12C9.5 10.07 11.07 8.5 13 8.5C14.93 8.5 16.5 10.07 16.5 12C16.5 13.93 14.93 15.5 13 15.5Z";
279 fill: MdTokens.on-surface;
280 accessible-role: none;
281 }
282
283 settings-touch := TouchArea {
284 enabled: root.control-enabled;
285 accessible-role: button;
286 accessible-label: root.field-label;
287 accessible-enabled: root.control-enabled;
288 clicked => { root.clicked(); }
289 }
290}
291
292// Dialog actions share one geometry and state-layer implementation.
293// Only the semantic color role changes for destructive actions.
294export component DialogActionButton inherits Rectangle {
295 in property <string> text;
296 in property <string> field-label;
297 in property <bool> destructive: false;
298 callback clicked();
299
300 width: 96px;
301 height: MdTokens.target;
302 background: root.destructive ? MdTokens.error-container : MdTokens.surface-container;
303 border-radius: MdTokens.button-radius;
304
305 Rectangle {
306 width: parent.width;
307 height: parent.height;
308 border-radius: parent.border-radius;
309 background: root.destructive ? MdTokens.on-error-container : MdTokens.on-surface;
310 opacity: action-touch.pressed ? 0.12 : (action-touch.has-hover ? 0.08 : 0);
311 animate opacity { duration: 100ms; easing: ease-in-out; }
312 }
313
314 Text {
315 width: parent.width;
316 height: parent.height;
317 text: root.text;
318 color: root.destructive ? MdTokens.on-error-container : MdTokens.on-surface;
319 font-size: MdTokens.type-body-medium;
320 font-weight: MdTokens.weight-medium;
321 horizontal-alignment: center;
322 vertical-alignment: center;
323 accessible-role: none;
324 }
325
326 action-touch := TouchArea {
327 accessible-role: button;
328 accessible-label: root.field-label;
329 clicked => { root.clicked(); }
330 }
331}
332
333// A single geometry owns the container, state layer, icon and hit target.
334// This avoids the text baseline and native button padding shifting small icons.
335export component MaterialIconButton inherits Rectangle {
336 in property <int> icon-kind; // 0 add, 1 refresh, 2 tune, 3 edit, 4 more, 5 calculate
337 in property <bool> compact: false;
338 in property <bool> control-enabled: true;
339 in property <string> field-label;
340 callback clicked();
341
342 width: root.compact ? 40px : MdTokens.target;
343 height: root.compact ? 40px : MdTokens.target;
344 background: MdTokens.surface-container;
345 border-width: 1px;
346 border-color: MdTokens.outline-variant;
347 border-radius: MdTokens.shape-medium;
348 opacity: root.control-enabled ? 1 : 0.38;
349
350 // Material state layer. It deliberately shares the exact outer geometry.
351 Rectangle {
352 width: parent.width;
353 height: parent.height;
354 border-radius: root.border-radius;
355 background: MdTokens.primary;
356 opacity: icon-touch.pressed ? 0.12 : (icon-touch.has-hover ? 0.08 : 0);
357 animate opacity { duration: 100ms; easing: ease-in-out; }
358 }
359
360 if root.icon-kind == 0 : Path {
361 x: (parent.width - self.width) / 2;
362 y: (parent.height - self.height) / 2;
363 width: 20px;
364 height: 20px;
365 viewbox-width: 24;
366 viewbox-height: 24;
367 commands: "M19 13H13V19H11V13H5V11H11V5H13V11H19V13Z";
368 fill: MdTokens.on-surface;
369 accessible-role: none;
370 }
371
372 // Google Material Icons refresh, adapted to Slint; see NOTICE.md and LICENSES/Apache-2.0.txt.
373 // https://github.com/google/material-design-icons/blob/master/src/navigation/refresh/materialicons/24px.svg
374 if root.icon-kind == 1 : Path {
375 x: (parent.width - self.width) / 2;
376 y: (parent.height - self.height) / 2;
377 width: 22px;
378 height: 22px;
379 viewbox-width: 24;
380 viewbox-height: 24;
381 commands: "M17.65 6.35C16.2 4.9 14.21 4 12 4C7.58 4 4.01 7.58 4.01 12C4.01 16.42 7.58 20 12 20C15.73 20 18.84 17.45 19.73 14H17.65C16.83 16.33 14.61 18 12 18C8.69 18 6 15.31 6 12C6 8.69 8.69 6 12 6C13.66 6 15.14 6.69 16.22 7.78L13 11H20V4L17.65 6.35Z";
382 fill: MdTokens.on-surface;
383 accessible-role: none;
384 }
385
386 if root.icon-kind == 2 : Path {
387 x: (parent.width - self.width) / 2;
388 y: (parent.height - self.height) / 2;
389 width: 22px;
390 height: 22px;
391 viewbox-width: 24;
392 viewbox-height: 24;
393 commands: "M3 17V19H9V17H3ZM3 5V7H13V5H3ZM13 21V19H21V17H13V15H11V21H13ZM7 9V11H3V13H7V15H9V9H7ZM21 13V11H11V13H21ZM15 9H17V7H21V5H17V3H15V9Z";
394 fill: MdTokens.on-surface;
395 accessible-role: none;
396 }
397
398 // Google Material Icons edit, adapted to Slint; see NOTICE.md and LICENSES/Apache-2.0.txt.
399 // https://github.com/google/material-design-icons/blob/master/src/image/edit/materialicons/24px.svg
400 if root.icon-kind == 3 : Path {
401 x: (parent.width - self.width) / 2;
402 y: (parent.height - self.height) / 2;
403 width: 22px;
404 height: 22px;
405 viewbox-width: 24;
406 viewbox-height: 24;
407 commands: "M3 17.25V21H6.75L17.81 9.94L14.06 6.19L3 17.25ZM20.71 7.04C21.1 6.65 21.1 6.02 20.71 5.63L18.37 3.29C17.98 2.9 17.35 2.9 16.96 3.29L15.13 5.12L18.88 8.87L20.71 7.04Z";
408 fill: MdTokens.on-surface;
409 accessible-role: none;
410 }
411
412 if root.icon-kind == 4 : Path {
413 x: (parent.width - self.width) / 2;
414 y: (parent.height - self.height) / 2;
415 width: 22px;
416 height: 22px;
417 viewbox-width: 24;
418 viewbox-height: 24;
419 commands: "M6 10C4.9 10 4 10.9 4 12C4 13.1 4.9 14 6 14C7.1 14 8 13.1 8 12C8 10.9 7.1 10 6 10ZM12 10C10.9 10 10 10.9 10 12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12C14 10.9 13.1 10 12 10ZM18 10C16.9 10 16 10.9 16 12C16 13.1 16.9 14 18 14C19.1 14 20 13.1 20 12C20 10.9 19.1 10 18 10Z";
420 fill: MdTokens.on-surface;
421 accessible-role: none;
422 }
423
424 if root.icon-kind == 5 : Path {
425 x: (parent.width - self.width) / 2;
426 y: (parent.height - self.height) / 2;
427 width: 22px;
428 height: 22px;
429 viewbox-width: 24;
430 viewbox-height: 24;
431 commands: "M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3ZM19 19H5V5H19V19ZM7 7H17V11H7V7ZM7 13H9V15H7V13ZM11 13H13V15H11V13ZM15 13H17V15H15V13ZM7 17H9V19H7V17ZM11 17H13V19H11V17ZM15 17H17V19H15V17Z";
432 fill: MdTokens.on-surface;
433 accessible-role: none;
434 }
435
436 icon-touch := TouchArea {
437 enabled: root.control-enabled;
438 accessible-role: button;
439 accessible-label: root.field-label;
440 clicked => { root.clicked(); }
441 }
442}
443