Add cancel confirmation, blinking cursor, custom keyboard icon glyphs

- uikeyboardopen_t gains cancelConfirm/cancelConfirmLabel, mirroring
  confirm/confirmLabel: cancelling (CANCEL button or back-with-nothing-
  left-to-delete) can now open an "are you sure?" dialog before closing.
- Added a blinking text-entry cursor, rendered as a separate label
  positioned at the end of the current text (accounting for newlines);
  it now stops at the last character rather than past it once maxLength
  is reached, matching uiKeyboardAppendChar's override-last-char
  behavior there.
- uibutton_t gains an `active` flag, independent of the focus-driven
  `highlighted` state, for a toggled-on indicator that survives focus
  moving elsewhere (used by the keyboard's CAPS/SHIFT keys).
- Added several custom icon glyphs to the default font by repurposing
  otherwise-blank/unused glyph slots (backtick, pipe, tilde, backslash,
  and the DEL trailing tile) - up arrows for Caps Lock/Shift, a return
  arrow for newline, a spacebar symbol, a left arrow for backspace, and
  a real underscore - replacing the keyboard's old text-abbreviation key
  labels ("CL"/"SL"/"NL"/"<") with single-glyph icons.
This commit is contained in:
2026-08-27 22:17:00 -05:00
parent 4387d223b9
commit a6e4e3f71f
7 changed files with 250 additions and 25 deletions
+33 -6
View File
@@ -76,11 +76,23 @@ const uint8_t FONT_DEFAULT_GLYPHS[FONT_DEFAULT_TILE_COUNT][
{ 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // Y { 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // Y
{ 0x00, 0x3E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3E, 0x00, 0x00 }, // Z { 0x00, 0x3E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3E, 0x00, 0x00 }, // Z
{ 0x00, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0C, 0x00, 0x00 }, // [ { 0x00, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0C, 0x00, 0x00 }, // [
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // backslash (not drawn in source font) // Custom icon glyph, not a real backslash - a backspace symbol: Caps
// Lock's up arrow (see FONT_ICON_CAPSLOCK) rotated 270 degrees to
// point left instead, adapted to this font's fixed 6x10 tile (each of
// Caps Lock's row widths becomes a column height here, centered
// vertically). Backslash was never drawn anyway, and isn't a key this
// virtual keyboard can type - see FONT_ICON_BACKSPACE.
{ 0x00, 0x00, 0x08, 0x1F, 0x3F, 0x3F, 0x1F, 0x08, 0x00, 0x00 }, // FONT_ICON_BACKSPACE
{ 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x18, 0x00, 0x00 }, // ] { 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x18, 0x00, 0x00 }, // ]
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ^ (not drawn in source font) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ^ (not drawn in source font)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // _ (not drawn in source font) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E }, // _
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ` (not drawn in source font) // Custom icon glyph, not a real backtick - an up arrow for Shift, same
// head as Caps Lock's (see FONT_ICON_CAPSLOCK) but with a shaft 2px
// thinner, plus one empty row near the bottom of it, so it reads as a
// lighter/broken version of Caps Lock's thicker uninterrupted one.
// Backtick was never drawn anyway, and isn't a key this virtual
// keyboard can type - see FONT_ICON_SHIFT.
{ 0x0C, 0x1E, 0x3F, 0x0C, 0x0C, 0x0C, 0x0C, 0x00, 0x0C, 0x00 }, // FONT_ICON_SHIFT
{ 0x00, 0x00, 0x00, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x00, 0x00 }, // a { 0x00, 0x00, 0x00, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x00, 0x00 }, // a
{ 0x00, 0x20, 0x20, 0x3C, 0x22, 0x22, 0x22, 0x3C, 0x00, 0x00 }, // b { 0x00, 0x20, 0x20, 0x3C, 0x22, 0x22, 0x22, 0x3C, 0x00, 0x00 }, // b
{ 0x00, 0x00, 0x00, 0x1C, 0x22, 0x20, 0x22, 0x1C, 0x00, 0x00 }, // c { 0x00, 0x00, 0x00, 0x1C, 0x22, 0x20, 0x22, 0x1C, 0x00, 0x00 }, // c
@@ -108,10 +120,25 @@ const uint8_t FONT_DEFAULT_GLYPHS[FONT_DEFAULT_TILE_COUNT][
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x1C }, // y { 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x1C }, // y
{ 0x00, 0x00, 0x00, 0x3E, 0x04, 0x08, 0x10, 0x3E, 0x00, 0x00 }, // z { 0x00, 0x00, 0x00, 0x3E, 0x04, 0x08, 0x10, 0x3E, 0x00, 0x00 }, // z
{ 0x00, 0x04, 0x08, 0x08, 0x10, 0x08, 0x08, 0x04, 0x00, 0x00 }, // { { 0x00, 0x04, 0x08, 0x08, 0x10, 0x08, 0x08, 0x04, 0x00, 0x00 }, // {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // | (not drawn in source font) // Custom icon glyph, not a real pipe - a "return" arrow for the
// keyboard's newline key: a vertical riser down the right side that
// hooks left into a leftward-pointing arrowhead, i.e. "<-|" rotated
// into an L. Pipe was never drawn anyway, and isn't a key this
// virtual keyboard can type - see FONT_ICON_NEWLINE.
{ 0x02, 0x02, 0x02, 0x02, 0x0E, 0x1E, 0x08, 0x00, 0x00, 0x00 }, // FONT_ICON_NEWLINE
{ 0x00, 0x10, 0x08, 0x08, 0x04, 0x08, 0x08, 0x10, 0x00, 0x00 }, // } { 0x00, 0x10, 0x08, 0x08, 0x04, 0x08, 0x08, 0x10, 0x00, 0x00 }, // }
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ~ (not drawn in source font) // Custom icon glyph, not a real tilde - a spacebar symbol for the
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // unused trailing tile // keyboard's space key: an underscore with a tick at each end, like
// "|___|". Tilde was never drawn anyway, and was explicitly dropped
// from this virtual keyboard's own key set - see FONT_ICON_SPACE.
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3F }, // FONT_ICON_SPACE
// Custom icon glyph, not a real ASCII character - a thick, solid,
// uninterrupted up arrow for Caps Lock (contrast FONT_ICON_SHIFT's
// same head but a thinner, gapped shaft). Assigned to char code 127
// (DEL) since that codepoint is never legitimately typed text and
// (unlike 128+) is still a positive value regardless of whether this
// platform's plain `char` is signed - see FONT_ICON_CAPSLOCK.
{ 0x0C, 0x1E, 0x3F, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x00 }, // FONT_ICON_CAPSLOCK
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // unused trailing tile { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // unused trailing tile
}; };
+36 -1
View File
@@ -28,10 +28,45 @@ typedef struct {
/** /**
* Number of glyphs defined in FONT_DEFAULT_GLYPHS (FONT_DEFAULT_COLUMNS * * Number of glyphs defined in FONT_DEFAULT_GLYPHS (FONT_DEFAULT_COLUMNS *
* FONT_DEFAULT_ROWS), covering the printable ASCII range starting at * FONT_DEFAULT_ROWS), covering the printable ASCII range starting at
* TEXT_CHAR_START ('!') plus a couple of unused trailing tiles. * TEXT_CHAR_START ('!'), one custom icon glyph in a trailing unused tile
* (see FONT_ICON_CAPSLOCK), plus one more genuinely unused trailing tile.
* FONT_ICON_SHIFT/FONT_ICON_NEWLINE reuse existing-but-blank slots within
* the printable range instead of more trailing tiles - see their own
* comments.
*/ */
#define FONT_DEFAULT_TILE_COUNT (FONT_DEFAULT_COLUMNS * FONT_DEFAULT_ROWS) #define FONT_DEFAULT_TILE_COUNT (FONT_DEFAULT_COLUMNS * FONT_DEFAULT_ROWS)
/**
* Custom (non-ASCII-meaning) icon glyphs baked into FONT_DEFAULT_GLYPHS
* at otherwise-unused codepoints within the range this font covers -
* safe to use anywhere a char_t string is expected, e.g. a uibutton_t
* label. Kept below 128: char_t is a plain `char`, whose signedness
* varies by platform, so a codepoint of 128 or above isn't safely
* representable everywhere this engine targets.
*/
// Thick, solid, uninterrupted up arrow - Caps Lock. A trailing tile
// (char code 127/DEL) that was never a real character to begin with.
#define FONT_ICON_CAPSLOCK "\x7F"
// Same up arrow, but with a thinner and gapped shaft - Shift. Reuses
// the backtick's glyph slot: backtick was never drawn by this font
// anyway, and isn't a key this engine's virtual keyboard can type.
#define FONT_ICON_SHIFT "`"
// A "return" arrow (down then left, with a leftward arrowhead) - the
// keyboard's newline key. Reuses the pipe's glyph slot, for the same
// reason as FONT_ICON_SHIFT.
#define FONT_ICON_NEWLINE "|"
// An underscore with a tick at each end ("|___|") - the keyboard's
// space key. Reuses the tilde's glyph slot: tilde was never drawn by
// this font, and was explicitly dropped from this engine's virtual
// keyboard's own key set.
#define FONT_ICON_SPACE "~"
// Caps Lock's arrow rotated to point left - the keyboard's backspace
// key. Reuses the backslash's glyph slot, for the same reason as
// FONT_ICON_SHIFT/FONT_ICON_NEWLINE/FONT_ICON_SPACE. Last free reused
// slot below 128 - one more custom icon after this needs
// FONT_DEFAULT_COLUMNS/ROWS grown to make room.
#define FONT_ICON_BACKSPACE "\\"
extern font_t FONT_DEFAULT; extern font_t FONT_DEFAULT;
/** /**
+80 -8
View File
@@ -17,12 +17,13 @@
#include "display/color.h" #include "display/color.h"
#include "display/text/font.h" #include "display/text/font.h"
#include "display/spritebatch/spritebatch.h" #include "display/spritebatch/spritebatch.h"
#include "time/time.h"
// Key cell size, expressed in font tiles so it scales if the font ever // Key cell size, expressed in font tiles so it scales if the font ever
// does. Row height is derived from this ratio in uiKeyboardDraw rather // does. Row height is derived from this ratio in uiKeyboardDraw rather
// than being its own fixed constant, so a key cell keeps its proportions // than being its own fixed constant, so a key cell keeps its proportions
// even when contentWidth is stretched wider by a long title/text label. // even when contentWidth is stretched wider by a long title/text label.
#define UI_KEYBOARD_KEY_WIDTH ((float_t)(FONT_DEFAULT_TILE_WIDTH * 5)) #define UI_KEYBOARD_KEY_WIDTH ((float_t)(FONT_DEFAULT_TILE_WIDTH * 4))
#define UI_KEYBOARD_KEY_HEIGHT ((float_t)(FONT_DEFAULT_TILE_HEIGHT * 2)) #define UI_KEYBOARD_KEY_HEIGHT ((float_t)(FONT_DEFAULT_TILE_HEIGHT * 2))
uikeyboard_t UI_KEYBOARD; uikeyboard_t UI_KEYBOARD;
@@ -49,9 +50,10 @@ static const char_t *const UI_KEYBOARD_KEYS_NONE[UI_KEYBOARD_KEY_COUNT] = {
// SHIFT z x c v b n m SHIFT // SHIFT z x c v b n m SHIFT
UI_KEYBOARD_KEY_SHIFT, UI_KEYBOARD_KEY_SHIFT,
NULL,
"z", "x", "c", "v", "b", "n", "m", "z", "x", "c", "v", "b", "n", "m",
NULL,
UI_KEYBOARD_KEY_SHIFT, UI_KEYBOARD_KEY_SHIFT,
NULL, NULL,
// (CANCEL) SPACE (CONFIRM) // (CANCEL) SPACE (CONFIRM)
NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL,
@@ -70,9 +72,10 @@ static const char_t *const UI_KEYBOARD_KEYS_CAPS[UI_KEYBOARD_KEY_COUNT] = {
"A", "S", "D", "F", "G", "H", "J", "K", "L", NULL, "A", "S", "D", "F", "G", "H", "J", "K", "L", NULL,
UI_KEYBOARD_KEY_SHIFT, UI_KEYBOARD_KEY_SHIFT,
NULL,
"Z", "X", "C", "V", "B", "N", "M", "Z", "X", "C", "V", "B", "N", "M",
NULL,
UI_KEYBOARD_KEY_SHIFT, UI_KEYBOARD_KEY_SHIFT,
NULL, NULL,
NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL,
NULL NULL
@@ -91,9 +94,10 @@ static const char_t *const UI_KEYBOARD_KEYS_SHIFT[UI_KEYBOARD_KEY_COUNT] = {
"A", "S", "D", "F", "G", "H", "J", "K", "L", NULL, "A", "S", "D", "F", "G", "H", "J", "K", "L", NULL,
UI_KEYBOARD_KEY_SHIFT, UI_KEYBOARD_KEY_SHIFT,
NULL,
"Z", "X", "C", "V", "B", "N", "M", "Z", "X", "C", "V", "B", "N", "M",
NULL,
UI_KEYBOARD_KEY_SHIFT, UI_KEYBOARD_KEY_SHIFT,
NULL, NULL,
NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL,
NULL NULL
@@ -141,8 +145,24 @@ void uiKeyboardUpdateDisplay(
const char_t *const *table = uiKeyboardSelectTable(shift, caps); const char_t *const *table = uiKeyboardSelectTable(shift, caps);
for(uint8_t i = 0; i < UI_KEYBOARD_KEY_COUNT; i++) { for(uint8_t i = 0; i < UI_KEYBOARD_KEY_COUNT; i++) {
// These reserved slots are blank in every shift/caps table - their
// real labels (NEWLINE/CANCEL/CONFIRM) are patched in once at open
// time and aren't affected by shift/caps, so leave them alone here.
if(
i == UI_KEYBOARD_NEWLINE_INDEX ||
i == UI_KEYBOARD_CANCEL_INDEX ||
i == UI_KEYBOARD_CONFIRM_INDEX
) continue;
items[i].button.label.text = uiKeyboardNormalizeKey(table[i]); items[i].button.label.text = uiKeyboardNormalizeKey(table[i]);
items[i].button.label.dirty = true; items[i].button.label.dirty = true;
const char_t *canonical = uiKeyboardGetCanonicalKey(i);
if(stringCompare(canonical, UI_KEYBOARD_KEY_CAPS) == 0) {
uiButtonSetActive(&items[i].button, caps);
} else if(stringCompare(canonical, UI_KEYBOARD_KEY_SHIFT) == 0) {
uiButtonSetActive(&items[i].button, shift);
}
} }
} }
@@ -244,8 +264,7 @@ bool_t uiKeyboardMenuCancel(const uimenu_t *menu) {
// and the next uiKeyboardOpen() tripping its already-open assert. // and the next uiKeyboardOpen() tripping its already-open assert.
if(!UI_KEYBOARD.cancel) return true; if(!UI_KEYBOARD.cancel) return true;
UI_KEYBOARD.confirmed = false; uiKeyboardDoCancel();
uiKeyboardClose();
return true; return true;
} }
@@ -256,6 +275,26 @@ void uiKeyboardConfirmSecondaryResult(const bool_t result, void *user) {
uiKeyboardClose(); uiKeyboardClose();
} }
void uiKeyboardDoCancel(void) {
if(UI_KEYBOARD.cancelConfirm) {
uiConfirmOpen(
UI_KEYBOARD.cancelConfirmQuestion,
uiKeyboardCancelConfirmSecondaryResult, NULL
);
return;
}
UI_KEYBOARD.confirmed = false;
uiKeyboardClose();
}
void uiKeyboardCancelConfirmSecondaryResult(const bool_t result, void *user) {
if(!result) return;
UI_KEYBOARD.confirmed = false;
uiKeyboardClose();
}
void uiKeyboardMenuSelected( void uiKeyboardMenuSelected(
const uimenu_t *menu, const uimenu_t *menu,
const uint8_t index, const uint8_t index,
@@ -282,8 +321,7 @@ void uiKeyboardMenuSelected(
} }
if(UI_KEYBOARD.cancel && index == UI_KEYBOARD_CANCEL_INDEX) { if(UI_KEYBOARD.cancel && index == UI_KEYBOARD_CANCEL_INDEX) {
UI_KEYBOARD.confirmed = false; uiKeyboardDoCancel();
uiKeyboardClose();
return; return;
} }
@@ -360,6 +398,11 @@ errorret_t uiKeyboardInit(void) {
&UI_KEYBOARD.textLabel, UI_KEYBOARD.text, &UI_KEYBOARD.textLabel, UI_KEYBOARD.text,
UI_KEYBOARD.textSprites, UI_KEYBOARD_TEXT_SPRITES_MAX UI_KEYBOARD.textSprites, UI_KEYBOARD_TEXT_SPRITES_MAX
); );
uiLabelInit(
&UI_KEYBOARD.cursorLabel, UI_KEYBOARD_CURSOR_TEXT,
UI_KEYBOARD.cursorSprites, UI_KEYBOARD_CURSOR_SPRITES_MAX
);
UI_KEYBOARD.cursorLabel.dirty = true;
errorOk(); errorOk();
} }
@@ -507,6 +550,35 @@ errorret_t uiKeyboardDraw(void) {
uiLabelSetY(&UI_KEYBOARD.textLabel, textY); uiLabelSetY(&UI_KEYBOARD.textLabel, textY);
errorChain(uiLabelRender(&UI_KEYBOARD.textLabel, COLOR_WHITE)); errorChain(uiLabelRender(&UI_KEYBOARD.textLabel, COLOR_WHITE));
// Insertion point is always the end of text - there's no mid-string
// cursor movement - so find it by counting characters since the last
// newline (column) and newlines overall (line). Once maxLength is
// reached, a new key overrides the last character (see
// uiKeyboardAppendChar) rather than appending one, so stop one
// character short here too - the cursor should sit on that last
// character, not past it, to reflect what typing will actually do.
size_t textLength = strlen(UI_KEYBOARD.text);
size_t cursorLimit = textLength >= UI_KEYBOARD.maxLength ?
textLength - 1 : textLength;
uint8_t cursorLine = 0;
uint8_t cursorCol = 0;
for(size_t i = 0; i < cursorLimit; i++) {
if(UI_KEYBOARD.text[i] == '\n') { cursorLine++; cursorCol = 0; }
else cursorCol++;
}
if(mathModFloat(TIME.time, UI_KEYBOARD_CURSOR_BLINK_RATE * 2.0f) <
UI_KEYBOARD_CURSOR_BLINK_RATE
) {
uiLabelSetX(
&UI_KEYBOARD.cursorLabel,
contentX + (float_t)cursorCol * (float_t)FONT_DEFAULT_TILE_WIDTH
);
uiLabelSetY(&UI_KEYBOARD.cursorLabel, textY + (float_t)cursorLine * rowHeight);
errorChain(uiLabelRender(&UI_KEYBOARD.cursorLabel, COLOR_WHITE));
}
// Drawn directly rather than via uiMenuDraw - that helper hardcodes row // Drawn directly rather than via uiMenuDraw - that helper hardcodes row
// spacing to the font's native tile height, which would keep the grid // spacing to the font's native tile height, which would keep the grid
// vertically compact no matter how much contentWidth grows. Focus // vertically compact no matter how much contentWidth grows. Focus
+59 -5
View File
@@ -9,6 +9,7 @@
#include "error/error.h" #include "error/error.h"
#include "ui/widget/uilabel.h" #include "ui/widget/uilabel.h"
#include "ui/widget/uimenu.h" #include "ui/widget/uimenu.h"
#include "display/text/font.h"
// A fixed 11x5 QWERTY grid - the only layout that exists (numbers/ // A fixed 11x5 QWERTY grid - the only layout that exists (numbers/
// symbols layouts were removed as separate concepts; digits already // symbols layouts were removed as separate concepts; digits already
@@ -46,13 +47,13 @@
// tell special keys apart from an ordinary single-character key. A blank // tell special keys apart from an ordinary single-character key. A blank
// ("") label is a filler cell - see above. Identical across every // ("") label is a filler cell - see above. Identical across every
// UI_KEYBOARD_KEYS_* table, since these don't change with shift/caps. // UI_KEYBOARD_KEYS_* table, since these don't change with shift/caps.
#define UI_KEYBOARD_KEY_SPACE "SPACE" #define UI_KEYBOARD_KEY_SPACE FONT_ICON_SPACE
#define UI_KEYBOARD_KEY_BACKSPACE "DEL" #define UI_KEYBOARD_KEY_BACKSPACE FONT_ICON_BACKSPACE
#define UI_KEYBOARD_KEY_SHIFT "SHIFT" #define UI_KEYBOARD_KEY_SHIFT FONT_ICON_SHIFT
#define UI_KEYBOARD_KEY_CAPS "CAPS" #define UI_KEYBOARD_KEY_CAPS FONT_ICON_CAPSLOCK
// Only patched into UI_KEYBOARD_NEWLINE_INDEX when // Only patched into UI_KEYBOARD_NEWLINE_INDEX when
// uikeyboardopen_t.lineCount > 1 - otherwise that slot stays blank. // uikeyboardopen_t.lineCount > 1 - otherwise that slot stays blank.
#define UI_KEYBOARD_KEY_NEWLINE "NEWLINE" #define UI_KEYBOARD_KEY_NEWLINE FONT_ICON_NEWLINE
#define UI_KEYBOARD_TITLE_TEXT_MAX 64 #define UI_KEYBOARD_TITLE_TEXT_MAX 64
#define UI_KEYBOARD_TITLE_SPRITES_MAX UI_KEYBOARD_TITLE_TEXT_MAX #define UI_KEYBOARD_TITLE_SPRITES_MAX UI_KEYBOARD_TITLE_TEXT_MAX
@@ -74,9 +75,20 @@
// Used when uikeyboardopen_t.confirmLabel is NULL. // Used when uikeyboardopen_t.confirmLabel is NULL.
#define UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT "Is this correct?" #define UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT "Is this correct?"
#define UI_KEYBOARD_CANCEL_QUESTION_MAX 64
// Used when uikeyboardopen_t.cancelConfirmLabel is NULL.
#define UI_KEYBOARD_CANCEL_QUESTION_DEFAULT "Discard your changes?"
// Maximum number of lines uikeyboardopen_t.lineCount may request. // Maximum number of lines uikeyboardopen_t.lineCount may request.
#define UI_KEYBOARD_LINE_COUNT_MAX 4 #define UI_KEYBOARD_LINE_COUNT_MAX 4
// Marks where the next typed character will be inserted (always the end
// of the current text - there's no mid-string cursor movement).
#define UI_KEYBOARD_CURSOR_TEXT "_"
#define UI_KEYBOARD_CURSOR_SPRITES_MAX 1
// Seconds per on/off half-cycle of the cursor blink.
#define UI_KEYBOARD_CURSOR_BLINK_RATE 0.5f
/** /**
* Callback invoked once the keyboard dialog is dismissed. * Callback invoked once the keyboard dialog is dismissed.
* *
@@ -138,6 +150,20 @@ typedef struct {
// UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT. Ignored when confirm is false. // UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT. Ignored when confirm is false.
const char_t *confirmLabel; const char_t *confirmLabel;
// When true, actually cancelling (the CANCEL button, or back with
// nothing left to delete - see uiKeyboardMenuCancel) opens a second
// uiConfirm "are you sure" dialog (see cancelConfirmLabel) before the
// keyboard closes - picking Cancel there returns to the keyboard with
// the text untouched. Ignored when cancel is false, since there's
// nothing to confirm.
bool_t cancelConfirm;
// Question text/locale message ID for the cancel confirm dialog when
// cancelConfirm is true; copied internally, safe to be transient. NULL
// uses UI_KEYBOARD_CANCEL_QUESTION_DEFAULT. Ignored when cancelConfirm
// is false.
const char_t *cancelConfirmLabel;
// If true, leading/trailing whitespace is trimmed from the entered // If true, leading/trailing whitespace is trimmed from the entered
// text when confirm is pressed, before it's checked against // text when confirm is pressed, before it's checked against
// allowBlank or passed to onInput. // allowBlank or passed to onInput.
@@ -167,10 +193,18 @@ typedef struct {
char_t text[UI_KEYBOARD_TEXT_MAX]; char_t text[UI_KEYBOARD_TEXT_MAX];
spritebatchsprite_t textSprites[UI_KEYBOARD_TEXT_SPRITES_MAX]; spritebatchsprite_t textSprites[UI_KEYBOARD_TEXT_SPRITES_MAX];
// Blinking insertion-point marker, positioned each draw at the end of
// text - a separate label rather than appending to text/textSprites,
// since neither buffer has spare room for it (see
// UI_KEYBOARD_TEXT_MAX/UI_KEYBOARD_TEXT_SPRITES_MAX).
uilabel_t cursorLabel;
spritebatchsprite_t cursorSprites[UI_KEYBOARD_CURSOR_SPRITES_MAX];
uimenu_t menu; uimenu_t menu;
uimenuitem_t items[UI_KEYBOARD_ITEMS_MAX]; uimenuitem_t items[UI_KEYBOARD_ITEMS_MAX];
char_t confirmQuestion[UI_KEYBOARD_CONFIRM_QUESTION_MAX]; char_t confirmQuestion[UI_KEYBOARD_CONFIRM_QUESTION_MAX];
char_t cancelConfirmQuestion[UI_KEYBOARD_CANCEL_QUESTION_MAX];
uikeyboardinputcallback_t onInput; uikeyboardinputcallback_t onInput;
uikeyboardkeypresscallback_t onKeyPress; uikeyboardkeypresscallback_t onKeyPress;
@@ -179,6 +213,7 @@ typedef struct {
uint8_t lineCount; uint8_t lineCount;
bool_t cancel; bool_t cancel;
bool_t confirm; bool_t confirm;
bool_t cancelConfirm;
bool_t trimmed; bool_t trimmed;
bool_t allowBlank; bool_t allowBlank;
bool_t open; bool_t open;
@@ -300,6 +335,25 @@ void uiKeyboardMenuSelected(
*/ */
void uiKeyboardConfirmSecondaryResult(const bool_t result, void *user); void uiKeyboardConfirmSecondaryResult(const bool_t result, void *user);
/**
* Actually cancels the keyboard - sets confirmed to false and closes the
* dialog. Shared by uiKeyboardMenuCancel (back with nothing to delete)
* and uiKeyboardMenuSelected (the CANCEL button); called directly when
* UI_KEYBOARD.cancelConfirm is false, or as the result callback (see
* uiKeyboardCancelConfirmSecondaryResult) once the "are you sure" dialog
* confirms it when cancelConfirm is true.
*/
void uiKeyboardDoCancel(void);
/**
* Result callback for the "are you sure" uiConfirm dialog opened when
* UI_KEYBOARD.cancelConfirm is set - see uiKeyboardDoCancel.
*
* @param result True if Confirm was picked there, false if Cancel.
* @param user Unused.
*/
void uiKeyboardCancelConfirmSecondaryResult(const bool_t result, void *user);
/** /**
* Internal menu callback - fires the result callback set by * Internal menu callback - fires the result callback set by
* uiKeyboardOpen once the menu finishes closing. * uiKeyboardOpen once the menu finishes closing.
+4 -2
View File
@@ -72,11 +72,13 @@ void uiMainMenuSelected(
uikeyboardopen_t open = { uikeyboardopen_t open = {
.onInput = uiMainMenuKeyboardTestInput, .onInput = uiMainMenuKeyboardTestInput,
.onKeyPress = uiMainMenuKeyboardTestKeyPress, .onKeyPress = uiMainMenuKeyboardTestKeyPress,
.cancel = false, .cancel = true,
.confirm = true, .confirm = true,
.cancelConfirm = true,
.user = NULL, .user = NULL,
.maxLength = 6, .maxLength = 6,
.lineCount = 2 .lineCount = 2,
.allowBlank = true,
}; };
uiKeyboardOpen(&open); uiKeyboardOpen(&open);
break; break;
+14 -1
View File
@@ -29,6 +29,16 @@ void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted) {
button->highlighted = highlighted; button->highlighted = highlighted;
} }
bool_t uiButtonIsActive(const uibutton_t *button) {
assertNotNull(button, "Button cannot be NULL");
return button->active;
}
void uiButtonSetActive(uibutton_t *button, const bool_t active) {
assertNotNull(button, "Button cannot be NULL");
button->active = active;
}
errorret_t uiButtonDraw( errorret_t uiButtonDraw(
uibutton_t *button, uibutton_t *button,
const float_t x, const float_t x,
@@ -40,6 +50,9 @@ errorret_t uiButtonDraw(
uiLabelSetY(&button->label, y); uiLabelSetY(&button->label, y);
return uiLabelRender( return uiLabelRender(
&button->label, button->highlighted ? COLOR_RED : COLOR_WHITE &button->label,
button->active ? COLOR_GREEN :
button->highlighted ? COLOR_RED :
COLOR_WHITE
); );
} }
+22
View File
@@ -15,6 +15,11 @@ typedef struct {
uilabel_t label; uilabel_t label;
spritebatchsprite_t sprites[UI_BUTTON_SPRITES_MAX]; spritebatchsprite_t sprites[UI_BUTTON_SPRITES_MAX];
bool_t highlighted; bool_t highlighted;
// Independent of highlighted (which the focus system drives based on
// what's currently focused) - for a button that also needs to show a
// toggled-on state regardless of focus, e.g. the keyboard's CAPS/SHIFT
// keys.
bool_t active;
} uibutton_t; } uibutton_t;
/** /**
@@ -42,6 +47,23 @@ bool_t uiButtonIsHighlighted(const uibutton_t *button);
*/ */
void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted); void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted);
/**
* Returns whether the button is active.
*
* @param button The button to query.
* @returns True if active.
*/
bool_t uiButtonIsActive(const uibutton_t *button);
/**
* Sets the active state of the button - a toggled-on indicator drawn
* the same way as highlighted, but independent of focus.
*
* @param button The button to update.
* @param active The new active state.
*/
void uiButtonSetActive(uibutton_t *button, const bool_t active);
/** /**
* Draws the button at the given screen position. * Draws the button at the given screen position.
* *