1use crossterm::event::{KeyCode, KeyEvent};
2use tty_interface::{pos, Interface, Position};
3use tty_text::Key;
4
5use crate::{
6 dependency::{DependencyId, DependencyState, Evaluation},
7 style::{help_style, muted_style},
8 text::{DrawerContents, Segment, Text},
9 Form,
10};
11
12use super::{InputResult, Step};
13
14pub struct YesNoStep {
16 prompt: String,
17 prefix: String,
18 omit_if_no: bool,
19 toggle_value: bool,
20 text_prompt: String,
21 text: tty_text::Text,
22 evaluation: Option<(DependencyId, Evaluation)>,
23}
24
25impl YesNoStep {
26 pub fn new(prompt: &str, description_prompt: &str, prefix: &str) -> Self {
27 Self {
28 prompt: prompt.to_string(),
29 prefix: prefix.to_string(),
30 omit_if_no: true,
31 toggle_value: false,
32 text_prompt: description_prompt.to_string(),
33 text: tty_text::Text::new(false),
34 evaluation: None,
35 }
36 }
37
38 pub fn set_omit_if_no(&mut self, omit: bool) {
39 self.omit_if_no = omit;
40 }
41
42 pub fn set_evaluation(&mut self, evaluation: Evaluation) -> DependencyId {
43 let id = DependencyId::new();
44 self.evaluation = Some((id, evaluation));
45 id
46 }
47
48 fn get_display_value(&self) -> String {
49 if !self.text.value().is_empty() {
50 self.text.value()
51 } else if self.toggle_value {
52 String::from("Yes")
53 } else {
54 String::from("No")
55 }
56 }
57}
58
59impl Step for YesNoStep {
60 fn initialize(&mut self, _dependency_state: &mut DependencyState, _index: usize) {}
61
62 fn render(
63 &self,
64 interface: &mut Interface,
65 _dependency_state: &DependencyState,
66 position: Position,
67 is_focused: bool,
68 ) -> u16 {
69 if self.toggle_value || is_focused || !self.omit_if_no {
70 let display_value = self.get_display_value();
71 if !self.toggle_value && (is_focused || !self.omit_if_no) {
72 interface.set_styled(
74 position,
75 &format!("{}: {}", self.prefix, display_value),
76 muted_style(),
77 );
78 } else if is_focused && self.toggle_value && self.text.value().is_empty() {
79 interface.set(position, &format!("{}:", self.prefix));
81
82 let value_position = pos!(self.prefix.len() as u16 + 2, position.y());
83 interface.set_styled(value_position, &display_value, muted_style());
84 } else if is_focused || self.toggle_value {
85 interface.set(position, &format!("{}: {}", self.prefix, display_value));
87 }
88
89 if is_focused && self.toggle_value {
90 let (cursor_column, _) = self.text.cursor();
91 let cursor = pos!((self.prefix.len() + 2 + cursor_column) as u16, position.y());
92 interface.set_cursor(Some(cursor));
93 }
94
95 return 1;
96 }
97
98 0
99 }
100
101 fn update(
102 &mut self,
103 dependency_state: &mut DependencyState,
104 input: KeyEvent,
105 ) -> Option<InputResult> {
106 match input.code {
107 KeyCode::Esc | KeyCode::BackTab => return Some(InputResult::RetreatForm),
108 KeyCode::Enter | KeyCode::Tab => return Some(InputResult::AdvanceForm),
109 _ => {}
110 };
111
112 if self.text.value().is_empty()
113 && (input.code == KeyCode::Up || input.code == KeyCode::Down)
114 {
115 self.toggle_value = !self.toggle_value;
116 }
117
118 if self.toggle_value {
119 match input.code {
120 KeyCode::Char(ch) => self.text.handle_input(Key::Char(ch)),
121 KeyCode::Backspace => self.text.handle_input(Key::Backspace),
122 KeyCode::Left => self.text.handle_input(Key::Left),
123 KeyCode::Right => self.text.handle_input(Key::Right),
124 _ => {}
125 };
126 }
127
128 if let Some((id, evaluation)) = &self.evaluation {
129 let value = match evaluation {
130 Evaluation::Equal(value) => value == &self.get_display_value(),
131 Evaluation::NotEqual(value) => value != &self.get_display_value(),
132 Evaluation::IsEmpty => false,
133 };
134
135 dependency_state.update_evaluation(&id, value);
136 }
137
138 None
139 }
140
141 fn help(&self) -> Segment {
142 Text::new_styled(
143 if self.toggle_value {
144 self.text_prompt.to_string()
145 } else {
146 self.prompt.to_string()
147 },
148 help_style(),
149 )
150 .as_segment()
151 }
152
153 fn drawer(&self) -> Option<DrawerContents> {
154 None
155 }
156
157 fn result(&self, _dependency_state: &DependencyState) -> String {
158 if self.omit_if_no && !self.toggle_value {
159 return String::new();
160 }
161
162 format!("{}: {}\n", self.prefix, self.get_display_value())
163 }
164
165 fn add_to(self, form: &mut Form) {
166 form.add_step(Box::new(self));
167 }
168}