ratatui_form/field/
text.rs1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
4use ratatui::buffer::Buffer;
5use ratatui::layout::Rect;
6use ratatui::style::{Color, Modifier, Style};
7use ratatui::text::{Line, Span};
8use ratatui::widgets::Widget;
9use serde_json::Value;
10use unicode_width::UnicodeWidthStr;
11
12use crate::field::Field;
13use crate::style::FormStyle;
14use crate::validation::{ValidationError, Validator};
15
16pub struct TextInput {
18 id: String,
19 label: String,
20 value: String,
21 cursor_position: usize,
22 placeholder: Option<String>,
23 required: bool,
24 validators: Vec<Box<dyn Validator>>,
25 validation_errors: Vec<ValidationError>,
26}
27
28impl TextInput {
29 pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
31 Self {
32 id: id.into(),
33 label: label.into(),
34 value: String::new(),
35 cursor_position: 0,
36 placeholder: None,
37 required: false,
38 validators: Vec::new(),
39 validation_errors: Vec::new(),
40 }
41 }
42
43 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
45 self.placeholder = Some(placeholder.into());
46 self
47 }
48
49 pub fn required(mut self) -> Self {
51 self.required = true;
52 self
53 }
54
55 pub fn validator(mut self, validator: Box<dyn Validator>) -> Self {
57 self.validators.push(validator);
58 self
59 }
60
61 pub fn initial_value(mut self, value: impl Into<String>) -> Self {
63 self.value = value.into();
64 self.cursor_position = self.value.len();
65 self
66 }
67
68 fn insert_char(&mut self, c: char) {
69 self.value.insert(self.cursor_position, c);
70 self.cursor_position += c.len_utf8();
71 }
72
73 fn delete_char_before_cursor(&mut self) {
74 if self.cursor_position > 0 {
75 let prev_char_boundary = self.value[..self.cursor_position]
76 .char_indices()
77 .last()
78 .map(|(i, _)| i)
79 .unwrap_or(0);
80 self.value.remove(prev_char_boundary);
81 self.cursor_position = prev_char_boundary;
82 }
83 }
84
85 fn delete_char_at_cursor(&mut self) {
86 if self.cursor_position < self.value.len() {
87 self.value.remove(self.cursor_position);
88 }
89 }
90
91 fn move_cursor_left(&mut self) {
92 if self.cursor_position > 0 {
93 self.cursor_position = self.value[..self.cursor_position]
94 .char_indices()
95 .last()
96 .map(|(i, _)| i)
97 .unwrap_or(0);
98 }
99 }
100
101 fn move_cursor_right(&mut self) {
102 if self.cursor_position < self.value.len() {
103 self.cursor_position = self.value[self.cursor_position..]
104 .char_indices()
105 .nth(1)
106 .map(|(i, _)| self.cursor_position + i)
107 .unwrap_or(self.value.len());
108 }
109 }
110
111 fn move_cursor_home(&mut self) {
112 self.cursor_position = 0;
113 }
114
115 fn move_cursor_end(&mut self) {
116 self.cursor_position = self.value.len();
117 }
118}
119
120impl Field for TextInput {
121 fn id(&self) -> &str {
122 &self.id
123 }
124
125 fn label(&self) -> &str {
126 &self.label
127 }
128
129 fn render(&self, area: Rect, buf: &mut Buffer, focused: bool, style: &FormStyle) {
130 if area.height < 1 || area.width < 1 {
131 return;
132 }
133
134 let label_style = if focused {
136 style.label_focused
137 } else {
138 style.label
139 };
140
141 let required_marker = if self.required { "*" } else { "" };
142 let label_text = format!("{}{}: ", self.label, required_marker);
143 let label_width = label_text.width().min(area.width as usize);
144
145 let label_span = Span::styled(&label_text, label_style);
146 let label_line = Line::from(label_span);
147 let label_area = Rect {
148 x: area.x,
149 y: area.y,
150 width: label_width as u16,
151 height: 1,
152 };
153 label_line.render(label_area, buf);
154
155 let input_x = area.x + label_width as u16;
157 let input_width = area.width.saturating_sub(label_width as u16);
158
159 if input_width == 0 {
160 return;
161 }
162
163 let (display_text, display_style) = if self.value.is_empty() {
165 if let Some(ref placeholder) = self.placeholder {
166 (placeholder.as_str(), style.placeholder)
167 } else {
168 ("", style.input)
169 }
170 } else {
171 (self.value.as_str(), style.input)
172 };
173
174 let input_bg_style = if focused {
176 style.input_focused
177 } else {
178 style.input
179 };
180
181 for x in input_x..input_x + input_width {
183 buf[(x, area.y)].set_style(input_bg_style);
184 buf[(x, area.y)].set_char(' ');
185 }
186
187 let visible_text: String = display_text.chars().take(input_width as usize).collect();
189 for (i, c) in visible_text.chars().enumerate() {
190 if input_x + i as u16 >= area.x + area.width {
191 break;
192 }
193 buf[(input_x + i as u16, area.y)].set_char(c);
194 buf[(input_x + i as u16, area.y)].set_style(display_style);
195 }
196
197 if focused {
199 let cursor_x = input_x + self.value[..self.cursor_position].width() as u16;
200 if cursor_x < area.x + area.width {
201 buf[(cursor_x, area.y)].set_style(
202 Style::default()
203 .bg(Color::White)
204 .fg(Color::Black)
205 .add_modifier(Modifier::SLOW_BLINK),
206 );
207 }
208 }
209
210 if !self.validation_errors.is_empty() && area.height > 1 {
212 let error_msg = &self.validation_errors[0].message;
213 let error_span = Span::styled(error_msg, style.error);
214 let error_line = Line::from(error_span);
215 let error_area = Rect {
216 x: input_x,
217 y: area.y + 1,
218 width: input_width,
219 height: 1,
220 };
221 error_line.render(error_area, buf);
222 }
223 }
224
225 fn handle_input(&mut self, event: &KeyEvent) -> bool {
226 match event.code {
227 KeyCode::Char(c) => {
228 if event.modifiers.contains(KeyModifiers::CONTROL) {
229 match c {
230 'a' => self.move_cursor_home(),
231 'e' => self.move_cursor_end(),
232 'u' => {
233 self.value.clear();
234 self.cursor_position = 0;
235 }
236 _ => return false,
237 }
238 } else {
239 self.insert_char(c);
240 }
241 true
242 }
243 KeyCode::Backspace => {
244 self.delete_char_before_cursor();
245 true
246 }
247 KeyCode::Delete => {
248 self.delete_char_at_cursor();
249 true
250 }
251 KeyCode::Left => {
252 self.move_cursor_left();
253 true
254 }
255 KeyCode::Right => {
256 self.move_cursor_right();
257 true
258 }
259 KeyCode::Home => {
260 self.move_cursor_home();
261 true
262 }
263 KeyCode::End => {
264 self.move_cursor_end();
265 true
266 }
267 _ => false,
268 }
269 }
270
271 fn value(&self) -> Value {
272 Value::String(self.value.clone())
273 }
274
275 fn validate(&self) -> Result<(), Vec<ValidationError>> {
276 let mut errors = Vec::new();
277
278 if self.required && self.value.trim().is_empty() {
280 errors.push(ValidationError {
281 field_id: self.id.clone(),
282 message: format!("{} is required", self.label),
283 });
284 }
285
286 for validator in &self.validators {
288 if let Err(msg) = validator.validate(&self.value) {
289 errors.push(ValidationError {
290 field_id: self.id.clone(),
291 message: msg,
292 });
293 }
294 }
295
296 if errors.is_empty() {
297 Ok(())
298 } else {
299 Err(errors)
300 }
301 }
302
303 fn height(&self) -> u16 {
304 if self.validation_errors.is_empty() {
305 1
306 } else {
307 2
308 }
309 }
310
311 fn is_required(&self) -> bool {
312 self.required
313 }
314}