Screen Keyboard
Source: src/screen_keyboard.c · 232 lines · 12 functions.
Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
nameCharacter() · source line 39
static bool nameCharacter(char character)
- Exit this function
return (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') || character == '_' || character == ' ';
Read original function source
static bool nameCharacter(char character)
{
return (character >= 'A' && character <= 'Z') ||
(character >= '0' && character <= '9') || character == '_' || character == ' ';
}textCharacter() · source line 45
static bool textCharacter(char character)
- Exit this function
return character >= 32 && character <= 126;
Read original function source
static bool textCharacter(char character)
{
return character >= 32 && character <= 126;
}currentCharacter() · source line 50
static char currentCharacter(size_t key)
- Decision
key < 26UTrue- Execute
char character = letters[key]; - Exit this function
return lowerCase && !symbolPage ? (char)(character + ('a' - 'A')) : character;
FalseContinue after this decision.
- Decision
key < KEYBOARD_CHARACTER_KEYSTrue- Exit this function
return letters[key];
FalseContinue after this decision.
- Exit this function
return '\0';
Read original function source
static char currentCharacter(size_t key)
{
if (key < 26U) {
char character = letters[key];
return lowerCase && !symbolPage ? (char)(character + ('a' - 'A')) : character;
}
if (key < KEYBOARD_CHARACTER_KEYS) return letters[key];
return '\0';
}appendCharacter() · source line 60
static void appendCharacter(char character)
- Decision
textLength >= characterLimit || character == '\0'True- Exit this function
return;
FalseContinue after this decision.
- Execute
text[textLength++] = character; - Execute
text[textLength] = '\0';
Read original function source
static void appendCharacter(char character)
{
if (textLength >= characterLimit || character == '\0') return;
text[textLength++] = character;
text[textLength] = '\0';
}hasNonSpaceText() · source line 67
static bool hasNonSpaceText(void)
- FOR loop
size_t index = 0; index < textLength; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
text[index] != ' 'True- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool hasNonSpaceText(void)
{
for (size_t index = 0; index < textLength; ++index) {
if (text[index] != ' ') return true;
}
return false;
}copyHeading() · source line 75
static void copyHeading(const char *title)
- Execute
const char *source = title == NULL ? "ENTER TEXT" : title; - Execute
size_t length = 0; - WHILE loop
source[length] != '\0' && length < SCREEN_KEYBOARD_TITLE_CHARSRepeat body while the loop condition permits; then continue below.
- Execute
heading[length] = source[length]; - Execute
++length;
- Execute
heading[length] = '\0';
Read original function source
static void copyHeading(const char *title)
{
const char *source = title == NULL ? "ENTER TEXT" : title;
size_t length = 0;
while (source[length] != '\0' && length < SCREEN_KEYBOARD_TITLE_CHARS) {
heading[length] = source[length];
++length;
}
heading[length] = '\0';
}startScreenKeyboard() · source line 86
void startScreenKeyboard(const char *initialText, size_t maxChars, KeyboardMode inputMode,
const char *title, uint32_t nowMs)Calls into this documentation:
- Execute
mode = inputMode; - Execute
characterLimit = maxChars > SCREEN_KEYBOARD_MAX_CHARS ? SCREEN_KEYBOARD_MAX_CHARS : maxChars; - Execute
textLength = 0; - Execute
lowerCase = inputMode != KEYBOARD_NAME; - Execute
symbolPage = false; - Execute
copyHeading(title); - Decision
initialText != NULLTrue- FOR loop
size_t index = 0; initialText[index] != '\0' && textLength < characterLimit; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
char character = initialText[index]; - Decision
mode == KEYBOARD_NAME && character >= 'a' && character <= 'z'True- Execute
character = (char)(character - ('a' - 'A'));
FalseContinue after this decision.
- Decision
(mode == KEYBOARD_NAME && nameCharacter(character)) || (mode != KEYBOARD_NAME && textCharacter(character))True- Execute
appendCharacter(character);
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
selectedKey = 0; - Execute
lastMove = nowMs; - Execute
result = KEYBOARD_EDITING;
Read original function source
void startScreenKeyboard(const char *initialText, size_t maxChars, KeyboardMode inputMode,
const char *title, uint32_t nowMs)
{
mode = inputMode;
characterLimit = maxChars > SCREEN_KEYBOARD_MAX_CHARS ? SCREEN_KEYBOARD_MAX_CHARS : maxChars;
textLength = 0;
// Password and general text entry normally begin in lowercase. Name entry
// remains uppercase-only for high-score compatibility.
lowerCase = inputMode != KEYBOARD_NAME;
symbolPage = false;
copyHeading(title);
if (initialText != NULL) {
for (size_t index = 0; initialText[index] != '\0' && textLength < characterLimit; ++index) {
char character = initialText[index];
if (mode == KEYBOARD_NAME && character >= 'a' && character <= 'z') {
character = (char)(character - ('a' - 'A'));
}
if ((mode == KEYBOARD_NAME && nameCharacter(character)) ||
(mode != KEYBOARD_NAME && textCharacter(character))) {
appendCharacter(character);
}
}
}
selectedKey = 0;
lastMove = nowMs;
result = KEYBOARD_EDITING;
}updateScreenKeyboard() · source line 116
KeyboardResult updateScreenKeyboard(int xOffset, int yOffset, bool valid, bool aPressed,
uint32_t nowMs)Calls into this documentation:
- Decision
result != KEYBOARD_EDITINGTrue- Exit this function
return result;
FalseContinue after this decision.
- Decision
valid && nowMs - lastMove >= JOYSTICK_REPEAT_MSTrue- Decision
xOffset > 600True- Execute
selectedKey = (selectedKey + 1U) % KEYBOARD_KEYS;
False- Decision
xOffset < -600True- Execute
selectedKey = (selectedKey + KEYBOARD_KEYS - 1U) % KEYBOARD_KEYS;
False- Decision
yOffset > 600True- Execute
selectedKey = (selectedKey + KEYBOARD_KEYS - KEYBOARD_COLUMNS) % KEYBOARD_KEYS;
False- Decision
yOffset < -600True- Execute
selectedKey = (selectedKey + KEYBOARD_COLUMNS) % KEYBOARD_KEYS;
False- Jump to label
goto handle_press;
- Execute
lastMove = nowMs;
FalseContinue after this decision.
- Execute
handle_press: if (!aPressed) return result; - Decision
selectedKey < KEYBOARD_CHARACTER_KEYSTrue- Execute
appendCharacter(symbolPage ? symbols[selectedKey % SYMBOL_COUNT] : currentCharacter(selectedKey));
False- Decision
selectedKey == KEYBOARD_SHIFT_KEY && mode != KEYBOARD_NAMETrue- Execute
lowerCase = !lowerCase; - Execute
symbolPage = false;
False- Decision
selectedKey == KEYBOARD_SYMBOLS_KEY && mode != KEYBOARD_NAMETrue- Execute
symbolPage = !symbolPage;
False- Decision
selectedKey == KEYBOARD_SPACE_KEYTrue- Execute
appendCharacter(' ');
False- Decision
selectedKey == KEYBOARD_DELETE_KEY && textLength > 0UTrue- Execute
text[--textLength] = '\0';
False- Decision
selectedKey == KEYBOARD_DONE_KEY && textLength > 0U && (mode != KEYBOARD_NAME || hasNonSpaceText())True- Execute
result = KEYBOARD_DONE;
False- Decision
selectedKey == KEYBOARD_CANCEL_KEYTrue- Execute
result = KEYBOARD_CANCELLED;
False- Decision
selectedKey == KEYBOARD_DASH_KEY && mode != KEYBOARD_NAMETrue- Execute
appendCharacter('-');
False- Decision
selectedKey == KEYBOARD_UNDERSCORE_KEYTrue- Execute
appendCharacter('_');
False- Decision
selectedKey == KEYBOARD_DOT_KEY && mode != KEYBOARD_NAMETrue- Execute
appendCharacter('.');
FalseContinue after this decision.
- Exit this function
return result;
Read original function source
KeyboardResult updateScreenKeyboard(int xOffset, int yOffset, bool valid, bool aPressed,
uint32_t nowMs)
{
if (result != KEYBOARD_EDITING) return result;
if (valid && nowMs - lastMove >= JOYSTICK_REPEAT_MS) {
if (xOffset > 600) selectedKey = (selectedKey + 1U) % KEYBOARD_KEYS;
else if (xOffset < -600) selectedKey = (selectedKey + KEYBOARD_KEYS - 1U) % KEYBOARD_KEYS;
else if (yOffset > 600) selectedKey = (selectedKey + KEYBOARD_KEYS - KEYBOARD_COLUMNS) % KEYBOARD_KEYS;
else if (yOffset < -600) selectedKey = (selectedKey + KEYBOARD_COLUMNS) % KEYBOARD_KEYS;
else goto handle_press;
lastMove = nowMs;
}
handle_press:
if (!aPressed) return result;
if (selectedKey < KEYBOARD_CHARACTER_KEYS) {
appendCharacter(symbolPage ? symbols[selectedKey % SYMBOL_COUNT] : currentCharacter(selectedKey));
} else if (selectedKey == KEYBOARD_SHIFT_KEY && mode != KEYBOARD_NAME) {
lowerCase = !lowerCase;
symbolPage = false;
} else if (selectedKey == KEYBOARD_SYMBOLS_KEY && mode != KEYBOARD_NAME) {
symbolPage = !symbolPage;
} else if (selectedKey == KEYBOARD_SPACE_KEY) {
appendCharacter(' ');
} else if (selectedKey == KEYBOARD_DELETE_KEY && textLength > 0U) {
text[--textLength] = '\0';
// High-score names must match the persistent score-table rules: no empty
// or space-only entry may reach high_scores_submit().
} else if (selectedKey == KEYBOARD_DONE_KEY && textLength > 0U &&
(mode != KEYBOARD_NAME || hasNonSpaceText())) {
result = KEYBOARD_DONE;
} else if (selectedKey == KEYBOARD_CANCEL_KEY) {
result = KEYBOARD_CANCELLED;
} else if (selectedKey == KEYBOARD_DASH_KEY && mode != KEYBOARD_NAME) {
appendCharacter('-');
} else if (selectedKey == KEYBOARD_UNDERSCORE_KEY) {
appendCharacter('_');
} else if (selectedKey == KEYBOARD_DOT_KEY && mode != KEYBOARD_NAME) {
appendCharacter('.');
}
return result;
}cancelScreenKeyboard() · source line 161
void cancelScreenKeyboard(void)
- Decision
result == KEYBOARD_EDITINGTrue- Execute
result = KEYBOARD_CANCELLED;
FalseContinue after this decision.
Read original function source
void cancelScreenKeyboard(void)
{
if (result == KEYBOARD_EDITING) result = KEYBOARD_CANCELLED;
}getScreenKeyboardText() · source line 166
bool getScreenKeyboardText(char *destination, size_t capacity)
- Decision
result != KEYBOARD_DONE || destination == NULL || capacity <= textLengthTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
memcpy(destination, text, textLength + 1U); - Exit this function
return true;
Read original function source
bool getScreenKeyboardText(char *destination, size_t capacity)
{
if (result != KEYBOARD_DONE || destination == NULL || capacity <= textLength) return false;
memcpy(destination, text, textLength + 1U);
return true;
}keyLabel() · source line 173
static const char *keyLabel(size_t key, char character[2])
Calls into this documentation:
- Decision
key < KEYBOARD_CHARACTER_KEYSTrue- Execute
character[0] = symbolPage ? symbols[key % SYMBOL_COUNT] : currentCharacter(key); - Execute
character[1] = '\0'; - Exit this function
return character;
FalseContinue after this decision.
- Decision
key == KEYBOARD_SHIFT_KEYTrue- Exit this function
return mode == KEYBOARD_NAME ? "" : (lowerCase ? "abc" : "ABC");
FalseContinue after this decision.
- Decision
key == KEYBOARD_SYMBOLS_KEYTrue- Exit this function
return mode == KEYBOARD_NAME ? "" : "SYM";
FalseContinue after this decision.
- Decision
key == KEYBOARD_SPACE_KEYTrue- Exit this function
return "SPACE";
FalseContinue after this decision.
- Decision
key == KEYBOARD_DELETE_KEYTrue- Exit this function
return "DEL";
FalseContinue after this decision.
- Decision
key == KEYBOARD_DONE_KEYTrue- Exit this function
return "DONE";
FalseContinue after this decision.
- Decision
key == KEYBOARD_CANCEL_KEYTrue- Exit this function
return "CANCEL";
FalseContinue after this decision.
- Decision
key == KEYBOARD_DASH_KEYTrue- Exit this function
return mode == KEYBOARD_NAME ? "" : "-";
FalseContinue after this decision.
- Decision
key == KEYBOARD_UNDERSCORE_KEYTrue- Exit this function
return "_";
FalseContinue after this decision.
- Exit this function
return mode == KEYBOARD_NAME ? "" : ".";
Read original function source
static const char *keyLabel(size_t key, char character[2])
{
if (key < KEYBOARD_CHARACTER_KEYS) {
character[0] = symbolPage ? symbols[key % SYMBOL_COUNT] : currentCharacter(key);
character[1] = '\0';
return character;
}
if (key == KEYBOARD_SHIFT_KEY) return mode == KEYBOARD_NAME ? "" : (lowerCase ? "abc" : "ABC");
if (key == KEYBOARD_SYMBOLS_KEY) return mode == KEYBOARD_NAME ? "" : "SYM";
if (key == KEYBOARD_SPACE_KEY) return "SPACE";
if (key == KEYBOARD_DELETE_KEY) return "DEL";
if (key == KEYBOARD_DONE_KEY) return "DONE";
if (key == KEYBOARD_CANCEL_KEY) return "CANCEL";
if (key == KEYBOARD_DASH_KEY) return mode == KEYBOARD_NAME ? "" : "-";
if (key == KEYBOARD_UNDERSCORE_KEY) return "_";
return mode == KEYBOARD_NAME ? "" : ".";
}drawScreenKeyboard() · source line 191
void drawScreenKeyboard(uint16_t *frame)
Calls into this documentation:
- Decision
frame == NULL || result != KEYBOARD_EDITINGTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - Execute
const int heading_x = mode == KEYBOARD_NAME ? 160 : 170; - Execute
drawPet2001TextCentredTransparent(frame, heading_x, 4, heading, COLOR_CYAN, 2); - Execute
char displayed[24]; - Execute
const size_t visible_limit = sizeof(displayed) - 1U; - Execute
const size_t start = textLength > visible_limit ? textLength - visible_limit : 0U; - Execute
const size_t visible_length = textLength - start; - FOR loop
size_t index = 0; index < visible_length; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
displayed[index] = mode == KEYBOARD_PASSWORD ? '*' : text[start + index];
- Execute
displayed[visible_length] = '\0'; - Execute
char count[16]; - Execute
snprintf(count, sizeof(count), "%u/%u", (unsigned)textLength, (unsigned)characterLimit); - Execute
const char *page = symbolPage ? "SYMBOLS" : (lowerCase ? "LOWER CASE" : "UPPER CASE"); - Execute
const uint16_t page_color = symbolPage ? COLOR_YELLOW : (lowerCase ? COLOR_GREEN : COLOR_CYAN); - Execute
drawPet2001TextTransparent(frame, 8, 16, page, page_color, 1); - Execute
drawPet2001TextTransparent(frame, 8, 27, displayed, COLOR_ORANGE, 2); - Execute
drawPet2001TextTransparent(frame, 270, 8, count, COLOR_LIGHTGREY, 1); - Execute
drawPet2001TextTransparent(frame, 8 + (int)visible_length * PET2001_FONT_GLYPH_WIDTH * 2, 27, "_", COLOR_CYAN, 2); - FOR loop
size_t key = 0; key < KEYBOARD_KEYS; ++keyRepeat body while the loop condition permits; then continue below.
- Execute
const int x = 1 + (int)(key % KEYBOARD_COLUMNS) * 35; - Execute
const int y = 50 + (int)(key / KEYBOARD_COLUMNS) * 21; - Execute
const bool selected = key == selectedKey; - Execute
char character[2] = {0}; - Execute
const char *label = keyLabel(key, character); - Execute
const int scale = key < KEYBOARD_CHARACTER_KEYS ? 2 : 1; - Execute
const int text_y = scale == 2 ? y + 2 : y + 5; - Execute
drawPet2001TextCentredTransparent(frame, x + 16, text_y, label, selected ? COLOR_ORANGE : COLOR_LIGHTGREY, scale);
Read original function source
void drawScreenKeyboard(uint16_t *frame)
{
if (frame == NULL || result != KEYBOARD_EDITING) return;
clear(frame, COLOR_BLACK);
// High-score names are centred; other keyboard headings retain their tuned offset.
const int heading_x = mode == KEYBOARD_NAME ? 160 : 170;
drawPet2001TextCentredTransparent(frame, heading_x, 4, heading, COLOR_CYAN, 2);
// Keep password characters masked, but show a clear count and a visible
// cursor so every accepted keypress has immediate on-screen feedback.
char displayed[24];
const size_t visible_limit = sizeof(displayed) - 1U;
const size_t start = textLength > visible_limit ? textLength - visible_limit : 0U;
const size_t visible_length = textLength - start;
for (size_t index = 0; index < visible_length; ++index) {
displayed[index] = mode == KEYBOARD_PASSWORD ? '*' : text[start + index];
}
displayed[visible_length] = '\0';
char count[16];
snprintf(count, sizeof(count), "%u/%u", (unsigned)textLength, (unsigned)characterLimit);
const char *page = symbolPage ? "SYMBOLS" : (lowerCase ? "LOWER CASE" : "UPPER CASE");
const uint16_t page_color = symbolPage ? COLOR_YELLOW : (lowerCase ? COLOR_GREEN : COLOR_CYAN);
// Keep the page indicator clear of the entered-text row; tune this y offset here.
drawPet2001TextTransparent(frame, 8, 16, page, page_color, 1);
drawPet2001TextTransparent(frame, 8, 27, displayed, COLOR_ORANGE, 2);
drawPet2001TextTransparent(frame, 270, 8, count, COLOR_LIGHTGREY, 1);
drawPet2001TextTransparent(frame, 8 + (int)visible_length * PET2001_FONT_GLYPH_WIDTH * 2,
27, "_", COLOR_CYAN, 2);
for (size_t key = 0; key < KEYBOARD_KEYS; ++key) {
const int x = 1 + (int)(key % KEYBOARD_COLUMNS) * 35;
const int y = 50 + (int)(key / KEYBOARD_COLUMNS) * 21;
const bool selected = key == selectedKey;
char character[2] = {0};
const char *label = keyLabel(key, character);
const int scale = key < KEYBOARD_CHARACTER_KEYS ? 2 : 1;
const int text_y = scale == 2 ? y + 2 : y + 5;
drawPet2001TextCentredTransparent(frame, x + 16, text_y, label,
selected ? COLOR_ORANGE : COLOR_LIGHTGREY, scale);
}
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "screen_keyboard.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include <stdio.h>
#include <string.h>
#define KEYBOARD_COLUMNS 9U
#define KEYBOARD_ROWS 5U
#define KEYBOARD_KEYS (KEYBOARD_COLUMNS * KEYBOARD_ROWS)
#define KEYBOARD_CHARACTER_KEYS 36U
#define SYMBOL_COUNT (sizeof(symbols) - 1U)
#define KEYBOARD_SHIFT_KEY 36U
#define KEYBOARD_SYMBOLS_KEY 37U
#define KEYBOARD_SPACE_KEY 38U
#define KEYBOARD_DELETE_KEY 39U
#define KEYBOARD_DONE_KEY 40U
#define KEYBOARD_CANCEL_KEY 41U
#define KEYBOARD_DASH_KEY 42U
#define KEYBOARD_UNDERSCORE_KEY 43U
#define KEYBOARD_DOT_KEY 44U
#define JOYSTICK_REPEAT_MS 180U
static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static const char symbols[] = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
static char text[SCREEN_KEYBOARD_MAX_CHARS + 1U];
static char heading[SCREEN_KEYBOARD_TITLE_CHARS + 1U];
static size_t textLength;
static size_t characterLimit;
static size_t selectedKey;
static uint32_t lastMove;
static KeyboardMode mode;
static bool lowerCase;
static bool symbolPage;
static KeyboardResult result = KEYBOARD_CANCELLED;
static bool nameCharacter(char character)
{
return (character >= 'A' && character <= 'Z') ||
(character >= '0' && character <= '9') || character == '_' || character == ' ';
}
static bool textCharacter(char character)
{
return character >= 32 && character <= 126;
}
static char currentCharacter(size_t key)
{
if (key < 26U) {
char character = letters[key];
return lowerCase && !symbolPage ? (char)(character + ('a' - 'A')) : character;
}
if (key < KEYBOARD_CHARACTER_KEYS) return letters[key];
return '\0';
}
static void appendCharacter(char character)
{
if (textLength >= characterLimit || character == '\0') return;
text[textLength++] = character;
text[textLength] = '\0';
}
static bool hasNonSpaceText(void)
{
for (size_t index = 0; index < textLength; ++index) {
if (text[index] != ' ') return true;
}
return false;
}
static void copyHeading(const char *title)
{
const char *source = title == NULL ? "ENTER TEXT" : title;
size_t length = 0;
while (source[length] != '\0' && length < SCREEN_KEYBOARD_TITLE_CHARS) {
heading[length] = source[length];
++length;
}
heading[length] = '\0';
}
void startScreenKeyboard(const char *initialText, size_t maxChars, KeyboardMode inputMode,
const char *title, uint32_t nowMs)
{
mode = inputMode;
characterLimit = maxChars > SCREEN_KEYBOARD_MAX_CHARS ? SCREEN_KEYBOARD_MAX_CHARS : maxChars;
textLength = 0;
// Password and general text entry normally begin in lowercase. Name entry
// remains uppercase-only for high-score compatibility.
lowerCase = inputMode != KEYBOARD_NAME;
symbolPage = false;
copyHeading(title);
if (initialText != NULL) {
for (size_t index = 0; initialText[index] != '\0' && textLength < characterLimit; ++index) {
char character = initialText[index];
if (mode == KEYBOARD_NAME && character >= 'a' && character <= 'z') {
character = (char)(character - ('a' - 'A'));
}
if ((mode == KEYBOARD_NAME && nameCharacter(character)) ||
(mode != KEYBOARD_NAME && textCharacter(character))) {
appendCharacter(character);
}
}
}
selectedKey = 0;
lastMove = nowMs;
result = KEYBOARD_EDITING;
}
KeyboardResult updateScreenKeyboard(int xOffset, int yOffset, bool valid, bool aPressed,
uint32_t nowMs)
{
if (result != KEYBOARD_EDITING) return result;
if (valid && nowMs - lastMove >= JOYSTICK_REPEAT_MS) {
if (xOffset > 600) selectedKey = (selectedKey + 1U) % KEYBOARD_KEYS;
else if (xOffset < -600) selectedKey = (selectedKey + KEYBOARD_KEYS - 1U) % KEYBOARD_KEYS;
else if (yOffset > 600) selectedKey = (selectedKey + KEYBOARD_KEYS - KEYBOARD_COLUMNS) % KEYBOARD_KEYS;
else if (yOffset < -600) selectedKey = (selectedKey + KEYBOARD_COLUMNS) % KEYBOARD_KEYS;
else goto handle_press;
lastMove = nowMs;
}
handle_press:
if (!aPressed) return result;
if (selectedKey < KEYBOARD_CHARACTER_KEYS) {
appendCharacter(symbolPage ? symbols[selectedKey % SYMBOL_COUNT] : currentCharacter(selectedKey));
} else if (selectedKey == KEYBOARD_SHIFT_KEY && mode != KEYBOARD_NAME) {
lowerCase = !lowerCase;
symbolPage = false;
} else if (selectedKey == KEYBOARD_SYMBOLS_KEY && mode != KEYBOARD_NAME) {
symbolPage = !symbolPage;
} else if (selectedKey == KEYBOARD_SPACE_KEY) {
appendCharacter(' ');
} else if (selectedKey == KEYBOARD_DELETE_KEY && textLength > 0U) {
text[--textLength] = '\0';
// High-score names must match the persistent score-table rules: no empty
// or space-only entry may reach high_scores_submit().
} else if (selectedKey == KEYBOARD_DONE_KEY && textLength > 0U &&
(mode != KEYBOARD_NAME || hasNonSpaceText())) {
result = KEYBOARD_DONE;
} else if (selectedKey == KEYBOARD_CANCEL_KEY) {
result = KEYBOARD_CANCELLED;
} else if (selectedKey == KEYBOARD_DASH_KEY && mode != KEYBOARD_NAME) {
appendCharacter('-');
} else if (selectedKey == KEYBOARD_UNDERSCORE_KEY) {
appendCharacter('_');
} else if (selectedKey == KEYBOARD_DOT_KEY && mode != KEYBOARD_NAME) {
appendCharacter('.');
}
return result;
}
void cancelScreenKeyboard(void)
{
if (result == KEYBOARD_EDITING) result = KEYBOARD_CANCELLED;
}
bool getScreenKeyboardText(char *destination, size_t capacity)
{
if (result != KEYBOARD_DONE || destination == NULL || capacity <= textLength) return false;
memcpy(destination, text, textLength + 1U);
return true;
}
static const char *keyLabel(size_t key, char character[2])
{
if (key < KEYBOARD_CHARACTER_KEYS) {
character[0] = symbolPage ? symbols[key % SYMBOL_COUNT] : currentCharacter(key);
character[1] = '\0';
return character;
}
if (key == KEYBOARD_SHIFT_KEY) return mode == KEYBOARD_NAME ? "" : (lowerCase ? "abc" : "ABC");
if (key == KEYBOARD_SYMBOLS_KEY) return mode == KEYBOARD_NAME ? "" : "SYM";
if (key == KEYBOARD_SPACE_KEY) return "SPACE";
if (key == KEYBOARD_DELETE_KEY) return "DEL";
if (key == KEYBOARD_DONE_KEY) return "DONE";
if (key == KEYBOARD_CANCEL_KEY) return "CANCEL";
if (key == KEYBOARD_DASH_KEY) return mode == KEYBOARD_NAME ? "" : "-";
if (key == KEYBOARD_UNDERSCORE_KEY) return "_";
return mode == KEYBOARD_NAME ? "" : ".";
}
void drawScreenKeyboard(uint16_t *frame)
{
if (frame == NULL || result != KEYBOARD_EDITING) return;
clear(frame, COLOR_BLACK);
// High-score names are centred; other keyboard headings retain their tuned offset.
const int heading_x = mode == KEYBOARD_NAME ? 160 : 170;
drawPet2001TextCentredTransparent(frame, heading_x, 4, heading, COLOR_CYAN, 2);
// Keep password characters masked, but show a clear count and a visible
// cursor so every accepted keypress has immediate on-screen feedback.
char displayed[24];
const size_t visible_limit = sizeof(displayed) - 1U;
const size_t start = textLength > visible_limit ? textLength - visible_limit : 0U;
const size_t visible_length = textLength - start;
for (size_t index = 0; index < visible_length; ++index) {
displayed[index] = mode == KEYBOARD_PASSWORD ? '*' : text[start + index];
}
displayed[visible_length] = '\0';
char count[16];
snprintf(count, sizeof(count), "%u/%u", (unsigned)textLength, (unsigned)characterLimit);
const char *page = symbolPage ? "SYMBOLS" : (lowerCase ? "LOWER CASE" : "UPPER CASE");
const uint16_t page_color = symbolPage ? COLOR_YELLOW : (lowerCase ? COLOR_GREEN : COLOR_CYAN);
// Keep the page indicator clear of the entered-text row; tune this y offset here.
drawPet2001TextTransparent(frame, 8, 16, page, page_color, 1);
drawPet2001TextTransparent(frame, 8, 27, displayed, COLOR_ORANGE, 2);
drawPet2001TextTransparent(frame, 270, 8, count, COLOR_LIGHTGREY, 1);
drawPet2001TextTransparent(frame, 8 + (int)visible_length * PET2001_FONT_GLYPH_WIDTH * 2,
27, "_", COLOR_CYAN, 2);
for (size_t key = 0; key < KEYBOARD_KEYS; ++key) {
const int x = 1 + (int)(key % KEYBOARD_COLUMNS) * 35;
const int y = 50 + (int)(key / KEYBOARD_COLUMNS) * 21;
const bool selected = key == selectedKey;
char character[2] = {0};
const char *label = keyLabel(key, character);
const int scale = key < KEYBOARD_CHARACTER_KEYS ? 2 : 1;
const int text_y = scale == 2 ? y + 2 : y + 5;
drawPet2001TextCentredTransparent(frame, x + 16, text_y, label,
selected ? COLOR_ORANGE : COLOR_LIGHTGREY, scale);
}
}