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:
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -47,11 +48,12 @@ static const char_t *const UI_KEYBOARD_KEYS_NONE[UI_KEYBOARD_KEY_COUNT] = {
|
|||||||
UI_KEYBOARD_KEY_CAPS,
|
UI_KEYBOARD_KEY_CAPS,
|
||||||
"a", "s", "d", "f", "g", "h", "j", "k", "l", NULL,
|
"a", "s", "d", "f", "g", "h", "j", "k", "l", NULL,
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
// 1 2 3 4 5 6 7 8 9 0 DEL
|
// 1 2 3 4 5 6 7 8 9 0 DEL
|
||||||
// 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)
|
// CAPS a s d f g h j k l (NEWLINE)
|
||||||
// SHIFT z x c v b n m SHIFT
|
// SHIFT z x c v b n m SHIFT
|
||||||
// (CANCEL) SPACE (CONFIRM)
|
// (CANCEL) SPACE (CONFIRM)
|
||||||
// Rows shorter than 11 columns are padded with blank filler buttons
|
// Rows shorter than 11 columns are padded with blank filler buttons
|
||||||
// (empty label) so every key lands in its intended position - the menu/
|
// (empty label) so every key lands in its intended position - the menu/
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user