1use super::{Widget, button::Button, column::Column, row::Row};
22use alloc::{
23 string::{String, ToString},
24 vec,
25 vec::Vec,
26};
27use embedded_graphics::{
28 pixelcolor::PixelColor, prelude::*, primitives::Rectangle, text::Alignment,
29};
30use zest_core::{Constraints, Length, RenderError, Renderer, TouchPhase};
31use zest_theme::{ButtonClass, Theme};
32
33const FIELD_H: u32 = 70;
35const SHOW_W: u32 = 56;
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum KeyboardMode {
41 TextLower,
43 TextUpper,
45 Special,
47 Number,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub enum KeyAction {
54 Char(char),
56 Backspace,
58 Newline,
60 CursorLeft,
62 CursorRight,
64 Mode(KeyboardMode),
67 Ready,
69 Cancel,
71 ToggleReveal,
74}
75
76struct Key {
79 label: String,
80 action: KeyAction,
81 weight: u32,
82}
83
84fn ch(c: char) -> Key {
86 Key {
87 label: c.to_string(),
88 action: KeyAction::Char(c),
89 weight: 1,
90 }
91}
92
93fn ctrl(label: &str, action: KeyAction, weight: u32) -> Key {
95 Key {
96 label: label.to_string(),
97 action,
98 weight,
99 }
100}
101
102fn bottom_row() -> Vec<Key> {
105 vec![
106 ctrl("▼", KeyAction::Cancel, 2),
107 ctrl("←", KeyAction::CursorLeft, 1),
108 ctrl("space", KeyAction::Char(' '), 6),
109 ctrl("→", KeyAction::CursorRight, 1),
110 ctrl("OK", KeyAction::Ready, 2),
111 ]
112}
113
114pub struct Keyboard<'a, C: PixelColor, M: Clone> {
117 bounds: Rectangle,
118 title: String,
119 input: String,
120 is_password: bool,
121 show_field: bool,
122 reveal: bool,
123 keys: Column<'a, C, M>,
124 width: Length,
125 height: Length,
126 toggle_reveal: M,
128}
129
130impl<'a, C: PixelColor + 'a, M: Clone + 'a> Keyboard<'a, C, M> {
131 pub fn new<F>(mode: KeyboardMode, on_action: F) -> Self
135 where
136 F: Fn(KeyAction) -> M + Copy + 'a,
137 {
138 let toggle_reveal = on_action(KeyAction::ToggleReveal);
139 let keys = Self::build_keys(mode, on_action);
140 Self {
141 bounds: Rectangle::zero(),
142 title: String::new(),
143 input: String::new(),
144 is_password: false,
145 show_field: false,
146 reveal: false,
147 keys,
148 width: Length::Fill,
149 height: Length::Fill,
150 toggle_reveal,
151 }
152 }
153
154 #[must_use]
156 pub fn title(mut self, title: impl Into<String>) -> Self {
157 self.title = title.into();
158 self
159 }
160
161 #[must_use]
163 pub fn input(mut self, input: impl Into<String>) -> Self {
164 self.input = input.into();
165 self
166 }
167
168 #[must_use]
172 pub fn is_password(mut self, is_password: bool) -> Self {
173 self.is_password = is_password;
174 self
175 }
176
177 #[must_use]
181 pub fn reveal(mut self, reveal: bool) -> Self {
182 self.reveal = reveal;
183 self
184 }
185
186 #[must_use]
191 pub fn show_field(mut self, show: bool) -> Self {
192 self.show_field = show;
193 self
194 }
195
196 #[must_use]
198 pub fn width(mut self, width: impl Into<Length>) -> Self {
199 self.width = width.into();
200 self
201 }
202
203 #[must_use]
205 pub fn height(mut self, height: impl Into<Length>) -> Self {
206 self.height = height.into();
207 self
208 }
209
210 fn key_bounds_for(&self, bounds: Rectangle) -> Rectangle {
211 let reserve: u32 = if self.show_field { FIELD_H } else { 0 };
212 Rectangle::new(
213 Point::new(bounds.top_left.x, bounds.top_left.y + reserve as i32),
214 Size::new(
215 bounds.size.width,
216 bounds.size.height.saturating_sub(reserve),
217 ),
218 )
219 }
220
221 fn show_button_rect(&self) -> Option<Rectangle> {
224 if !(self.is_password && self.show_field) {
225 return None;
226 }
227 let x0 = self.bounds.top_left.x;
228 let y0 = self.bounds.top_left.y;
229 let width = self.bounds.size.width;
230 Some(Rectangle::new(
231 Point::new(x0 + width as i32 - 8 - SHOW_W as i32, y0 + 30),
232 Size::new(SHOW_W, 28),
233 ))
234 }
235
236 fn build_keys<F>(mode: KeyboardMode, on_action: F) -> Column<'a, C, M>
237 where
238 F: Fn(KeyAction) -> M + Copy + 'a,
239 {
240 let mut col = Column::new().spacing(2);
241 for row in keymap(mode) {
242 col = col.push(Self::build_row(row, on_action));
243 }
244 col
245 }
246
247 fn build_row<F>(spec: Vec<Key>, on_action: F) -> Row<'a, C, M>
248 where
249 F: Fn(KeyAction) -> M + Copy + 'a,
250 {
251 let mut row = Row::new().spacing(2);
252 for key in spec {
253 let class = match key.action {
254 KeyAction::Ready => ButtonClass::Suggested,
255 KeyAction::Cancel => ButtonClass::Destructive,
256 _ => ButtonClass::Standard,
257 };
258 row = row.push(
259 Button::new(key.label)
260 .on_press(on_action(key.action))
261 .class(class)
262 .width(Length::FillPortion(key.weight)),
263 );
264 }
265 row
266 }
267}
268
269fn keymap(mode: KeyboardMode) -> Vec<Vec<Key>> {
271 use KeyAction::{Backspace, CursorLeft, CursorRight, Mode, Newline, Ready};
272 use KeyboardMode::{Number, Special, TextLower, TextUpper};
273
274 fn text_row(lead: Key, mids: &str, tail: Key) -> Vec<Key> {
277 let mut r = vec![lead];
278 r.extend(mids.chars().map(ch));
279 r.push(tail);
280 r
281 }
282
283 match mode {
284 TextLower => vec![
285 text_row(
286 ctrl("123", Mode(Special), 3),
287 "qwertyuiop",
288 ctrl("⌫", Backspace, 3),
289 ),
290 text_row(
291 ctrl("ABC", Mode(TextUpper), 3),
292 "asdfghjkl",
293 ctrl("↵", Newline, 3),
294 ),
295 "_-zxcvbnm.,:".chars().map(ch).collect(),
296 bottom_row(),
297 ],
298 TextUpper => vec![
299 text_row(
300 ctrl("123", Mode(Special), 3),
301 "QWERTYUIOP",
302 ctrl("⌫", Backspace, 3),
303 ),
304 text_row(
305 ctrl("abc", Mode(TextLower), 3),
306 "ASDFGHJKL",
307 ctrl("↵", Newline, 3),
308 ),
309 "_-ZXCVBNM.,:".chars().map(ch).collect(),
310 bottom_row(),
311 ],
312 Special => vec![
313 {
314 let mut r: Vec<Key> = "0123456789".chars().map(ch).collect();
315 r.push(ctrl("⌫", Backspace, 3));
316 r
317 },
318 text_row(
319 ctrl("abc", Mode(TextLower), 3),
320 "+-/*=%!?#<>",
321 ctrl("↵", Newline, 3),
322 ),
323 "\\@$(){}[];\"'".chars().map(ch).collect(),
324 bottom_row(),
325 ],
326 Number => vec![
327 {
328 let mut r: Vec<Key> = "123".chars().map(ch).collect();
329 r.push(ctrl("▼", KeyAction::Cancel, 1));
330 r
331 },
332 {
333 let mut r: Vec<Key> = "456".chars().map(ch).collect();
334 r.push(ctrl("OK", Ready, 1));
335 r
336 },
337 {
338 let mut r: Vec<Key> = "789".chars().map(ch).collect();
339 r.push(ctrl("⌫", Backspace, 1));
340 r
341 },
342 vec![
343 ctrl("abc", Mode(TextLower), 1),
344 ch('0'),
345 ch('.'),
346 ctrl("←", CursorLeft, 1),
347 ctrl("→", CursorRight, 1),
348 ],
349 ],
350 }
351}
352
353impl<'a, C: PixelColor + 'a, M: Clone + 'a> Widget<C, M> for Keyboard<'a, C, M> {
354 fn measure(&mut self, constraints: Constraints) -> Size {
355 let w = self
356 .width
357 .resolve(constraints.max.width, constraints.max.width);
358 let h = self
359 .height
360 .resolve(constraints.max.height, constraints.max.height);
361 constraints.clamp(Size::new(w, h))
362 }
363
364 fn preferred_size(&self) -> (Length, Length) {
365 (self.width, self.height)
366 }
367
368 fn arrange(&mut self, rect: Rectangle) {
369 self.bounds = rect;
370 let kb_bounds = self.key_bounds_for(rect);
371 self.keys.arrange(kb_bounds);
372 }
373
374 fn rect(&self) -> Rectangle {
375 self.bounds
376 }
377
378 fn handle_touch(&mut self, point: Point, phase: TouchPhase) -> Option<M> {
379 if let Some(btn) = self.show_button_rect() {
380 let br = btn.top_left + Point::new(btn.size.width as i32, btn.size.height as i32);
381 let inside = point.x >= btn.top_left.x
382 && point.x < br.x
383 && point.y >= btn.top_left.y
384 && point.y < br.y;
385 if inside {
386 return (phase == TouchPhase::Up).then(|| self.toggle_reveal.clone());
389 }
390 }
391 self.keys.handle_touch(point, phase)
392 }
393
394 fn mark_pressed(&mut self, point: Point) {
395 self.keys.mark_pressed(point);
396 }
397
398 fn draw<'t>(
399 &self,
400 renderer: &mut dyn Renderer<C>,
401 theme: &Theme<'t, C>,
402 ) -> Result<(), RenderError> {
403 renderer.fill_rect(self.bounds, theme.background.base)?;
404
405 if self.show_field {
406 let x0 = self.bounds.top_left.x;
407 let y0 = self.bounds.top_left.y;
408 let width = self.bounds.size.width;
409
410 renderer.draw_text(
412 &self.title,
413 Point::new(x0 + (width / 2) as i32, y0 + 18),
414 theme.default_font(),
415 theme.background.on_base,
416 Alignment::Center,
417 )?;
418
419 let field_w = if self.is_password {
422 width.saturating_sub(16 + SHOW_W + 4)
423 } else {
424 width.saturating_sub(16)
425 };
426 let field = Rectangle::new(Point::new(x0 + 8, y0 + 30), Size::new(field_w, 28));
427 renderer.stroke_rect(field, theme.button.border)?;
428
429 let display_text = if self.is_password && !self.reveal {
431 "*".repeat(self.input.chars().count())
432 } else {
433 self.input.clone()
434 };
435 renderer.draw_text(
436 &display_text,
437 Point::new(x0 + 14, y0 + 49),
438 theme.default_font(),
439 theme.background.on_base,
440 Alignment::Left,
441 )?;
442
443 if let Some(btn) = self.show_button_rect() {
445 renderer.fill_rect(btn, theme.button.base)?;
446 renderer.stroke_rect(btn, theme.button.border)?;
447 let label = if self.reveal { "Hide" } else { "Show" };
448 let glyph_h = theme.default_font().character_size.height as i32;
449 renderer.draw_text(
450 label,
451 Point::new(
452 btn.top_left.x + btn.size.width as i32 / 2,
453 btn.top_left.y + btn.size.height as i32 / 2 + glyph_h / 3,
454 ),
455 theme.default_font(),
456 theme.button.on_base,
457 Alignment::Center,
458 )?;
459 }
460 }
461
462 self.keys.draw(renderer, theme)
464 }
465}