1use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
2use crate::event::Event;
3use crate::geom::{Rect, Size};
4use crate::layout::Constraint;
5use crate::render::RenderCx;
6use crate::style::Style;
7use crate::text::Text;
8
9pub struct Input {
15 text: String,
16 cursor_byte: usize,
18 focused: bool,
19 placeholder: Text,
20 style: Style,
21 focus_style: Style,
22 on_submit: Option<Box<dyn FnMut(&str)>>,
24}
25
26impl Input {
27 pub fn new() -> Self {
29 Self {
30 text: String::new(),
31 cursor_byte: 0,
32 focused: false,
33 placeholder: Text::from(""),
34 style: Style::default(),
35 focus_style: Style::default()
36 .bg(crate::style::Color::White)
37 .fg(crate::style::Color::Black),
38 on_submit: None,
39 }
40 }
41
42 pub fn placeholder(mut self, text: impl Into<Text>) -> Self {
43 self.placeholder = text.into();
44 self
45 }
46
47 pub fn style(mut self, style: Style) -> Self {
48 self.style = style;
49 self
50 }
51
52 pub fn focus_style(mut self, style: Style) -> Self {
53 self.focus_style = style;
54 self
55 }
56
57 pub fn on_submit(mut self, f: impl FnMut(&str) + 'static) -> Self {
60 self.on_submit = Some(Box::new(f));
61 self
62 }
63
64 pub fn text(&self) -> &str {
65 &self.text
66 }
67
68 fn clamp_cursor(&mut self) {
69 if self.cursor_byte > self.text.len() {
70 self.cursor_byte = self.text.len();
71 }
72 }
73
74 fn cursor_left(&mut self) {
75 if self.cursor_byte == 0 {
76 return;
77 }
78 if let Some((i, _)) = self.text.char_indices().rev().find(|&(i, _)| i < self.cursor_byte) {
80 self.cursor_byte = i;
81 } else {
82 self.cursor_byte = 0;
83 }
84 }
85
86 fn cursor_right(&mut self) {
87 if let Some((i, _)) = self
89 .text
90 .char_indices()
91 .find(|&(i, _)| i > self.cursor_byte)
92 {
93 self.cursor_byte = i;
94 } else {
95 self.cursor_byte = self.text.len();
96 }
97 }
98
99 fn cursor_char_index(&self) -> usize {
101 self.text[..self.cursor_byte].chars().count()
102 }
103}
104
105impl Component for Input {
106 fn render(&self, cx: &mut RenderCx) {
107 let placeholder_mode = self.text.is_empty() && !self.focused;
108 let display_str: String = if placeholder_mode {
109 self.placeholder.first_text().to_string()
110 } else {
111 self.text.clone()
112 };
113
114 let base_style = if placeholder_mode {
115 Style::default().fg(crate::style::Color::Gray)
116 } else {
117 Style::default()
118 };
119
120 let cursor_style = Style::default()
121 .bg(crate::style::Color::White)
122 .fg(crate::style::Color::Black);
123
124 let cursor_char = if placeholder_mode {
125 0
126 } else {
127 self.cursor_char_index()
128 };
129 for (i, ch) in display_str.chars().enumerate() {
130 if i == cursor_char && self.focused {
131 cx.set_style(cursor_style.clone());
132 } else {
133 cx.set_style(base_style.clone());
134 }
135 cx.text(ch.to_string());
136 }
137
138 if self.cursor_byte >= self.text.len() && self.focused {
139 cx.set_style(cursor_style);
140 cx.text(" ");
141 }
142 }
143
144 fn measure(&self, _constraint: Constraint, _cx: &mut MeasureCx) -> Size {
145 let display_str: String = if self.text.is_empty() {
146 self.placeholder.first_text().to_string()
147 } else {
148 self.text.clone()
149 };
150 let width: u16 = display_str
151 .chars()
152 .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u16)
153 .sum();
154 Size {
155 width: (width + 1),
156 height: 1,
157 }
158 }
159
160 fn event(&mut self, event: &Event, cx: &mut EventCx) {
161 match event {
162 Event::Focus => {
163 self.focused = true;
164 cx.invalidate_paint();
165 return;
166 }
167 Event::Blur => {
168 self.focused = false;
169 cx.invalidate_paint();
170 return;
171 }
172 _ => {}
173 }
174
175 if cx.phase() != crate::event::EventPhase::Target {
176 return;
177 }
178
179 if let Event::Key(key_event) = event {
180 if key_event.key == crate::event::Key::Char('c') && key_event.modifiers.ctrl {
182 cx.quit();
183 return;
184 }
185 if key_event.key == crate::event::Key::Char('d') && key_event.modifiers.ctrl {
187 if self.cursor_byte < self.text.len() {
188 let end = self.text[self.cursor_byte..]
189 .chars()
190 .next()
191 .map(|c| self.cursor_byte + c.len_utf8())
192 .unwrap_or(self.cursor_byte);
193 self.text.drain(self.cursor_byte..end);
194 cx.invalidate_paint();
195 }
196 return;
197 }
198 if key_event.modifiers.ctrl || key_event.modifiers.alt {
199 return;
200 }
201
202 self.clamp_cursor();
203
204 match &key_event.key {
205 crate::event::Key::Enter => {
206 if let Some(ref mut f) = self.on_submit {
207 f(&self.text);
208 }
209 }
210 crate::event::Key::Char(ch) => {
211 self.text.insert(self.cursor_byte, *ch);
212 self.cursor_byte += ch.len_utf8();
213 cx.invalidate_paint();
214 }
215 crate::event::Key::Backspace => {
216 if self.cursor_byte > 0 {
217 let old = self.cursor_byte;
218 self.cursor_left();
219 self.text.drain(self.cursor_byte..old);
220 cx.invalidate_paint();
221 }
222 }
223 crate::event::Key::Delete => {
224 if self.cursor_byte < self.text.len() {
225 let end = self.text[self.cursor_byte..]
226 .chars()
227 .next()
228 .map(|c| self.cursor_byte + c.len_utf8())
229 .unwrap_or(self.cursor_byte);
230 self.text.drain(self.cursor_byte..end);
231 cx.invalidate_paint();
232 }
233 }
234 crate::event::Key::Left => {
235 let old = self.cursor_byte;
236 self.cursor_left();
237 if self.cursor_byte != old {
238 cx.invalidate_paint();
239 }
240 }
241 crate::event::Key::Right => {
242 let old = self.cursor_byte;
243 self.cursor_right();
244 if self.cursor_byte != old {
245 cx.invalidate_paint();
246 }
247 }
248 crate::event::Key::Home => {
249 if self.cursor_byte != 0 {
250 self.cursor_byte = 0;
251 cx.invalidate_paint();
252 }
253 }
254 crate::event::Key::End => {
255 if self.cursor_byte != self.text.len() {
256 self.cursor_byte = self.text.len();
257 cx.invalidate_paint();
258 }
259 }
260 _ => {}
261 }
262 }
263 }
264
265 fn layout(&mut self, _rect: Rect, _cx: &mut LayoutCx) {}
266
267 fn style(&self) -> Style {
268 self.style.clone()
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 }
276