requestty_ui/
char_input.rs1use crate::{
2 backend::Backend,
3 events::{KeyCode, KeyEvent},
4 layout::Layout,
5};
6
7#[derive(Debug, Clone)]
17pub struct CharInput<F = super::widgets::FilterMapChar> {
18 value: Option<char>,
19 filter_map: F,
20}
21
22impl CharInput {
23 pub fn new() -> Self {
25 Self::with_filter_map(super::widgets::no_filter)
26 }
27}
28
29impl<F> CharInput<F>
30where
31 F: Fn(char) -> Option<char>,
32{
33 pub fn with_filter_map(filter_map: F) -> Self {
35 Self {
36 value: None,
37 filter_map,
38 }
39 }
40
41 pub fn value(&self) -> Option<char> {
43 self.value
44 }
45
46 pub fn set_value(&mut self, value: char) {
48 self.value = Some(value);
49 }
50
51 pub fn clear_value(&mut self) {
53 self.value = None;
54 }
55}
56
57impl<F> super::Widget for CharInput<F>
58where
59 F: Fn(char) -> Option<char>,
60{
61 fn handle_key(&mut self, key: KeyEvent) -> bool {
62 match key.code {
63 KeyCode::Char(c) => {
64 if let Some(c) = (self.filter_map)(c) {
65 self.value = Some(c);
66
67 return true;
68 }
69
70 false
71 }
72
73 KeyCode::Backspace | KeyCode::Delete if self.value.is_some() => {
74 self.value = None;
75 true
76 }
77
78 _ => false,
79 }
80 }
81
82 fn render<B: Backend>(&mut self, layout: &mut Layout, backend: &mut B) -> std::io::Result<()> {
83 if let Some(value) = self.value {
84 layout.line_offset += char_width(value);
85
86 write!(backend, "{}", value)?;
87 }
88 Ok(())
89 }
90
91 fn height(&mut self, layout: &mut Layout) -> u16 {
92 layout.line_offset += self.value.map(char_width).unwrap_or(0);
93 1
94 }
95
96 fn cursor_pos(&mut self, layout: Layout) -> (u16, u16) {
98 layout.offset_cursor((
99 layout.line_offset + self.value.map(char_width).unwrap_or(0),
100 0,
101 ))
102 }
103}
104
105impl Default for CharInput {
106 fn default() -> Self {
107 Self::new()
108 }
109}
110
111fn char_width(c: char) -> u16 {
112 let mut buf = [0u8; 4];
113 textwrap::core::display_width(c.encode_utf8(&mut buf)) as u16
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::{backend::TestBackend, events::KeyModifiers, Widget};
120
121 #[test]
122 fn test_cursor_pos() {
123 let layout = Layout::new(0, (100, 20).into());
124 let mut input = CharInput::default();
125
126 assert_eq!(input.cursor_pos(layout), (0, 0));
127 assert_eq!(input.cursor_pos(layout.with_line_offset(5)), (5, 0));
128
129 assert_eq!(input.cursor_pos(layout.with_offset(0, 3)), (0, 3));
130 assert_eq!(
131 input.cursor_pos(layout.with_offset(0, 3).with_line_offset(5)),
132 (5, 3)
133 );
134
135 input.set_value('c');
136
137 assert_eq!(input.cursor_pos(layout), (1, 0));
138 assert_eq!(input.cursor_pos(layout.with_line_offset(5)), (6, 0));
139
140 assert_eq!(input.cursor_pos(layout.with_offset(0, 3)), (1, 3));
141 assert_eq!(
142 input.cursor_pos(layout.with_offset(0, 3).with_line_offset(5)),
143 (6, 3)
144 );
145
146 input.set_value('🔥');
147
148 assert_eq!(input.cursor_pos(layout), (2, 0));
149 assert_eq!(input.cursor_pos(layout.with_line_offset(5)), (7, 0));
150
151 assert_eq!(input.cursor_pos(layout.with_offset(0, 3)), (2, 3));
152 assert_eq!(
153 input.cursor_pos(layout.with_offset(0, 3).with_line_offset(5)),
154 (7, 3)
155 );
156 }
157
158 #[test]
159 fn test_handle_key() {
160 let modifiers = KeyModifiers::empty();
161
162 let mut input = CharInput::default();
163 assert!(input.handle_key(KeyEvent::new(KeyCode::Char('c'), modifiers)));
164 assert_eq!(input.value(), Some('c'));
165 assert!(!input.handle_key(KeyEvent::new(KeyCode::Tab, modifiers)));
166 assert!(input.handle_key(KeyEvent::new(KeyCode::Char('d'), modifiers)));
167 assert_eq!(input.value(), Some('d'));
168 assert!(input.handle_key(KeyEvent::new(KeyCode::Backspace, modifiers)));
169 assert_eq!(input.value(), None);
170 assert!(input.handle_key(KeyEvent::new(KeyCode::Char('c'), modifiers)));
171 assert_eq!(input.value(), Some('c'));
172 assert!(input.handle_key(KeyEvent::new(KeyCode::Delete, modifiers)));
173 assert_eq!(input.value(), None);
174 assert!(!input.handle_key(KeyEvent::new(KeyCode::Delete, modifiers)));
175 assert!(!input.handle_key(KeyEvent::new(KeyCode::Backspace, modifiers)));
176
177 let mut input =
178 CharInput::with_filter_map(|c| if c.is_uppercase() { None } else { Some(c) });
179 assert!(!input.handle_key(KeyEvent::new(KeyCode::Char('C'), modifiers)));
180 assert_eq!(input.value(), None);
181 assert!(input.handle_key(KeyEvent::new(KeyCode::Char('c'), modifiers)));
182 assert_eq!(input.value(), Some('c'));
183 assert!(!input.handle_key(KeyEvent::new(KeyCode::Char('C'), modifiers)));
184 assert_eq!(input.value(), Some('c'));
185 }
186
187 #[test]
188 fn test_render() {
189 let size = (30, 10).into();
190 let mut layout = Layout::new(0, size);
191 let mut input = CharInput::default();
192
193 let mut backend = TestBackend::new(size);
194 input.render(&mut layout, &mut backend).unwrap();
195 assert_eq!(backend, TestBackend::new(size));
196
197 assert_eq!(layout, Layout::new(0, size));
198
199 input.set_value('c');
200
201 let mut backend = TestBackend::new(size);
202 input.render(&mut layout, &mut backend).unwrap();
203
204 crate::assert_backend_snapshot!(backend);
205
206 assert_eq!(layout, Layout::new(0, size).with_line_offset(1));
207 }
208}