Add validation options and a full physical layout to the keyboard
uikeyboardopen_t grows: maxLength (auto-focuses confirm once full), lineCount for multi-line entry with a NEWLINE key, trimmed/allowBlank validation on confirm, and a second "are you sure" uiConfirm step. Reworks uikeyboardqwerty from a packed flat list into a fixed 14x5 grid shaped like a real keyboard - number row with -, =, ~ and backspace, caps and shift (one-shot, XORed with caps for real-keyboard behavior), and a bottom row with cancel/space/confirm in their normal corners. Shorter rows are padded with invisible filler cells so every key lands in its physical position; NEWLINE/CONFIRM/CANCEL are reserved slots uikeyboard.c patches in conditionally, otherwise left as inert blanks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,11 +26,10 @@
|
||||
uikeyboard_t UI_KEYBOARD;
|
||||
|
||||
void uiKeyboardFocusConfirm(void) {
|
||||
uint8_t confirmIndex = UI_KEYBOARD.menu.itemCount - (UI_KEYBOARD.cancel ? 2 : 1);
|
||||
uiMenuSetPosition(
|
||||
&UI_KEYBOARD.menu,
|
||||
confirmIndex % UI_KEYBOARD.menu.columns,
|
||||
confirmIndex / UI_KEYBOARD.menu.columns
|
||||
UI_KEYBOARD_QWERTY_CONFIRM_INDEX % UI_KEYBOARD_QWERTY_COLUMNS,
|
||||
UI_KEYBOARD_QWERTY_CONFIRM_INDEX / UI_KEYBOARD_QWERTY_COLUMNS
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,8 +110,7 @@ void uiKeyboardMenuSelected(
|
||||
const uint8_t index,
|
||||
const uimenuitem_t *item
|
||||
) {
|
||||
uint8_t confirmIndex = menu->itemCount - (UI_KEYBOARD.cancel ? 2 : 1);
|
||||
if(index == confirmIndex) {
|
||||
if(index == UI_KEYBOARD_QWERTY_CONFIRM_INDEX) {
|
||||
if(UI_KEYBOARD.trimmed) {
|
||||
stringTrim(UI_KEYBOARD.text);
|
||||
UI_KEYBOARD.textLabel.dirty = true;
|
||||
@@ -132,18 +130,32 @@ void uiKeyboardMenuSelected(
|
||||
return;
|
||||
}
|
||||
|
||||
if(UI_KEYBOARD.cancel && index == menu->itemCount - 1) {
|
||||
if(UI_KEYBOARD.cancel && index == UI_KEYBOARD_QWERTY_CANCEL_INDEX) {
|
||||
UI_KEYBOARD.confirmed = false;
|
||||
uiKeyboardClose();
|
||||
return;
|
||||
}
|
||||
|
||||
const char_t *key = item->button.label.text;
|
||||
|
||||
// Blank filler/reserved cell (includes an unused cancel/newline slot).
|
||||
if(key[0] == '\0') return;
|
||||
|
||||
if(stringCompare(key, UI_KEYBOARD_QWERTY_KEY_BACKSPACE) == 0) {
|
||||
uiKeyboardBackspace();
|
||||
return;
|
||||
}
|
||||
|
||||
if(stringCompare(key, UI_KEYBOARD_QWERTY_KEY_SHIFT) == 0) {
|
||||
UI_KEYBOARD.shift = !UI_KEYBOARD.shift;
|
||||
return;
|
||||
}
|
||||
|
||||
if(stringCompare(key, UI_KEYBOARD_QWERTY_KEY_CAPS) == 0) {
|
||||
UI_KEYBOARD.caps = !UI_KEYBOARD.caps;
|
||||
return;
|
||||
}
|
||||
|
||||
char_t c;
|
||||
if(stringCompare(key, UI_KEYBOARD_QWERTY_KEY_SPACE) == 0) {
|
||||
c = ' ';
|
||||
@@ -152,6 +164,13 @@ void uiKeyboardMenuSelected(
|
||||
stringCompare(key, UI_KEYBOARD_KEY_NEWLINE) == 0
|
||||
) {
|
||||
c = '\n';
|
||||
} else if(key[0] >= 'A' && key[0] <= 'Z') {
|
||||
// Letter keys are always drawn uppercase - shift/caps only decide
|
||||
// the case of the character actually entered. Shift is a one-shot,
|
||||
// consumed here; caps is a persistent toggle.
|
||||
bool_t upper = UI_KEYBOARD.caps != UI_KEYBOARD.shift;
|
||||
c = upper ? key[0] : (char_t)tolower(key[0]);
|
||||
UI_KEYBOARD.shift = false;
|
||||
} else {
|
||||
c = key[0];
|
||||
}
|
||||
@@ -221,31 +240,40 @@ void uiKeyboardOpen(const uikeyboardopen_t *open) {
|
||||
UI_KEYBOARD.allowBlank = open->allowBlank;
|
||||
UI_KEYBOARD.user = open->user;
|
||||
UI_KEYBOARD.confirmed = false;
|
||||
UI_KEYBOARD.caps = false;
|
||||
UI_KEYBOARD.shift = false;
|
||||
UI_KEYBOARD.open = true;
|
||||
|
||||
uint8_t index = uiKeyboardQwertyBuildItems(UI_KEYBOARD.items);
|
||||
// Builds the full fixed grid, including blank filler at the
|
||||
// newline/confirm/cancel reserved slots (see uikeyboardqwerty.h) -
|
||||
// patched with real buttons below where applicable.
|
||||
uiKeyboardQwertyBuildItems(UI_KEYBOARD.items);
|
||||
|
||||
if(UI_KEYBOARD.lineCount > 1) {
|
||||
UI_KEYBOARD.items[index].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
||||
uiButtonInit(&UI_KEYBOARD.items[index].button, UI_KEYBOARD_KEY_NEWLINE);
|
||||
index++;
|
||||
uiButtonInit(
|
||||
&UI_KEYBOARD.items[UI_KEYBOARD_QWERTY_NEWLINE_INDEX].button,
|
||||
UI_KEYBOARD_KEY_NEWLINE
|
||||
);
|
||||
}
|
||||
|
||||
UI_KEYBOARD.items[index].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
||||
uiButtonInit(&UI_KEYBOARD.items[index].button, UI_KEYBOARD_CONFIRM_LABEL);
|
||||
index++;
|
||||
uiButtonInit(
|
||||
&UI_KEYBOARD.items[UI_KEYBOARD_QWERTY_CONFIRM_INDEX].button,
|
||||
UI_KEYBOARD_CONFIRM_LABEL
|
||||
);
|
||||
|
||||
if(UI_KEYBOARD.cancel) {
|
||||
UI_KEYBOARD.items[index].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
||||
uiButtonInit(&UI_KEYBOARD.items[index].button, UI_KEYBOARD_CANCEL_LABEL);
|
||||
index++;
|
||||
uiButtonInit(
|
||||
&UI_KEYBOARD.items[UI_KEYBOARD_QWERTY_CANCEL_INDEX].button,
|
||||
UI_KEYBOARD_CANCEL_LABEL
|
||||
);
|
||||
}
|
||||
|
||||
uiMenuInit(
|
||||
&UI_KEYBOARD.menu, uiKeyboardMenuSelected, uiKeyboardMenuClosed, NULL
|
||||
);
|
||||
uiMenuSetItems(
|
||||
&UI_KEYBOARD.menu, UI_KEYBOARD.items, index, UI_KEYBOARD_QWERTY_COLUMNS
|
||||
&UI_KEYBOARD.menu, UI_KEYBOARD.items, UI_KEYBOARD_QWERTY_KEY_COUNT,
|
||||
UI_KEYBOARD_QWERTY_COLUMNS
|
||||
);
|
||||
|
||||
// Back deletes a character rather than closing the dialog outright -
|
||||
|
||||
@@ -15,18 +15,20 @@
|
||||
#define UI_KEYBOARD_TITLE_SPRITES_MAX UI_KEYBOARD_TITLE_TEXT_MAX
|
||||
#define UI_KEYBOARD_TEXT_MAX 64
|
||||
#define UI_KEYBOARD_TEXT_SPRITES_MAX UI_KEYBOARD_TEXT_MAX
|
||||
// Room for the active layout's keys plus the newline/confirm/cancel
|
||||
// buttons uiKeyboard appends itself - only the QWERTY layout exists
|
||||
// today.
|
||||
#define UI_KEYBOARD_ITEMS_MAX (UI_KEYBOARD_QWERTY_KEY_COUNT + 3)
|
||||
// The grid is a fixed size (see uikeyboardqwerty.h) - only the QWERTY
|
||||
// layout exists today, so this is just its key count.
|
||||
#define UI_KEYBOARD_ITEMS_MAX UI_KEYBOARD_QWERTY_KEY_COUNT
|
||||
|
||||
// Hardcoded for now - a uiModalOpen-style title/button text override is
|
||||
// planned (see uikeyboardopen_t) but not built yet.
|
||||
#define UI_KEYBOARD_TITLE_DEFAULT "ENTER YOUR TEXT"
|
||||
// planned (see uikeyboardopen_t) but not built yet. Patched into the
|
||||
// grid's reserved slots (UI_KEYBOARD_QWERTY_CONFIRM_INDEX etc.) rather
|
||||
// than appended, since the grid is a fixed physical-keyboard shape.
|
||||
#define UI_KEYBOARD_CONFIRM_LABEL "CONFIRM"
|
||||
#define UI_KEYBOARD_CANCEL_LABEL "CANCEL"
|
||||
// Only added to the grid when uikeyboardopen_t.lineCount > 1.
|
||||
// Only patched into UI_KEYBOARD_QWERTY_NEWLINE_INDEX when
|
||||
// uikeyboardopen_t.lineCount > 1 - otherwise that slot stays blank.
|
||||
#define UI_KEYBOARD_KEY_NEWLINE "NEWLINE"
|
||||
#define UI_KEYBOARD_TITLE_DEFAULT "ENTER YOUR TEXT"
|
||||
|
||||
#define UI_KEYBOARD_CONFIRM_QUESTION_MAX 64
|
||||
// Used when uikeyboardopen_t.confirmLabel is NULL.
|
||||
@@ -141,6 +143,15 @@ typedef struct {
|
||||
bool_t allowBlank;
|
||||
bool_t open;
|
||||
bool_t confirmed;
|
||||
|
||||
// Persistent caps-lock toggle, and a one-shot shift consumed by the
|
||||
// next letter typed - see uiKeyboardMenuSelected. A letter's effective
|
||||
// case is caps != shift (matches physical keyboards: shift while caps
|
||||
// is on gives lowercase). Key labels are always drawn uppercase
|
||||
// regardless of this state - only the appended character's case
|
||||
// changes.
|
||||
bool_t caps;
|
||||
bool_t shift;
|
||||
} uikeyboard_t;
|
||||
|
||||
extern uikeyboard_t UI_KEYBOARD;
|
||||
@@ -230,6 +241,8 @@ void uiKeyboardBackspace(void);
|
||||
* onKeyPress before a character key is applied. When UI_KEYBOARD.confirm
|
||||
* is set, picking confirm opens a second uiConfirm dialog (see
|
||||
* uiKeyboardConfirmSecondaryResult) instead of closing immediately.
|
||||
* SHIFT/CAPS toggle UI_KEYBOARD.shift/caps instead of entering anything.
|
||||
* A blank ("") label is a filler/reserved grid cell and is a no-op.
|
||||
*
|
||||
* @param menu The keyboard's menu.
|
||||
* @param index Index of the picked item.
|
||||
|
||||
@@ -13,12 +13,28 @@ uikeyboardqwerty_t UI_KEYBOARD_QWERTY;
|
||||
|
||||
// String literals have static storage duration, so these stay valid for
|
||||
// as long as the process runs - safe for uiButtonInit's non-copying
|
||||
// label contract.
|
||||
// label contract. "" cells are blank filler/reserved slots - see
|
||||
// uikeyboardqwerty.h.
|
||||
static const char_t *const UI_KEYBOARD_QWERTY_KEYS[UI_KEYBOARD_QWERTY_KEY_COUNT] = {
|
||||
"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P",
|
||||
"A", "S", "D", "F", "G", "H", "J", "K", "L",
|
||||
// ~ 1 2 3 4 5 6 7 8 9 0 - = DEL
|
||||
"~", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=",
|
||||
UI_KEYBOARD_QWERTY_KEY_BACKSPACE,
|
||||
|
||||
// Q W E R T Y U I O P
|
||||
"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "", "", "", "",
|
||||
|
||||
// CAPS A S D F G H J K L (NEWLINE)
|
||||
UI_KEYBOARD_QWERTY_KEY_CAPS,
|
||||
"A", "S", "D", "F", "G", "H", "J", "K", "L", "", "", "", "",
|
||||
|
||||
// SHIFT Z X C V B N M SHIFT
|
||||
UI_KEYBOARD_QWERTY_KEY_SHIFT,
|
||||
"Z", "X", "C", "V", "B", "N", "M",
|
||||
UI_KEYBOARD_QWERTY_KEY_SPACE, UI_KEYBOARD_QWERTY_KEY_BACKSPACE
|
||||
UI_KEYBOARD_QWERTY_KEY_SHIFT,
|
||||
"", "", "", "", "",
|
||||
|
||||
// (CANCEL) SPACE (CONFIRM)
|
||||
"", "", "", "", "", "", UI_KEYBOARD_QWERTY_KEY_SPACE, "", "", "", "", "", "", ""
|
||||
};
|
||||
|
||||
errorret_t uiKeyboardQwertyInit(void) {
|
||||
|
||||
@@ -9,18 +9,43 @@
|
||||
#include "error/error.h"
|
||||
#include "ui/widget/uimenu.h"
|
||||
|
||||
// 26 letters plus SPACE and DEL, laid out as one flat columns-wide grid
|
||||
// rather than the staggered rows a physical QWERTY keyboard uses - the
|
||||
// menu/focus grid only supports uniform columns today, so exact row
|
||||
// staggering is left for later.
|
||||
#define UI_KEYBOARD_QWERTY_COLUMNS 10
|
||||
#define UI_KEYBOARD_QWERTY_KEY_COUNT 28
|
||||
// A fixed 14x5 grid shaped like a physical keyboard:
|
||||
// ~ 1 2 3 4 5 6 7 8 9 0 - = DEL
|
||||
// Q W E R T Y U I O P
|
||||
// CAPS A S D F G H J K L (NEWLINE)
|
||||
// SHIFT Z X C V B N M SHIFT
|
||||
// (CANCEL) SPACE (CONFIRM)
|
||||
// Rows shorter than 14 columns are padded with blank filler buttons
|
||||
// (empty label) so every key lands in its real physical-keyboard
|
||||
// position - the menu/focus grid only supports uniform columns, it has
|
||||
// no concept of a key spanning multiple cells or a row being narrower
|
||||
// than the grid, so this is the only way to get real positioning out of
|
||||
// it. A filler cell is still a focusable/navigable-to grid slot, it just
|
||||
// does nothing when selected - see uiKeyboardMenuSelected's blank-label
|
||||
// check.
|
||||
//
|
||||
// NEWLINE/CANCEL/CONFIRM are reserved slots this builder always fills
|
||||
// with blank filler - uiKeyboard patches them with real buttons
|
||||
// depending on lineCount/cancel (CONFIRM is unconditional, just needed
|
||||
// as a fixed slot the caller can address by constant).
|
||||
#define UI_KEYBOARD_QWERTY_COLUMNS 14
|
||||
#define UI_KEYBOARD_QWERTY_ROWS 5
|
||||
#define UI_KEYBOARD_QWERTY_KEY_COUNT \
|
||||
(UI_KEYBOARD_QWERTY_COLUMNS * UI_KEYBOARD_QWERTY_ROWS)
|
||||
|
||||
// Reserved slot indices uiKeyboard patches with real buttons - see
|
||||
// uikeyboard.c's uiKeyboardOpen.
|
||||
#define UI_KEYBOARD_QWERTY_NEWLINE_INDEX 38
|
||||
#define UI_KEYBOARD_QWERTY_CANCEL_INDEX 56
|
||||
#define UI_KEYBOARD_QWERTY_CONFIRM_INDEX 69
|
||||
|
||||
// Labels uiKeyboard checks for by pointer/string compare (see
|
||||
// uiKeyboardQwertyBuildItems) to tell the space/backspace keys apart from
|
||||
// an ordinary single-letter key.
|
||||
// uiKeyboardMenuSelected) to tell special keys apart from an ordinary
|
||||
// single-character key. A blank ("") label is a filler cell - see above.
|
||||
#define UI_KEYBOARD_QWERTY_KEY_SPACE "SPACE"
|
||||
#define UI_KEYBOARD_QWERTY_KEY_BACKSPACE "DEL"
|
||||
#define UI_KEYBOARD_QWERTY_KEY_SHIFT "SHIFT"
|
||||
#define UI_KEYBOARD_QWERTY_KEY_CAPS "CAPS"
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -37,12 +62,13 @@ extern uikeyboardqwerty_t UI_KEYBOARD_QWERTY;
|
||||
errorret_t uiKeyboardQwertyInit(void);
|
||||
|
||||
/**
|
||||
* Builds the QWERTY layout's key buttons (26 letters, then SPACE and
|
||||
* DEL) into items, starting at index 0, one UI_MENU_WIDGET_TYPE_BUTTON
|
||||
* per key. Each button's label is either a single uppercase letter or the
|
||||
* literal UI_KEYBOARD_QWERTY_KEY_SPACE/UI_KEYBOARD_QWERTY_KEY_BACKSPACE -
|
||||
* uiKeyboard reads a selected item's own label back to know which key it
|
||||
* was, rather than this function returning a separate mapping.
|
||||
* Builds the QWERTY layout's key buttons into items, starting at index
|
||||
* 0, one UI_MENU_WIDGET_TYPE_BUTTON per grid cell (UI_KEYBOARD_QWERTY_KEY_COUNT
|
||||
* of them, in row-major order across UI_KEYBOARD_QWERTY_COLUMNS columns).
|
||||
* Each button's label is a single character, one of the
|
||||
* UI_KEYBOARD_QWERTY_KEY_* literals, or "" for a blank filler/reserved
|
||||
* slot - uiKeyboard reads a selected item's own label back to know which
|
||||
* key it was, rather than this function returning a separate mapping.
|
||||
*
|
||||
* @param items Destination array; must have room for at least
|
||||
* UI_KEYBOARD_QWERTY_KEY_COUNT entries.
|
||||
|
||||
Reference in New Issue
Block a user