tty_form/step/
compound.rs1use crossterm::event::{KeyCode, KeyEvent};
2use tty_interface::{pos, Interface, Position};
3
4use crate::{
5 control::Control,
6 dependency::{Action, DependencyState},
7 style::{error_style, muted_style},
8 text::{
9 get_segment_length, set_segment_style, set_segment_subset_style, DrawerContents, Segment,
10 Text,
11 },
12 utility::render_segment,
13 Form,
14};
15
16use super::{InputResult, Step};
17
18pub struct CompoundStep {
36 index: Option<usize>,
37 controls: Vec<Box<dyn Control>>,
38 max_line_length: Option<u16>,
39 active_control: usize,
40 max_control: usize,
41}
42
43impl CompoundStep {
44 pub fn new() -> Self {
46 Self {
47 index: None,
48 controls: Vec::new(),
49 max_line_length: None,
50 active_control: 0,
51 max_control: 0,
52 }
53 }
54
55 pub fn add_control(&mut self, control: Box<dyn Control>) {
57 self.controls.push(control);
58 }
59
60 pub fn set_max_line_length(&mut self, max_length: u16) {
62 self.max_line_length = Some(max_length);
63 }
64
65 fn advance_control(&mut self) -> bool {
68 let mut reached_last_control = false;
69 loop {
70 if self.active_control + 1 >= self.controls.len() {
71 reached_last_control = true;
72 break;
73 }
74
75 self.active_control += 1;
76
77 if self.controls[self.active_control].focusable() {
78 break;
79 }
80 }
81
82 if self.active_control > self.max_control {
84 self.max_control = self.active_control;
85 loop {
86 if self.max_control + 1 >= self.controls.len() {
87 break;
88 }
89
90 if !self.controls[self.max_control + 1].focusable() {
91 self.max_control += 1;
92 } else {
93 break;
94 }
95 }
96 }
97
98 reached_last_control
99 }
100
101 fn retreat_control(&mut self) -> bool {
104 loop {
105 if self.active_control == 0 {
106 return true;
107 }
108
109 self.active_control -= 1;
110
111 if self.controls[self.active_control].focusable() {
112 break;
113 }
114 }
115
116 false
117 }
118}
119
120impl Step for CompoundStep {
121 fn initialize(&mut self, dependency_state: &mut DependencyState, index: usize) {
122 self.index = Some(index);
123
124 if !self.controls[0].focusable() {
126 self.advance_control();
127 }
128
129 for (control_index, control) in self.controls.iter().enumerate() {
131 for (id, evaluation) in control.evaluation() {
132 dependency_state.register_evaluation(&id, index, control_index);
133
134 let value = control.evaluate(&evaluation);
135 dependency_state.update_evaluation(&id, value);
136 }
137 }
138 }
139
140 fn render(
141 &self,
142 interface: &mut Interface,
143 dependency_state: &DependencyState,
144 mut position: Position,
145 is_focused: bool,
146 ) -> u16 {
147 interface.clear_line(position.y());
148
149 let mut cursor_position = None;
150 for (control_index, control) in self.controls.iter().enumerate() {
151 let (mut segment, cursor_offset) = control.text();
152
153 if control_index == self.active_control {
155 if let Some(offset) = cursor_offset {
156 cursor_position = Some(pos!(position.x() + offset, position.y()));
157 }
158 }
159
160 let mut should_hide = false;
162 if let Some((id, action)) = control.dependency() {
163 let control_touched = control_index <= self.max_control;
164 let evaluation_result = dependency_state.get_evaluation(&id);
165
166 match action {
167 Action::Hide => {
168 if control_touched && evaluation_result {
169 let (step_index, control_index) = dependency_state.get_source(&id);
171 let source_is_focused = step_index == self.index.unwrap()
172 && control_index == self.active_control;
173
174 if source_is_focused {
176 set_segment_style(&mut segment, muted_style());
177 } else {
178 should_hide = true;
179 }
180 }
181 }
182 Action::Show => should_hide = !evaluation_result,
183 }
184 }
185
186 if let Some(max_length) = self.max_line_length {
188 let segment_length = get_segment_length(&segment) as u16;
189 if position.x() + segment_length > max_length {
190 let error_starts_at = max_length - position.x();
191 set_segment_subset_style(
192 &mut segment,
193 error_starts_at.into(),
194 segment_length.into(),
195 error_style(),
196 );
197 }
198 }
199
200 if !should_hide {
201 position = render_segment(interface, position, segment);
202 }
203 }
204
205 if is_focused {
206 interface.set_cursor(cursor_position);
207 }
208
209 1
210 }
211
212 fn update(
213 &mut self,
214 dependency_state: &mut DependencyState,
215 input: KeyEvent,
216 ) -> Option<InputResult> {
217 match input.code {
218 KeyCode::Enter | KeyCode::Tab => {
219 if self.advance_control() {
220 return Some(InputResult::AdvanceForm);
221 }
222 }
223 KeyCode::Esc | KeyCode::BackTab => {
224 if self.retreat_control() {
225 return Some(InputResult::RetreatForm);
226 }
227 }
228 _ => {
229 let control = &mut self.controls[self.active_control];
230 control.update(input);
231
232 if let Some((id, evaluation)) = control.evaluation() {
234 let value = control.evaluate(&evaluation);
235 dependency_state.update_evaluation(&id, value);
236 }
237 }
238 }
239
240 None
241 }
242
243 fn help(&self) -> Segment {
244 self.controls[self.active_control]
245 .help()
246 .unwrap_or(Text::new(String::new()).as_segment())
247 }
248
249 fn drawer(&self) -> Option<DrawerContents> {
250 self.controls[self.active_control].drawer()
251 }
252
253 fn result(&self, dependency_state: &DependencyState) -> String {
254 let mut result = String::new();
255
256 for control in &self.controls {
257 if let Some((id, action)) = control.dependency() {
258 let evaluation_result = dependency_state.get_evaluation(&id);
259 match action {
260 Action::Hide => {
261 if evaluation_result {
262 continue;
263 }
264 }
265 Action::Show => {
266 if !evaluation_result {
267 continue;
268 }
269 }
270 }
271 }
272
273 let (segments, _) = control.text();
274 segments
275 .iter()
276 .for_each(|text| result.push_str(text.content()));
277 }
278
279 result.push('\n');
280
281 result
282 }
283
284 fn add_to(self, form: &mut Form) {
285 form.add_step(Box::new(self));
286 }
287}