tty_form/control/
selectinput.rs1use crossterm::event::{KeyCode, KeyEvent};
2
3use crate::{
4 dependency::{Action, DependencyId, Evaluation},
5 step::CompoundStep,
6 style::{drawer_selected_style, drawer_style, help_style},
7 text::{DrawerContents, Segment, Text},
8};
9
10use super::Control;
11
12pub struct SelectInput {
31 prompt: String,
32 options: Vec<SelectInputOption>,
33 selected_option: usize,
34}
35
36impl SelectInput {
37 pub fn new(prompt: &str, options: Vec<(&str, &str)>) -> Self {
39 Self {
40 prompt: prompt.to_string(),
41 options: options
42 .iter()
43 .map(|(value, description)| SelectInputOption::new(value, description))
44 .collect(),
45 selected_option: 0,
46 }
47 }
48
49 pub fn set_prompt(&mut self, prompt: &str) {
51 self.prompt = prompt.to_string();
52 }
53
54 pub fn add_option(&mut self, option: SelectInputOption) {
56 self.options.push(option);
57 }
58
59 pub fn set_options(&mut self, options: Vec<SelectInputOption>) {
61 self.options = options;
62 }
63
64 fn selected_option_value(&self) -> &str {
66 &self.options[self.selected_option].value
67 }
68}
69
70impl Control for SelectInput {
71 fn focusable(&self) -> bool {
72 true
73 }
74
75 fn update(&mut self, input: KeyEvent) {
76 match input.code {
77 KeyCode::Up => {
78 if self.selected_option == 0 {
79 self.selected_option = self.options.len() - 1;
80 } else {
81 self.selected_option -= 1;
82 }
83 }
84 KeyCode::Down => {
85 if self.selected_option + 1 == self.options.len() {
86 self.selected_option = 0;
87 } else {
88 self.selected_option += 1;
89 }
90 }
91 _ => {}
92 }
93 }
94
95 fn help(&self) -> Option<Segment> {
96 Some(Text::new_styled(self.prompt.clone(), help_style()).as_segment())
97 }
98
99 fn text(&self) -> (Segment, Option<u16>) {
100 let value = self.selected_option_value();
101 let segment = Text::new(value.to_string()).as_segment();
102
103 (segment, Some(0))
104 }
105
106 fn drawer(&self) -> Option<DrawerContents> {
107 let mut items = Vec::new();
108
109 for (option_index, option) in self.options.iter().enumerate() {
110 let mut text = format!(" {} - {}", option.value, option.description);
111 let mut style = drawer_style();
112
113 if option_index == self.selected_option {
114 style = drawer_selected_style();
115 text.replace_range(1..2, ">");
116 }
117
118 items.push(Text::new_styled(text, style).as_segment());
119 }
120
121 Some(items)
122 }
123
124 fn evaluation(&self) -> Option<(DependencyId, Evaluation)> {
125 None
126 }
127
128 fn dependency(&self) -> Option<(DependencyId, Action)> {
129 None
130 }
131
132 fn evaluate(&self, evaluation: &Evaluation) -> bool {
133 match evaluation {
134 Evaluation::Equal(value) => self.selected_option_value() == value,
135 Evaluation::NotEqual(value) => self.selected_option_value() != value,
136 Evaluation::IsEmpty => false,
137 }
138 }
139
140 fn add_to(self, step: &mut CompoundStep) {
141 step.add_control(Box::new(self))
142 }
143}
144
145pub struct SelectInputOption {
147 value: String,
148 description: String,
149}
150
151impl SelectInputOption {
152 pub fn new(value: &str, description: &str) -> Self {
154 Self {
155 value: value.to_string(),
156 description: description.to_string(),
157 }
158 }
159
160 pub fn value(&self) -> &str {
162 &self.value
163 }
164
165 pub fn description(&self) -> &str {
167 &self.description
168 }
169}