1use crossterm::event::KeyCode;
2use ratatui::{prelude::*, widgets::*};
3
4use crate::widget::Renderer;
5
6pub enum FieldStatus {
7 Valid,
8 Invalid,
9}
10
11impl Into<String> for Field<'_> {
12 fn into(self) -> String {
13 self.fd.val.to_string()
14 }
15}
16
17pub struct Field<'a> {
19 fd: FieldData<'a>,
20 status: FieldStatus,
21}
22
23impl<'a> Field<'a> {
24 pub fn valid(name: &'a str, val: &'a str) -> Field<'a> {
25 Self {
26 fd: FieldData { name, val },
27 status: FieldStatus::Valid,
28 }
29 }
30
31 pub fn invalid(name: &'a str, val: &'a str) -> Field<'a> {
32 Self {
33 fd: FieldData { name, val },
34 status: FieldStatus::Invalid,
35 }
36 }
37
38 pub fn name(&self) -> &str {
39 self.fd.name
40 }
41
42 pub fn value(&self) -> &str {
43 self.fd.val
44 }
45
46 fn inner(&self) -> &FieldData<'_> {
47 &self.fd
48 }
49
50 pub fn is_valid(&self) -> bool {
51 match self.status {
52 FieldStatus::Valid => true,
53 FieldStatus::Invalid => false,
54 }
55 }
56}
57
58#[derive(Clone)]
59struct FieldData<'a> {
60 name: &'a str,
61 val: &'a str,
62}
63
64type FormFieldStatus<'a> = Vec<Field<'a>>;
65#[derive(PartialEq)]
66pub enum FormSelection {
67 NoSelection,
68 Hovered(usize),
69 Active(usize),
70}
71
72pub(crate) struct FieldBuffer {
73 name: String,
74 val: String,
75}
76
77impl From<Vec<(&str, &str)>> for Form {
78 fn from(value: Vec<(&str, &str)>) -> Self {
79 Self {
80 fields: value
81 .into_iter()
82 .map(|(d_name, d_val)| FieldBuffer {
83 name: d_name.to_string(),
84 val: d_val.to_string(),
85 })
86 .collect(),
87 ..Default::default()
88 }
89 }
90}
91
92impl From<Vec<&str>> for Form {
93 fn from(value: Vec<&str>) -> Self {
94 Self {
95 fields: value
96 .into_iter()
97 .map(|d_name| FieldBuffer {
98 name: d_name.to_string(),
99 val: String::new(),
100 })
101 .collect(),
102 ..Default::default()
103 }
104 }
105}
106
107impl From<Vec<FieldBuffer>> for Form {
108 fn from(value: Vec<FieldBuffer>) -> Self {
109 Self {
110 fields: value,
111 ..Default::default()
112 }
113 }
114}
115
116pub struct Form {
135 selected: FormSelection,
136 pub(crate) fields: Vec<FieldBuffer>,
137 pub(crate) submitted: bool,
138 validation_fn: Box<dyn Fn(&str) -> bool + 'static>,
139 pub(crate) default_field_style: Style,
140 pub(crate) invalid_field_style: Style,
141 pub(crate) hovered_field_style: Style,
142 pub(crate) active_field_style: Style,
143}
144
145impl Default for Form {
146 fn default() -> Self {
147 Self {
148 selected: FormSelection::NoSelection,
149 fields: Vec::new(),
150 submitted: false,
151 validation_fn: Box::new(|f| !f.is_empty()),
152 default_field_style: Style::default(),
153 invalid_field_style: Style::default().red().bold(),
154 hovered_field_style: Style::default().cyan(),
155 active_field_style: Style::default().cyan().bold(),
156 }
157 }
158}
159
160impl Form {
161 pub fn new(fields: &[&str], validation_fn: impl Fn(&str) -> bool + 'static) -> Self {
164 let fields = fields
165 .iter()
166 .map(|&title| FieldBuffer {
167 name: title.to_string(),
168 val: String::new(),
169 })
170 .collect();
171
172 Self {
173 fields,
174 validation_fn: Box::new(validation_fn),
175 ..Default::default()
176 }
177 }
178
179 pub fn widget(&self) -> impl Widget + '_ {
180 Renderer::new(self)
181 }
182
183 pub fn select(&mut self, s: FormSelection) {
184 self.selected = s;
185 }
186
187 pub fn selected(&self) -> &FormSelection {
188 &self.selected
189 }
190
191 pub fn submit(&mut self) -> FormFieldStatus {
193 self.submitted = true;
194 self.status()
195 }
196
197 pub fn status(&self) -> FormFieldStatus {
200 if self.submitted {
201 self.fields
202 .iter()
203 .map(|fb| {
204 if (self.validation_fn)(&fb.val) {
205 Field::valid(&fb.name, &fb.val)
206 } else {
207 Field::invalid(&fb.name, &fb.val)
208 }
209 })
210 .collect()
211 } else {
212 self.fields
213 .iter()
214 .map(|fb| Field::valid(&fb.name, &fb.val))
215 .collect()
216 }
217 }
218
219 pub fn input(&mut self, key: KeyCode) {
220 if let FormSelection::Active(i) = self.selected {
221 match key {
222 KeyCode::Enter => self.next_field(),
223 KeyCode::Esc => self.select(FormSelection::Hovered(i)),
224 KeyCode::Backspace => self.pop_field(i),
225 KeyCode::Char(ch) => self.append_field(ch, i),
226 _ => {}
227 }
228 } else {
229 match key {
230 KeyCode::Esc => self.select(FormSelection::NoSelection),
231 KeyCode::Char('j') => self.next_field(),
232 KeyCode::Char('k') => self.prev_field(),
233 KeyCode::Enter => {
234 if let FormSelection::Hovered(i) = self.selected {
235 self.selected = FormSelection::Active(i)
236 } else {
237 self.selected = FormSelection::Active(0)
238 }
239 }
240 _ => {}
241 }
242 }
243 }
244
245 fn pop_field(&mut self, field: usize) {
246 self.fields[field].val.pop();
247 }
248
249 fn append_field(&mut self, ch: char, field: usize) {
250 self.fields[field].val.push(ch)
251 }
252
253 pub fn append_selection(&mut self, ch: char) {
254 match self.selected() {
255 FormSelection::NoSelection => {}
256 FormSelection::Hovered(_) => {}
257 FormSelection::Active(i) => self.append_field(ch, *i),
258 }
259 }
260
261 pub fn pop_selection(&mut self) {
262 match self.selected() {
263 FormSelection::NoSelection => {}
264 FormSelection::Hovered(_) => {}
265 FormSelection::Active(i) => self.pop_field(*i),
266 }
267 }
268
269 pub fn deselect(&mut self) {
270 self.selected = FormSelection::NoSelection
271 }
272
273 pub fn next_field(&mut self) {
274 self.selected = match self.selected {
275 FormSelection::NoSelection => FormSelection::Hovered(0),
276 FormSelection::Hovered(i) => {
277 FormSelection::Hovered((i + 1).rem_euclid(self.fields.len()))
278 }
279 FormSelection::Active(i) => {
280 FormSelection::Active((i + 1).rem_euclid(self.fields.len()))
281 }
282 }
283 }
284
285 pub fn prev_field(&mut self) {
286 self.selected = match self.selected {
287 FormSelection::NoSelection => FormSelection::Hovered(0),
288 FormSelection::Hovered(i) => {
289 let i = if i == 0 { self.fields.len() - 1 } else { i - 1 };
290 FormSelection::Hovered(i)
291 }
292 FormSelection::Active(i) => {
293 let i = if i == 0 { self.fields.len() - 1 } else { i - 1 };
294 FormSelection::Active(i)
295 }
296 }
297 }
298
299 pub fn submitted(&mut self, submitted: bool) {
301 self.submitted = submitted;
302 }
303
304 pub fn active_field_style(&mut self, style: Style) {
305 self.active_field_style = style;
306 }
307
308 pub fn invalid_field_style(&mut self, style: Style) {
309 self.invalid_field_style = style;
310 }
311
312 pub fn hovered_field_style(&mut self, style: Style) {
313 self.hovered_field_style = style;
314 }
315
316 pub fn default_field_style(&mut self, style: Style) {
317 self.default_field_style = style;
318 }
319}