1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: MIT |
3 | |
4 | import { StyleMetrics } from "std-widgets.slint" ; |
5 | |
6 | component SideBarItem inherits Rectangle { |
7 | in property <bool> selected; |
8 | in property <bool> has-focus; |
9 | in-out property <string> text <=> label.text; |
10 | |
11 | callback clicked <=> touch.clicked; |
12 | |
13 | min-height: l.preferred-height; |
14 | |
15 | states [ |
16 | pressed when touch.pressed : { |
17 | state.opacity: 0.8; |
18 | } |
19 | hover when touch.has-hover : { |
20 | state.opacity: 0.6; |
21 | } |
22 | selected when root.selected : { |
23 | state.opacity: 1; |
24 | } |
25 | focused when root.has-focus : { |
26 | state.opacity: 0.8; |
27 | } |
28 | ] |
29 | |
30 | state := Rectangle { |
31 | opacity: 0; |
32 | background: StyleMetrics.window-background; |
33 | |
34 | animate opacity { duration: 150ms; } |
35 | } |
36 | |
37 | l := HorizontalLayout { |
38 | y: (parent.height - self.height) / 2; |
39 | padding: StyleMetrics.layout-padding; |
40 | spacing: 0px; |
41 | |
42 | label := Text { |
43 | color: StyleMetrics.default-text-color; |
44 | vertical-alignment: center; |
45 | } |
46 | } |
47 | |
48 | touch := TouchArea { |
49 | width: 100%; |
50 | height: 100%; |
51 | } |
52 | } |
53 | |
54 | export component SideBar inherits Rectangle { |
55 | in property <[string]> model: []; |
56 | in property <string> title <=> label.text; |
57 | out property <int> current-item: 0; |
58 | out property <int> current-focused: fs.has-focus ? fs.focused-tab : -1; // The currently focused tab |
59 | |
60 | width: 180px; |
61 | forward-focus: fs; |
62 | accessible-role: tab; |
63 | accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-item; |
64 | |
65 | Rectangle { |
66 | background: StyleMetrics.window-background.darker(0.2); |
67 | |
68 | fs := FocusScope { |
69 | key-pressed(event) => { |
70 | if (event.text == "\n" ) { |
71 | root.current-item = root.current-focused; |
72 | return accept; |
73 | } |
74 | if (event.text == Key.UpArrow) { |
75 | self.focused-tab = Math.max(self.focused-tab - 1, 0); |
76 | return accept; |
77 | } |
78 | if (event.text == Key.DownArrow) { |
79 | self.focused-tab = Math.min(self.focused-tab + 1, root.model.length - 1); |
80 | return accept; |
81 | } |
82 | return reject; |
83 | } |
84 | |
85 | key-released(event) => { |
86 | if (event.text == " " ) { |
87 | root.current-item = root.current-focused; |
88 | return accept; |
89 | } |
90 | return reject; |
91 | } |
92 | |
93 | property <int> focused-tab: 0; |
94 | |
95 | x: 0; |
96 | width: 0; // Do not react on clicks |
97 | } |
98 | } |
99 | |
100 | VerticalLayout { |
101 | padding-top: StyleMetrics.layout-padding; |
102 | padding-bottom: StyleMetrics.layout-padding; |
103 | spacing: StyleMetrics.layout-spacing; |
104 | alignment: start; |
105 | |
106 | label := Text { |
107 | font-size: 16px; |
108 | horizontal-alignment: center; |
109 | } |
110 | |
111 | navigation := VerticalLayout { |
112 | alignment: start; |
113 | vertical-stretch: 0; |
114 | for item[index] in root.model : SideBarItem { |
115 | clicked => { root.current-item = index; } |
116 | |
117 | has-focus: index == root.current-focused; |
118 | text: item; |
119 | selected: index == root.current-item; |
120 | } |
121 | } |
122 | |
123 | VerticalLayout { |
124 | bottom := VerticalLayout { |
125 | padding-left: StyleMetrics.layout-padding; |
126 | padding-right: StyleMetrics.layout-padding; |
127 | |
128 | @children |
129 | } |
130 | } |
131 | } |
132 | } |
133 | |