titik/widget/
text_input.rs

1use crate::Event;
2use crate::{buffer::Buffer, Cmd, InputBuffer, Widget};
3use crossterm::event::{KeyEvent, MouseEvent};
4use expanse::{
5    geometry::Size,
6    result::Layout,
7    style::{Dimension, PositionType, Style},
8};
9use ito_canvas::unicode_canvas::{Border, Canvas};
10use std::any::Any;
11
12/// A one line text input
13#[derive(Default, Debug)]
14pub struct TextInput {
15    layout: Option<Layout>,
16    input_buffer: InputBuffer,
17    is_rounded: bool,
18    has_border: bool,
19    focused: bool,
20    width: Option<f32>,
21    height: Option<f32>,
22    id: Option<String>,
23}
24
25impl TextInput {
26    /// creates a new text input with initial value
27    pub fn new<S>(value: S) -> Self
28    where
29        S: ToString,
30    {
31        TextInput {
32            layout: None,
33            input_buffer: InputBuffer::new_with_value(value),
34            is_rounded: false,
35            has_border: true,
36            id: None,
37            ..Default::default()
38        }
39    }
40
41    /// process the key event for this text input
42    pub fn process_key(&mut self, key_event: KeyEvent) {
43        self.input_buffer.process_key_event(key_event);
44    }
45
46    /// set the value of the buffer
47    pub fn set_value<S: ToString>(&mut self, value: S) {
48        self.input_buffer = InputBuffer::new_with_value(value);
49    }
50
51    /// returns a reference to the text value of this text input widget
52    pub fn get_value(&self) -> &str {
53        self.input_buffer.get_content()
54    }
55
56    /// set whether to use rounded corner when drawing the border of the text input
57    pub fn set_rounded(&mut self, rounded: bool) {
58        self.is_rounded = rounded;
59    }
60
61    fn border_top(&self) -> f32 {
62        if self.has_border {
63            1.0
64        } else {
65            0.0
66        }
67    }
68
69    fn border_bottom(&self) -> f32 {
70        if self.has_border {
71            1.0
72        } else {
73            0.0
74        }
75    }
76
77    fn border_left(&self) -> f32 {
78        if self.has_border {
79            1.0
80        } else {
81            0.0
82        }
83    }
84
85    fn border_right(&self) -> f32 {
86        if self.has_border {
87            1.0
88        } else {
89            0.0
90        }
91    }
92
93    #[allow(dead_code)]
94    fn inner_height(&self, layout: &Layout) -> usize {
95        let ih = layout.size.height.round()
96            - self.border_top()
97            - self.border_bottom();
98        if ih > 0.0 {
99            ih as usize
100        } else {
101            0
102        }
103    }
104
105    fn inner_width(&self, layout: &Layout) -> usize {
106        let iw = layout.size.width.round()
107            - self.border_left()
108            - self.border_right();
109        if iw > 0.0 {
110            iw as usize
111        } else {
112            0
113        }
114    }
115}
116
117impl<MSG> Widget<MSG> for TextInput {
118    fn layout(&self) -> Option<&Layout> {
119        self.layout.as_ref()
120    }
121    fn set_layout(&mut self, layout: Layout) {
122        self.layout = Some(layout);
123    }
124    fn style(&self) -> Style {
125        Style {
126            position_type: PositionType::Relative,
127            size: Size {
128                width: if let Some(width) = self.width {
129                    Dimension::Points(width)
130                } else {
131                    Dimension::Percent(1.0)
132                },
133                height: if let Some(height) = self.height {
134                    Dimension::Points(height)
135                } else {
136                    Dimension::Points(3.0)
137                },
138            },
139            min_size: Size {
140                width: if let Some(width) = self.width {
141                    Dimension::Points(width)
142                } else {
143                    Dimension::Percent(1.0)
144                },
145                height: if let Some(height) = self.height {
146                    Dimension::Points(height)
147                } else {
148                    Dimension::Points(3.0)
149                },
150            },
151            ..Default::default()
152        }
153    }
154
155    /// draw this button to the buffer, with the given computed layout
156    fn draw(&self, buf: &mut Buffer) -> Vec<Cmd> {
157        let layout = self.layout.expect("must have a layout");
158        let loc_x = layout.location.x;
159        let loc_y = layout.location.y;
160        let width = layout.size.width;
161        let height = layout.size.height;
162
163        let left = loc_x;
164        let top = loc_y;
165        let bottom = top + height - 1.0;
166        let right = left + width - 1.0;
167
168        if self.has_border {
169            let border = Border {
170                use_thick_border: self.focused,
171                has_top: true,
172                has_bottom: true,
173                has_left: true,
174                has_right: true,
175                is_top_left_rounded: self.is_rounded,
176                is_top_right_rounded: self.is_rounded,
177                is_bottom_left_rounded: self.is_rounded,
178                is_bottom_right_rounded: self.is_rounded,
179            };
180            let mut canvas = Canvas::new();
181            canvas.draw_rect(
182                (left as usize, top as usize),
183                (right as usize, bottom as usize),
184                border,
185            );
186            buf.write_canvas(canvas);
187        }
188
189        let inner_width = self.inner_width(&layout);
190        for (t, ch) in self.get_value().chars().enumerate() {
191            buf.set_symbol(
192                (left + self.border_left() + t as f32) as usize,
193                (top + self.border_top()) as usize,
194                ch,
195            );
196        }
197
198        let cursor_loc_x = self.input_buffer.get_cursor_location() as f32;
199        if self.focused {
200            vec![
201                Cmd::ShowCursor,
202                Cmd::MoveTo(
203                    (left + cursor_loc_x + self.border_left()) as usize,
204                    (top + self.border_top()) as usize,
205                ),
206            ]
207        } else {
208            vec![]
209        }
210    }
211
212    fn set_focused(&mut self, focused: bool) {
213        self.focused = focused;
214    }
215
216    fn as_any(&self) -> &dyn Any {
217        self
218    }
219
220    fn as_any_mut(&mut self) -> &mut dyn Any {
221        self
222    }
223
224    fn set_size(&mut self, width: Option<f32>, height: Option<f32>) {
225        self.width = width;
226        self.height = height;
227    }
228
229    fn process_event(&mut self, event: Event) -> Vec<MSG> {
230        let layout = self.layout.expect("must have a layout set");
231        match event {
232            Event::Key(ke) => {
233                self.process_key(ke);
234                vec![]
235            }
236            Event::Mouse(MouseEvent::Down(_btn, x, _y, _modifier)) => {
237                let cursor_loc = x as i32 - layout.location.x.round() as i32;
238                self.input_buffer.set_cursor_loc(cursor_loc as usize);
239                vec![]
240            }
241            _ => vec![],
242        }
243    }
244
245    fn set_id(&mut self, id: &str) {
246        self.id = Some(id.to_string());
247    }
248
249    fn get_id(&self) -> &Option<String> {
250        &self.id
251    }
252}