Skip to main content

promkit_widgets/checkbox/
checkbox.rs

1use std::{collections::HashSet, fmt};
2
3use promkit_core::grapheme::StyledGraphemes;
4
5use crate::listbox::Listbox;
6
7/// A `Checkbox` struct that encapsulates a listbox
8/// for item selection and a set of picked (selected) indices.
9/// It allows for multiple selections,
10/// toggling the selection state of items,
11/// and navigating through the items.
12#[derive(Clone)]
13pub struct Checkbox {
14    listbox: Listbox,
15    picked: HashSet<usize>,
16}
17
18impl Checkbox {
19    /// Creates a new `Checkbox` from a vector of `fmt::Display`.
20    pub fn from_displayable<E: fmt::Display, I: IntoIterator<Item = E>>(items: I) -> Self {
21        Self {
22            listbox: Listbox::from(items),
23            picked: HashSet::new(),
24        }
25    }
26
27    /// Creates a new `Checkbox` from a vector of `StyledGraphemes`.
28    pub fn from_styled_graphemes(items: Vec<StyledGraphemes>) -> Self {
29        Self {
30            listbox: Listbox::from_styled_graphemes(items),
31            picked: HashSet::new(),
32        }
33    }
34
35    /// Creates a `Checkbox` from an iterator of tuples where the first element
36    /// implements the `Display` trait and the second element is a bool indicating
37    /// if the item is picked (selected).
38    /// Each item is added to the listbox, and the set of picked indices is
39    /// initialized based on the bool values.
40    pub fn new_with_checked<T: fmt::Display, I: IntoIterator<Item = (T, bool)>>(iter: I) -> Self {
41        let (listbox, picked): (Vec<_>, Vec<_>) = iter
42            .into_iter()
43            .enumerate()
44            .map(|(index, (item, is_picked))| ((index, item), is_picked))
45            .unzip(); // `unzip` を使用して、アイテムと選択状態を分けます。
46
47        let listbox_items = listbox
48            .into_iter()
49            .map(|(_, item)| item)
50            .collect::<Vec<_>>();
51        let picked_indices = picked
52            .into_iter()
53            .enumerate()
54            .filter_map(
55                |(index, is_picked)| {
56                    if is_picked { Some(index) } else { None }
57                },
58            )
59            .collect::<HashSet<usize>>();
60
61        Self {
62            listbox: Listbox::from(listbox_items),
63            picked: picked_indices,
64        }
65    }
66
67    /// Returns a reference to the vector of items in the listbox.
68    pub fn items(&self) -> &Vec<StyledGraphemes> {
69        self.listbox.items()
70    }
71
72    /// Returns the current position of the cursor within the listbox.
73    pub fn position(&self) -> usize {
74        self.listbox.position()
75    }
76
77    /// Returns a reference to the set of picked (selected) indices.
78    pub fn picked_indexes(&self) -> &HashSet<usize> {
79        &self.picked
80    }
81
82    /// Retrieves the items at the picked (selected) indices as a vector of strings.
83    pub fn get(&self) -> Vec<StyledGraphemes> {
84        self.picked
85            .iter()
86            .fold(Vec::<StyledGraphemes>::new(), |mut ret, idx| {
87                ret.push(self.listbox.items().get(*idx).unwrap().to_owned());
88                ret
89            })
90    }
91
92    /// Toggles the selection state of the item at the current cursor position within the listbox.
93    pub fn toggle(&mut self) {
94        if self.picked.contains(&self.listbox.position()) {
95            self.picked.remove(&self.listbox.position());
96        } else {
97            self.picked.insert(self.listbox.position());
98        }
99    }
100
101    /// Moves the cursor backward in the listbox, if possible.
102    /// Returns `true` if the cursor was successfully moved backward, `false` otherwise.
103    pub fn backward(&mut self) -> bool {
104        self.listbox.backward()
105    }
106
107    /// Moves the cursor forward in the listbox, if possible.
108    /// Returns `true` if the cursor was successfully moved forward, `false` otherwise.
109    pub fn forward(&mut self) -> bool {
110        self.listbox.forward()
111    }
112
113    /// Moves the cursor to the head (beginning) of the listbox.
114    pub fn move_to_head(&mut self) {
115        self.listbox.move_to_head()
116    }
117
118    /// Moves the cursor to the tail of the listbox.
119    pub fn move_to_tail(&mut self) {
120        self.listbox.move_to_tail()
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    mod new_with_checked {
129        use super::*;
130
131        #[test]
132        fn test() {
133            // Prepare a list of items with their checked status
134            let items = vec![
135                (String::from("1"), true),
136                (String::from("2"), false),
137                (String::from("3"), true),
138            ];
139
140            // Create a Checkbox using `new_with_checked`
141            let checkbox = Checkbox::new_with_checked(items);
142
143            // Verify the items in the listbox
144            assert_eq!(
145                checkbox.items(),
146                &vec![
147                    StyledGraphemes::from("1"),
148                    StyledGraphemes::from("2"),
149                    StyledGraphemes::from("3"),
150                ]
151            );
152
153            // Verify the picked (selected) indices
154            let expected_picked_indexes: HashSet<usize> = [0, 2].iter().cloned().collect();
155            assert_eq!(checkbox.picked_indexes(), &expected_picked_indexes);
156        }
157    }
158}