1use alloc::boxed::Box;
27use alloc::string::String;
28use alloc::string::ToString;
29use rlvgl_core::event::Event;
30use rlvgl_core::renderer::Renderer;
31use rlvgl_core::widget::{Rect, Widget};
32
33use crate::button_matrix::{BUTTON_NONE, ButtonId, ButtonMatrix};
34
35static MAP_LOWER: &[&str] = &[
39 "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "\n", "a", "s", "d", "f", "g", "h", "j", "k",
40 "l", "\n", "ABC", "z", "x", "c", "v", "b", "n", "m", "⌫", "\n", "123", " ", "↵",
41];
42
43static MAP_UPPER: &[&str] = &[
45 "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "\n", "A", "S", "D", "F", "G", "H", "J", "K",
46 "L", "\n", "abc", "Z", "X", "C", "V", "B", "N", "M", "⌫", "\n", "123", " ", "↵",
47];
48
49static MAP_NUMBER: &[&str] = &[
51 "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "\n", "+", "-", "/", "*", "=", "%", "!", "?",
52 "#", "<", ">", "\n", "\\", "@", "$", "(", ")", "{", "}", "[", "]", ";", ":", "'", "\n", "abc",
53 " ", "↵", "⌫",
54];
55
56static MAP_SPECIAL: &[&str] = &[
58 ":)", ":(", ";)", ":D", ":O", ":-|", ":P", "\n", "abc", " ", "↵", "⌫",
59];
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum KeyboardMode {
68 TextLower,
70 TextUpper,
72 Number,
74 Special,
76 User1,
78 User2,
80 User3,
82 User4,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
90pub enum KeyboardControl {
91 SwitchMode(KeyboardMode),
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum KeyOutput {
100 Char(char),
102 Backspace,
104 Enter,
106 Tab,
108 Escape,
110 Control(KeyboardControl),
112}
113
114fn resolve_key(label: &str) -> Option<KeyOutput> {
116 match label {
117 "⌫" => Some(KeyOutput::Backspace),
118 "↵" => Some(KeyOutput::Enter),
119 "ABC" => Some(KeyOutput::Control(KeyboardControl::SwitchMode(
120 KeyboardMode::TextUpper,
121 ))),
122 "abc" => Some(KeyOutput::Control(KeyboardControl::SwitchMode(
123 KeyboardMode::TextLower,
124 ))),
125 "123" => Some(KeyOutput::Control(KeyboardControl::SwitchMode(
126 KeyboardMode::Number,
127 ))),
128 " " => Some(KeyOutput::Char(' ')),
129 s if s.chars().count() == 1 => Some(KeyOutput::Char(s.chars().next().unwrap())),
130 _ => None,
131 }
132}
133
134pub struct Keyboard {
141 matrix: ButtonMatrix,
142 mode: KeyboardMode,
143 popovers: bool,
144 last_output: Option<KeyOutput>,
146 hook: Option<Box<dyn FnMut(KeyOutput)>>,
148}
149
150impl Keyboard {
151 pub fn new(bounds: Rect) -> Self {
153 let mut matrix = ButtonMatrix::new(bounds);
154 matrix.set_map(MAP_LOWER);
155 Self {
156 matrix,
157 mode: KeyboardMode::TextLower,
158 popovers: false,
159 last_output: None,
160 hook: None,
161 }
162 }
163
164 pub fn set_mode(&mut self, mode: KeyboardMode) {
168 self.mode = mode;
169 let map: &[&str] = match mode {
170 KeyboardMode::TextLower => MAP_LOWER,
171 KeyboardMode::TextUpper => MAP_UPPER,
172 KeyboardMode::Number => MAP_NUMBER,
173 KeyboardMode::Special => MAP_SPECIAL,
174 KeyboardMode::User1
176 | KeyboardMode::User2
177 | KeyboardMode::User3
178 | KeyboardMode::User4 => return,
179 };
180 self.matrix.set_map(map);
181 }
182
183 pub fn mode(&self) -> KeyboardMode {
185 self.mode
186 }
187
188 pub fn set_popovers(&mut self, enable: bool) {
193 self.popovers = enable;
194 }
195
196 pub fn popovers(&self) -> bool {
198 self.popovers
199 }
200
201 pub fn set_key_output_hook<F>(&mut self, hook: F)
208 where
209 F: FnMut(KeyOutput) + 'static,
210 {
211 self.hook = Some(Box::new(hook));
212 }
213
214 pub fn last_key_output(&mut self) -> Option<KeyOutput> {
217 self.last_output.take()
218 }
219
220 pub fn navigate_next(&mut self) {
226 self.matrix.navigate_next();
227 }
228
229 pub fn navigate_prev(&mut self) {
233 self.matrix.navigate_prev();
234 }
235
236 pub fn activate_selected(&mut self) {
241 let id = self.matrix.selected_button();
242 if id == BUTTON_NONE {
243 return;
244 }
245 let label_owned: String = self.matrix.button_text(id).unwrap_or_default().to_string();
247 self.matrix.activate_selected();
248 self.emit_for_label(&label_owned);
249 }
250
251 fn emit_for_label(&mut self, label: &str) {
256 let output = match resolve_key(label) {
257 Some(out) => out,
258 None => return,
259 };
260
261 if let KeyOutput::Control(KeyboardControl::SwitchMode(new_mode)) = &output {
263 let m = *new_mode;
264 self.set_mode(m);
265 }
266
267 self.last_output = Some(output.clone());
269
270 if let Some(hook) = &mut self.hook {
272 hook(output);
273 }
274 }
275
276 pub fn matrix(&self) -> &ButtonMatrix {
278 &self.matrix
279 }
280
281 pub fn matrix_mut(&mut self) -> &mut ButtonMatrix {
283 &mut self.matrix
284 }
285
286 pub fn selected_button(&self) -> ButtonId {
288 self.matrix.selected_button()
289 }
290}
291
292impl Widget for Keyboard {
293 fn bounds(&self) -> Rect {
294 self.matrix.bounds()
295 }
296
297 fn set_bounds(&mut self, bounds: Rect) {
298 self.matrix.set_bounds(bounds);
299 }
300
301 fn draw(&self, renderer: &mut dyn Renderer) {
302 self.matrix.draw(renderer);
303 }
304
305 fn handle_event(&mut self, event: &Event) -> bool {
306 let consumed = self.matrix.handle_event(event);
307 if consumed && matches!(event, Event::PressRelease { .. }) {
308 let activated_id = self.matrix.selected_button();
311 if activated_id != BUTTON_NONE {
312 let label_owned: String = self
313 .matrix
314 .button_text(activated_id)
315 .unwrap_or_default()
316 .to_string();
317 self.emit_for_label(&label_owned);
318 }
319 }
320 consumed
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use rlvgl_core::widget::{Color, Rect};
328
329 fn rect(x: i32, y: i32, w: i32, h: i32) -> Rect {
330 Rect {
331 x,
332 y,
333 width: w,
334 height: h,
335 }
336 }
337
338 struct NullRenderer;
339 impl rlvgl_core::renderer::Renderer for NullRenderer {
340 fn fill_rect(&mut self, _: Rect, _: Color) {}
341 fn draw_text(&mut self, _: (i32, i32), _: &str, _: Color) {}
342 }
343
344 #[test]
345 fn new_starts_in_lower_mode() {
346 let kb = Keyboard::new(rect(0, 0, 320, 160));
347 assert_eq!(kb.mode(), KeyboardMode::TextLower);
348 }
349
350 #[test]
351 fn set_mode_upper_changes_map() {
352 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
353 kb.set_mode(KeyboardMode::TextUpper);
354 assert_eq!(kb.mode(), KeyboardMode::TextUpper);
355 assert_eq!(kb.matrix().button_text(ButtonId(0)), Some("Q"));
357 }
358
359 #[test]
360 fn set_mode_number_changes_map() {
361 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
362 kb.set_mode(KeyboardMode::Number);
363 assert_eq!(kb.mode(), KeyboardMode::Number);
364 assert_eq!(kb.matrix().button_text(ButtonId(0)), Some("1"));
365 }
366
367 #[test]
368 fn set_mode_special_changes_map() {
369 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
370 kb.set_mode(KeyboardMode::Special);
371 assert_eq!(kb.mode(), KeyboardMode::Special);
372 }
373
374 #[test]
375 fn activate_char_key_emits_char_output() {
376 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
377 kb.matrix_mut().set_selected_button(ButtonId(0));
379 kb.activate_selected();
380 assert_eq!(kb.last_key_output(), Some(KeyOutput::Char('q')));
381 assert_eq!(kb.last_key_output(), None);
383 }
384
385 #[test]
386 fn activate_backspace_emits_backspace() {
387 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
388 let count = kb.matrix().button_count();
390 let mut bs_id = None;
391 for i in 0..count {
392 if kb.matrix().button_text(ButtonId(i as u16)) == Some("⌫") {
393 bs_id = Some(ButtonId(i as u16));
394 break;
395 }
396 }
397 let bs_id = bs_id.expect("backspace button not found");
398 kb.matrix_mut().set_selected_button(bs_id);
399 kb.activate_selected();
400 assert_eq!(kb.last_key_output(), Some(KeyOutput::Backspace));
401 }
402
403 #[test]
404 fn activate_enter_emits_enter() {
405 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
406 let count = kb.matrix().button_count();
407 let mut enter_id = None;
408 for i in 0..count {
409 if kb.matrix().button_text(ButtonId(i as u16)) == Some("↵") {
410 enter_id = Some(ButtonId(i as u16));
411 break;
412 }
413 }
414 let enter_id = enter_id.expect("enter button not found");
415 kb.matrix_mut().set_selected_button(enter_id);
416 kb.activate_selected();
417 assert_eq!(kb.last_key_output(), Some(KeyOutput::Enter));
418 }
419
420 #[test]
421 fn mode_switch_key_changes_mode() {
422 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
423 let count = kb.matrix().button_count();
425 let mut abc_id = None;
426 for i in 0..count {
427 if kb.matrix().button_text(ButtonId(i as u16)) == Some("ABC") {
428 abc_id = Some(ButtonId(i as u16));
429 break;
430 }
431 }
432 let abc_id = abc_id.expect("ABC mode-switch button not found");
433 kb.matrix_mut().set_selected_button(abc_id);
434 kb.activate_selected();
435 assert_eq!(kb.mode(), KeyboardMode::TextUpper);
436 }
437
438 #[test]
439 fn hook_is_called_on_key_activation() {
440 use alloc::rc::Rc;
441 use core::cell::Cell;
442
443 let called = Rc::new(Cell::new(false));
444 let called2 = called.clone();
445
446 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
447 kb.set_key_output_hook(move |_out| {
448 called2.set(true);
449 });
450 kb.matrix_mut().set_selected_button(ButtonId(0));
451 kb.activate_selected();
452 assert!(called.get());
453 }
454
455 #[test]
456 fn navigate_next_and_prev_work() {
457 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
458 kb.navigate_next();
459 assert_eq!(kb.selected_button(), ButtonId(0));
460 kb.navigate_next();
461 assert_eq!(kb.selected_button(), ButtonId(1));
462 kb.navigate_prev();
463 assert_eq!(kb.selected_button(), ButtonId(0));
464 }
465
466 #[test]
467 fn set_bounds_propagates_to_matrix() {
468 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
469 kb.set_bounds(rect(10, 20, 400, 200));
470 assert_eq!(kb.bounds(), rect(10, 20, 400, 200));
471 }
472
473 #[test]
474 fn draw_does_not_panic() {
475 let kb = Keyboard::new(rect(0, 0, 320, 160));
476 let mut r = NullRenderer;
477 kb.draw(&mut r);
478 }
479
480 #[test]
481 fn popovers_stored() {
482 let mut kb = Keyboard::new(rect(0, 0, 320, 160));
483 kb.set_popovers(true);
484 assert!(kb.popovers());
485 kb.set_popovers(false);
486 assert!(!kb.popovers());
487 }
488}