Skip to main content

ratatui_form/
form.rs

1//! Form and FormBuilder implementation.
2
3use std::fs::File;
4use std::io::{self, Write};
5use std::path::Path;
6
7use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
8use ratatui::buffer::Buffer;
9use ratatui::layout::{Constraint, Layout, Rect};
10use ratatui::text::{Line, Span};
11use ratatui::widgets::{Block, Borders, Padding, Widget};
12use serde_json::{Map, Value};
13
14use crate::block::Block as FormBlock;
15use crate::field::{Checkbox, Field, Select, TextInput};
16use crate::navigation::FocusManager;
17use crate::style::FormStyle;
18use crate::validation::ValidationError;
19
20/// Result of form submission.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum FormResult {
23    /// Form was submitted successfully.
24    Submitted,
25    /// Form was cancelled.
26    Cancelled,
27    /// Form is still active.
28    Active,
29}
30
31/// A form with fields and navigation.
32pub struct Form {
33    title: Option<String>,
34    fields: Vec<Box<dyn Field>>,
35    focus_manager: FocusManager,
36    style: FormStyle,
37    result: FormResult,
38    validation_errors: Vec<ValidationError>,
39}
40
41impl Form {
42    /// Creates a new form builder.
43    pub fn builder() -> FormBuilder {
44        FormBuilder::new()
45    }
46
47    /// Returns the current form result.
48    pub fn result(&self) -> &FormResult {
49        &self.result
50    }
51
52    /// Returns whether the form is still active.
53    pub fn is_active(&self) -> bool {
54        self.result == FormResult::Active
55    }
56
57    /// Handles keyboard input.
58    pub fn handle_input(&mut self, event: KeyEvent) {
59        // Handle global keys
60        match event.code {
61            KeyCode::Esc => {
62                self.result = FormResult::Cancelled;
63                return;
64            }
65            KeyCode::Tab => {
66                if event.modifiers.contains(KeyModifiers::SHIFT) {
67                    self.focus_manager.focus_previous();
68                } else {
69                    self.focus_manager.focus_next();
70                }
71                return;
72            }
73            KeyCode::Enter if self.focus_manager.is_submit_focused() => {
74                self.try_submit();
75                return;
76            }
77            KeyCode::Down => {
78                // Only move focus if the current field doesn't consume the event
79                if !self.delegate_to_focused_field(&event) {
80                    self.focus_manager.focus_next();
81                }
82                return;
83            }
84            KeyCode::Up => {
85                if !self.delegate_to_focused_field(&event) {
86                    self.focus_manager.focus_previous();
87                }
88                return;
89            }
90            _ => {}
91        }
92
93        // Delegate to focused field
94        self.delegate_to_focused_field(&event);
95    }
96
97    fn delegate_to_focused_field(&mut self, event: &KeyEvent) -> bool {
98        if self.focus_manager.is_submit_focused() {
99            return false;
100        }
101
102        let index = self.focus_manager.current_index();
103        if let Some(field) = self.fields.get_mut(index) {
104            field.handle_input(event)
105        } else {
106            false
107        }
108    }
109
110    fn try_submit(&mut self) {
111        self.validation_errors.clear();
112
113        for field in &self.fields {
114            if let Err(errors) = field.validate() {
115                self.validation_errors.extend(errors);
116            }
117        }
118
119        if self.validation_errors.is_empty() {
120            self.result = FormResult::Submitted;
121        } else {
122            // Focus on the first field with an error
123            if let Some(error) = self.validation_errors.first() {
124                for (i, field) in self.fields.iter().enumerate() {
125                    if field.id() == error.field_id {
126                        self.focus_manager.focus_field(i);
127                        break;
128                    }
129                }
130            }
131        }
132    }
133
134    /// Returns the form data as a JSON object.
135    pub fn to_json(&self) -> Value {
136        let mut map = Map::new();
137
138        for field in &self.fields {
139            map.insert(field.id().to_string(), field.value());
140        }
141
142        Value::Object(map)
143    }
144
145    /// Writes the form data to a JSON file.
146    pub fn write_json(&self, path: impl AsRef<Path>) -> io::Result<()> {
147        let json = self.to_json();
148        let mut file = File::create(path)?;
149        let formatted = serde_json::to_string_pretty(&json)?;
150        file.write_all(formatted.as_bytes())?;
151        Ok(())
152    }
153
154    /// Returns validation errors.
155    pub fn validation_errors(&self) -> &[ValidationError] {
156        &self.validation_errors
157    }
158
159    /// Renders the form to a buffer.
160    pub fn render(&self, area: Rect, buf: &mut Buffer) {
161        // Create the outer block with border
162        let border_style = if self.focus_manager.is_submit_focused() {
163            self.style.border
164        } else {
165            self.style.border_focused
166        };
167
168        let mut block = Block::default()
169            .borders(Borders::ALL)
170            .border_style(border_style)
171            .padding(Padding::horizontal(1));
172
173        if let Some(ref title) = self.title {
174            block = block.title(Span::styled(title, self.style.title));
175        }
176
177        let inner_area = block.inner(area);
178        block.render(area, buf);
179
180        if inner_area.height < 2 || inner_area.width < 10 {
181            return;
182        }
183
184        // Layout for fields and submit button
185        let field_count = self.fields.len();
186        let mut constraints = Vec::with_capacity(field_count + 2);
187
188        for field in &self.fields {
189            constraints.push(Constraint::Length(field.height()));
190        }
191        constraints.push(Constraint::Length(1)); // Spacer
192        constraints.push(Constraint::Length(1)); // Submit button
193        constraints.push(Constraint::Min(0)); // Remaining space
194
195        let layout = Layout::vertical(constraints).split(inner_area);
196
197        // Render each field
198        for (i, field) in self.fields.iter().enumerate() {
199            let is_focused = !self.focus_manager.is_submit_focused()
200                && i == self.focus_manager.current_index();
201            field.render(layout[i], buf, is_focused, &self.style);
202        }
203
204        // Render submit button
205        let submit_idx = field_count + 1;
206        if submit_idx < layout.len() {
207            self.render_submit_button(layout[submit_idx], buf);
208        }
209
210        // Render validation errors summary if any
211        if !self.validation_errors.is_empty() {
212            let error_count = self.validation_errors.len();
213            let error_msg = if error_count == 1 {
214                "1 validation error".to_string()
215            } else {
216                format!("{} validation errors", error_count)
217            };
218
219            let error_area = Rect {
220                x: inner_area.x,
221                y: inner_area.y + inner_area.height.saturating_sub(1),
222                width: inner_area.width,
223                height: 1,
224            };
225
226            let error_line = Line::from(Span::styled(error_msg, self.style.error));
227            error_line.render(error_area, buf);
228        }
229    }
230
231    fn render_submit_button(&self, area: Rect, buf: &mut Buffer) {
232        let is_focused = self.focus_manager.is_submit_focused();
233        let style = if is_focused {
234            self.style.button_focused
235        } else {
236            self.style.button
237        };
238
239        let text = if is_focused { "[ Submit ]" } else { "  Submit  " };
240
241        // Center the button
242        let button_width = text.len() as u16;
243        let x = area.x + (area.width.saturating_sub(button_width)) / 2;
244
245        for (i, c) in text.chars().enumerate() {
246            if x + (i as u16) < area.x + area.width {
247                buf[(x + i as u16, area.y)].set_char(c);
248                buf[(x + i as u16, area.y)].set_style(style);
249            }
250        }
251    }
252}
253
254/// Builder for creating forms.
255pub struct FormBuilder {
256    title: Option<String>,
257    fields: Vec<Box<dyn Field>>,
258    style: FormStyle,
259}
260
261impl FormBuilder {
262    /// Creates a new form builder.
263    pub fn new() -> Self {
264        Self {
265            title: None,
266            fields: Vec::new(),
267            style: FormStyle::default(),
268        }
269    }
270
271    /// Sets the form title.
272    pub fn title(mut self, title: impl Into<String>) -> Self {
273        self.title = Some(title.into());
274        self
275    }
276
277    /// Sets the form style.
278    pub fn style(mut self, style: FormStyle) -> Self {
279        self.style = style;
280        self
281    }
282
283    /// Starts building a text field.
284    pub fn text(self, id: impl Into<String>, label: impl Into<String>) -> TextFieldBuilder {
285        TextFieldBuilder::new(self, id.into(), label.into())
286    }
287
288    /// Starts building a select field.
289    pub fn select(self, id: impl Into<String>, label: impl Into<String>) -> SelectFieldBuilder {
290        SelectFieldBuilder::new(self, id.into(), label.into())
291    }
292
293    /// Starts building a checkbox field.
294    pub fn checkbox(self, id: impl Into<String>, label: impl Into<String>) -> CheckboxFieldBuilder {
295        CheckboxFieldBuilder::new(self, id.into(), label.into())
296    }
297
298    /// Adds a pre-built field.
299    pub fn field(mut self, field: Box<dyn Field>) -> Self {
300        self.fields.push(field);
301        self
302    }
303
304    /// Adds all fields from a block.
305    pub fn block(mut self, block: impl FormBlock) -> Self {
306        for field in block.fields() {
307            self.fields.push(field);
308        }
309        self
310    }
311
312    /// Builds the form.
313    pub fn build(self) -> Form {
314        let field_count = self.fields.len();
315        Form {
316            title: self.title,
317            fields: self.fields,
318            focus_manager: FocusManager::new(field_count),
319            style: self.style,
320            result: FormResult::Active,
321            validation_errors: Vec::new(),
322        }
323    }
324}
325
326impl Default for FormBuilder {
327    fn default() -> Self {
328        Self::new()
329    }
330}
331
332/// Builder for text fields.
333pub struct TextFieldBuilder {
334    form_builder: FormBuilder,
335    field: TextInput,
336}
337
338impl TextFieldBuilder {
339    fn new(form_builder: FormBuilder, id: String, label: String) -> Self {
340        Self {
341            form_builder,
342            field: TextInput::new(id, label),
343        }
344    }
345
346    /// Sets a placeholder.
347    pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
348        self.field = self.field.placeholder(placeholder);
349        self
350    }
351
352    /// Marks the field as required.
353    pub fn required(mut self) -> Self {
354        self.field = self.field.required();
355        self
356    }
357
358    /// Sets the initial value.
359    pub fn initial_value(mut self, value: impl Into<String>) -> Self {
360        self.field = self.field.initial_value(value);
361        self
362    }
363
364    /// Adds a validator.
365    pub fn validator(mut self, validator: Box<dyn crate::validation::Validator>) -> Self {
366        self.field = self.field.validator(validator);
367        self
368    }
369
370    /// Finishes building this field and returns to the form builder.
371    pub fn done(mut self) -> FormBuilder {
372        self.form_builder.fields.push(Box::new(self.field));
373        self.form_builder
374    }
375}
376
377/// Builder for select fields.
378pub struct SelectFieldBuilder {
379    form_builder: FormBuilder,
380    field: Select,
381}
382
383impl SelectFieldBuilder {
384    fn new(form_builder: FormBuilder, id: String, label: String) -> Self {
385        Self {
386            form_builder,
387            field: Select::new(id, label),
388        }
389    }
390
391    /// Adds an option.
392    pub fn option(mut self, value: impl Into<String>, display: impl Into<String>) -> Self {
393        self.field = self.field.option(value, display);
394        self
395    }
396
397    /// Adds multiple options.
398    pub fn options(mut self, options: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
399        self.field = self.field.options(options);
400        self
401    }
402
403    /// Marks the field as required.
404    pub fn required(mut self) -> Self {
405        self.field = self.field.required();
406        self
407    }
408
409    /// Sets the initial value.
410    pub fn initial_value(mut self, value: &str) -> Self {
411        self.field = self.field.initial_value(value);
412        self
413    }
414
415    /// Finishes building this field and returns to the form builder.
416    pub fn done(mut self) -> FormBuilder {
417        self.form_builder.fields.push(Box::new(self.field));
418        self.form_builder
419    }
420}
421
422/// Builder for checkbox fields.
423pub struct CheckboxFieldBuilder {
424    form_builder: FormBuilder,
425    field: Checkbox,
426}
427
428impl CheckboxFieldBuilder {
429    fn new(form_builder: FormBuilder, id: String, label: String) -> Self {
430        Self {
431            form_builder,
432            field: Checkbox::new(id, label),
433        }
434    }
435
436    /// Sets the initial checked state.
437    pub fn checked(mut self, checked: bool) -> Self {
438        self.field = self.field.checked(checked);
439        self
440    }
441
442    /// Marks the field as required (must be checked).
443    pub fn required(mut self) -> Self {
444        self.field = self.field.required();
445        self
446    }
447
448    /// Finishes building this field and returns to the form builder.
449    pub fn done(mut self) -> FormBuilder {
450        self.form_builder.fields.push(Box::new(self.field));
451        self.form_builder
452    }
453}