Skip to main content

slt/
widgets.rs

1//! Widget state types passed to [`Context`](crate::Context) widget methods.
2//!
3//! Each interactive widget (text input, list, tabs, table, etc.) has a
4//! corresponding state struct defined here. Create the state once, then pass
5//! a `&mut` reference each frame.
6
7use std::collections::HashSet;
8use unicode_width::UnicodeWidthStr;
9
10type FormValidator = fn(&str) -> Result<(), String>;
11
12/// State for a single-line text input widget.
13///
14/// Pass a mutable reference to `Context::text_input` each frame. The widget
15/// handles all keyboard events when focused.
16///
17/// # Example
18///
19/// ```no_run
20/// # use slt::widgets::TextInputState;
21/// # slt::run(|ui: &mut slt::Context| {
22/// let mut input = TextInputState::with_placeholder("Type here...");
23/// ui.text_input(&mut input);
24/// println!("{}", input.value);
25/// # });
26/// ```
27pub struct TextInputState {
28    /// The current input text.
29    pub value: String,
30    /// Cursor position as a character index into `value`.
31    pub cursor: usize,
32    /// Placeholder text shown when `value` is empty.
33    pub placeholder: String,
34    /// Maximum character count. Input is rejected beyond this limit.
35    pub max_length: Option<usize>,
36    /// The most recent validation error message, if any.
37    pub validation_error: Option<String>,
38    /// When `true`, input is displayed as `•` characters (for passwords).
39    pub masked: bool,
40}
41
42impl TextInputState {
43    /// Create an empty text input state.
44    pub fn new() -> Self {
45        Self {
46            value: String::new(),
47            cursor: 0,
48            placeholder: String::new(),
49            max_length: None,
50            validation_error: None,
51            masked: false,
52        }
53    }
54
55    /// Create a text input with placeholder text shown when the value is empty.
56    pub fn with_placeholder(p: impl Into<String>) -> Self {
57        Self {
58            placeholder: p.into(),
59            ..Self::new()
60        }
61    }
62
63    /// Set the maximum allowed character count.
64    pub fn max_length(mut self, len: usize) -> Self {
65        self.max_length = Some(len);
66        self
67    }
68
69    /// Validate the current value and store the latest error message.
70    ///
71    /// Sets [`TextInputState::validation_error`] to `None` when validation
72    /// succeeds, or to `Some(error)` when validation fails.
73    pub fn validate(&mut self, validator: impl Fn(&str) -> Result<(), String>) {
74        self.validation_error = validator(&self.value).err();
75    }
76}
77
78impl Default for TextInputState {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84/// A single form field with label and validation.
85pub struct FormField {
86    /// Field label shown above the input.
87    pub label: String,
88    /// Text input state for this field.
89    pub input: TextInputState,
90    /// Validation error shown below the input when present.
91    pub error: Option<String>,
92}
93
94impl FormField {
95    /// Create a new form field with the given label.
96    pub fn new(label: impl Into<String>) -> Self {
97        Self {
98            label: label.into(),
99            input: TextInputState::new(),
100            error: None,
101        }
102    }
103
104    /// Set placeholder text for this field's input.
105    pub fn placeholder(mut self, p: impl Into<String>) -> Self {
106        self.input.placeholder = p.into();
107        self
108    }
109}
110
111/// State for a form with multiple fields.
112pub struct FormState {
113    /// Ordered list of form fields.
114    pub fields: Vec<FormField>,
115    /// Whether the form has been successfully submitted.
116    pub submitted: bool,
117}
118
119impl FormState {
120    /// Create an empty form state.
121    pub fn new() -> Self {
122        Self {
123            fields: Vec::new(),
124            submitted: false,
125        }
126    }
127
128    /// Add a field and return the updated form for chaining.
129    pub fn field(mut self, field: FormField) -> Self {
130        self.fields.push(field);
131        self
132    }
133
134    /// Validate all fields with the given validators.
135    ///
136    /// Returns `true` when all validations pass.
137    pub fn validate(&mut self, validators: &[FormValidator]) -> bool {
138        let mut all_valid = true;
139        for (i, field) in self.fields.iter_mut().enumerate() {
140            if let Some(validator) = validators.get(i) {
141                match validator(&field.input.value) {
142                    Ok(()) => field.error = None,
143                    Err(msg) => {
144                        field.error = Some(msg);
145                        all_valid = false;
146                    }
147                }
148            }
149        }
150        all_valid
151    }
152
153    /// Get field value by index.
154    pub fn value(&self, index: usize) -> &str {
155        self.fields
156            .get(index)
157            .map(|f| f.input.value.as_str())
158            .unwrap_or("")
159    }
160}
161
162impl Default for FormState {
163    fn default() -> Self {
164        Self::new()
165    }
166}
167
168/// State for toast notification display.
169///
170/// Add messages with [`ToastState::info`], [`ToastState::success`],
171/// [`ToastState::warning`], or [`ToastState::error`], then pass the state to
172/// `Context::toast` each frame. Expired messages are removed automatically.
173pub struct ToastState {
174    /// Active toast messages, ordered oldest-first.
175    pub messages: Vec<ToastMessage>,
176}
177
178/// A single toast notification message.
179pub struct ToastMessage {
180    /// The text content of the notification.
181    pub text: String,
182    /// Severity level, used to choose the display color.
183    pub level: ToastLevel,
184    /// The tick at which this message was created.
185    pub created_tick: u64,
186    /// How many ticks the message remains visible.
187    pub duration_ticks: u64,
188}
189
190/// Severity level for a [`ToastMessage`].
191pub enum ToastLevel {
192    /// Informational message (primary color).
193    Info,
194    /// Success message (success color).
195    Success,
196    /// Warning message (warning color).
197    Warning,
198    /// Error message (error color).
199    Error,
200}
201
202impl ToastState {
203    /// Create an empty toast state with no messages.
204    pub fn new() -> Self {
205        Self {
206            messages: Vec::new(),
207        }
208    }
209
210    /// Push an informational toast visible for 30 ticks.
211    pub fn info(&mut self, text: impl Into<String>, tick: u64) {
212        self.push(text, ToastLevel::Info, tick, 30);
213    }
214
215    /// Push a success toast visible for 30 ticks.
216    pub fn success(&mut self, text: impl Into<String>, tick: u64) {
217        self.push(text, ToastLevel::Success, tick, 30);
218    }
219
220    /// Push a warning toast visible for 50 ticks.
221    pub fn warning(&mut self, text: impl Into<String>, tick: u64) {
222        self.push(text, ToastLevel::Warning, tick, 50);
223    }
224
225    /// Push an error toast visible for 80 ticks.
226    pub fn error(&mut self, text: impl Into<String>, tick: u64) {
227        self.push(text, ToastLevel::Error, tick, 80);
228    }
229
230    /// Push a toast with a custom level and duration.
231    pub fn push(
232        &mut self,
233        text: impl Into<String>,
234        level: ToastLevel,
235        tick: u64,
236        duration_ticks: u64,
237    ) {
238        self.messages.push(ToastMessage {
239            text: text.into(),
240            level,
241            created_tick: tick,
242            duration_ticks,
243        });
244    }
245
246    /// Remove all messages whose display duration has elapsed.
247    ///
248    /// Called automatically by `Context::toast` before rendering.
249    pub fn cleanup(&mut self, current_tick: u64) {
250        self.messages.retain(|message| {
251            current_tick < message.created_tick.saturating_add(message.duration_ticks)
252        });
253    }
254}
255
256impl Default for ToastState {
257    fn default() -> Self {
258        Self::new()
259    }
260}
261
262/// State for a multi-line text area widget.
263///
264/// Pass a mutable reference to `Context::textarea` each frame along with the
265/// number of visible rows. The widget handles all keyboard events when focused.
266pub struct TextareaState {
267    /// The lines of text, one entry per line.
268    pub lines: Vec<String>,
269    /// Row index of the cursor (0-based, logical line).
270    pub cursor_row: usize,
271    /// Column index of the cursor within the current row (character index).
272    pub cursor_col: usize,
273    /// Maximum total character count across all lines.
274    pub max_length: Option<usize>,
275    /// When set, lines longer than this display-column width are soft-wrapped.
276    pub wrap_width: Option<u32>,
277    /// First visible visual line (managed internally by `textarea()`).
278    pub scroll_offset: usize,
279}
280
281impl TextareaState {
282    /// Create an empty text area state with one blank line.
283    pub fn new() -> Self {
284        Self {
285            lines: vec![String::new()],
286            cursor_row: 0,
287            cursor_col: 0,
288            max_length: None,
289            wrap_width: None,
290            scroll_offset: 0,
291        }
292    }
293
294    /// Return all lines joined with newline characters.
295    pub fn value(&self) -> String {
296        self.lines.join("\n")
297    }
298
299    /// Replace the content with the given text, splitting on newlines.
300    ///
301    /// Resets the cursor to the beginning of the first line.
302    pub fn set_value(&mut self, text: impl Into<String>) {
303        let value = text.into();
304        self.lines = value.split('\n').map(str::to_string).collect();
305        if self.lines.is_empty() {
306            self.lines.push(String::new());
307        }
308        self.cursor_row = 0;
309        self.cursor_col = 0;
310        self.scroll_offset = 0;
311    }
312
313    /// Set the maximum allowed total character count.
314    pub fn max_length(mut self, len: usize) -> Self {
315        self.max_length = Some(len);
316        self
317    }
318
319    /// Enable soft word-wrap at the given display-column width.
320    pub fn word_wrap(mut self, width: u32) -> Self {
321        self.wrap_width = Some(width);
322        self
323    }
324}
325
326impl Default for TextareaState {
327    fn default() -> Self {
328        Self::new()
329    }
330}
331
332/// State for an animated spinner widget.
333///
334/// Create with [`SpinnerState::dots`] or [`SpinnerState::line`], then pass to
335/// `Context::spinner` each frame. The frame advances automatically with the
336/// tick counter.
337pub struct SpinnerState {
338    chars: Vec<char>,
339}
340
341impl SpinnerState {
342    /// Create a dots-style spinner using braille characters.
343    ///
344    /// Cycles through: `⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏`
345    pub fn dots() -> Self {
346        Self {
347            chars: vec!['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
348        }
349    }
350
351    /// Create a line-style spinner using ASCII characters.
352    ///
353    /// Cycles through: `| / - \`
354    pub fn line() -> Self {
355        Self {
356            chars: vec!['|', '/', '-', '\\'],
357        }
358    }
359
360    /// Return the spinner character for the given tick.
361    pub fn frame(&self, tick: u64) -> char {
362        if self.chars.is_empty() {
363            return ' ';
364        }
365        self.chars[tick as usize % self.chars.len()]
366    }
367}
368
369impl Default for SpinnerState {
370    fn default() -> Self {
371        Self::dots()
372    }
373}
374
375/// State for a selectable list widget.
376///
377/// Pass a mutable reference to `Context::list` each frame. Up/Down arrow
378/// keys (and `k`/`j`) move the selection when the widget is focused.
379pub struct ListState {
380    /// The list items as display strings.
381    pub items: Vec<String>,
382    /// Index of the currently selected item.
383    pub selected: usize,
384}
385
386impl ListState {
387    /// Create a list with the given items. The first item is selected initially.
388    pub fn new(items: Vec<impl Into<String>>) -> Self {
389        Self {
390            items: items.into_iter().map(Into::into).collect(),
391            selected: 0,
392        }
393    }
394
395    /// Get the currently selected item text, or `None` if the list is empty.
396    pub fn selected_item(&self) -> Option<&str> {
397        self.items.get(self.selected).map(String::as_str)
398    }
399}
400
401/// State for a tab navigation widget.
402///
403/// Pass a mutable reference to `Context::tabs` each frame. Left/Right arrow
404/// keys cycle through tabs when the widget is focused.
405pub struct TabsState {
406    /// The tab labels displayed in the bar.
407    pub labels: Vec<String>,
408    /// Index of the currently active tab.
409    pub selected: usize,
410}
411
412impl TabsState {
413    /// Create tabs with the given labels. The first tab is active initially.
414    pub fn new(labels: Vec<impl Into<String>>) -> Self {
415        Self {
416            labels: labels.into_iter().map(Into::into).collect(),
417            selected: 0,
418        }
419    }
420
421    /// Get the currently selected tab label, or `None` if there are no tabs.
422    pub fn selected_label(&self) -> Option<&str> {
423        self.labels.get(self.selected).map(String::as_str)
424    }
425}
426
427/// State for a data table widget.
428///
429/// Pass a mutable reference to `Context::table` each frame. Up/Down arrow
430/// keys move the row selection when the widget is focused. Column widths are
431/// computed automatically from header and cell content.
432pub struct TableState {
433    /// Column header labels.
434    pub headers: Vec<String>,
435    /// Table rows, each a `Vec` of cell strings.
436    pub rows: Vec<Vec<String>>,
437    /// Index of the currently selected row.
438    pub selected: usize,
439    column_widths: Vec<u32>,
440    dirty: bool,
441}
442
443impl TableState {
444    /// Create a table with headers and rows. Column widths are computed immediately.
445    pub fn new(headers: Vec<impl Into<String>>, rows: Vec<Vec<impl Into<String>>>) -> Self {
446        let headers: Vec<String> = headers.into_iter().map(Into::into).collect();
447        let rows: Vec<Vec<String>> = rows
448            .into_iter()
449            .map(|r| r.into_iter().map(Into::into).collect())
450            .collect();
451        let mut state = Self {
452            headers,
453            rows,
454            selected: 0,
455            column_widths: Vec::new(),
456            dirty: true,
457        };
458        state.recompute_widths();
459        state
460    }
461
462    /// Replace all rows, preserving the selection index if possible.
463    ///
464    /// If the current selection is beyond the new row count, it is clamped to
465    /// the last row.
466    pub fn set_rows(&mut self, rows: Vec<Vec<impl Into<String>>>) {
467        self.rows = rows
468            .into_iter()
469            .map(|r| r.into_iter().map(Into::into).collect())
470            .collect();
471        self.dirty = true;
472        self.selected = self.selected.min(self.rows.len().saturating_sub(1));
473    }
474
475    /// Get the currently selected row data, or `None` if the table is empty.
476    pub fn selected_row(&self) -> Option<&[String]> {
477        self.rows.get(self.selected).map(|r| r.as_slice())
478    }
479
480    pub(crate) fn recompute_widths(&mut self) {
481        let col_count = self.headers.len();
482        self.column_widths = vec![0u32; col_count];
483        for (i, header) in self.headers.iter().enumerate() {
484            self.column_widths[i] = UnicodeWidthStr::width(header.as_str()) as u32;
485        }
486        for row in &self.rows {
487            for (i, cell) in row.iter().enumerate() {
488                if i < col_count {
489                    let w = UnicodeWidthStr::width(cell.as_str()) as u32;
490                    self.column_widths[i] = self.column_widths[i].max(w);
491                }
492            }
493        }
494        self.dirty = false;
495    }
496
497    pub(crate) fn column_widths(&self) -> &[u32] {
498        &self.column_widths
499    }
500
501    pub(crate) fn is_dirty(&self) -> bool {
502        self.dirty
503    }
504}
505
506/// State for a scrollable container.
507///
508/// Pass a mutable reference to `Context::scrollable` each frame. The context
509/// updates `offset` and the internal bounds automatically based on mouse wheel
510/// and drag events.
511pub struct ScrollState {
512    /// Current vertical scroll offset in rows.
513    pub offset: usize,
514    content_height: u32,
515    viewport_height: u32,
516}
517
518impl ScrollState {
519    /// Create scroll state starting at offset 0.
520    pub fn new() -> Self {
521        Self {
522            offset: 0,
523            content_height: 0,
524            viewport_height: 0,
525        }
526    }
527
528    /// Check if scrolling upward is possible (offset is greater than 0).
529    pub fn can_scroll_up(&self) -> bool {
530        self.offset > 0
531    }
532
533    /// Check if scrolling downward is possible (content extends below the viewport).
534    pub fn can_scroll_down(&self) -> bool {
535        (self.offset as u32) + self.viewport_height < self.content_height
536    }
537
538    /// Scroll up by the given number of rows, clamped to 0.
539    pub fn scroll_up(&mut self, amount: usize) {
540        self.offset = self.offset.saturating_sub(amount);
541    }
542
543    /// Scroll down by the given number of rows, clamped to the maximum offset.
544    pub fn scroll_down(&mut self, amount: usize) {
545        let max_offset = self.content_height.saturating_sub(self.viewport_height) as usize;
546        self.offset = (self.offset + amount).min(max_offset);
547    }
548
549    pub(crate) fn set_bounds(&mut self, content_height: u32, viewport_height: u32) {
550        self.content_height = content_height;
551        self.viewport_height = viewport_height;
552    }
553}
554
555impl Default for ScrollState {
556    fn default() -> Self {
557        Self::new()
558    }
559}
560
561/// Visual variant for buttons.
562///
563/// Controls the color scheme used when rendering a button. Pass to
564/// [`crate::Context::button_with`] to create styled button variants.
565///
566/// - `Default` — theme text color, primary when focused (same as `button()`)
567/// - `Primary` — primary color background with contrasting text
568/// - `Danger` — error/red color for destructive actions
569/// - `Outline` — bordered appearance without fill
570#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
571pub enum ButtonVariant {
572    /// Standard button style.
573    #[default]
574    Default,
575    /// Filled button with primary background color.
576    Primary,
577    /// Filled button with error/danger background color.
578    Danger,
579    /// Bordered button without background fill.
580    Outline,
581}
582
583// ── Select / Dropdown ─────────────────────────────────────────────────
584
585/// State for a dropdown select widget.
586///
587/// Renders as a single-line button showing the selected option. When activated,
588/// expands into a vertical list overlay for picking an option.
589pub struct SelectState {
590    pub items: Vec<String>,
591    pub selected: usize,
592    pub open: bool,
593    pub placeholder: String,
594    cursor: usize,
595}
596
597impl SelectState {
598    pub fn new(items: Vec<impl Into<String>>) -> Self {
599        Self {
600            items: items.into_iter().map(Into::into).collect(),
601            selected: 0,
602            open: false,
603            placeholder: String::new(),
604            cursor: 0,
605        }
606    }
607
608    pub fn placeholder(mut self, p: impl Into<String>) -> Self {
609        self.placeholder = p.into();
610        self
611    }
612
613    pub fn selected_item(&self) -> Option<&str> {
614        self.items.get(self.selected).map(String::as_str)
615    }
616
617    pub(crate) fn cursor(&self) -> usize {
618        self.cursor
619    }
620
621    pub(crate) fn set_cursor(&mut self, c: usize) {
622        self.cursor = c;
623    }
624}
625
626// ── Radio ─────────────────────────────────────────────────────────────
627
628/// State for a radio button group.
629///
630/// Renders a vertical list of mutually-exclusive options with `●`/`○` markers.
631pub struct RadioState {
632    pub items: Vec<String>,
633    pub selected: usize,
634}
635
636impl RadioState {
637    pub fn new(items: Vec<impl Into<String>>) -> Self {
638        Self {
639            items: items.into_iter().map(Into::into).collect(),
640            selected: 0,
641        }
642    }
643
644    pub fn selected_item(&self) -> Option<&str> {
645        self.items.get(self.selected).map(String::as_str)
646    }
647}
648
649// ── Multi-Select ──────────────────────────────────────────────────────
650
651/// State for a multi-select list.
652///
653/// Like [`ListState`] but allows toggling multiple items with Space.
654pub struct MultiSelectState {
655    pub items: Vec<String>,
656    pub cursor: usize,
657    pub selected: HashSet<usize>,
658}
659
660impl MultiSelectState {
661    pub fn new(items: Vec<impl Into<String>>) -> Self {
662        Self {
663            items: items.into_iter().map(Into::into).collect(),
664            cursor: 0,
665            selected: HashSet::new(),
666        }
667    }
668
669    pub fn selected_items(&self) -> Vec<&str> {
670        let mut indices: Vec<usize> = self.selected.iter().copied().collect();
671        indices.sort();
672        indices
673            .iter()
674            .filter_map(|&i| self.items.get(i).map(String::as_str))
675            .collect()
676    }
677
678    pub fn toggle(&mut self, index: usize) {
679        if self.selected.contains(&index) {
680            self.selected.remove(&index);
681        } else {
682            self.selected.insert(index);
683        }
684    }
685}
686
687// ── Tree ──────────────────────────────────────────────────────────────
688
689/// A node in a tree view.
690pub struct TreeNode {
691    pub label: String,
692    pub children: Vec<TreeNode>,
693    pub expanded: bool,
694}
695
696impl TreeNode {
697    pub fn new(label: impl Into<String>) -> Self {
698        Self {
699            label: label.into(),
700            children: Vec::new(),
701            expanded: false,
702        }
703    }
704
705    pub fn expanded(mut self) -> Self {
706        self.expanded = true;
707        self
708    }
709
710    pub fn children(mut self, children: Vec<TreeNode>) -> Self {
711        self.children = children;
712        self
713    }
714
715    pub fn is_leaf(&self) -> bool {
716        self.children.is_empty()
717    }
718
719    fn flatten(&self, depth: usize, out: &mut Vec<FlatTreeEntry>) {
720        out.push(FlatTreeEntry {
721            depth,
722            label: self.label.clone(),
723            is_leaf: self.is_leaf(),
724            expanded: self.expanded,
725        });
726        if self.expanded {
727            for child in &self.children {
728                child.flatten(depth + 1, out);
729            }
730        }
731    }
732}
733
734pub(crate) struct FlatTreeEntry {
735    pub depth: usize,
736    pub label: String,
737    pub is_leaf: bool,
738    pub expanded: bool,
739}
740
741/// State for a hierarchical tree view widget.
742pub struct TreeState {
743    pub nodes: Vec<TreeNode>,
744    pub selected: usize,
745}
746
747impl TreeState {
748    pub fn new(nodes: Vec<TreeNode>) -> Self {
749        Self { nodes, selected: 0 }
750    }
751
752    pub(crate) fn flatten(&self) -> Vec<FlatTreeEntry> {
753        let mut entries = Vec::new();
754        for node in &self.nodes {
755            node.flatten(0, &mut entries);
756        }
757        entries
758    }
759
760    pub(crate) fn toggle_at(&mut self, flat_index: usize) {
761        let mut counter = 0usize;
762        Self::toggle_recursive(&mut self.nodes, flat_index, &mut counter);
763    }
764
765    fn toggle_recursive(nodes: &mut [TreeNode], target: usize, counter: &mut usize) -> bool {
766        for node in nodes.iter_mut() {
767            if *counter == target {
768                if !node.is_leaf() {
769                    node.expanded = !node.expanded;
770                }
771                return true;
772            }
773            *counter += 1;
774            if node.expanded && Self::toggle_recursive(&mut node.children, target, counter) {
775                return true;
776            }
777        }
778        false
779    }
780}
781
782// ── Command Palette ───────────────────────────────────────────────────
783
784/// A single command entry in the palette.
785pub struct PaletteCommand {
786    pub label: String,
787    pub description: String,
788    pub shortcut: Option<String>,
789}
790
791impl PaletteCommand {
792    pub fn new(label: impl Into<String>, description: impl Into<String>) -> Self {
793        Self {
794            label: label.into(),
795            description: description.into(),
796            shortcut: None,
797        }
798    }
799
800    pub fn shortcut(mut self, s: impl Into<String>) -> Self {
801        self.shortcut = Some(s.into());
802        self
803    }
804}
805
806/// State for a command palette overlay.
807///
808/// Renders as a modal with a search input and filtered command list.
809pub struct CommandPaletteState {
810    pub commands: Vec<PaletteCommand>,
811    pub input: String,
812    pub cursor: usize,
813    pub open: bool,
814    selected: usize,
815}
816
817impl CommandPaletteState {
818    pub fn new(commands: Vec<PaletteCommand>) -> Self {
819        Self {
820            commands,
821            input: String::new(),
822            cursor: 0,
823            open: false,
824            selected: 0,
825        }
826    }
827
828    pub fn toggle(&mut self) {
829        self.open = !self.open;
830        if self.open {
831            self.input.clear();
832            self.cursor = 0;
833            self.selected = 0;
834        }
835    }
836
837    pub(crate) fn filtered_indices(&self) -> Vec<usize> {
838        if self.input.is_empty() {
839            return (0..self.commands.len()).collect();
840        }
841        let query = self.input.to_lowercase();
842        self.commands
843            .iter()
844            .enumerate()
845            .filter(|(_, cmd)| {
846                cmd.label.to_lowercase().contains(&query)
847                    || cmd.description.to_lowercase().contains(&query)
848            })
849            .map(|(i, _)| i)
850            .collect()
851    }
852
853    pub(crate) fn selected(&self) -> usize {
854        self.selected
855    }
856
857    pub(crate) fn set_selected(&mut self, s: usize) {
858        self.selected = s;
859    }
860}