1use crossterm::{
2 cursor,
3 event::{Event, KeyCode},
4 style,
5 terminal::{self, ClearType},
6 QueueableCommand,
7};
8use log::debug;
9use std::{
10 cmp::Ordering,
11 io::{self, Stdout, Write},
12};
13
14use crate::{
15 app::{EventHandlerResult, EventResult},
16 input::{Input, Select},
17 label::Label,
18 pos::Pos,
19 select_form::SelectForm,
20};
21
22#[derive(Debug, Clone)]
24pub struct Form {
25 pub(crate) labels: Vec<Label>,
26 pub(crate) inputs: Vec<Input>,
27 pub(crate) current_pos: Pos,
28 pub(crate) size: Pos,
29 pub(crate) select_form: Option<SelectForm>,
30}
31
32impl Form {
33 pub fn new(size: impl Into<Pos>) -> io::Result<Self> {
35 Ok(Self {
36 labels: Default::default(),
37 inputs: Default::default(),
38 current_pos: (0, 0).into(),
39 size: size.into(),
40 select_form: None,
41 })
42 }
43
44 pub(crate) fn event_handler(&mut self, event: &Event) -> io::Result<EventHandlerResult> {
46 if let Some(select_form) = self.select_form.as_mut() {
48 let result = select_form.event_handler(event)?;
49 match result {
50 EventHandlerResult::Handled(EventResult::Submit) => {
51 let selected = select_form.get_selection();
52 if select_form.select_type == Select::Single {
53 debug!("Selected: {:?}", selected);
54 assert!(
55 selected.len() <= 1,
56 "Single should only return none or one item"
57 );
58 }
59 self.select_form = None;
60 if let Some(f) = self.current_field() {
61 f.value = selected
62 .first()
63 .map(|s| s.as_str())
64 .unwrap_or_default()
65 .to_string();
66 };
67 self.current_pos = self
68 .current_field()
69 .map(|f| f.pos)
70 .unwrap_or(self.current_pos);
71 return Ok(EventHandlerResult::Handled(EventResult::None));
72 }
73 EventHandlerResult::Handled(EventResult::Abort) => {
74 debug!("Aborted");
75 self.select_form = None;
76 return Ok(EventHandlerResult::Handled(EventResult::None));
77 }
78 _ => return Ok(result),
79 };
80 }
81
82 let mut current_pos = self.current_pos;
83
84 if let Some(current_field) = self.current_field() {
85 if let EventHandlerResult::Handled(result) =
86 current_field.event_handler(event, &mut current_pos)?
87 {
88 self.current_pos = current_pos;
89 return Ok(EventHandlerResult::Handled(result));
90 }
91 }
92
93 match event {
94 Event::Key(k) if k.code == KeyCode::Esc => {
95 return Ok(EventHandlerResult::Handled(EventResult::Abort));
96 }
97 Event::Key(k) if k.code == KeyCode::Enter => {
98 return Ok(EventHandlerResult::Handled(EventResult::Submit));
99 }
100 Event::Key(k) if k.code == KeyCode::Left => {
101 self.move_event(k.code);
102 }
103 Event::Key(k) if k.code == KeyCode::Right => {
104 self.move_event(k.code);
105 }
106 Event::Key(k) if k.code == KeyCode::Up => {
107 self.move_event(k.code);
108 }
109 Event::Key(k) if k.code == KeyCode::Down => {
110 self.move_event(k.code);
111 }
112 Event::Key(k) if k.code == KeyCode::Tab => {
113 self.next_input();
114 }
115 Event::Key(k) if k.code == KeyCode::BackTab => {
116 self.prev_input();
117 }
118 Event::Key(k) if k.code == KeyCode::F(4) => {
119 debug!("Display select form");
120 let Some(current_field) = self.current_field() else {
121 return Ok(EventHandlerResult::Handled(EventResult::None));
122 };
123
124 if current_field.select == Select::Single {
125 let mut select_form =
126 SelectForm::new(¤t_field.select_static, (80, 24), Select::Single)?;
127 select_form.display(&mut std::io::stdout())?;
128
129 self.select_form = Some(select_form);
130 }
131 }
132 _ => return Ok(EventHandlerResult::NotHandled),
133 }
134
135 Ok(EventHandlerResult::Handled(EventResult::None))
136 }
137
138 pub(crate) fn display(&mut self, stdout: &mut Stdout) -> io::Result<()> {
139 if let Some(select_form) = self.select_form.as_mut() {
140 return select_form.display(stdout);
141 }
142
143 stdout
145 .queue(cursor::MoveTo(self.size.x, self.size.y))?
146 .queue(terminal::Clear(ClearType::FromCursorUp))?;
147
148 stdout.queue(style::SetForegroundColor(style::Color::DarkGreen))?;
150 for y in 0..24 {
151 stdout
152 .queue(cursor::MoveTo(80, y))?
153 .queue(style::Print('│'))?;
154 }
155 stdout
156 .queue(cursor::MoveTo(0, 24))?
157 .queue(style::Print("─".repeat(80)))?
158 .queue(style::Print('┘'))?;
159
160 stdout
161 .queue(cursor::MoveTo(2, 24))?
162 .queue(style::Print(" Esc=Abort "))?
163 .queue(style::Print('─'))?
164 .queue(style::Print(" Enter=Submit "))?;
165
166 if self
167 .inputs
168 .iter()
169 .find(|i| i.has_focus(self.current_pos))
170 .filter(|i| i.select != Select::None)
171 .is_some()
172 {
173 stdout
174 .queue(cursor::MoveTo(82 - 6 - 10, 24))?
175 .queue(style::SetForegroundColor(style::Color::DarkGreen))?
176 .queue(style::Print(" F4 - Select "))?;
177 }
178
179 for label in self.labels.clone() {
180 stdout
181 .queue(cursor::MoveTo(label.pos.x, label.pos.y))?
182 .queue(style::SetForegroundColor(style::Color::White))?
183 .queue(style::Print(label.text))?;
184 }
185
186 for input in self.inputs.clone() {
187 display_generic(stdout, &input)?;
188 }
189
190 stdout.queue(cursor::MoveTo(self.current_pos.x, self.current_pos.y))?;
191 stdout.queue(cursor::SetCursorStyle::SteadyUnderScore)?;
192
193 stdout.flush()
194 }
195
196 pub(crate) fn move_event(&mut self, code: KeyCode) {
197 self.current_pos = match code {
198 KeyCode::Left => Pos {
199 x: self.current_pos.x.checked_sub(1).unwrap_or_default(),
200 y: self.current_pos.y,
201 },
202 KeyCode::Right => Pos {
203 x: self.current_pos.x + 1,
204 y: self.current_pos.y,
205 },
206 KeyCode::Up => Pos {
207 x: self.current_pos.x,
208 y: self.current_pos.y.checked_sub(1).unwrap_or_default(),
209 },
210 KeyCode::Down => Pos {
211 x: self.current_pos.x,
212 y: self.current_pos.y + 1,
213 },
214 _ => self.current_pos,
215 }
216 .constrain(self.size)
217 }
218
219 pub(crate) fn find_next_input(&mut self) -> Option<Pos> {
220 let mut inputs = self.inputs.clone();
221 inputs.sort();
222
223 let mut i = inputs.iter();
224 let mut first_pos = None;
225
226 loop {
227 let Some(input) = i.next() else {
229 return first_pos;
230 };
231
232 if first_pos.is_none() {
234 first_pos = Some(input.pos);
235 }
236
237 match self.current_pos.cmp(&input.pos) {
238 Ordering::Less => {
240 return Some(input.pos);
241 }
242
243 Ordering::Equal => {
246 if let Some(next) = i.next() {
247 return Some(next.pos);
249 } else {
250 return first_pos;
252 }
253 }
254 _ => (),
255 }
256 }
257 }
258
259 pub(crate) fn find_prev_input(&mut self) -> Option<Pos> {
260 let mut inputs = self.inputs.clone();
261 inputs.sort();
262
263 let mut i = inputs.iter().rev();
264
265 loop {
266 let Some(input) = i.next() else {
268 debug!("Returning last");
269 return inputs.last().map(|w| w.pos);
270 };
271
272 debug!("Comparing {:?} to {:?}", self.current_pos, input.pos);
273
274 match self.current_pos.cmp(&input.pos) {
275 Ordering::Greater => {
277 return Some(input.pos);
278 }
279
280 Ordering::Equal => {
283 if let Some(next) = i.next() {
284 return Some(next.pos);
286 } else {
287 return inputs.iter().map(|w| w.pos).last();
289 }
290 }
291 _ => (),
292 }
293 }
294 }
295
296 pub fn next_input(&mut self) {
298 if let Some(pos) = self.find_next_input() {
299 self.current_pos = pos;
300 }
301 }
302
303 pub fn prev_input(&mut self) {
305 if let Some(pos) = self.find_prev_input() {
306 self.current_pos = pos;
307 }
308 }
309
310 pub fn current_field(&mut self) -> Option<&mut Input> {
312 self.inputs
313 .iter_mut()
314 .find(|f| f.has_focus(self.current_pos))
315 }
316
317 #[allow(dead_code)]
319 pub fn add_text(mut self, pos: impl Into<Pos>, text: impl Into<String>) -> Self {
320 self.labels.push(Label::new_label(pos, text));
321
322 self
323 }
324
325 pub fn add_label(&mut self, label: Label) {
327 self.labels.push(label);
328 }
329
330 pub fn add_input(&mut self, input: Input) {
332 self.inputs.push(input);
333 }
334
335 pub fn add_select(&mut self, input: String, id: String, value: String) {
342 let input = self.inputs.iter_mut().find(|i| i.name == input);
343
344 if let Some(input) = input {
345 if input.select == Select::None {
346 input.select = Select::Single;
347 }
348
349 input.select_static.push((id, value));
350 debug!("List: {:?}", input.select_static);
351 } else {
352 panic!("Input not found");
353 }
354 }
355
356 pub fn place_cursor(mut self) -> Self {
359 self.current_pos = self.find_next_input().unwrap_or((0, 0).into());
360
361 self
362 }
363
364 #[allow(dead_code)]
365 fn get_input(&self, field_name: &'static str) -> Option<String> {
366 self.inputs.iter().find_map(|input| {
367 if input.name == field_name {
368 Some(input.value.to_string())
369 } else {
370 None
371 }
372 })
373 }
374
375 pub fn get_field_and_data(&self) -> Vec<(&str, &str)> {
377 let mut output = Vec::new();
378
379 for input in &self.inputs {
380 output.push((input.name.as_str(), input.value.as_str()));
381 }
382
383 output
384 }
385}
386
387fn display_string(stdout: &mut Stdout, input: &Input) -> io::Result<()> {
388 stdout
389 .queue(cursor::MoveTo(input.pos.x, input.pos.y))?
390 .queue(style::SetAttribute(style::Attribute::Underlined))?;
391 for i in 0..(input.length as usize) {
392 match (
393 input.value.chars().nth(i),
394 input.default_value.chars().nth(i),
395 ) {
396 (Some(s), Some(d)) if s != d => {
397 stdout
398 .queue(style::SetForegroundColor(style::Color::DarkRed))?
399 .queue(style::Print(s))?;
400 }
401 (Some(s), Some(_)) => {
402 stdout
403 .queue(style::SetForegroundColor(style::Color::DarkGreen))?
404 .queue(style::Print(s))?;
405 }
406 (Some(s), None) => {
407 stdout
408 .queue(style::SetForegroundColor(style::Color::DarkRed))?
409 .queue(style::Print(s))?;
410 }
411 _ => {
412 stdout
413 .queue(style::SetForegroundColor(style::Color::DarkGreen))?
414 .queue(style::Print(" "))?;
415 }
416 }
417 }
418
419 stdout.queue(style::SetAttribute(style::Attribute::NoUnderline))?;
420
421 Ok(())
422}
423
424fn display_password(stdout: &mut Stdout, input: &Input) -> io::Result<()> {
425 let pass_len = input.value.chars().count();
426
427 let mask_char = input.mask_char.unwrap();
429 stdout
430 .queue(cursor::MoveTo(input.pos.x, input.pos.y))?
431 .queue(style::SetAttribute(style::Attribute::Underlined))?
432 .queue(style::SetForegroundColor(style::Color::DarkRed))?
433 .queue(style::Print(mask_char.to_string().repeat(pass_len)))?
434 .queue(style::SetForegroundColor(style::Color::DarkGreen))?
435 .queue(style::Print(" ".repeat(input.length as usize - pass_len)))?
436 .queue(style::SetAttribute(style::Attribute::NoUnderline))?;
437
438 Ok(())
439}
440
441fn display_generic(stdout: &mut Stdout, input: &Input) -> io::Result<()> {
442 if input.mask_char.is_some() {
443 display_password(stdout, input)?;
444 } else {
445 display_string(stdout, input)?;
446 }
447
448 Ok(())
449}