titik/widget/
text_input.rs1use 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#[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 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 pub fn process_key(&mut self, key_event: KeyEvent) {
43 self.input_buffer.process_key_event(key_event);
44 }
45
46 pub fn set_value<S: ToString>(&mut self, value: S) {
48 self.input_buffer = InputBuffer::new_with_value(value);
49 }
50
51 pub fn get_value(&self) -> &str {
53 self.input_buffer.get_content()
54 }
55
56 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 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}