cli_prompts/prompts/options/
selection.rs1use crate::{
2 engine::CommandBuffer,
3 input::Key,
4 prompts::{options::Options, AbortReason, EventOutcome, Prompt},
5 style::SelectionStyle,
6};
7
8use super::multioption_prompt::MultiOptionPrompt;
9
10const DEFAULT_OPTIONS_COUNT: u16 = 5;
11
12pub struct Selection<T> {
39 label: String,
40 options: Options<T>,
41 current_selection: usize,
42 max_options: u16,
43 current_filter: String,
44 is_submitted: bool,
45 style: SelectionStyle,
46}
47
48impl<T> Selection<T> {
49
50 pub fn new<S, I>(label: S, options: I) -> Self
53 where
54 T: Into<String> + Clone,
55 S: Into<String>,
56 I: Iterator<Item = T>,
57 {
58 let options = Options::from_iter(options);
59 Self::new_internal(label.into(), options)
60 }
61}
62
63impl<T> Selection<T> {
64
65 pub fn new_with_transformation<S, I, F>(label: S, options: I, transformation: F) -> Self
68 where
69 S: Into<String>,
70 I: Iterator<Item = T>,
71 F: Fn(&T) -> String,
72 {
73 let options = Options::from_iter_transformed(options, transformation);
74 Self::new_internal(label.into(), options)
75 }
76
77 pub fn displayed_options_count(mut self, options_count: u16) -> Self {
79 self.max_options = options_count;
80 self
81 }
82
83 pub fn style(mut self, style: SelectionStyle) -> Self {
85 self.style = style;
86 self
87 }
88
89 fn new_internal(label: String, options: Options<T>) -> Self {
90 Selection {
91 label,
92 options,
93 current_selection: 0_usize,
94 max_options: DEFAULT_OPTIONS_COUNT,
95 current_filter: String::new(),
96 is_submitted: false,
97 style: SelectionStyle::default(),
98 }
99 }
100}
101
102impl<T> MultiOptionPrompt<T> for Selection<T> {
103 fn max_options_count(&self) -> u16 {
104 self.max_options
105 }
106
107 fn options(&self) -> &Options<T> {
108 &self.options
109 }
110
111 fn currently_selected_index(&self) -> usize {
112 self.current_selection
113 }
114
115 fn draw_option(
116 &self,
117 _: usize,
118 option_label: &str,
119 is_selected: bool,
120 cmd_buffer: &mut impl CommandBuffer,
121 ) {
122 if is_selected {
123 self.style.selected_marker.print(cmd_buffer);
124 self.style
125 .selected_option_formatting
126 .print(option_label, cmd_buffer);
127 } else {
128 self.style.not_selected_marker.print(cmd_buffer);
129 self.style
130 .option_formatting
131 .print(option_label, cmd_buffer)
132 }
133 }
134
135 fn draw_header(&self, commands: &mut impl CommandBuffer, is_submitted: bool) {
136 if is_submitted {
137 let selected_option_index = self.options.filtered_options()[self.current_selection];
138 let selected_option = &self.options.transformed_options()[selected_option_index];
139 self.style
140 .submitted_formatting
141 .print(selected_option, commands);
142 } else {
143 self.style
144 .filter_formatting
145 .print(&self.current_filter, commands);
146 }
147 }
148}
149
150impl<T> Prompt<T> for Selection<T> {
151 fn draw(&self, commands: &mut impl CommandBuffer) {
152 self.draw_multioption(
153 &self.label,
154 self.is_submitted,
155 &self.style.label_style,
156 commands,
157 )
158 }
159
160 fn on_key_pressed(&mut self, key: Key) -> EventOutcome<T> {
161 match key {
162 Key::Char(c) => {
163 self.current_filter.push(c);
164 self.options.filter(&self.current_filter);
165 self.current_selection = 0;
166 EventOutcome::Continue
167 }
168 Key::Backspace if self.current_filter.len() > 0 => {
169 self.current_filter.pop();
170 self.options.filter(&self.current_filter);
171 self.current_selection = 0;
172 EventOutcome::Continue
173 }
174 Key::Up if self.current_selection > 0 => {
175 self.current_selection -= 1;
176 EventOutcome::Continue
177 }
178 Key::Down if self.current_selection < self.options.filtered_options().len() - 1 => {
179 self.current_selection += 1;
180 EventOutcome::Continue
181 }
182 Key::Enter if self.options.filtered_options().len() > 0 => {
183 self.is_submitted = true;
184 let selected_option_index = self.options.filtered_options()[self.current_selection];
185 let result = self.options.all_options_mut().remove(selected_option_index);
186 EventOutcome::Done(result)
187 }
188 Key::Esc => EventOutcome::Abort(AbortReason::Interrupt),
189 _ => EventOutcome::Continue,
190 }
191 }
192}