Implement on-screen keyboard dialog with QWERTY layout
Builds out uikeyboard/uikeyboardqwerty from placeholders into a working controller-navigable text entry dialog: title/current-text labels, a flat QWERTY key grid (letters, space, backspace), and confirm/cancel buttons in one combined menu so d-pad navigation flows across all of it. uikeyboardopen_t configures each dialog: onInput/onKeyPress callbacks, optional cancel button, maxLength (auto-focuses confirm once full), lineCount for multi-line entry (adds a NEWLINE key, reserves vertical space up front), a second "are you sure" uiConfirm step, and trimmed/allowBlank validation on confirm. Back now deletes the last character first, falling through to close only when cancel is allowed and the buffer is empty - this needed a general cancel-intercept callback added to uifocusitem_t/uimenu_t (uiMenuSetCancelCallback), since the focus system previously only supported popping or fully swallowing back. Wired a temporary "LOAD GAME (TEST)" entry into the main menu with console-logging test callbacks for interactive testing; marked TODO for removal once the keyboard is wired up somewhere real. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,16 +6,329 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "uikeyboard.h"
|
#include "uikeyboard.h"
|
||||||
|
#include "ui/widget/uiframe.h"
|
||||||
|
#include "ui/widget/uibutton.h"
|
||||||
|
#include "ui/dialog/uiconfirm.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
#include "util/math.h"
|
||||||
|
#include "display/screen/screen.h"
|
||||||
|
#include "display/color.h"
|
||||||
|
#include "display/text/font.h"
|
||||||
|
#include "display/spritebatch/spritebatch.h"
|
||||||
|
#include "display/texture/texture.h"
|
||||||
|
#include "display/shader/shaderunlit.h"
|
||||||
|
|
||||||
|
#define UI_KEYBOARD_BACKDROP_COLOR color4b(0, 0, 0, 160)
|
||||||
|
#define UI_KEYBOARD_KEY_WIDTH 20.0f
|
||||||
|
|
||||||
uikeyboard_t UI_KEYBOARD;
|
uikeyboard_t UI_KEYBOARD;
|
||||||
|
|
||||||
|
void uiKeyboardFocusConfirm(void) {
|
||||||
|
uint8_t confirmIndex = UI_KEYBOARD.menu.itemCount - (UI_KEYBOARD.cancel ? 2 : 1);
|
||||||
|
uiMenuSetPosition(
|
||||||
|
&UI_KEYBOARD.menu,
|
||||||
|
confirmIndex % UI_KEYBOARD.menu.columns,
|
||||||
|
confirmIndex / UI_KEYBOARD.menu.columns
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t uiKeyboardTextIsBlank(void) {
|
||||||
|
for(const char_t *c = UI_KEYBOARD.text; *c != '\0'; c++) {
|
||||||
|
if(!stringIsWhitespace(*c)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiKeyboardAppendChar(const char_t c) {
|
||||||
|
size_t length = strlen(UI_KEYBOARD.text);
|
||||||
|
if(length >= UI_KEYBOARD.maxLength) return;
|
||||||
|
|
||||||
|
if(c == '\n') {
|
||||||
|
uint8_t lines = 1;
|
||||||
|
for(size_t i = 0; i < length; i++) {
|
||||||
|
if(UI_KEYBOARD.text[i] == '\n') lines++;
|
||||||
|
}
|
||||||
|
if(lines >= UI_KEYBOARD.lineCount) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UI_KEYBOARD.text[length] = c;
|
||||||
|
UI_KEYBOARD.text[length + 1] = '\0';
|
||||||
|
UI_KEYBOARD.textLabel.dirty = true;
|
||||||
|
uiLabelRebuffer(&UI_KEYBOARD.textLabel);
|
||||||
|
|
||||||
|
// Nothing more can be typed - jump focus to confirm so accepting is a
|
||||||
|
// single button press away.
|
||||||
|
if(length + 1 >= UI_KEYBOARD.maxLength) uiKeyboardFocusConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiKeyboardBackspace(void) {
|
||||||
|
size_t length = strlen(UI_KEYBOARD.text);
|
||||||
|
if(length == 0) return;
|
||||||
|
|
||||||
|
UI_KEYBOARD.text[length - 1] = '\0';
|
||||||
|
UI_KEYBOARD.textLabel.dirty = true;
|
||||||
|
uiLabelRebuffer(&UI_KEYBOARD.textLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiKeyboardMenuClosed(const uimenu_t *menu) {
|
||||||
|
if(UI_KEYBOARD.onInput != NULL) {
|
||||||
|
UI_KEYBOARD.onInput(
|
||||||
|
UI_KEYBOARD.text, UI_KEYBOARD.confirmed, UI_KEYBOARD.user
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t uiKeyboardMenuCancel(const uimenu_t *menu) {
|
||||||
|
if(strlen(UI_KEYBOARD.text) > 0) {
|
||||||
|
uiKeyboardBackspace();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing left to delete - close, but only if this dialog is even
|
||||||
|
// allowed to be cancelled. Closed explicitly (rather than returning
|
||||||
|
// false to fall through to uiFocusUpdate's default pop) since that
|
||||||
|
// path only pops the focus stack - it never clears UI_KEYBOARD.open,
|
||||||
|
// which would leave uiKeyboardDraw drawing a focus-less dialog forever
|
||||||
|
// and the next uiKeyboardOpen() tripping its already-open assert.
|
||||||
|
if(!UI_KEYBOARD.cancel) return true;
|
||||||
|
|
||||||
|
UI_KEYBOARD.confirmed = false;
|
||||||
|
uiKeyboardClose();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiKeyboardConfirmSecondaryResult(const bool_t result, void *user) {
|
||||||
|
if(!result) return;
|
||||||
|
|
||||||
|
UI_KEYBOARD.confirmed = true;
|
||||||
|
uiKeyboardClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiKeyboardMenuSelected(
|
||||||
|
const uimenu_t *menu,
|
||||||
|
const uint8_t index,
|
||||||
|
const uimenuitem_t *item
|
||||||
|
) {
|
||||||
|
uint8_t confirmIndex = menu->itemCount - (UI_KEYBOARD.cancel ? 2 : 1);
|
||||||
|
if(index == confirmIndex) {
|
||||||
|
if(UI_KEYBOARD.trimmed) {
|
||||||
|
stringTrim(UI_KEYBOARD.text);
|
||||||
|
UI_KEYBOARD.textLabel.dirty = true;
|
||||||
|
uiLabelRebuffer(&UI_KEYBOARD.textLabel);
|
||||||
|
}
|
||||||
|
if(!UI_KEYBOARD.allowBlank && uiKeyboardTextIsBlank()) return;
|
||||||
|
|
||||||
|
if(UI_KEYBOARD.confirm) {
|
||||||
|
uiConfirmOpen(
|
||||||
|
UI_KEYBOARD.confirmQuestion, uiKeyboardConfirmSecondaryResult, NULL
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UI_KEYBOARD.confirmed = true;
|
||||||
|
uiKeyboardClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(UI_KEYBOARD.cancel && index == menu->itemCount - 1) {
|
||||||
|
UI_KEYBOARD.confirmed = false;
|
||||||
|
uiKeyboardClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char_t *key = item->button.label.text;
|
||||||
|
if(stringCompare(key, UI_KEYBOARD_QWERTY_KEY_BACKSPACE) == 0) {
|
||||||
|
uiKeyboardBackspace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char_t c;
|
||||||
|
if(stringCompare(key, UI_KEYBOARD_QWERTY_KEY_SPACE) == 0) {
|
||||||
|
c = ' ';
|
||||||
|
} else if(
|
||||||
|
UI_KEYBOARD.lineCount > 1 &&
|
||||||
|
stringCompare(key, UI_KEYBOARD_KEY_NEWLINE) == 0
|
||||||
|
) {
|
||||||
|
c = '\n';
|
||||||
|
} else {
|
||||||
|
c = key[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(UI_KEYBOARD.onKeyPress != NULL && !UI_KEYBOARD.onKeyPress(c, UI_KEYBOARD.user)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uiKeyboardAppendChar(c);
|
||||||
|
}
|
||||||
|
|
||||||
errorret_t uiKeyboardInit(void) {
|
errorret_t uiKeyboardInit(void) {
|
||||||
// Nothing to initialize yet -- uikeyboard_t is currently a
|
memoryZero(&UI_KEYBOARD, sizeof(uikeyboard_t));
|
||||||
// placeholder with no fields.
|
|
||||||
|
uiLabelInit(
|
||||||
|
&UI_KEYBOARD.titleLabel, UI_KEYBOARD.titleText,
|
||||||
|
UI_KEYBOARD.titleSprites, UI_KEYBOARD_TITLE_SPRITES_MAX
|
||||||
|
);
|
||||||
|
uiLabelInit(
|
||||||
|
&UI_KEYBOARD.textLabel, UI_KEYBOARD.text,
|
||||||
|
UI_KEYBOARD.textSprites, UI_KEYBOARD_TEXT_SPRITES_MAX
|
||||||
|
);
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool_t uiKeyboardIsOpen(void) {
|
||||||
|
return UI_KEYBOARD.open;
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiKeyboardOpen(const uikeyboardopen_t *open) {
|
||||||
|
assertNotNull(open, "Open parameters cannot be NULL");
|
||||||
|
assertFalse(UI_KEYBOARD.open, "Keyboard is already open");
|
||||||
|
assertTrue(
|
||||||
|
open->maxLength <= UI_KEYBOARD_TEXT_MAX - 1,
|
||||||
|
"maxLength exceeds UI_KEYBOARD_TEXT_MAX"
|
||||||
|
);
|
||||||
|
assertTrue(
|
||||||
|
open->lineCount <= UI_KEYBOARD_LINE_COUNT_MAX,
|
||||||
|
"lineCount exceeds UI_KEYBOARD_LINE_COUNT_MAX"
|
||||||
|
);
|
||||||
|
|
||||||
|
stringCopy(
|
||||||
|
UI_KEYBOARD.titleText, UI_KEYBOARD_TITLE_DEFAULT,
|
||||||
|
UI_KEYBOARD_TITLE_TEXT_MAX - 1
|
||||||
|
);
|
||||||
|
UI_KEYBOARD.titleLabel.dirty = true;
|
||||||
|
uiLabelRebuffer(&UI_KEYBOARD.titleLabel);
|
||||||
|
|
||||||
|
UI_KEYBOARD.text[0] = '\0';
|
||||||
|
UI_KEYBOARD.textLabel.dirty = true;
|
||||||
|
uiLabelRebuffer(&UI_KEYBOARD.textLabel);
|
||||||
|
|
||||||
|
UI_KEYBOARD.onInput = open->onInput;
|
||||||
|
UI_KEYBOARD.onKeyPress = open->onKeyPress;
|
||||||
|
UI_KEYBOARD.cancel = open->cancel;
|
||||||
|
UI_KEYBOARD.maxLength = open->maxLength == 0 ?
|
||||||
|
UI_KEYBOARD_TEXT_MAX - 1 : open->maxLength;
|
||||||
|
UI_KEYBOARD.lineCount = open->lineCount == 0 ? 1 : open->lineCount;
|
||||||
|
UI_KEYBOARD.confirm = open->confirm;
|
||||||
|
stringCopy(
|
||||||
|
UI_KEYBOARD.confirmQuestion,
|
||||||
|
open->confirmLabel != NULL ?
|
||||||
|
open->confirmLabel : UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT,
|
||||||
|
UI_KEYBOARD_CONFIRM_QUESTION_MAX - 1
|
||||||
|
);
|
||||||
|
UI_KEYBOARD.trimmed = open->trimmed;
|
||||||
|
UI_KEYBOARD.allowBlank = open->allowBlank;
|
||||||
|
UI_KEYBOARD.user = open->user;
|
||||||
|
UI_KEYBOARD.confirmed = false;
|
||||||
|
UI_KEYBOARD.open = true;
|
||||||
|
|
||||||
|
uint8_t index = uiKeyboardQwertyBuildItems(UI_KEYBOARD.items);
|
||||||
|
|
||||||
|
if(UI_KEYBOARD.lineCount > 1) {
|
||||||
|
UI_KEYBOARD.items[index].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
||||||
|
uiButtonInit(&UI_KEYBOARD.items[index].button, UI_KEYBOARD_KEY_NEWLINE);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
UI_KEYBOARD.items[index].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
||||||
|
uiButtonInit(&UI_KEYBOARD.items[index].button, UI_KEYBOARD_CONFIRM_LABEL);
|
||||||
|
index++;
|
||||||
|
|
||||||
|
if(UI_KEYBOARD.cancel) {
|
||||||
|
UI_KEYBOARD.items[index].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
||||||
|
uiButtonInit(&UI_KEYBOARD.items[index].button, UI_KEYBOARD_CANCEL_LABEL);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uiMenuInit(
|
||||||
|
&UI_KEYBOARD.menu, uiKeyboardMenuSelected, uiKeyboardMenuClosed, NULL
|
||||||
|
);
|
||||||
|
uiMenuSetItems(
|
||||||
|
&UI_KEYBOARD.menu, UI_KEYBOARD.items, index, UI_KEYBOARD_QWERTY_COLUMNS
|
||||||
|
);
|
||||||
|
|
||||||
|
// Back deletes a character rather than closing the dialog outright -
|
||||||
|
// see uiKeyboardMenuCancel.
|
||||||
|
uiMenuSetCancelCallback(&UI_KEYBOARD.menu, uiKeyboardMenuCancel);
|
||||||
|
uiMenuOpen(&UI_KEYBOARD.menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiKeyboardClose(void) {
|
||||||
|
if(!UI_KEYBOARD.open) return;
|
||||||
|
UI_KEYBOARD.open = false;
|
||||||
|
|
||||||
|
if(uiMenuIsActive(&UI_KEYBOARD.menu)) {
|
||||||
|
uiMenuClose(&UI_KEYBOARD.menu);
|
||||||
|
} else {
|
||||||
|
uiKeyboardMenuClosed(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
errorret_t uiKeyboardDraw(void) {
|
errorret_t uiKeyboardDraw(void) {
|
||||||
|
if(!UI_KEYBOARD.open) errorOk();
|
||||||
|
|
||||||
|
spritebatchsprite_t backdropSprite = {
|
||||||
|
.min = { 0.0f, 0.0f, 0.0f },
|
||||||
|
.max = { (float_t)SCREEN.width, (float_t)SCREEN.height, 0.0f },
|
||||||
|
.uvMin = { 0.0f, 0.0f },
|
||||||
|
.uvMax = { 1.0f, 1.0f }
|
||||||
|
};
|
||||||
|
shadermaterial_t backdropMaterial = {
|
||||||
|
.unlit = {
|
||||||
|
.color = UI_KEYBOARD_BACKDROP_COLOR,
|
||||||
|
.texture = &TEXTURE_WHITE
|
||||||
|
}
|
||||||
|
};
|
||||||
|
errorChain(
|
||||||
|
spriteBatchBuffer(&backdropSprite, 1, &SHADER_UNLIT, backdropMaterial)
|
||||||
|
);
|
||||||
|
errorChain(spriteBatchFlush());
|
||||||
|
|
||||||
|
float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
||||||
|
// When multi-line, reserve the full lineCount rows up front so the
|
||||||
|
// dialog doesn't resize as newlines are typed - otherwise fall back to
|
||||||
|
// the label's own measured height (floored at one row for an empty
|
||||||
|
// string, which measures to zero).
|
||||||
|
float_t textHeight = UI_KEYBOARD.lineCount > 1 ?
|
||||||
|
(float_t)UI_KEYBOARD.lineCount * rowHeight :
|
||||||
|
mathMax(rowHeight, (float_t)UI_KEYBOARD.textLabel.height);
|
||||||
|
uint8_t rows = (UI_KEYBOARD.menu.itemCount + UI_KEYBOARD.menu.columns - 1) /
|
||||||
|
UI_KEYBOARD.menu.columns;
|
||||||
|
|
||||||
|
float_t contentWidth = mathMax(
|
||||||
|
mathMax((float_t)UI_KEYBOARD.titleLabel.width, (float_t)UI_KEYBOARD.textLabel.width),
|
||||||
|
(float_t)UI_KEYBOARD.menu.columns * UI_KEYBOARD_KEY_WIDTH
|
||||||
|
);
|
||||||
|
float_t width = contentWidth + (UI_FRAME_START_X * 2);
|
||||||
|
float_t height = (UI_FRAME_START_Y * 2)
|
||||||
|
+ rowHeight + UI_FRAME_PADDING_Y
|
||||||
|
+ textHeight + UI_FRAME_PADDING_Y
|
||||||
|
+ ((float_t)rows * rowHeight);
|
||||||
|
|
||||||
|
float_t x = (float_t)SCREEN.scanX +
|
||||||
|
((float_t)SCREEN.scanWidth - width) * 0.5f;
|
||||||
|
float_t y = (float_t)SCREEN.scanY +
|
||||||
|
((float_t)SCREEN.scanHeight - height) * 0.5f;
|
||||||
|
|
||||||
|
errorChain(uiFrameDraw(x, y, width, height));
|
||||||
|
|
||||||
|
float_t contentX = x + UI_FRAME_START_X;
|
||||||
|
float_t contentY = y + UI_FRAME_START_Y;
|
||||||
|
|
||||||
|
uiLabelSetX(&UI_KEYBOARD.titleLabel, contentX);
|
||||||
|
uiLabelSetY(&UI_KEYBOARD.titleLabel, contentY);
|
||||||
|
errorChain(uiLabelRender(&UI_KEYBOARD.titleLabel, COLOR_WHITE));
|
||||||
|
|
||||||
|
float_t textY = contentY + rowHeight + UI_FRAME_PADDING_Y;
|
||||||
|
uiLabelSetX(&UI_KEYBOARD.textLabel, contentX);
|
||||||
|
uiLabelSetY(&UI_KEYBOARD.textLabel, textY);
|
||||||
|
errorChain(uiLabelRender(&UI_KEYBOARD.textLabel, COLOR_WHITE));
|
||||||
|
|
||||||
|
float_t menuY = textY + textHeight + UI_FRAME_PADDING_Y;
|
||||||
|
errorChain(uiMenuDraw(&UI_KEYBOARD.menu, contentX, menuY, contentWidth, rowHeight));
|
||||||
|
|
||||||
|
errorChain(spriteBatchFlush());
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,31 +7,267 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
|
#include "ui/widget/uilabel.h"
|
||||||
|
#include "ui/widget/uimenu.h"
|
||||||
|
#include "ui/dialog/keyboard/uikeyboardqwerty.h"
|
||||||
|
|
||||||
|
#define UI_KEYBOARD_TITLE_TEXT_MAX 64
|
||||||
|
#define UI_KEYBOARD_TITLE_SPRITES_MAX UI_KEYBOARD_TITLE_TEXT_MAX
|
||||||
|
#define UI_KEYBOARD_TEXT_MAX 64
|
||||||
|
#define UI_KEYBOARD_TEXT_SPRITES_MAX UI_KEYBOARD_TEXT_MAX
|
||||||
|
// Room for the active layout's keys plus the newline/confirm/cancel
|
||||||
|
// buttons uiKeyboard appends itself - only the QWERTY layout exists
|
||||||
|
// today.
|
||||||
|
#define UI_KEYBOARD_ITEMS_MAX (UI_KEYBOARD_QWERTY_KEY_COUNT + 3)
|
||||||
|
|
||||||
|
// Hardcoded for now - a uiModalOpen-style title/button text override is
|
||||||
|
// planned (see uikeyboardopen_t) but not built yet.
|
||||||
|
#define UI_KEYBOARD_TITLE_DEFAULT "ENTER YOUR TEXT"
|
||||||
|
#define UI_KEYBOARD_CONFIRM_LABEL "CONFIRM"
|
||||||
|
#define UI_KEYBOARD_CANCEL_LABEL "CANCEL"
|
||||||
|
// Only added to the grid when uikeyboardopen_t.lineCount > 1.
|
||||||
|
#define UI_KEYBOARD_KEY_NEWLINE "NEWLINE"
|
||||||
|
|
||||||
|
#define UI_KEYBOARD_CONFIRM_QUESTION_MAX 64
|
||||||
|
// Used when uikeyboardopen_t.confirmLabel is NULL.
|
||||||
|
#define UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT "Is this correct?"
|
||||||
|
|
||||||
|
// Maximum number of lines uikeyboardopen_t.lineCount may request.
|
||||||
|
#define UI_KEYBOARD_LINE_COUNT_MAX 4
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked once the keyboard dialog is dismissed.
|
||||||
|
*
|
||||||
|
* @param text The entered text. Only meaningful when confirmed is true;
|
||||||
|
* points at UI_KEYBOARD's own buffer, so copy it out if it needs to
|
||||||
|
* outlive the next uiKeyboardOpen call.
|
||||||
|
* @param confirmed True if the confirm button was picked, false if
|
||||||
|
* cancelled.
|
||||||
|
* @param user Arbitrary pointer passed via uikeyboardopen_t.user.
|
||||||
|
*/
|
||||||
|
typedef void (*uikeyboardinputcallback_t)(
|
||||||
|
const char_t *text,
|
||||||
|
const bool_t confirmed,
|
||||||
|
void *user
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked before a key press is applied, giving the caller a
|
||||||
|
* chance to reject it - e.g. an email field rejecting "%".
|
||||||
|
*
|
||||||
|
* @param c The character about to be entered (SPACE included, DEL/
|
||||||
|
* confirm/cancel excluded - those aren't text being entered).
|
||||||
|
* @param user Arbitrary pointer passed via uikeyboardopen_t.user.
|
||||||
|
* @returns True to allow the character, false to reject it (ignored,
|
||||||
|
* nothing is entered).
|
||||||
|
*/
|
||||||
|
typedef bool_t (*uikeyboardkeypresscallback_t)(const char_t c, void *user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for uiKeyboardOpen. Expect this to grow (e.g. title/button
|
||||||
|
* text overrides) as more dialogs start using the keyboard.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
// Fired once the dialog is dismissed, confirmed or cancelled. May be
|
||||||
|
// NULL.
|
||||||
|
uikeyboardinputcallback_t onInput;
|
||||||
|
|
||||||
|
// Fired for each key press before it's applied. May be NULL to allow
|
||||||
|
// everything.
|
||||||
|
uikeyboardkeypresscallback_t onKeyPress;
|
||||||
|
|
||||||
|
// Whether the cancel button is shown, and whether pressing back with
|
||||||
|
// no text entered closes the dialog - see uiKeyboardMenuCancel.
|
||||||
|
bool_t cancel;
|
||||||
|
|
||||||
|
// Maximum length of the entered text, in characters excluding the
|
||||||
|
// null terminator. 0 defaults to the buffer's full capacity
|
||||||
|
// (UI_KEYBOARD_TEXT_MAX - 1). Must not exceed that.
|
||||||
|
size_t maxLength;
|
||||||
|
|
||||||
|
// When true, picking confirm opens a second uiConfirm "are you sure"
|
||||||
|
// dialog (see confirmLabel) on top of the keyboard before actually
|
||||||
|
// closing it - picking Cancel there returns to the keyboard with the
|
||||||
|
// text untouched.
|
||||||
|
bool_t confirm;
|
||||||
|
|
||||||
|
// Question text/locale message ID for the second confirm dialog when
|
||||||
|
// confirm is true; copied internally, safe to be transient. NULL uses
|
||||||
|
// UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT. Ignored when confirm is false.
|
||||||
|
const char_t *confirmLabel;
|
||||||
|
|
||||||
|
// If true, leading/trailing whitespace is trimmed from the entered
|
||||||
|
// text when confirm is pressed, before it's checked against
|
||||||
|
// allowBlank or passed to onInput.
|
||||||
|
bool_t trimmed;
|
||||||
|
|
||||||
|
// If true, a blank result is accepted when confirm is pressed - blank
|
||||||
|
// means an empty string, or (when trimmed is false) a string of only
|
||||||
|
// spaces. When false, pressing confirm does nothing while the text is
|
||||||
|
// blank - see uiKeyboardTextIsBlank.
|
||||||
|
bool_t allowBlank;
|
||||||
|
|
||||||
|
// Maximum number of lines the entered text can span, from 1 to
|
||||||
|
// UI_KEYBOARD_LINE_COUNT_MAX. 0 defaults to 1. When > 1, a NEWLINE key
|
||||||
|
// is added to the grid, capped at inserting lineCount - 1 newlines.
|
||||||
|
uint8_t lineCount;
|
||||||
|
|
||||||
|
// Arbitrary pointer passed to onInput/onKeyPress.
|
||||||
|
void *user;
|
||||||
|
} uikeyboardopen_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
uilabel_t titleLabel;
|
||||||
|
char_t titleText[UI_KEYBOARD_TITLE_TEXT_MAX];
|
||||||
|
spritebatchsprite_t titleSprites[UI_KEYBOARD_TITLE_SPRITES_MAX];
|
||||||
|
|
||||||
|
uilabel_t textLabel;
|
||||||
|
char_t text[UI_KEYBOARD_TEXT_MAX];
|
||||||
|
spritebatchsprite_t textSprites[UI_KEYBOARD_TEXT_SPRITES_MAX];
|
||||||
|
|
||||||
|
uimenu_t menu;
|
||||||
|
uimenuitem_t items[UI_KEYBOARD_ITEMS_MAX];
|
||||||
|
|
||||||
|
char_t confirmQuestion[UI_KEYBOARD_CONFIRM_QUESTION_MAX];
|
||||||
|
|
||||||
|
uikeyboardinputcallback_t onInput;
|
||||||
|
uikeyboardkeypresscallback_t onKeyPress;
|
||||||
|
void *user;
|
||||||
|
size_t maxLength;
|
||||||
|
uint8_t lineCount;
|
||||||
|
bool_t cancel;
|
||||||
|
bool_t confirm;
|
||||||
|
bool_t trimmed;
|
||||||
|
bool_t allowBlank;
|
||||||
|
bool_t open;
|
||||||
|
bool_t confirmed;
|
||||||
} uikeyboard_t;
|
} uikeyboard_t;
|
||||||
|
|
||||||
extern uikeyboard_t UI_KEYBOARD;
|
extern uikeyboard_t UI_KEYBOARD;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the on-screen keyboard. Currently a placeholder -- no
|
* Initializes the on-screen keyboard.
|
||||||
* behavior implemented yet.
|
|
||||||
*
|
*
|
||||||
* @return Any error that occurs.
|
* @return Any error that occurs.
|
||||||
*/
|
*/
|
||||||
errorret_t uiKeyboardInit(void);
|
errorret_t uiKeyboardInit(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the on-screen keyboard. Currently a placeholder -- no-op.
|
* Draws the on-screen keyboard: a semi-transparent black backdrop
|
||||||
|
* covering the whole screen, then its own centered frame with the title,
|
||||||
|
* currently entered text, and the active layout's key grid. No-op when
|
||||||
|
* not open.
|
||||||
*
|
*
|
||||||
* @return Any error that occurs.
|
* @return Any error that occurs.
|
||||||
*/
|
*/
|
||||||
errorret_t uiKeyboardDraw(void);
|
errorret_t uiKeyboardDraw(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when the keyboard dialog is currently open.
|
||||||
|
*
|
||||||
|
* @returns True if open.
|
||||||
|
*/
|
||||||
|
bool_t uiKeyboardIsOpen(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the on-screen keyboard with an empty text buffer.
|
||||||
|
*
|
||||||
|
* @param open Parameters for this dialog; copied internally, safe to be
|
||||||
|
* transient. Cannot be NULL.
|
||||||
|
*/
|
||||||
|
void uiKeyboardOpen(const uikeyboardopen_t *open);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the keyboard dialog, invoking the result callback set by
|
||||||
|
* uiKeyboardOpen as cancelled (confirmed=false) unless the confirm
|
||||||
|
* button was what triggered this close. No-op when already closed.
|
||||||
|
*/
|
||||||
|
void uiKeyboardClose(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disposes of the on-screen keyboard.
|
* Disposes of the on-screen keyboard.
|
||||||
*
|
*
|
||||||
* @return Any error that occurs.
|
* @return Any error that occurs.
|
||||||
*/
|
*/
|
||||||
errorret_t uiKeyboardDispose(void);
|
errorret_t uiKeyboardDispose(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a character to the currently entered text, silently ignoring
|
||||||
|
* it once UI_KEYBOARD.maxLength is reached, or (for '\n') once the text
|
||||||
|
* already has UI_KEYBOARD.lineCount - 1 newlines. Does not consult
|
||||||
|
* onKeyPress - that happens in uiKeyboardMenuSelected before this is
|
||||||
|
* called. Moves focus to the confirm button once this append fills the
|
||||||
|
* text to maxLength - see uiKeyboardFocusConfirm.
|
||||||
|
*
|
||||||
|
* @param c The character to append.
|
||||||
|
*/
|
||||||
|
void uiKeyboardAppendChar(const char_t c);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the menu's focus to the confirm button.
|
||||||
|
*/
|
||||||
|
void uiKeyboardFocusConfirm(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the currently entered text counts as blank for
|
||||||
|
* UI_KEYBOARD.allowBlank purposes - an empty string, or (when
|
||||||
|
* UI_KEYBOARD.trimmed is false, so whitespace-only text was never
|
||||||
|
* reduced to empty) a string of only whitespace.
|
||||||
|
*
|
||||||
|
* @returns True if blank.
|
||||||
|
*/
|
||||||
|
bool_t uiKeyboardTextIsBlank(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the last character of the currently entered text. No-op when
|
||||||
|
* already empty.
|
||||||
|
*/
|
||||||
|
void uiKeyboardBackspace(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal menu callback - routes a picked key/confirm/cancel item to
|
||||||
|
* uiKeyboardAppendChar/uiKeyboardBackspace/uiKeyboardClose, consulting
|
||||||
|
* onKeyPress before a character key is applied. When UI_KEYBOARD.confirm
|
||||||
|
* is set, picking confirm opens a second uiConfirm dialog (see
|
||||||
|
* uiKeyboardConfirmSecondaryResult) instead of closing immediately.
|
||||||
|
*
|
||||||
|
* @param menu The keyboard's menu.
|
||||||
|
* @param index Index of the picked item.
|
||||||
|
* @param item The picked item.
|
||||||
|
*/
|
||||||
|
void uiKeyboardMenuSelected(
|
||||||
|
const uimenu_t *menu,
|
||||||
|
const uint8_t index,
|
||||||
|
const uimenuitem_t *item
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result callback for the second "are you sure" uiConfirm dialog opened
|
||||||
|
* when UI_KEYBOARD.confirm is set - see uiKeyboardMenuSelected.
|
||||||
|
*
|
||||||
|
* @param result True if Confirm was picked there, false if Cancel.
|
||||||
|
* @param user Unused.
|
||||||
|
*/
|
||||||
|
void uiKeyboardConfirmSecondaryResult(const bool_t result, void *user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal menu callback - fires the result callback set by
|
||||||
|
* uiKeyboardOpen once the menu finishes closing.
|
||||||
|
*
|
||||||
|
* @param menu Unused; matches uimenuclosedcallback_t.
|
||||||
|
*/
|
||||||
|
void uiKeyboardMenuClosed(const uimenu_t *menu);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal menu cancel callback (see uiMenuSetCancelCallback): back
|
||||||
|
* deletes the last entered character; with nothing left to delete, it
|
||||||
|
* closes the dialog as cancelled via uiKeyboardClose, but only when
|
||||||
|
* UI_KEYBOARD.cancel allows it - otherwise back is swallowed and does
|
||||||
|
* nothing.
|
||||||
|
*
|
||||||
|
* @param menu The keyboard's menu.
|
||||||
|
* @returns Always true - this always fully handles back itself
|
||||||
|
* (backspace, explicit close, or swallow), never falling through to the
|
||||||
|
* default pop.
|
||||||
|
*/
|
||||||
|
bool_t uiKeyboardMenuCancel(const uimenu_t *menu);
|
||||||
|
|||||||
@@ -6,17 +6,34 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "uikeyboardqwerty.h"
|
#include "uikeyboardqwerty.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "ui/widget/uibutton.h"
|
||||||
|
|
||||||
uikeyboardqwerty_t UI_KEYBOARD_QWERTY;
|
uikeyboardqwerty_t UI_KEYBOARD_QWERTY;
|
||||||
|
|
||||||
|
// String literals have static storage duration, so these stay valid for
|
||||||
|
// as long as the process runs - safe for uiButtonInit's non-copying
|
||||||
|
// label contract.
|
||||||
|
static const char_t *const UI_KEYBOARD_QWERTY_KEYS[UI_KEYBOARD_QWERTY_KEY_COUNT] = {
|
||||||
|
"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P",
|
||||||
|
"A", "S", "D", "F", "G", "H", "J", "K", "L",
|
||||||
|
"Z", "X", "C", "V", "B", "N", "M",
|
||||||
|
UI_KEYBOARD_QWERTY_KEY_SPACE, UI_KEYBOARD_QWERTY_KEY_BACKSPACE
|
||||||
|
};
|
||||||
|
|
||||||
errorret_t uiKeyboardQwertyInit(void) {
|
errorret_t uiKeyboardQwertyInit(void) {
|
||||||
// Nothing to initialize yet -- uikeyboardqwerty_t is currently a
|
|
||||||
// placeholder with no fields.
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t uiKeyboardQwertyDraw(void) {
|
uint8_t uiKeyboardQwertyBuildItems(uimenuitem_t *items) {
|
||||||
errorOk();
|
assertNotNull(items, "Items cannot be NULL");
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < UI_KEYBOARD_QWERTY_KEY_COUNT; i++) {
|
||||||
|
items[i].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
||||||
|
uiButtonInit(&items[i].button, UI_KEYBOARD_QWERTY_KEYS[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return UI_KEYBOARD_QWERTY_KEY_COUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t uiKeyboardQwertyDispose(void) {
|
errorret_t uiKeyboardQwertyDispose(void) {
|
||||||
|
|||||||
@@ -7,6 +7,20 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
|
#include "ui/widget/uimenu.h"
|
||||||
|
|
||||||
|
// 26 letters plus SPACE and DEL, laid out as one flat columns-wide grid
|
||||||
|
// rather than the staggered rows a physical QWERTY keyboard uses - the
|
||||||
|
// menu/focus grid only supports uniform columns today, so exact row
|
||||||
|
// staggering is left for later.
|
||||||
|
#define UI_KEYBOARD_QWERTY_COLUMNS 10
|
||||||
|
#define UI_KEYBOARD_QWERTY_KEY_COUNT 28
|
||||||
|
|
||||||
|
// Labels uiKeyboard checks for by pointer/string compare (see
|
||||||
|
// uiKeyboardQwertyBuildItems) to tell the space/backspace keys apart from
|
||||||
|
// an ordinary single-letter key.
|
||||||
|
#define UI_KEYBOARD_QWERTY_KEY_SPACE "SPACE"
|
||||||
|
#define UI_KEYBOARD_QWERTY_KEY_BACKSPACE "DEL"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
||||||
@@ -15,22 +29,29 @@ typedef struct {
|
|||||||
extern uikeyboardqwerty_t UI_KEYBOARD_QWERTY;
|
extern uikeyboardqwerty_t UI_KEYBOARD_QWERTY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the QWERTY keyboard layout. Currently a placeholder --
|
* Initializes the QWERTY keyboard layout. Currently a no-op - the key
|
||||||
* no behavior implemented yet.
|
* labels are static string literals with no per-instance state.
|
||||||
*
|
*
|
||||||
* @return Any error that occurs.
|
* @return Any error that occurs.
|
||||||
*/
|
*/
|
||||||
errorret_t uiKeyboardQwertyInit(void);
|
errorret_t uiKeyboardQwertyInit(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the QWERTY keyboard layout. Currently a placeholder -- no-op.
|
* Builds the QWERTY layout's key buttons (26 letters, then SPACE and
|
||||||
|
* DEL) into items, starting at index 0, one UI_MENU_WIDGET_TYPE_BUTTON
|
||||||
|
* per key. Each button's label is either a single uppercase letter or the
|
||||||
|
* literal UI_KEYBOARD_QWERTY_KEY_SPACE/UI_KEYBOARD_QWERTY_KEY_BACKSPACE -
|
||||||
|
* uiKeyboard reads a selected item's own label back to know which key it
|
||||||
|
* was, rather than this function returning a separate mapping.
|
||||||
*
|
*
|
||||||
* @return Any error that occurs.
|
* @param items Destination array; must have room for at least
|
||||||
|
* UI_KEYBOARD_QWERTY_KEY_COUNT entries.
|
||||||
|
* @returns The number of items written (UI_KEYBOARD_QWERTY_KEY_COUNT).
|
||||||
*/
|
*/
|
||||||
errorret_t uiKeyboardQwertyDraw(void);
|
uint8_t uiKeyboardQwertyBuildItems(uimenuitem_t *items);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disposes of the QWERTY keyboard layout.
|
* Disposes of the QWERTY keyboard layout. Currently a no-op.
|
||||||
*
|
*
|
||||||
* @return Any error that occurs.
|
* @return Any error that occurs.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ void uiFocusUpdate(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(inputPressed(INPUT_ACTION_CANCEL)) {
|
if(inputPressed(INPUT_ACTION_CANCEL)) {
|
||||||
|
if(item->cancel != NULL && item->cancel(item)) return;
|
||||||
if(!item->disableBack) uiFocusPop();
|
if(!item->disableBack) uiFocusPop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,19 @@ struct uifocusitem_s {
|
|||||||
uifocusitemcallback_t changed;
|
uifocusitemcallback_t changed;
|
||||||
uifocusitemcallback_t closed;
|
uifocusitemcallback_t closed;
|
||||||
uifocusitemdirectioncallback_t direction;
|
uifocusitemdirectioncallback_t direction;
|
||||||
|
|
||||||
|
// Called when INPUT_ACTION_CANCEL is pressed, before disableBack/the
|
||||||
|
// default pop is applied; may be NULL. Returns true if fully handled
|
||||||
|
// (e.g. it did something else instead, or popped itself), which skips
|
||||||
|
// the default pop; false to fall through to the default disableBack/
|
||||||
|
// pop behavior below.
|
||||||
|
uifocusitemcallback_t cancel;
|
||||||
|
|
||||||
void *user;
|
void *user;
|
||||||
|
|
||||||
// When true, INPUT_ACTION_CANCEL is ignored while this item is the
|
// When true, INPUT_ACTION_CANCEL is ignored while this item is the
|
||||||
// topmost focus item - it does not pop. Does not affect a
|
// topmost focus item - it does not pop. Does not affect a
|
||||||
// programmatic uiFocusPop()/uiFocusPopItem() call.
|
// programmatic uiFocusPop()/uiFocusPopItem() call. Only consulted
|
||||||
|
// when cancel is unset or returns false.
|
||||||
bool_t disableBack;
|
bool_t disableBack;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,11 +16,15 @@
|
|||||||
#include "locale/localemanager.h"
|
#include "locale/localemanager.h"
|
||||||
#include "asset/loader/locale/assetlocaleloader.h"
|
#include "asset/loader/locale/assetlocaleloader.h"
|
||||||
#include "ui/dialog/uiconfirm.h"
|
#include "ui/dialog/uiconfirm.h"
|
||||||
|
#include "ui/dialog/keyboard/uikeyboard.h"
|
||||||
#include "scene/mainmenu/scenemainmenu.h"
|
#include "scene/mainmenu/scenemainmenu.h"
|
||||||
|
#include "console/console.h"
|
||||||
|
|
||||||
#define UI_MAIN_MENU_INDEX_START_GAME 0
|
#define UI_MAIN_MENU_INDEX_START_GAME 0
|
||||||
#define UI_MAIN_MENU_INDEX_OPTIONS 1
|
#define UI_MAIN_MENU_INDEX_OPTIONS 1
|
||||||
#define UI_MAIN_MENU_INDEX_QUIT 2
|
#define UI_MAIN_MENU_INDEX_QUIT 2
|
||||||
|
// TODO: temporary - see UI_MAIN_MENU_ITEM_COUNT in uimainmenu.h.
|
||||||
|
#define UI_MAIN_MENU_INDEX_LOAD_GAME_TEST 3
|
||||||
|
|
||||||
uimainmenu_t UI_MAIN_MENU;
|
uimainmenu_t UI_MAIN_MENU;
|
||||||
|
|
||||||
@@ -28,6 +32,22 @@ void uiMainMenuQuitConfirmed(const bool_t result, void *user) {
|
|||||||
if(result) ENGINE.running = false;
|
if(result) ENGINE.running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool_t uiMainMenuKeyboardTestKeyPress(const char_t c, void *user) {
|
||||||
|
consolePrint("uiKeyboard onKeyPress: '%c'", c);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiMainMenuKeyboardTestInput(
|
||||||
|
const char_t *text,
|
||||||
|
const bool_t confirmed,
|
||||||
|
void *user
|
||||||
|
) {
|
||||||
|
consolePrint(
|
||||||
|
"uiKeyboard onInput: confirmed=%s text='%s'",
|
||||||
|
confirmed ? "true" : "false", text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void uiMainMenuSelected(
|
void uiMainMenuSelected(
|
||||||
const uimenu_t *menu,
|
const uimenu_t *menu,
|
||||||
const uint8_t index,
|
const uint8_t index,
|
||||||
@@ -48,6 +68,20 @@ void uiMainMenuSelected(
|
|||||||
uiConfirmOpen("main_menu.quit_confirm", uiMainMenuQuitConfirmed, NULL);
|
uiConfirmOpen("main_menu.quit_confirm", uiMainMenuQuitConfirmed, NULL);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case UI_MAIN_MENU_INDEX_LOAD_GAME_TEST: {
|
||||||
|
uikeyboardopen_t open = {
|
||||||
|
.onInput = uiMainMenuKeyboardTestInput,
|
||||||
|
.onKeyPress = uiMainMenuKeyboardTestKeyPress,
|
||||||
|
.cancel = false,
|
||||||
|
.confirm = true,
|
||||||
|
.user = NULL,
|
||||||
|
.maxLength = 6,
|
||||||
|
.lineCount = 2
|
||||||
|
};
|
||||||
|
uiKeyboardOpen(&open);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -84,6 +118,8 @@ errorret_t uiMainMenuInit(void) {
|
|||||||
MENU_BUTTON(UI_MAIN_MENU.startGameLabel);
|
MENU_BUTTON(UI_MAIN_MENU.startGameLabel);
|
||||||
MENU_BUTTON(UI_MAIN_MENU.optionsLabel);
|
MENU_BUTTON(UI_MAIN_MENU.optionsLabel);
|
||||||
MENU_BUTTON(UI_MAIN_MENU.quitLabel);
|
MENU_BUTTON(UI_MAIN_MENU.quitLabel);
|
||||||
|
// TODO: temporary - see UI_MAIN_MENU_ITEM_COUNT in uimainmenu.h.
|
||||||
|
MENU_BUTTON("LOAD GAME (TEST)");
|
||||||
MENU_END(UI_MAIN_MENU.items, 1);
|
MENU_END(UI_MAIN_MENU.items, 1);
|
||||||
|
|
||||||
// The main menu is the root screen once past the initial boot check -
|
// The main menu is the root screen once past the initial boot check -
|
||||||
|
|||||||
@@ -9,9 +9,12 @@
|
|||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "ui/widget/uimenu.h"
|
#include "ui/widget/uimenu.h"
|
||||||
|
|
||||||
#define UI_MAIN_MENU_ITEM_COUNT 3
|
// TODO: item count/height bumped for the temporary "LOAD GAME (TEST)"
|
||||||
|
// keyboard test entry in uiMainMenuSelected - drop back to 3/160 once
|
||||||
|
// the keyboard is wired up somewhere real and this is removed.
|
||||||
|
#define UI_MAIN_MENU_ITEM_COUNT 4
|
||||||
#define UI_MAIN_MENU_WIDTH 200.0f
|
#define UI_MAIN_MENU_WIDTH 200.0f
|
||||||
#define UI_MAIN_MENU_HEIGHT 160.0f
|
#define UI_MAIN_MENU_HEIGHT 200.0f
|
||||||
#define UI_MAIN_MENU_LABEL_MAX 32
|
#define UI_MAIN_MENU_LABEL_MAX 32
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -49,3 +52,29 @@ errorret_t uiMainMenuDraw(void);
|
|||||||
* @return Any error that occurs.
|
* @return Any error that occurs.
|
||||||
*/
|
*/
|
||||||
errorret_t uiMainMenuDispose(void);
|
errorret_t uiMainMenuDispose(void);
|
||||||
|
|
||||||
|
// TODO: temporary keyboard test hooks - see UI_MAIN_MENU_INDEX_LOAD_GAME_TEST
|
||||||
|
// in uimainmenu.c. Remove once the keyboard is wired up somewhere real.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test onKeyPress callback - console-prints every key press and allows
|
||||||
|
* all of them.
|
||||||
|
*
|
||||||
|
* @param c The character about to be entered.
|
||||||
|
* @param user Unused.
|
||||||
|
* @returns Always true.
|
||||||
|
*/
|
||||||
|
bool_t uiMainMenuKeyboardTestKeyPress(const char_t c, void *user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test onInput callback - console-prints the final result.
|
||||||
|
*
|
||||||
|
* @param text The entered text.
|
||||||
|
* @param confirmed Whether confirm (vs cancel) was picked.
|
||||||
|
* @param user Unused.
|
||||||
|
*/
|
||||||
|
void uiMainMenuKeyboardTestInput(
|
||||||
|
const char_t *text,
|
||||||
|
const bool_t confirmed,
|
||||||
|
void *user
|
||||||
|
);
|
||||||
|
|||||||
@@ -101,6 +101,11 @@ uielement_t UI_ELEMENTS[] = {
|
|||||||
.update = uiTextboxMiniListUpdate,
|
.update = uiTextboxMiniListUpdate,
|
||||||
.draw = uiTextboxMiniListDraw
|
.draw = uiTextboxMiniListDraw
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.init = uiKeyboardInit,
|
||||||
|
.draw = uiKeyboardDraw,
|
||||||
|
.dispose = uiKeyboardDispose
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.init = uiModalInit,
|
.init = uiModalInit,
|
||||||
.draw = uiModalDraw,
|
.draw = uiModalDraw,
|
||||||
@@ -110,11 +115,6 @@ uielement_t UI_ELEMENTS[] = {
|
|||||||
.init = uiConfirmInit,
|
.init = uiConfirmInit,
|
||||||
.dispose = uiConfirmDispose
|
.dispose = uiConfirmDispose
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.init = uiKeyboardInit,
|
|
||||||
.draw = uiKeyboardDraw,
|
|
||||||
.dispose = uiKeyboardDispose
|
|
||||||
},
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Overlayed components, can even outdraw all UI things.
|
// Overlayed components, can even outdraw all UI things.
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ void uiMenuOpen(uimenu_t *menu) {
|
|||||||
menu
|
menu
|
||||||
);
|
);
|
||||||
menu->focusItem->disableBack = menu->disableBack;
|
menu->focusItem->disableBack = menu->disableBack;
|
||||||
|
menu->focusItem->cancel = menu->cancel != NULL ? uiMenuFocusCancel : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void uiMenuClose(uimenu_t *menu) {
|
void uiMenuClose(uimenu_t *menu) {
|
||||||
@@ -83,6 +84,14 @@ void uiMenuSetDisableBack(uimenu_t *menu, const bool_t disableBack) {
|
|||||||
if(menu->focusItem != NULL) menu->focusItem->disableBack = disableBack;
|
if(menu->focusItem != NULL) menu->focusItem->disableBack = disableBack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void uiMenuSetCancelCallback(uimenu_t *menu, uimenucancelcallback_t cancel) {
|
||||||
|
assertNotNull(menu, "Menu cannot be NULL");
|
||||||
|
menu->cancel = cancel;
|
||||||
|
if(menu->focusItem != NULL) {
|
||||||
|
menu->focusItem->cancel = cancel != NULL ? uiMenuFocusCancel : NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool_t uiMenuIsActive(const uimenu_t *menu) {
|
bool_t uiMenuIsActive(const uimenu_t *menu) {
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
assertNotNull(menu, "Menu cannot be NULL");
|
||||||
return menu->focusItem != NULL;
|
return menu->focusItem != NULL;
|
||||||
@@ -259,6 +268,14 @@ bool_t uiMenuFocusDirection(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool_t uiMenuFocusCancel(const uifocusitem_t *focusItem) {
|
||||||
|
assertNotNull(focusItem, "Focus item cannot be NULL");
|
||||||
|
assertNotNull(focusItem->user, "Focus item user cannot be NULL");
|
||||||
|
uimenu_t *menu = (uimenu_t *)focusItem->user;
|
||||||
|
if(menu->cancel == NULL) return false;
|
||||||
|
return menu->cancel(menu);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t uiMenuFocusableCount(const uimenu_t *menu) {
|
uint8_t uiMenuFocusableCount(const uimenu_t *menu) {
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
assertNotNull(menu, "Menu cannot be NULL");
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
|
|||||||
@@ -55,6 +55,16 @@ typedef void (*uimenuchangedcallback_t)(
|
|||||||
|
|
||||||
typedef void (*uimenuclosedcallback_t)(const uimenu_t *menu);
|
typedef void (*uimenuclosedcallback_t)(const uimenu_t *menu);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked when INPUT_ACTION_CANCEL is pressed while the menu is
|
||||||
|
* focused - see uiMenuSetCancelCallback.
|
||||||
|
*
|
||||||
|
* @param menu The menu that was cancelled.
|
||||||
|
* @returns True if fully handled (skips the default disableBack/pop
|
||||||
|
* behavior); false to fall through to it.
|
||||||
|
*/
|
||||||
|
typedef bool_t (*uimenucancelcallback_t)(const uimenu_t *menu);
|
||||||
|
|
||||||
typedef struct uimenu_s {
|
typedef struct uimenu_s {
|
||||||
uimenuitem_t *items;
|
uimenuitem_t *items;
|
||||||
uint8_t itemCount;
|
uint8_t itemCount;
|
||||||
@@ -64,6 +74,7 @@ typedef struct uimenu_s {
|
|||||||
uimenuselectedcallback_t selected;
|
uimenuselectedcallback_t selected;
|
||||||
uimenuclosedcallback_t closed;
|
uimenuclosedcallback_t closed;
|
||||||
uimenuchangedcallback_t changed;
|
uimenuchangedcallback_t changed;
|
||||||
|
uimenucancelcallback_t cancel;
|
||||||
|
|
||||||
void *user;
|
void *user;
|
||||||
|
|
||||||
@@ -138,6 +149,17 @@ void uiMenuClose(uimenu_t *menu);
|
|||||||
*/
|
*/
|
||||||
void uiMenuSetDisableBack(uimenu_t *menu, const bool_t disableBack);
|
void uiMenuSetDisableBack(uimenu_t *menu, const bool_t disableBack);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a callback given first refusal of INPUT_ACTION_CANCEL while this
|
||||||
|
* menu is the topmost focus item, before disableBack/the default pop is
|
||||||
|
* applied. Safe to call before or after uiMenuOpen().
|
||||||
|
*
|
||||||
|
* @param menu The menu to update.
|
||||||
|
* @param cancel The callback to install, or NULL to remove it and
|
||||||
|
* restore default disableBack/pop behavior.
|
||||||
|
*/
|
||||||
|
void uiMenuSetCancelCallback(uimenu_t *menu, uimenucancelcallback_t cancel);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the menu is currently active (on the UI focus stack).
|
* Returns whether the menu is currently active (on the UI focus stack).
|
||||||
*
|
*
|
||||||
@@ -230,6 +252,15 @@ bool_t uiMenuFocusDirection(
|
|||||||
const uifocusdirection_t direction
|
const uifocusdirection_t direction
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal focus callback - forwards INPUT_ACTION_CANCEL to the menu's
|
||||||
|
* cancel handler, if one is set.
|
||||||
|
*
|
||||||
|
* @param focusItem The active focus item; user field must point to uimenu_t.
|
||||||
|
* @returns The cancel handler's result, or false if none is set.
|
||||||
|
*/
|
||||||
|
bool_t uiMenuFocusCancel(const uifocusitem_t *focusItem);
|
||||||
|
|
||||||
// Helper macros
|
// Helper macros
|
||||||
#define MENU_BEGIN(menuPtr, itemsArray, selected, closed, changed) \
|
#define MENU_BEGIN(menuPtr, itemsArray, selected, closed, changed) \
|
||||||
uimenu_t *menu = (menuPtr); \
|
uimenu_t *menu = (menuPtr); \
|
||||||
|
|||||||
Reference in New Issue
Block a user