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;
|
uikeyboard_t UI_KEYBOARD;
|
||||||
|
|
||||||
void uiKeyboardFocusConfirm(void) {
|
void uiKeyboardFocusConfirm(void) {
|
||||||
uint8_t confirmIndex = UI_KEYBOARD.menu.itemCount - (UI_KEYBOARD.cancel ? 2 : 1);
|
|
||||||
uiMenuSetPosition(
|
uiMenuSetPosition(
|
||||||
&UI_KEYBOARD.menu,
|
&UI_KEYBOARD.menu,
|
||||||
confirmIndex % UI_KEYBOARD.menu.columns,
|
UI_KEYBOARD_QWERTY_CONFIRM_INDEX % UI_KEYBOARD_QWERTY_COLUMNS,
|
||||||
confirmIndex / UI_KEYBOARD.menu.columns
|
UI_KEYBOARD_QWERTY_CONFIRM_INDEX / UI_KEYBOARD_QWERTY_COLUMNS
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,8 +110,7 @@ void uiKeyboardMenuSelected(
|
|||||||
const uint8_t index,
|
const uint8_t index,
|
||||||
const uimenuitem_t *item
|
const uimenuitem_t *item
|
||||||
) {
|
) {
|
||||||
uint8_t confirmIndex = menu->itemCount - (UI_KEYBOARD.cancel ? 2 : 1);
|
if(index == UI_KEYBOARD_QWERTY_CONFIRM_INDEX) {
|
||||||
if(index == confirmIndex) {
|
|
||||||
if(UI_KEYBOARD.trimmed) {
|
if(UI_KEYBOARD.trimmed) {
|
||||||
stringTrim(UI_KEYBOARD.text);
|
stringTrim(UI_KEYBOARD.text);
|
||||||
UI_KEYBOARD.textLabel.dirty = true;
|
UI_KEYBOARD.textLabel.dirty = true;
|
||||||
@@ -132,18 +130,32 @@ void uiKeyboardMenuSelected(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(UI_KEYBOARD.cancel && index == menu->itemCount - 1) {
|
if(UI_KEYBOARD.cancel && index == UI_KEYBOARD_QWERTY_CANCEL_INDEX) {
|
||||||
UI_KEYBOARD.confirmed = false;
|
UI_KEYBOARD.confirmed = false;
|
||||||
uiKeyboardClose();
|
uiKeyboardClose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char_t *key = item->button.label.text;
|
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) {
|
if(stringCompare(key, UI_KEYBOARD_QWERTY_KEY_BACKSPACE) == 0) {
|
||||||
uiKeyboardBackspace();
|
uiKeyboardBackspace();
|
||||||
return;
|
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;
|
char_t c;
|
||||||
if(stringCompare(key, UI_KEYBOARD_QWERTY_KEY_SPACE) == 0) {
|
if(stringCompare(key, UI_KEYBOARD_QWERTY_KEY_SPACE) == 0) {
|
||||||
c = ' ';
|
c = ' ';
|
||||||
@@ -152,6 +164,13 @@ void uiKeyboardMenuSelected(
|
|||||||
stringCompare(key, UI_KEYBOARD_KEY_NEWLINE) == 0
|
stringCompare(key, UI_KEYBOARD_KEY_NEWLINE) == 0
|
||||||
) {
|
) {
|
||||||
c = '\n';
|
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 {
|
} else {
|
||||||
c = key[0];
|
c = key[0];
|
||||||
}
|
}
|
||||||
@@ -221,31 +240,40 @@ void uiKeyboardOpen(const uikeyboardopen_t *open) {
|
|||||||
UI_KEYBOARD.allowBlank = open->allowBlank;
|
UI_KEYBOARD.allowBlank = open->allowBlank;
|
||||||
UI_KEYBOARD.user = open->user;
|
UI_KEYBOARD.user = open->user;
|
||||||
UI_KEYBOARD.confirmed = false;
|
UI_KEYBOARD.confirmed = false;
|
||||||
|
UI_KEYBOARD.caps = false;
|
||||||
|
UI_KEYBOARD.shift = false;
|
||||||
UI_KEYBOARD.open = true;
|
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) {
|
if(UI_KEYBOARD.lineCount > 1) {
|
||||||
UI_KEYBOARD.items[index].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
uiButtonInit(
|
||||||
uiButtonInit(&UI_KEYBOARD.items[index].button, UI_KEYBOARD_KEY_NEWLINE);
|
&UI_KEYBOARD.items[UI_KEYBOARD_QWERTY_NEWLINE_INDEX].button,
|
||||||
index++;
|
UI_KEYBOARD_KEY_NEWLINE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
UI_KEYBOARD.items[index].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
uiButtonInit(
|
||||||
uiButtonInit(&UI_KEYBOARD.items[index].button, UI_KEYBOARD_CONFIRM_LABEL);
|
&UI_KEYBOARD.items[UI_KEYBOARD_QWERTY_CONFIRM_INDEX].button,
|
||||||
index++;
|
UI_KEYBOARD_CONFIRM_LABEL
|
||||||
|
);
|
||||||
|
|
||||||
if(UI_KEYBOARD.cancel) {
|
if(UI_KEYBOARD.cancel) {
|
||||||
UI_KEYBOARD.items[index].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
uiButtonInit(
|
||||||
uiButtonInit(&UI_KEYBOARD.items[index].button, UI_KEYBOARD_CANCEL_LABEL);
|
&UI_KEYBOARD.items[UI_KEYBOARD_QWERTY_CANCEL_INDEX].button,
|
||||||
index++;
|
UI_KEYBOARD_CANCEL_LABEL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
uiMenuInit(
|
uiMenuInit(
|
||||||
&UI_KEYBOARD.menu, uiKeyboardMenuSelected, uiKeyboardMenuClosed, NULL
|
&UI_KEYBOARD.menu, uiKeyboardMenuSelected, uiKeyboardMenuClosed, NULL
|
||||||
);
|
);
|
||||||
uiMenuSetItems(
|
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 -
|
// 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_TITLE_SPRITES_MAX UI_KEYBOARD_TITLE_TEXT_MAX
|
||||||
#define UI_KEYBOARD_TEXT_MAX 64
|
#define UI_KEYBOARD_TEXT_MAX 64
|
||||||
#define UI_KEYBOARD_TEXT_SPRITES_MAX UI_KEYBOARD_TEXT_MAX
|
#define UI_KEYBOARD_TEXT_SPRITES_MAX UI_KEYBOARD_TEXT_MAX
|
||||||
// Room for the active layout's keys plus the newline/confirm/cancel
|
// The grid is a fixed size (see uikeyboardqwerty.h) - only the QWERTY
|
||||||
// buttons uiKeyboard appends itself - only the QWERTY layout exists
|
// layout exists today, so this is just its key count.
|
||||||
// today.
|
#define UI_KEYBOARD_ITEMS_MAX UI_KEYBOARD_QWERTY_KEY_COUNT
|
||||||
#define UI_KEYBOARD_ITEMS_MAX (UI_KEYBOARD_QWERTY_KEY_COUNT + 3)
|
|
||||||
|
|
||||||
// Hardcoded for now - a uiModalOpen-style title/button text override is
|
// Hardcoded for now - a uiModalOpen-style title/button text override is
|
||||||
// planned (see uikeyboardopen_t) but not built yet.
|
// planned (see uikeyboardopen_t) but not built yet. Patched into the
|
||||||
#define UI_KEYBOARD_TITLE_DEFAULT "ENTER YOUR TEXT"
|
// 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_CONFIRM_LABEL "CONFIRM"
|
||||||
#define UI_KEYBOARD_CANCEL_LABEL "CANCEL"
|
#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_KEY_NEWLINE "NEWLINE"
|
||||||
|
#define UI_KEYBOARD_TITLE_DEFAULT "ENTER YOUR TEXT"
|
||||||
|
|
||||||
#define UI_KEYBOARD_CONFIRM_QUESTION_MAX 64
|
#define UI_KEYBOARD_CONFIRM_QUESTION_MAX 64
|
||||||
// Used when uikeyboardopen_t.confirmLabel is NULL.
|
// Used when uikeyboardopen_t.confirmLabel is NULL.
|
||||||
@@ -141,6 +143,15 @@ typedef struct {
|
|||||||
bool_t allowBlank;
|
bool_t allowBlank;
|
||||||
bool_t open;
|
bool_t open;
|
||||||
bool_t confirmed;
|
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;
|
} uikeyboard_t;
|
||||||
|
|
||||||
extern uikeyboard_t UI_KEYBOARD;
|
extern uikeyboard_t UI_KEYBOARD;
|
||||||
@@ -230,6 +241,8 @@ void uiKeyboardBackspace(void);
|
|||||||
* onKeyPress before a character key is applied. When UI_KEYBOARD.confirm
|
* onKeyPress before a character key is applied. When UI_KEYBOARD.confirm
|
||||||
* is set, picking confirm opens a second uiConfirm dialog (see
|
* is set, picking confirm opens a second uiConfirm dialog (see
|
||||||
* uiKeyboardConfirmSecondaryResult) instead of closing immediately.
|
* 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 menu The keyboard's menu.
|
||||||
* @param index Index of the picked item.
|
* @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
|
// String literals have static storage duration, so these stay valid for
|
||||||
// as long as the process runs - safe for uiButtonInit's non-copying
|
// 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] = {
|
static const char_t *const UI_KEYBOARD_QWERTY_KEYS[UI_KEYBOARD_QWERTY_KEY_COUNT] = {
|
||||||
"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P",
|
// ~ 1 2 3 4 5 6 7 8 9 0 - = DEL
|
||||||
"A", "S", "D", "F", "G", "H", "J", "K", "L",
|
"~", "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",
|
"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) {
|
errorret_t uiKeyboardQwertyInit(void) {
|
||||||
|
|||||||
@@ -9,18 +9,43 @@
|
|||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "ui/widget/uimenu.h"
|
#include "ui/widget/uimenu.h"
|
||||||
|
|
||||||
// 26 letters plus SPACE and DEL, laid out as one flat columns-wide grid
|
// A fixed 14x5 grid shaped like a physical keyboard:
|
||||||
// rather than the staggered rows a physical QWERTY keyboard uses - the
|
// ~ 1 2 3 4 5 6 7 8 9 0 - = DEL
|
||||||
// menu/focus grid only supports uniform columns today, so exact row
|
// Q W E R T Y U I O P
|
||||||
// staggering is left for later.
|
// CAPS A S D F G H J K L (NEWLINE)
|
||||||
#define UI_KEYBOARD_QWERTY_COLUMNS 10
|
// SHIFT Z X C V B N M SHIFT
|
||||||
#define UI_KEYBOARD_QWERTY_KEY_COUNT 28
|
// (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
|
// Labels uiKeyboard checks for by pointer/string compare (see
|
||||||
// uiKeyboardQwertyBuildItems) to tell the space/backspace keys apart from
|
// uiKeyboardMenuSelected) to tell special keys apart from an ordinary
|
||||||
// an ordinary single-letter key.
|
// single-character key. A blank ("") label is a filler cell - see above.
|
||||||
#define UI_KEYBOARD_QWERTY_KEY_SPACE "SPACE"
|
#define UI_KEYBOARD_QWERTY_KEY_SPACE "SPACE"
|
||||||
#define UI_KEYBOARD_QWERTY_KEY_BACKSPACE "DEL"
|
#define UI_KEYBOARD_QWERTY_KEY_BACKSPACE "DEL"
|
||||||
|
#define UI_KEYBOARD_QWERTY_KEY_SHIFT "SHIFT"
|
||||||
|
#define UI_KEYBOARD_QWERTY_KEY_CAPS "CAPS"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
||||||
@@ -37,12 +62,13 @@ extern uikeyboardqwerty_t UI_KEYBOARD_QWERTY;
|
|||||||
errorret_t uiKeyboardQwertyInit(void);
|
errorret_t uiKeyboardQwertyInit(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the QWERTY layout's key buttons (26 letters, then SPACE and
|
* Builds the QWERTY layout's key buttons into items, starting at index
|
||||||
* DEL) into items, starting at index 0, one UI_MENU_WIDGET_TYPE_BUTTON
|
* 0, one UI_MENU_WIDGET_TYPE_BUTTON per grid cell (UI_KEYBOARD_QWERTY_KEY_COUNT
|
||||||
* per key. Each button's label is either a single uppercase letter or the
|
* of them, in row-major order across UI_KEYBOARD_QWERTY_COLUMNS columns).
|
||||||
* literal UI_KEYBOARD_QWERTY_KEY_SPACE/UI_KEYBOARD_QWERTY_KEY_BACKSPACE -
|
* Each button's label is a single character, one of the
|
||||||
* uiKeyboard reads a selected item's own label back to know which key it
|
* UI_KEYBOARD_QWERTY_KEY_* literals, or "" for a blank filler/reserved
|
||||||
* was, rather than this function returning a separate mapping.
|
* 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
|
* @param items Destination array; must have room for at least
|
||||||
* UI_KEYBOARD_QWERTY_KEY_COUNT entries.
|
* UI_KEYBOARD_QWERTY_KEY_COUNT entries.
|
||||||
|
|||||||
Reference in New Issue
Block a user