glyph_ui/view/
input_line.rs1use std::convert::TryFrom;
4
5use euclid::Size2D;
6use keyboard_types::Key;
7use unicode_segmentation::UnicodeSegmentation;
8
9use crate::{event::Event, unit::Cell, Printer, View as ViewTrait};
10
11const SOFT_MIN_LINE_WIDTH: u16 = 10;
14
15pub fn new<F, M>(state: &mut State, on_submit: F) -> View<'_, F>
19where
20 F: Fn(String) -> M,
21{
22 View::new(state, on_submit)
23}
24
25pub struct View<'a, F> {
27 state: &'a mut State,
28 on_submit: F,
29 clear_on_submit: bool,
30 placeholder: Option<char>,
31 echo: Echo,
32}
33
34impl<'a, F, M> View<'a, F>
35where
36 F: Fn(String) -> M,
37{
38 pub fn new(state: &'a mut State, on_submit: F) -> Self {
39 Self {
40 state,
41 on_submit,
42 clear_on_submit: true,
43 placeholder: Some('_'),
44 echo: Default::default(),
45 }
46 }
47
48 pub fn placeholder(mut self, placeholder: Option<char>) -> Self {
52 self.placeholder = placeholder;
53
54 self
55 }
56
57 pub fn echo(mut self, echo: Echo) -> Self {
63 self.echo = echo;
64
65 self
66 }
67
68 pub fn clear_on_submit(mut self, enable: bool) -> Self {
74 self.clear_on_submit = enable;
75
76 self
77 }
78}
79
80pub enum Echo {
82 On,
86
87 Faux(char),
94
95 Off,
100}
101
102impl Default for Echo {
103 fn default() -> Self {
104 Self::On
105 }
106}
107
108#[derive(Default)]
110pub struct State {
111 input: String,
112}
113
114impl State {
115 pub fn content(&self) -> &str {
117 &self.input
118 }
119}
120
121impl<T, M, F> ViewTrait<T, M> for View<'_, F>
122where
123 F: Fn(String) -> M,
124 M: 'static,
125{
126 fn draw(&self, printer: &Printer, focused: bool) {
127 if let Some(c) = self.placeholder {
128 let line = std::iter::repeat(c)
129 .take(printer.size().width.into())
130 .fold(String::new(), |mut s, c| {
131 s.push(c);
132
133 s
134 });
135
136 printer.print(&line, (0, 0)).unwrap();
137 }
138
139 let width = printer.size().width;
142 let start = self
143 .state
144 .input
145 .len()
146 .saturating_add(1)
147 .saturating_sub(width.into());
148
149 match self.echo {
152 Echo::On => {
154 printer.print(&self.state.input[start..], (0, 0)).unwrap();
155 }
156
157 Echo::Faux(c) => {
160 let line: String =
161 std::iter::repeat(c).take(self.state.input.len()).collect();
162
163 printer.print(&line[start..], (0, 0)).unwrap();
164 }
165
166 Echo::Off => (),
168 }
169
170 if focused {
172 match self.echo {
173 Echo::On | Echo::Faux(_) => {
176 let cursor_x =
177 u16::try_from(self.state.input.graphemes(true).count())
178 .unwrap()
179 .min(width.saturating_sub(1));
180
181 printer.show_cursor_at((cursor_x, 0)).unwrap();
182 }
183
184 Echo::Off => {
186 printer.show_cursor_at((0, 0)).unwrap();
187 }
188 }
189 }
190 }
191
192 fn width(&self) -> Size2D<u16, Cell> {
193 (SOFT_MIN_LINE_WIDTH, 1).into()
194 }
195
196 fn height(&self) -> Size2D<u16, Cell> {
197 (SOFT_MIN_LINE_WIDTH, 1).into()
198 }
199
200 fn layout(&self, constraint: Size2D<u16, Cell>) -> Size2D<u16, Cell> {
201 (constraint.width, 1).into()
202 }
203
204 fn event(
205 &mut self,
206 event: &Event<T>,
207 focused: bool,
208 ) -> Box<dyn Iterator<Item = M>> {
209 if !focused {
210 return Box::new(std::iter::empty());
211 }
212
213 let message = if let Event::Key(k) = event {
214 match &k.key {
215 Key::Character(c) => {
216 self.state.input.push_str(c);
217
218 None
219 }
220 Key::Enter => {
221 let line = self.state.input.clone();
222 if self.clear_on_submit {
223 self.state.input.clear();
224 }
225 Some((self.on_submit)(line))
226 }
227 Key::Backspace => {
228 self.state.input.pop();
229
230 None
231 }
232 _ => None,
234 }
235 } else {
236 None
237 };
238
239 if let Some(message) = message {
240 Box::new(std::iter::once(message))
241 } else {
242 Box::new(std::iter::empty())
243 }
244 }
245
246 fn interactive(&self) -> bool {
247 true
248 }
249}