mfform_lib/
input.rs

1use std::io;
2
3use crossterm::event::{Event, KeyCode};
4use log::debug;
5
6use crate::{
7    app::{EventHandlerResult, EventResult},
8    pos::Pos,
9};
10
11#[allow(dead_code)]
12#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
13pub enum Select {
14    None,
15    Single,
16    Multi,
17}
18
19/// Generic input field, supporting masked input (password) and number fields.
20///
21/// Also supports 'select'able fields, where the user can press F4 to get a list
22/// of predefined values.
23#[derive(Debug, Clone)]
24pub struct Input {
25    pub pos: Pos,
26    pub length: u16,
27    pub name: String,
28    pub value: String,
29    pub default_value: String,
30    pub allowed_characters: Option<Vec<char>>,
31    pub mask_char: Option<char>,
32    pub select: Select,
33    pub select_static: Vec<(String, String)>,
34}
35
36impl Ord for Input {
37    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
38        self.pos.cmp(&other.pos)
39    }
40}
41
42impl Eq for Input {}
43
44impl PartialEq for Input {
45    fn eq(&self, other: &Self) -> bool {
46        self.pos == other.pos // TODO: && self.widget_type == other.widget_type
47    }
48}
49
50impl PartialOrd for Input {
51    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
52        Some(self.cmp(other))
53    }
54}
55
56impl Input {
57    pub(crate) fn has_focus(&self, cursor: Pos) -> bool {
58        cursor.within(self.pos, self.length).is_some()
59    }
60
61    /// Create a InputBuilder for creating a new Input field.
62    pub fn builder(pos: impl Into<Pos>, length: u16, name: impl Into<String>) -> InputBuilder {
63        InputBuilder {
64            pos: pos.into(),
65            length,
66            name: name.into(),
67            value: Default::default(),
68            default_value: Default::default(),
69            allowed_characters: Default::default(),
70            mask_char: Default::default(),
71            select: Select::None,
72            select_static: Default::default(),
73        }
74    }
75
76    pub(crate) fn event_handler(
77        &mut self,
78        event: &Event,
79        current_pos: &mut Pos,
80    ) -> std::io::Result<EventHandlerResult> {
81        match event {
82            Event::Key(k) if k.code == KeyCode::Backspace => {
83                self.key_backspace(current_pos)?;
84            }
85            Event::Key(k) if k.code == KeyCode::Delete => {
86                self.key_delete(current_pos)?;
87            }
88            Event::Key(k) if k.modifiers.is_empty() => {
89                if let KeyCode::Char(c) = k.code {
90                    if let Some(ac) = &self.allowed_characters {
91                        if !ac.contains(&c) {
92                            debug!("{} is not an allowed character for input {}", c, self.name);
93                            return Ok(EventHandlerResult::Handled(EventResult::None));
94                        }
95                    }
96
97                    self.key(c, current_pos);
98                } else {
99                    return Ok(EventHandlerResult::NotHandled);
100                }
101            }
102            _ => return Ok(EventHandlerResult::NotHandled),
103        }
104
105        Ok(EventHandlerResult::Handled(EventResult::None))
106    }
107
108    pub(crate) fn key(&mut self, key: char, current_pos: &mut Pos) {
109        let str_pos = current_pos.x - self.pos.x;
110        *current_pos = current_pos.move_x(1, self.pos.x + self.length);
111
112        self.value = Self::set_char_in_string(&self.value, str_pos as usize, key);
113    }
114
115    pub(crate) fn set_char_in_string(s: &str, pos: usize, ch: char) -> String {
116        let mut s = s.to_string();
117
118        let len = s.chars().count();
119
120        if len <= pos {
121            let rep = " ".repeat(pos - len + 1);
122            s.push_str(&rep);
123        }
124
125        let mut output: String = s.chars().take(pos).collect();
126
127        output.push(ch);
128        output.extend(s.chars().skip(pos + 1));
129
130        output
131    }
132
133    pub(crate) fn delete_in_string(input: &str, pos: usize) -> String {
134        let input_len = input.chars().count();
135        if pos > input_len {
136            return input.to_string();
137        }
138
139        let mut output: String = input.chars().take(pos).collect();
140        output.extend(input.chars().skip(pos + 1));
141
142        output
143    }
144
145    pub(crate) fn key_backspace(&mut self, current_pos: &mut Pos) -> io::Result<()> {
146        let str_pos = current_pos.x - self.pos.x;
147
148        // No backspace at start of field
149        if str_pos == 0 {
150            return Ok(());
151        }
152
153        *current_pos = current_pos.move_x(-1, self.pos.x + self.length);
154
155        self.value = Self::delete_in_string(&self.value, (str_pos - 1) as usize);
156        Ok(())
157    }
158
159    fn key_delete(&mut self, current_pos: &Pos) -> io::Result<()> {
160        let str_pos = current_pos.x - self.pos.x;
161
162        self.value = Self::delete_in_string(&self.value, str_pos as usize);
163        Ok(())
164    }
165}
166
167pub struct InputBuilder {
168    pub pos: Pos,
169    pub length: u16,
170    pub name: String,
171    pub value: String,
172    pub default_value: String,
173    pub allowed_characters: Option<Vec<char>>,
174    pub mask_char: Option<char>,
175    pub select: Select,
176    pub select_static: Vec<(String, String)>,
177}
178
179impl InputBuilder {
180    pub fn with_value(mut self, value: impl Into<String>) -> Self {
181        self.value = value.into();
182
183        self
184    }
185
186    pub fn with_default_value(mut self, default_value: impl Into<String>) -> Self {
187        self.default_value = default_value.into();
188
189        self
190    }
191
192    pub fn with_allowed_characters(
193        mut self,
194        allowed_characters: impl IntoIterator<Item = char>,
195    ) -> Self {
196        self.allowed_characters = Some(allowed_characters.into_iter().collect());
197
198        self
199    }
200
201    pub fn with_mask_char(mut self, mask_char: char) -> Self {
202        self.mask_char = Some(mask_char);
203
204        self
205    }
206
207    #[allow(dead_code)]
208    pub fn with_select_static(mut self, select_static: &[(String, String)]) -> Self {
209        self.select_static = select_static.into();
210
211        self
212    }
213
214    pub fn build(self) -> Input {
215        Input {
216            pos: self.pos,
217            length: self.length,
218            name: self.name,
219            value: self.value,
220            default_value: self.default_value,
221            allowed_characters: self.allowed_characters,
222            mask_char: self.mask_char,
223            select: self.select,
224            select_static: self.select_static,
225        }
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn set_char_in_string_plain() {
235        let input = "1234567890";
236        let pos = 2;
237        let ch = 'a';
238        let expected = "12a4567890";
239
240        let output = Input::set_char_in_string(input, pos, ch);
241
242        assert_eq!(output, expected);
243    }
244
245    #[test]
246    fn set_char_in_string_nls() {
247        let input = "1æ34567890";
248        let pos = 2;
249        let ch = 'ö';
250        let expected = "1æö4567890";
251
252        let output = Input::set_char_in_string(input, pos, ch);
253
254        assert_eq!(output, expected);
255    }
256}