Files
MeshCore-Solo/examples/companion_radio/ui-new/KeyboardWidget.h
2026-05-13 21:09:47 +02:00

203 lines
6.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <helpers/ui/DisplayDriver.h>
#include <Arduino.h>
// Layout constants shared by all keyboard users
static const char KB_CHARS[4][10] = {
{'a','b','c','d','e','f','g','h','i','j'},
{'k','l','m','n','o','p','q','r','s','t'},
{'u','v','w','x','y','z','.',' ','!','?'},
{'1','2','3','4','5','6','7','8','9','0'},
};
static const int KB_ROWS_CHAR = 4;
static const int KB_COLS_CHAR = 10;
static const int KB_SPECIAL = 5; // [^] [Sp] [Del] [{}] [OK]
static const char* KB_PH_LIST[] = { "{loc}", "{time}" };
static const int KB_PH_COUNT = 2;
static const int KB_MAX_LEN = 139;
static const int KB_CELL_W = 12; // 10 cols × 12px = 120px of 128px display
static const int KB_CELL_H = 9;
static const int KB_TEXT_Y = 0;
static const int KB_SEP_Y = 9;
static const int KB_CHARS_Y = 11;
static const int KB_SPECIAL_Y = 48;
struct KeyboardWidget {
char buf[KB_MAX_LEN + 1];
int len;
int max_len;
int row, col;
bool caps;
bool ph_mode;
int ph_sel;
enum Result { NONE, DONE, CANCELLED };
void begin(const char* initial = "", int max = KB_MAX_LEN) {
max_len = (max > KB_MAX_LEN) ? KB_MAX_LEN : max;
strncpy(buf, initial, max_len);
buf[max_len] = '\0';
len = strlen(buf);
row = col = 0;
caps = ph_mode = false;
ph_sel = 0;
}
int render(DisplayDriver& display) {
display.setTextSize(1);
display.setColor(DisplayDriver::LIGHT);
// text preview: last 20 chars + cursor
const char* disp_start = buf;
int disp_len = len;
if (disp_len > 20) { disp_start = buf + (disp_len - 20); disp_len = 20; }
char preview[24];
snprintf(preview, sizeof(preview), "%.*s_", disp_len, disp_start);
display.setCursor(0, KB_TEXT_Y);
display.print(preview);
display.fillRect(0, KB_SEP_Y, display.width(), 1);
// character grid
for (int r = 0; r < KB_ROWS_CHAR; r++) {
int y = KB_CHARS_Y + r * KB_CELL_H;
for (int c = 0; c < KB_COLS_CHAR; c++) {
bool sel = (row == r && col == c);
char ch = KB_CHARS[r][c];
if (caps && ch >= 'a' && ch <= 'z') ch = ch - 'a' + 'A';
char ch_buf[2] = { ch == ' ' ? '_' : ch, '\0' };
int cx = c * KB_CELL_W;
if (sel) {
display.setColor(DisplayDriver::LIGHT);
display.fillRect(cx, y - 1, KB_CELL_W - 1, KB_CELL_H);
display.setColor(DisplayDriver::DARK);
} else {
display.setColor(DisplayDriver::LIGHT);
}
display.setCursor(cx + 3, y);
display.print(ch_buf);
}
}
// special row: [^] [Sp] [Dl] [{}] [OK]
const char* spec[] = { "[^]", "[Sp]", "[Dl]", "[{}]", "[OK]" };
for (int i = 0; i < KB_SPECIAL; i++) {
bool sel = (row == KB_ROWS_CHAR && col == i);
bool active = (i == 0 && caps);
int sx = i * 25;
if (sel || active) {
display.setColor(DisplayDriver::LIGHT);
display.fillRect(sx, KB_SPECIAL_Y - 1, 24, KB_CELL_H);
display.setColor(DisplayDriver::DARK);
} else {
display.setColor(DisplayDriver::LIGHT);
}
display.setCursor(sx + 1, KB_SPECIAL_Y);
display.print(spec[i]);
display.setColor(DisplayDriver::LIGHT);
}
// placeholder picker overlay
if (ph_mode) {
display.setColor(DisplayDriver::DARK);
display.fillRect(20, 20, 88, 12 + KB_PH_COUNT * 10);
display.setColor(DisplayDriver::LIGHT);
display.drawRect(20, 20, 88, 12 + KB_PH_COUNT * 10);
display.setCursor(24, 21);
display.print("Placeholder:");
display.fillRect(20, 30, 88, 1);
for (int i = 0; i < KB_PH_COUNT; i++) {
int py = 33 + i * 10;
if (i == ph_sel) {
display.setColor(DisplayDriver::LIGHT);
display.fillRect(21, py - 1, 86, 10);
display.setColor(DisplayDriver::DARK);
} else {
display.setColor(DisplayDriver::LIGHT);
}
display.setCursor(24, py);
display.print(KB_PH_LIST[i]);
}
display.setColor(DisplayDriver::LIGHT);
}
return 50;
}
Result handleInput(char c) {
// placeholder overlay consumes all input
if (ph_mode) {
if (c == KEY_UP && ph_sel > 0) { ph_sel--; return NONE; }
if (c == KEY_DOWN && ph_sel < KB_PH_COUNT - 1){ ph_sel++; return NONE; }
if (c == KEY_ENTER) {
const char* ph = KB_PH_LIST[ph_sel];
int ph_len = strlen(ph);
if (len + ph_len <= max_len) {
memcpy(buf + len, ph, ph_len);
len += ph_len;
buf[len] = '\0';
}
ph_mode = false;
return NONE;
}
ph_mode = false; // CANCEL or any other key closes the overlay
return NONE;
}
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) return CANCELLED;
if (c == KEY_UP) {
if (row > 0) {
row--;
if (row == KB_ROWS_CHAR - 1) // leaving special row upward
col = col * KB_COLS_CHAR / KB_SPECIAL;
}
return NONE;
}
if (c == KEY_DOWN) {
if (row < KB_ROWS_CHAR) {
row++;
if (row == KB_ROWS_CHAR) // entering special row
col = col * KB_SPECIAL / KB_COLS_CHAR;
}
return NONE;
}
if (c == KEY_LEFT) {
if (col > 0) col--;
return NONE;
}
if (c == KEY_RIGHT) {
int max_col = (row == KB_ROWS_CHAR) ? KB_SPECIAL - 1 : KB_COLS_CHAR - 1;
if (col < max_col) col++;
return NONE;
}
if (c == KEY_ENTER) {
if (row < KB_ROWS_CHAR) {
if (len < max_len) {
char ch = KB_CHARS[row][col];
if (caps && ch >= 'a' && ch <= 'z') ch = ch - 'a' + 'A';
buf[len++] = ch;
buf[len] = '\0';
}
} else {
switch (col) {
case 0: caps = !caps; break;
case 1:
if (len < max_len) { buf[len++] = ' '; buf[len] = '\0'; }
break;
case 2:
if (len > 0) buf[--len] = '\0';
break;
case 3:
ph_mode = true;
ph_sel = 0;
break;
case 4:
return DONE;
}
}
}
return NONE;
}
};