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