anathema_extras/
input.rs

1use std::ops::Deref;
2
3use anathema::component::*;
4use anathema::default_widgets::Overflow;
5use anathema::prelude::*;
6
7// -----------------------------------------------------------------------------
8//   - Change -
9// -----------------------------------------------------------------------------
10#[derive(Debug, Copy, Clone)]
11pub enum InputChange {
12    Insert(char, usize),
13    Remove(char, usize),
14}
15
16impl InputChange {
17    pub fn remove(character: char, pos: i32) -> Self {
18        Self::Remove(character, pos as usize)
19    }
20
21    pub fn insert(character: char, pos: i32) -> Self {
22        Self::Insert(character, pos as usize)
23    }
24}
25
26// -----------------------------------------------------------------------------
27//   - Text -
28// -----------------------------------------------------------------------------
29pub struct Text(String);
30
31impl Deref for Text {
32    type Target = str;
33
34    fn deref(&self) -> &Self::Target {
35        self.0.deref()
36    }
37}
38
39// -----------------------------------------------------------------------------
40//   - State -
41// -----------------------------------------------------------------------------
42#[derive(Debug, Default, State)]
43pub struct InputState {
44    text: Value<String>,
45    screen_cursor: Value<i32>,
46}
47
48impl InputState {
49    pub fn new() -> Self {
50        Self {
51            text: String::new().into(),
52            screen_cursor: 0.into(),
53        }
54    }
55
56    // Update visible cursor pos
57    fn update_cursor(&mut self, screen_cursor: i32) {
58        self.screen_cursor.set(screen_cursor);
59    }
60
61    fn char_count(&self) -> usize {
62        self.text.to_ref().chars().count()
63    }
64}
65
66// -----------------------------------------------------------------------------
67//   - Component -
68// -----------------------------------------------------------------------------
69#[derive(Debug, Default)]
70pub struct Input {
71    cursor: i32,
72}
73
74impl Input {
75    const ON_BLUR: &str = "on_blur";
76    const ON_CHANGE: &str = "on_change";
77    const ON_ENTER: &str = "on_enter";
78    const ON_FOCUS: &str = "on_focus";
79    pub const TEMPLATE: &'static str = include_str!("templates/input.aml");
80
81    pub fn new() -> Self {
82        Self { cursor: 0 }
83    }
84
85    pub fn template() -> SourceKind {
86        Self::TEMPLATE.to_template()
87    }
88
89    fn update_cursor(&mut self, state: &mut InputState, overflow: &mut Overflow, width: i32) {
90        let mut display_cursor = self.cursor - overflow.offset().x;
91
92        if display_cursor < 0 {
93            overflow.scroll_right_by(display_cursor);
94            display_cursor = 0;
95        }
96
97        if display_cursor >= width {
98            let offset = display_cursor + 1 - width;
99            overflow.scroll_right_by(offset);
100            display_cursor = width - 1;
101        }
102
103        state.update_cursor(display_cursor);
104    }
105
106    fn insert_char(&mut self, c: char, state: &mut InputState) {
107        if self.cursor == state.char_count() as i32 {
108            state.text.to_mut().push(c);
109        } else {
110            state.text.to_mut().insert(self.cursor as usize, c);
111        }
112        self.cursor += 1;
113    }
114}
115
116impl Component for Input {
117    type Message = ();
118    type State = InputState;
119
120    fn on_key(
121        &mut self,
122        key: KeyEvent,
123        state: &mut Self::State,
124        mut children: Children<'_, '_>,
125        mut context: Context<'_, '_, Self::State>,
126    ) {
127        children.elements().by_tag("overflow").first(|el, _| {
128            let width = el.size().width as i32;
129            let overflow = el.to::<Overflow>();
130
131            match key.code {
132                KeyCode::Char(c) => {
133                    self.insert_char(c, state);
134
135                    let change = InputChange::insert(c, self.cursor);
136                    context.publish(Self::ON_CHANGE, change);
137                }
138                KeyCode::Backspace if self.cursor > 0 => {
139                    self.cursor -= 1;
140                    let c = state.text.to_mut().remove(self.cursor as usize);
141
142                    let change = InputChange::remove(c, self.cursor);
143                    context.publish(Self::ON_CHANGE, change);
144                }
145                KeyCode::Enter if !state.text.to_ref().is_empty() => {
146                    let clear_on_enter = context
147                        .attributes
148                        .get_as::<bool>("clear_on_enter")
149                        .unwrap_or(true);
150                    let text = match clear_on_enter {
151                        true => {
152                            self.cursor = 0;
153                            std::mem::take(&mut *state.text.to_mut())
154                        }
155                        false => state.text.to_ref().clone(),
156                    };
157
158                    context.publish(Self::ON_ENTER, Text(text));
159                }
160                KeyCode::Left if self.cursor > 0 => self.cursor -= 1,
161                KeyCode::Right if state.char_count() as i32 > self.cursor => self.cursor += 1,
162                KeyCode::Home => self.cursor = 0,
163                KeyCode::End => self.cursor = state.char_count() as i32,
164                KeyCode::Delete if state.char_count() > 0 => {
165                    let c = state.text.to_mut().remove(self.cursor as usize);
166                    let change = InputChange::remove(c, self.cursor);
167                    context.publish(Self::ON_CHANGE, change);
168                    return;
169                }
170                _ => (),
171            }
172
173            self.update_cursor(state, overflow, width);
174        });
175    }
176
177    fn on_blur(
178        &mut self,
179        _: &mut Self::State,
180        _: Children<'_, '_>,
181        mut context: Context<'_, '_, Self::State>,
182    ) {
183        context.publish(Self::ON_BLUR, ());
184    }
185
186    fn on_focus(
187        &mut self,
188        _: &mut Self::State,
189        _: Children<'_, '_>,
190        mut context: Context<'_, '_, Self::State>,
191    ) {
192        context.publish(Self::ON_FOCUS, ());
193    }
194}