term_inquiry/
checkbox_list.rs1struct Item<'a, T> {
2 message: &'a str,
3 value: T,
4 selected: bool,
5}
6
7pub struct CheckboxList<'a, T> {
8 message: &'a str,
9 selection_list: Vec<Item<'a, T>>,
10 term_data: TermData,
11}
12
13impl<'a, T> CheckboxList<'a, T> {
14 pub fn new(message: &'a str) -> Self {
15 Self {
16 message,
17 selection_list: Vec::new(),
18 term_data: TermData::new(),
19 }
20 }
21
22 pub fn add_item(mut self, selection_name: &'a str, item: T) -> Self {
23 let item = Item::<T> {
24 message: selection_name,
25 value: item,
26 selected: false,
27 };
28
29 self.selection_list.push(item);
30 self
31 }
32
33 pub fn inquire(mut self) -> Result<Vec<T>, InquiryMessage> {
34 if !self.term_data.enable_raw() {
35 return Err(InquiryMessage::TermEnableRawErr);
36 }
37
38 AnsiBuilder::new()
39 .text("[")
40 .color()
41 .fg()
42 .bright_green()
43 .text("?")
44 .reset_attributes()
45 .text("] ")
46 .text(&self.message)
47 .cursor()
48 .save()
49 .cursor()
50 .hide()
51 .color()
52 .fg()
53 .gray()
54 .text(" Press 'a' to accept selection")
55 .println();
56 let mut selected_index = 0;
57
58 let list_len = self.selection_list.len();
59 for _ in 0..list_len - 1 {
60 println!();
61 }
62
63 loop {
64 AnsiBuilder::new().cursor().up(list_len).print();
65
66 for i in 0..list_len {
67 AnsiBuilder::new()
68 .text("\n\r")
69 .erase_line(ClearMode::EntireLine)
70 .print();
71
72 if i == selected_index {
73 AnsiBuilder::new()
74 .color()
75 .fg()
76 .bright_green()
77 .style()
78 .bold()
79 .text(" → ")
80 .reset_attributes()
81 .print();
82
83 CheckboxList::render_item(&self.selection_list[i]);
84 continue;
85 }
86
87 AnsiBuilder::new().text(" ").print();
88
89 CheckboxList::render_item(&self.selection_list[i]);
90 }
91
92 match stdout().lock().flush() {
93 Ok(..) => {}
94 Err(..) => return Err(InquiryMessage::FlushLockErr),
95 };
96
97 let key = Keys::from(stdin());
98
99 match key {
100 Keys::Up => {
101 if selected_index > 0 {
102 selected_index -= 1
103 }
104 }
105 Keys::Down => {
106 if selected_index < self.selection_list.len() - 1 {
107 selected_index += 1;
108 }
109 }
110 Keys::Enter => {
111 self.selection_list[selected_index].selected =
112 !self.selection_list[selected_index].selected;
113 }
114 Keys::A => {
115 let mut selected_items = Vec::new();
116 let mut selected_names = String::new();
117
118 let mut i = 0;
119 while i < self.selection_list.len() {
120 if !self.selection_list[i].selected {
121 i += 1;
122 continue;
123 }
124
125 selected_names.push_str(self.selection_list[i].message);
126 selected_names.push_str(", ");
127 selected_items.push(self.selection_list.remove(i).value);
128 }
129
130 selected_names.pop();
131 selected_names.pop();
132
133 AnsiBuilder::new()
134 .cursor()
135 .restore()
136 .color()
137 .fg()
138 .blue()
139 .text(&format!(" {}", selected_names))
140 .reset_attributes()
141 .cursor()
142 .save()
143 .erase_in_display(EraseMode::CursorToEnd)
144 .cursor()
145 .restore()
146 .cursor()
147 .show()
148 .println();
149
150 if !self.term_data.disable_raw() {
151 return Err(InquiryMessage::TermDisableRawErr);
152 }
153
154 return Ok(selected_items);
155 }
156 Keys::CtrlC | Keys::CtrlZ => {
157 AnsiBuilder::new().cursor().show().print();
158
159 if !self.term_data.disable_raw() {
160 return Err(InquiryMessage::TermDisableRawErr);
161 }
162
163 return Err(InquiryMessage::CloseRequested);
164 }
165 _ => { }
170 }
171 }
172 }
173
174 fn render_item(item: &Item<T>) {
175 if item.selected {
176 AnsiBuilder::new()
177 .text("[")
178 .color()
179 .fg()
180 .bright_green()
181 .text("✓")
182 .reset_attributes()
183 .text("] ")
184 .text(&item.message)
185 .print();
186 return;
187 }
188
189 AnsiBuilder::new()
190 .text("[")
191 .color()
192 .fg()
193 .bright_red()
194 .text("✘")
195 .reset_attributes()
196 .text("] ")
197 .text(&item.message)
198 .print();
199 }
200}
201
202use std::io::{stdin, stdout, Write};
203
204use ansi_builder::{AnsiBuilder, ClearMode, EraseMode};
205
206use crate::{term_data::TermData, InquiryMessage, Keys};