1#[derive(Debug, Clone, Default)]
6pub struct SelectState {
7 pub items: Vec<String>,
9 pub selected: usize,
11 pub open: bool,
13 pub placeholder: String,
15 cursor: usize,
16}
17
18impl SelectState {
19 pub fn new(items: Vec<impl Into<String>>) -> Self {
21 Self {
22 items: items.into_iter().map(Into::into).collect(),
23 selected: 0,
24 open: false,
25 placeholder: String::new(),
26 cursor: 0,
27 }
28 }
29
30 pub fn placeholder(mut self, p: impl Into<String>) -> Self {
32 self.placeholder = p.into();
33 self
34 }
35
36 pub fn selected_item(&self) -> Option<&str> {
38 self.items.get(self.selected).map(String::as_str)
39 }
40
41 pub(crate) fn cursor(&self) -> usize {
42 self.cursor
43 }
44
45 pub(crate) fn set_cursor(&mut self, c: usize) {
46 self.cursor = c;
47 }
48}
49
50#[derive(Debug, Clone, Default)]
56pub struct RadioState {
57 pub items: Vec<String>,
59 pub selected: usize,
61}
62
63impl RadioState {
64 pub fn new(items: Vec<impl Into<String>>) -> Self {
66 Self {
67 items: items.into_iter().map(Into::into).collect(),
68 selected: 0,
69 }
70 }
71
72 pub fn selected_item(&self) -> Option<&str> {
74 self.items.get(self.selected).map(String::as_str)
75 }
76}
77
78#[derive(Debug, Clone)]
84pub struct MultiSelectState {
85 pub items: Vec<String>,
87 pub cursor: usize,
89 pub selected: HashSet<usize>,
91}
92
93impl MultiSelectState {
94 pub fn new(items: Vec<impl Into<String>>) -> Self {
96 Self {
97 items: items.into_iter().map(Into::into).collect(),
98 cursor: 0,
99 selected: HashSet::new(),
100 }
101 }
102
103 pub fn selected_items(&self) -> Vec<&str> {
105 let mut indices: Vec<usize> = self.selected.iter().copied().collect();
106 indices.sort();
107 indices
108 .iter()
109 .filter_map(|&i| self.items.get(i).map(String::as_str))
110 .collect()
111 }
112
113 pub fn toggle(&mut self, index: usize) {
115 if self.selected.contains(&index) {
116 self.selected.remove(&index);
117 } else {
118 self.selected.insert(index);
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
127pub struct TreeNode {
128 pub label: String,
130 pub children: Vec<TreeNode>,
132 pub expanded: bool,
134}
135
136impl TreeNode {
137 pub fn new(label: impl Into<String>) -> Self {
139 Self {
140 label: label.into(),
141 children: Vec::new(),
142 expanded: false,
143 }
144 }
145
146 pub fn expanded(mut self) -> Self {
148 self.expanded = true;
149 self
150 }
151
152 pub fn children(mut self, children: Vec<TreeNode>) -> Self {
154 self.children = children;
155 self
156 }
157
158 pub fn is_leaf(&self) -> bool {
160 self.children.is_empty()
161 }
162
163 fn flatten(&self, depth: usize, out: &mut Vec<FlatTreeEntry>) {
164 out.push(FlatTreeEntry {
165 depth,
166 label: self.label.clone(),
167 is_leaf: self.is_leaf(),
168 expanded: self.expanded,
169 });
170 if self.expanded {
171 for child in &self.children {
172 child.flatten(depth + 1, out);
173 }
174 }
175 }
176}
177
178pub(crate) struct FlatTreeEntry {
179 pub depth: usize,
180 pub label: String,
181 pub is_leaf: bool,
182 pub expanded: bool,
183}
184
185#[derive(Debug, Clone)]
187pub struct TreeState {
188 pub nodes: Vec<TreeNode>,
190 pub selected: usize,
192}
193
194impl TreeState {
195 pub fn new(nodes: Vec<TreeNode>) -> Self {
197 Self { nodes, selected: 0 }
198 }
199
200 pub(crate) fn flatten(&self) -> Vec<FlatTreeEntry> {
201 let mut entries = Vec::new();
202 for node in &self.nodes {
203 node.flatten(0, &mut entries);
204 }
205 entries
206 }
207
208 pub(crate) fn toggle_at(&mut self, flat_index: usize) {
209 let mut counter = 0usize;
210 Self::toggle_recursive(&mut self.nodes, flat_index, &mut counter);
211 }
212
213 fn toggle_recursive(nodes: &mut [TreeNode], target: usize, counter: &mut usize) -> bool {
214 for node in nodes.iter_mut() {
215 if *counter == target {
216 if !node.is_leaf() {
217 node.expanded = !node.expanded;
218 }
219 return true;
220 }
221 *counter += 1;
222 if node.expanded && Self::toggle_recursive(&mut node.children, target, counter) {
223 return true;
224 }
225 }
226 false
227 }
228}
229
230#[derive(Debug, Clone)]
232pub struct DirectoryTreeState {
233 pub tree: TreeState,
235 pub show_icons: bool,
237}
238
239impl DirectoryTreeState {
240 pub fn new(nodes: Vec<TreeNode>) -> Self {
242 Self {
243 tree: TreeState::new(nodes),
244 show_icons: true,
245 }
246 }
247
248 pub fn from_paths(paths: &[&str]) -> Self {
250 let mut roots: Vec<TreeNode> = Vec::new();
251
252 for raw_path in paths {
253 let parts: Vec<&str> = raw_path
254 .split('/')
255 .filter(|part| !part.is_empty())
256 .collect();
257 if parts.is_empty() {
258 continue;
259 }
260 insert_path(&mut roots, &parts, 0);
261 }
262
263 Self::new(roots)
264 }
265
266 pub fn selected_label(&self) -> Option<&str> {
268 let mut cursor = 0usize;
269 selected_label_in_nodes(&self.tree.nodes, self.tree.selected, &mut cursor)
270 }
271}
272
273impl Default for DirectoryTreeState {
274 fn default() -> Self {
275 Self::new(Vec::<TreeNode>::new())
276 }
277}
278
279fn insert_path(nodes: &mut Vec<TreeNode>, parts: &[&str], depth: usize) {
280 let Some(label) = parts.get(depth) else {
281 return;
282 };
283
284 let is_last = depth + 1 == parts.len();
285 let idx = nodes
286 .iter()
287 .position(|node| node.label == *label)
288 .unwrap_or_else(|| {
289 let mut node = TreeNode::new(*label);
290 if !is_last {
291 node.expanded = true;
292 }
293 nodes.push(node);
294 nodes.len() - 1
295 });
296
297 if is_last {
298 return;
299 }
300
301 nodes[idx].expanded = true;
302 insert_path(&mut nodes[idx].children, parts, depth + 1);
303}
304
305fn selected_label_in_nodes<'a>(
306 nodes: &'a [TreeNode],
307 target: usize,
308 cursor: &mut usize,
309) -> Option<&'a str> {
310 for node in nodes {
311 if *cursor == target {
312 return Some(node.label.as_str());
313 }
314 *cursor += 1;
315 if node.expanded {
316 if let Some(found) = selected_label_in_nodes(&node.children, target, cursor) {
317 return Some(found);
318 }
319 }
320 }
321 None
322}
323
324#[derive(Debug, Clone)]
328pub struct PaletteCommand {
329 pub label: String,
331 pub description: String,
333 pub shortcut: Option<String>,
335}
336
337impl PaletteCommand {
338 pub fn new(label: impl Into<String>, description: impl Into<String>) -> Self {
340 Self {
341 label: label.into(),
342 description: description.into(),
343 shortcut: None,
344 }
345 }
346
347 pub fn shortcut(mut self, s: impl Into<String>) -> Self {
349 self.shortcut = Some(s.into());
350 self
351 }
352}