use std::fs::File;
use std::io::{self, Write};
use std::path::Path;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Padding, Widget};
use serde_json::{Map, Value};
use crate::block::Block as FormBlock;
use crate::field::{Checkbox, Field, Select, TextInput};
use crate::navigation::FocusManager;
use crate::style::FormStyle;
use crate::validation::ValidationError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FormResult {
Submitted,
Cancelled,
Active,
}
pub struct Form {
title: Option<String>,
fields: Vec<Box<dyn Field>>,
focus_manager: FocusManager,
style: FormStyle,
result: FormResult,
validation_errors: Vec<ValidationError>,
}
impl Form {
pub fn builder() -> FormBuilder {
FormBuilder::new()
}
pub fn result(&self) -> &FormResult {
&self.result
}
pub fn is_active(&self) -> bool {
self.result == FormResult::Active
}
pub fn handle_input(&mut self, event: KeyEvent) {
match event.code {
KeyCode::Esc => {
self.result = FormResult::Cancelled;
return;
}
KeyCode::Tab => {
if event.modifiers.contains(KeyModifiers::SHIFT) {
self.focus_manager.focus_previous();
} else {
self.focus_manager.focus_next();
}
return;
}
KeyCode::Enter if self.focus_manager.is_submit_focused() => {
self.try_submit();
return;
}
KeyCode::Down => {
if !self.delegate_to_focused_field(&event) {
self.focus_manager.focus_next();
}
return;
}
KeyCode::Up => {
if !self.delegate_to_focused_field(&event) {
self.focus_manager.focus_previous();
}
return;
}
_ => {}
}
self.delegate_to_focused_field(&event);
}
fn delegate_to_focused_field(&mut self, event: &KeyEvent) -> bool {
if self.focus_manager.is_submit_focused() {
return false;
}
let index = self.focus_manager.current_index();
if let Some(field) = self.fields.get_mut(index) {
field.handle_input(event)
} else {
false
}
}
fn try_submit(&mut self) {
self.validation_errors.clear();
for field in &self.fields {
if let Err(errors) = field.validate() {
self.validation_errors.extend(errors);
}
}
if self.validation_errors.is_empty() {
self.result = FormResult::Submitted;
} else {
if let Some(error) = self.validation_errors.first() {
for (i, field) in self.fields.iter().enumerate() {
if field.id() == error.field_id {
self.focus_manager.focus_field(i);
break;
}
}
}
}
}
pub fn to_json(&self) -> Value {
let mut map = Map::new();
for field in &self.fields {
map.insert(field.id().to_string(), field.value());
}
Value::Object(map)
}
pub fn write_json(&self, path: impl AsRef<Path>) -> io::Result<()> {
let json = self.to_json();
let mut file = File::create(path)?;
let formatted = serde_json::to_string_pretty(&json)?;
file.write_all(formatted.as_bytes())?;
Ok(())
}
pub fn validation_errors(&self) -> &[ValidationError] {
&self.validation_errors
}
pub fn render(&self, area: Rect, buf: &mut Buffer) {
let border_style = if self.focus_manager.is_submit_focused() {
self.style.border
} else {
self.style.border_focused
};
let mut block = Block::default()
.borders(Borders::ALL)
.border_style(border_style)
.padding(Padding::horizontal(1));
if let Some(ref title) = self.title {
block = block.title(Span::styled(title, self.style.title));
}
let inner_area = block.inner(area);
block.render(area, buf);
if inner_area.height < 2 || inner_area.width < 10 {
return;
}
let field_count = self.fields.len();
let mut constraints = Vec::with_capacity(field_count + 2);
for field in &self.fields {
constraints.push(Constraint::Length(field.height()));
}
constraints.push(Constraint::Length(1)); constraints.push(Constraint::Length(1)); constraints.push(Constraint::Min(0));
let layout = Layout::vertical(constraints).split(inner_area);
for (i, field) in self.fields.iter().enumerate() {
let is_focused = !self.focus_manager.is_submit_focused()
&& i == self.focus_manager.current_index();
field.render(layout[i], buf, is_focused, &self.style);
}
let submit_idx = field_count + 1;
if submit_idx < layout.len() {
self.render_submit_button(layout[submit_idx], buf);
}
if !self.validation_errors.is_empty() {
let error_count = self.validation_errors.len();
let error_msg = if error_count == 1 {
"1 validation error".to_string()
} else {
format!("{} validation errors", error_count)
};
let error_area = Rect {
x: inner_area.x,
y: inner_area.y + inner_area.height.saturating_sub(1),
width: inner_area.width,
height: 1,
};
let error_line = Line::from(Span::styled(error_msg, self.style.error));
error_line.render(error_area, buf);
}
}
fn render_submit_button(&self, area: Rect, buf: &mut Buffer) {
let is_focused = self.focus_manager.is_submit_focused();
let style = if is_focused {
self.style.button_focused
} else {
self.style.button
};
let text = if is_focused { "[ Submit ]" } else { " Submit " };
let button_width = text.len() as u16;
let x = area.x + (area.width.saturating_sub(button_width)) / 2;
for (i, c) in text.chars().enumerate() {
if x + (i as u16) < area.x + area.width {
buf[(x + i as u16, area.y)].set_char(c);
buf[(x + i as u16, area.y)].set_style(style);
}
}
}
}
pub struct FormBuilder {
title: Option<String>,
fields: Vec<Box<dyn Field>>,
style: FormStyle,
}
impl FormBuilder {
pub fn new() -> Self {
Self {
title: None,
fields: Vec::new(),
style: FormStyle::default(),
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn style(mut self, style: FormStyle) -> Self {
self.style = style;
self
}
pub fn text(self, id: impl Into<String>, label: impl Into<String>) -> TextFieldBuilder {
TextFieldBuilder::new(self, id.into(), label.into())
}
pub fn select(self, id: impl Into<String>, label: impl Into<String>) -> SelectFieldBuilder {
SelectFieldBuilder::new(self, id.into(), label.into())
}
pub fn checkbox(self, id: impl Into<String>, label: impl Into<String>) -> CheckboxFieldBuilder {
CheckboxFieldBuilder::new(self, id.into(), label.into())
}
pub fn field(mut self, field: Box<dyn Field>) -> Self {
self.fields.push(field);
self
}
pub fn block(mut self, block: impl FormBlock) -> Self {
for field in block.fields() {
self.fields.push(field);
}
self
}
pub fn build(self) -> Form {
let field_count = self.fields.len();
Form {
title: self.title,
fields: self.fields,
focus_manager: FocusManager::new(field_count),
style: self.style,
result: FormResult::Active,
validation_errors: Vec::new(),
}
}
}
impl Default for FormBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct TextFieldBuilder {
form_builder: FormBuilder,
field: TextInput,
}
impl TextFieldBuilder {
fn new(form_builder: FormBuilder, id: String, label: String) -> Self {
Self {
form_builder,
field: TextInput::new(id, label),
}
}
pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
self.field = self.field.placeholder(placeholder);
self
}
pub fn required(mut self) -> Self {
self.field = self.field.required();
self
}
pub fn initial_value(mut self, value: impl Into<String>) -> Self {
self.field = self.field.initial_value(value);
self
}
pub fn validator(mut self, validator: Box<dyn crate::validation::Validator>) -> Self {
self.field = self.field.validator(validator);
self
}
pub fn done(mut self) -> FormBuilder {
self.form_builder.fields.push(Box::new(self.field));
self.form_builder
}
}
pub struct SelectFieldBuilder {
form_builder: FormBuilder,
field: Select,
}
impl SelectFieldBuilder {
fn new(form_builder: FormBuilder, id: String, label: String) -> Self {
Self {
form_builder,
field: Select::new(id, label),
}
}
pub fn option(mut self, value: impl Into<String>, display: impl Into<String>) -> Self {
self.field = self.field.option(value, display);
self
}
pub fn options(mut self, options: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
self.field = self.field.options(options);
self
}
pub fn required(mut self) -> Self {
self.field = self.field.required();
self
}
pub fn initial_value(mut self, value: &str) -> Self {
self.field = self.field.initial_value(value);
self
}
pub fn done(mut self) -> FormBuilder {
self.form_builder.fields.push(Box::new(self.field));
self.form_builder
}
}
pub struct CheckboxFieldBuilder {
form_builder: FormBuilder,
field: Checkbox,
}
impl CheckboxFieldBuilder {
fn new(form_builder: FormBuilder, id: String, label: String) -> Self {
Self {
form_builder,
field: Checkbox::new(id, label),
}
}
pub fn checked(mut self, checked: bool) -> Self {
self.field = self.field.checked(checked);
self
}
pub fn required(mut self) -> Self {
self.field = self.field.required();
self
}
pub fn done(mut self) -> FormBuilder {
self.form_builder.fields.push(Box::new(self.field));
self.form_builder
}
}