tty_form/control/
textinput.rs

1use crossterm::event::{KeyCode, KeyEvent};
2use tty_text::Key;
3
4use crate::{
5    dependency::{Action, DependencyId, Evaluation},
6    step::CompoundStep,
7    style::help_style,
8    text::{DrawerContents, Segment, Text},
9};
10
11use super::Control;
12
13/// A single-line text field input. May be used as an evaluation for dependent form elements.
14///
15/// # Examples
16/// ```
17/// use tty_form::{
18///     step::CompoundStep,
19///     control::{Control, TextInput},
20/// };
21///
22/// let mut step = CompoundStep::new();
23/// TextInput::new("Enter your name:", false).add_to(&mut step);
24/// ```
25pub struct TextInput {
26    prompt: String,
27    text: tty_text::Text,
28    force_lowercase: bool,
29    evaluation: Option<(DependencyId, Evaluation)>,
30}
31
32impl TextInput {
33    /// Create a new text input control with the specified prompt and casing-rules.
34    pub fn new(prompt: &str, force_lowercase: bool) -> Self {
35        Self {
36            prompt: prompt.to_string(),
37            text: tty_text::Text::new(false),
38            force_lowercase,
39            evaluation: None,
40        }
41    }
42
43    /// Update this input's prompt text.
44    pub fn set_prompt(&mut self, prompt: &str) {
45        self.prompt = prompt.to_string();
46    }
47
48    /// Specify whether this input should force its value to be lowercase.
49    pub fn set_force_lowercase(&mut self, force: bool) {
50        self.force_lowercase = force;
51    }
52
53    /// Sets the dependency evaluation which other form elements can react to.
54    pub fn set_evaluation(&mut self, evaluation: Evaluation) -> DependencyId {
55        let id = DependencyId::new();
56        self.evaluation = Some((id, evaluation));
57        id
58    }
59}
60
61impl Control for TextInput {
62    fn focusable(&self) -> bool {
63        true
64    }
65
66    fn update(&mut self, input: KeyEvent) {
67        match input.code {
68            KeyCode::Char(mut ch) => {
69                if self.force_lowercase {
70                    ch = ch.to_lowercase().next().unwrap();
71                }
72
73                self.text.handle_input(Key::Char(ch));
74            }
75            KeyCode::Backspace => self.text.handle_input(Key::Backspace),
76            KeyCode::Left => self.text.handle_input(Key::Left),
77            KeyCode::Right => self.text.handle_input(Key::Right),
78            _ => {}
79        };
80    }
81
82    fn help(&self) -> Option<Segment> {
83        Some(Text::new_styled(self.prompt.clone(), help_style()).as_segment())
84    }
85
86    fn text(&self) -> (Segment, Option<u16>) {
87        let segment = Text::new(self.text.value()).as_segment();
88        let cursor_column = self.text.cursor().0 as u16;
89
90        (segment, Some(cursor_column))
91    }
92
93    fn drawer(&self) -> Option<DrawerContents> {
94        None
95    }
96
97    fn evaluation(&self) -> Option<(DependencyId, Evaluation)> {
98        self.evaluation.clone()
99    }
100
101    fn dependency(&self) -> Option<(DependencyId, Action)> {
102        None
103    }
104
105    fn evaluate(&self, evaluation: &Evaluation) -> bool {
106        match evaluation {
107            Evaluation::Equal(value) => &self.text.value() == value,
108            Evaluation::NotEqual(value) => &self.text.value() != value,
109            Evaluation::IsEmpty => self.text.value().is_empty(),
110        }
111    }
112
113    fn add_to(self, step: &mut CompoundStep) {
114        step.add_control(Box::new(self))
115    }
116}