Wire keyboard into save naming, add fatal error overlay, cutscene keyboard item
- uimainmenu: removed the temporary keyboard test entry now that the keyboard has a real caller. - uiselectsave: picking an empty save slot now opens the keyboard to name it (localized title "Enter game save file name"), writes the named slot to disk (mirroring the existing delete-confirm write pattern), and on a write failure opens the new fatal error overlay. - uikeyboardopen_t gains a title override (NULL keeps the default "ENTER YOUR TEXT"). - New ui/overlay/uifatalerror: a minimal fullscreen, non-dismissible overlay (message + QUIT button, back disabled) for unrecoverable errors - a nicer alternative to a raw assert crash. Draws above everything else in the UI element list. - New CUTSCENE_ITEM_TYPE_KEYBOARD cutscene item: opens the keyboard with an embedded uikeyboardopen_t, blocks the cutscene until it closes, then caches the entered text via a new general-purpose cutscenesystem_t.textCache (CUTSCENE_TEXT_CACHE_MAX, with get/set accessors) mirroring the existing entity/area/text-mini id caching pattern.
This commit is contained in:
@@ -82,6 +82,9 @@ msgstr "Delete a Save"
|
||||
msgid "ui.select_save.delete_confirm"
|
||||
msgstr "Are you sure you want to delete this save?"
|
||||
|
||||
msgid "ui.select_save.name_title"
|
||||
msgstr "Enter game save file name"
|
||||
|
||||
msgid "ui.save_slot.number_format"
|
||||
msgstr "Slot %d"
|
||||
|
||||
|
||||
@@ -161,6 +161,16 @@ typedef struct cutscene_s {
|
||||
#define CUTSCENE_MODAL_CLOSE() \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_MODAL_CLOSE }
|
||||
|
||||
// Opens the on-screen keyboard and blocks the cutscene until it closes,
|
||||
// then caches whatever was typed - see cutsceneSystemGetTextCache to
|
||||
// read it back afterwards. __VA_ARGS__ are uikeyboardopen_t designated
|
||||
// initializers, e.g. CUTSCENE_KEYBOARD(.cancel = true, .maxLength = 8).
|
||||
#define CUTSCENE_KEYBOARD(...) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_KEYBOARD, \
|
||||
.keyboard = { .open = { __VA_ARGS__ } } \
|
||||
}
|
||||
|
||||
// (Re)requests an available save device and jumps straight to
|
||||
// SUCCESS_MARKER or FAILURE_MARKER once it resolves, same as a
|
||||
// CUTSCENE_MODAL_OPTIONS callback - it does not fall through to
|
||||
|
||||
@@ -35,6 +35,7 @@ void cutsceneSystemPrepare(
|
||||
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.textCache[0] = '\0';
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
|
||||
CUTSCENE_SYSTEM.onComplete = NULL;
|
||||
}
|
||||
@@ -109,6 +110,7 @@ void cutsceneSystemNext() {
|
||||
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.textCache[0] = '\0';
|
||||
CUTSCENE_SYSTEM.onComplete = NULL;
|
||||
|
||||
if(onComplete != NULL) onComplete(userData);
|
||||
@@ -218,6 +220,16 @@ uint8_t cutsceneSystemGetTextMiniId(const uint8_t index) {
|
||||
return index;
|
||||
}
|
||||
|
||||
const char_t * cutsceneSystemGetTextCache(void) {
|
||||
return CUTSCENE_SYSTEM.textCache;
|
||||
}
|
||||
|
||||
void cutsceneSystemSetTextCache(const char_t *text) {
|
||||
stringCopy(
|
||||
CUTSCENE_SYSTEM.textCache, text, CUTSCENE_TEXT_CACHE_MAX - 1
|
||||
);
|
||||
}
|
||||
|
||||
void cutsceneSystemDispose() {
|
||||
CUTSCENE_SYSTEM.scene = NULL;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
||||
@@ -228,5 +240,6 @@ void cutsceneSystemDispose() {
|
||||
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.textCache[0] = '\0';
|
||||
CUTSCENE_SYSTEM.onComplete = NULL;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ typedef struct entity_s entity_t;
|
||||
// cutscene_t.dataSize.
|
||||
#define CUTSCENE_SYSTEM_SIZE_MAX 8192
|
||||
|
||||
// Size of CUTSCENE_SYSTEM.textCache - keep >= UI_KEYBOARD_TEXT_MAX (see
|
||||
// ui/dialog/keyboard/uikeyboard.h) so text entered via a
|
||||
// CUTSCENE_ITEM_TYPE_KEYBOARD item is never truncated caching it here.
|
||||
#define CUTSCENE_TEXT_CACHE_MAX 64
|
||||
|
||||
typedef struct {
|
||||
const cutscene_t *scene;
|
||||
uint8_t currentItem;
|
||||
@@ -32,6 +37,12 @@ typedef struct {
|
||||
uint8_t areaLastCreated;
|
||||
uint8_t textMiniLastCreated;
|
||||
|
||||
// Free-form text cache, e.g. holding whatever was last typed via a
|
||||
// CUTSCENE_ITEM_TYPE_KEYBOARD item - see cutsceneSystemGetTextCache/
|
||||
// cutsceneSystemSetTextCache. Not tied to any one item type; any item
|
||||
// may read or write it.
|
||||
char_t textCache[CUTSCENE_TEXT_CACHE_MAX];
|
||||
|
||||
// Data (used by the current item).
|
||||
cutsceneitemdata_t data;
|
||||
|
||||
@@ -141,6 +152,27 @@ uint8_t cutsceneSystemGetAreaId(const uint8_t areaId);
|
||||
*/
|
||||
uint8_t cutsceneSystemGetTextMiniId(const uint8_t index);
|
||||
|
||||
/**
|
||||
* Returns CUTSCENE_SYSTEM.textCache - whatever was last written there via
|
||||
* cutsceneSystemSetTextCache (e.g. by a CUTSCENE_ITEM_TYPE_KEYBOARD item
|
||||
* once its keyboard closes). Empty ("") if nothing has been cached yet
|
||||
* for the running cutscene.
|
||||
*
|
||||
* @returns The cached text.
|
||||
*/
|
||||
const char_t * cutsceneSystemGetTextCache(void);
|
||||
|
||||
/**
|
||||
* Overwrites CUTSCENE_SYSTEM.textCache with a copy of text. Any item may
|
||||
* call this - it isn't tied to any one item type - so later items can
|
||||
* read back whatever the caller wants to pass along, up to
|
||||
* CUTSCENE_TEXT_CACHE_MAX - 1 characters.
|
||||
*
|
||||
* @param text The text to cache; copied internally, safe to be
|
||||
* transient. Must not exceed CUTSCENE_TEXT_CACHE_MAX - 1 characters.
|
||||
*/
|
||||
void cutsceneSystemSetTextCache(const char_t *text);
|
||||
|
||||
/**
|
||||
* Advance to the next item in the cutscene.
|
||||
*/
|
||||
|
||||
@@ -172,6 +172,11 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
[CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS] = {
|
||||
.init = cutsceneSaveLoadAllSlotsStart,
|
||||
.update = cutsceneSaveLoadAllSlotsUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_KEYBOARD] = {
|
||||
.init = cutsceneKeyboardStart,
|
||||
.update = cutsceneKeyboardUpdate
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "ui/cutsceneshake.h"
|
||||
#include "ui/cutscenemodal.h"
|
||||
#include "ui/cutscenemodaloptionsmarkers.h"
|
||||
#include "ui/cutscenekeyboard.h"
|
||||
#include "item/cutsceneitemgive.h"
|
||||
#include "maparea/cutscenemapareaadd.h"
|
||||
#include "maparea/cutscenemaparearemove.h"
|
||||
@@ -76,6 +77,7 @@ typedef enum {
|
||||
CUTSCENE_ITEM_TYPE_SCENE,
|
||||
CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK,
|
||||
CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS,
|
||||
CUTSCENE_ITEM_TYPE_KEYBOARD,
|
||||
|
||||
CUTSCENE_ITEM_TYPE_COUNT
|
||||
} cutsceneitemtype_t;
|
||||
@@ -115,6 +117,7 @@ struct cutsceneitem_s {
|
||||
cutscenescene_t sceneChange;
|
||||
cutscenesavedevicecheck_t saveDeviceCheck;
|
||||
cutscenesaveloadallslots_t saveLoadAllSlots;
|
||||
cutscenekeyboard_t keyboard;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -13,4 +13,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
cutsceneshake.c
|
||||
cutscenemodal.c
|
||||
cutscenemodaloptionsmarkers.c
|
||||
cutscenekeyboard.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "ui/dialog/keyboard/uikeyboard.h"
|
||||
|
||||
void cutsceneKeyboardStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
uiKeyboardOpen(&item->keyboard.open);
|
||||
}
|
||||
|
||||
bool_t cutsceneKeyboardUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
if(uiKeyboardIsOpen()) return false;
|
||||
|
||||
cutsceneSystemSetTextCache(UI_KEYBOARD.text);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "ui/dialog/keyboard/uikeyboard.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
// Passed straight through to uiKeyboardOpen - see uikeyboardopen_t.
|
||||
uikeyboardopen_t open;
|
||||
} cutscenekeyboard_t;
|
||||
|
||||
/**
|
||||
* Starts a keyboard item (opens the on-screen keyboard with the item's
|
||||
* open parameters, unmodified - see uiKeyboardOpen).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneKeyboardStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a keyboard item. Blocks the cutscene while the keyboard is
|
||||
* open; once it closes (confirmed or cancelled), caches its resulting
|
||||
* text via cutsceneSystemSetTextCache (see cutsceneSystemGetTextCache)
|
||||
* before completing, regardless of whether confirm or cancel was
|
||||
* picked - check UI_KEYBOARD.confirmed from the item's own onInput
|
||||
* callback if that distinction matters.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once the keyboard has closed; false while still open.
|
||||
*/
|
||||
bool_t cutsceneKeyboardUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -424,7 +424,8 @@ void uiKeyboardOpen(const uikeyboardopen_t *open) {
|
||||
);
|
||||
|
||||
stringCopy(
|
||||
UI_KEYBOARD.titleText, UI_KEYBOARD_TITLE_DEFAULT,
|
||||
UI_KEYBOARD.titleText,
|
||||
open->title != NULL ? open->title : UI_KEYBOARD_TITLE_DEFAULT,
|
||||
UI_KEYBOARD_TITLE_TEXT_MAX - 1
|
||||
);
|
||||
UI_KEYBOARD.titleLabel.dirty = true;
|
||||
|
||||
@@ -118,10 +118,16 @@ typedef void (*uikeyboardinputcallback_t)(
|
||||
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.
|
||||
* Parameters for uiKeyboardOpen. Expect this to grow (e.g. button text
|
||||
* overrides) as more dialogs start using the keyboard.
|
||||
*/
|
||||
typedef struct {
|
||||
// Title text shown above the entered text; copied internally, safe to
|
||||
// be transient. Displayed as-is - resolve any locale string yourself
|
||||
// before passing it in. NULL uses UI_KEYBOARD_TITLE_DEFAULT ("ENTER
|
||||
// YOUR TEXT").
|
||||
const char_t *title;
|
||||
|
||||
// Fired once the dialog is dismissed, confirmed or cancelled. May be
|
||||
// NULL.
|
||||
uikeyboardinputcallback_t onInput;
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
#include "uiselectsave.h"
|
||||
#include "ui/widget/uiframe.h"
|
||||
#include "ui/dialog/uiconfirm.h"
|
||||
#include "ui/dialog/keyboard/uikeyboard.h"
|
||||
#include "ui/overlay/uifatalerror.h"
|
||||
#include "save/save.h"
|
||||
#include "time/timeepoch.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/color.h"
|
||||
@@ -89,6 +92,57 @@ void uiSelectSaveConfirmDelete(const uint8_t index) {
|
||||
);
|
||||
}
|
||||
|
||||
void uiSelectSaveNameEntered(
|
||||
const char_t *text,
|
||||
const bool_t confirmed,
|
||||
void *user
|
||||
) {
|
||||
if(!confirmed) return;
|
||||
|
||||
assertTrue(SAVE.deviceCurrent != 0xFF, "No current device");
|
||||
const uint8_t index = UI_SELECT_SAVE.pendingNameIndex;
|
||||
|
||||
saveslot_t slot;
|
||||
saveSlotInit(&slot);
|
||||
stringCopy(slot.cachedData.name, text, SAVE_SLOT_NAME_LENGTH);
|
||||
|
||||
errorret_t writeResult = saveDeviceSlotWrite(
|
||||
&SAVE.devices[SAVE.deviceCurrent], &slot, index
|
||||
);
|
||||
if(errorIsNotOk(writeResult)) {
|
||||
errorCatch(errorPrint(writeResult));
|
||||
uiFatalErrorOpen(writeResult.state->message);
|
||||
return;
|
||||
}
|
||||
|
||||
SAVE.caches[index] = slot.cachedData;
|
||||
uiSelectSaveRefreshSlot(index);
|
||||
|
||||
UI_SELECT_SAVE.result = index;
|
||||
uiSelectSaveClose();
|
||||
}
|
||||
|
||||
void uiSelectSaveNameEmptySlot(const uint8_t index) {
|
||||
UI_SELECT_SAVE.pendingNameIndex = index;
|
||||
|
||||
char_t title[UI_KEYBOARD_TITLE_TEXT_MAX];
|
||||
errorret_t result = assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale, "ui.select_save.name_title", 0,
|
||||
title, sizeof(title)
|
||||
);
|
||||
if(errorIsNotOk(result)) errorCatch(result);
|
||||
|
||||
uikeyboardopen_t open = {
|
||||
.title = title,
|
||||
.onInput = uiSelectSaveNameEntered,
|
||||
.cancel = true,
|
||||
.maxLength = SAVE_SLOT_NAME_LENGTH,
|
||||
.trimmed = true,
|
||||
.allowBlank = false
|
||||
};
|
||||
uiKeyboardOpen(&open);
|
||||
}
|
||||
|
||||
void uiSelectSaveSetActionLabel(const uiselectsavetype_t type) {
|
||||
const char_t *labelKey = type == UI_SELECT_SAVE_TYPE_LOAD ?
|
||||
"ui.select_save.delete_mode" : "ui.confirm.cancel";
|
||||
@@ -132,6 +186,11 @@ void uiSelectSaveSlotSelected(
|
||||
return;
|
||||
}
|
||||
|
||||
if(!saveSlotInUse(&SAVE.caches[index])) {
|
||||
uiSelectSaveNameEmptySlot(index);
|
||||
return;
|
||||
}
|
||||
|
||||
UI_SELECT_SAVE.result = index;
|
||||
uiSelectSaveClose();
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ typedef struct {
|
||||
uisaveslot_t slots[SAVE_SLOT_COUNT];
|
||||
uiscrolling_t scroll;
|
||||
uint8_t pendingDeleteIndex;
|
||||
uint8_t pendingNameIndex;
|
||||
|
||||
uiselectsaveresultcallback_t callback;
|
||||
void *user;
|
||||
|
||||
@@ -8,4 +8,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
uicrop.c
|
||||
uifullbox.c
|
||||
uiloading.c
|
||||
uifatalerror.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uifatalerror.h"
|
||||
#include "ui/widget/uiframe.h"
|
||||
#include "ui/widget/uibutton.h"
|
||||
#include "engine/engine.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"
|
||||
|
||||
uifatalerror_t UI_FATAL_ERROR;
|
||||
|
||||
void uiFatalErrorMenuSelected(
|
||||
const uimenu_t *menu,
|
||||
const uint8_t index,
|
||||
const uimenuitem_t *item
|
||||
) {
|
||||
ENGINE.running = false;
|
||||
}
|
||||
|
||||
errorret_t uiFatalErrorInit(void) {
|
||||
memoryZero(&UI_FATAL_ERROR, sizeof(uifatalerror_t));
|
||||
|
||||
uiLabelInit(
|
||||
&UI_FATAL_ERROR.messageLabel, UI_FATAL_ERROR.messageText,
|
||||
UI_FATAL_ERROR.messageSprites, UI_FATAL_ERROR_MESSAGE_SPRITES_MAX
|
||||
);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void uiFatalErrorOpen(const char_t *message) {
|
||||
assertNotNull(message, "Message cannot be NULL");
|
||||
// This is the last line of defense - it must never itself fail, so a
|
||||
// second fatal error while one is already showing is silently dropped
|
||||
// rather than asserting.
|
||||
if(UI_FATAL_ERROR.open) return;
|
||||
|
||||
stringCopy(
|
||||
UI_FATAL_ERROR.messageText, message, UI_FATAL_ERROR_MESSAGE_MAX - 1
|
||||
);
|
||||
UI_FATAL_ERROR.messageLabel.dirty = true;
|
||||
uiLabelRebuffer(&UI_FATAL_ERROR.messageLabel);
|
||||
|
||||
UI_FATAL_ERROR.open = true;
|
||||
|
||||
uiMenuInit(&UI_FATAL_ERROR.menu, uiFatalErrorMenuSelected, NULL, NULL);
|
||||
UI_FATAL_ERROR.items[0].type = UI_MENU_WIDGET_TYPE_BUTTON;
|
||||
uiButtonInit(&UI_FATAL_ERROR.items[0].button, UI_FATAL_ERROR_QUIT_LABEL);
|
||||
uiMenuSetItems(
|
||||
&UI_FATAL_ERROR.menu, UI_FATAL_ERROR.items, UI_FATAL_ERROR_ITEM_COUNT, 1
|
||||
);
|
||||
// No way out but the QUIT button - this is a fatal, unrecoverable
|
||||
// condition.
|
||||
uiMenuSetDisableBack(&UI_FATAL_ERROR.menu, true);
|
||||
uiMenuOpen(&UI_FATAL_ERROR.menu);
|
||||
}
|
||||
|
||||
bool_t uiFatalErrorIsOpen(void) {
|
||||
return UI_FATAL_ERROR.open;
|
||||
}
|
||||
|
||||
errorret_t uiFatalErrorDraw(void) {
|
||||
if(!UI_FATAL_ERROR.open) errorOk();
|
||||
|
||||
const float_t x = (float_t)SCREEN.scanX;
|
||||
const float_t y = (float_t)SCREEN.scanY;
|
||||
const float_t width = (float_t)SCREEN.scanWidth;
|
||||
const float_t height = (float_t)SCREEN.scanHeight;
|
||||
|
||||
errorChain(uiFrameDraw(x, y, width, height));
|
||||
|
||||
const float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
||||
const float_t contentWidth = mathMax(
|
||||
(float_t)UI_FATAL_ERROR.messageLabel.width,
|
||||
(float_t)UI_FATAL_ERROR.items[0].button.label.width
|
||||
);
|
||||
const float_t contentHeight = (float_t)UI_FATAL_ERROR.messageLabel.height +
|
||||
UI_FRAME_PADDING_Y + rowHeight;
|
||||
|
||||
const float_t contentX = x + (width - contentWidth) * 0.5f;
|
||||
const float_t contentY = y + (height - contentHeight) * 0.5f;
|
||||
|
||||
uiLabelSetX(&UI_FATAL_ERROR.messageLabel, contentX);
|
||||
uiLabelSetY(&UI_FATAL_ERROR.messageLabel, contentY);
|
||||
errorChain(uiLabelRender(&UI_FATAL_ERROR.messageLabel, COLOR_WHITE));
|
||||
|
||||
const float_t menuY = contentY +
|
||||
(float_t)UI_FATAL_ERROR.messageLabel.height + UI_FRAME_PADDING_Y;
|
||||
errorChain(uiMenuDraw(&UI_FATAL_ERROR.menu, contentX, menuY, contentWidth, rowHeight));
|
||||
|
||||
errorChain(spriteBatchFlush());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiFatalErrorDispose(void) {
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "ui/widget/uilabel.h"
|
||||
#include "ui/widget/uimenu.h"
|
||||
|
||||
#define UI_FATAL_ERROR_MESSAGE_MAX 256
|
||||
#define UI_FATAL_ERROR_MESSAGE_SPRITES_MAX UI_FATAL_ERROR_MESSAGE_MAX
|
||||
#define UI_FATAL_ERROR_ITEM_COUNT 1
|
||||
#define UI_FATAL_ERROR_QUIT_LABEL "QUIT"
|
||||
|
||||
typedef struct {
|
||||
bool_t open;
|
||||
|
||||
uilabel_t messageLabel;
|
||||
char_t messageText[UI_FATAL_ERROR_MESSAGE_MAX];
|
||||
spritebatchsprite_t messageSprites[UI_FATAL_ERROR_MESSAGE_SPRITES_MAX];
|
||||
|
||||
uimenu_t menu;
|
||||
uimenuitem_t items[UI_FATAL_ERROR_ITEM_COUNT];
|
||||
} uifatalerror_t;
|
||||
|
||||
extern uifatalerror_t UI_FATAL_ERROR;
|
||||
|
||||
/**
|
||||
* Initializes the fatal error overlay.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiFatalErrorInit(void);
|
||||
|
||||
/**
|
||||
* Opens the fatal error overlay, a fullscreen, non-dismissible display
|
||||
* for an unrecoverable error - the only way out is its QUIT button,
|
||||
* which sets ENGINE.running to false. Intended as a last-resort, nicer-
|
||||
* looking alternative to a raw assert crash for a genuinely fatal
|
||||
* condition, so it must never itself fail: a second call while already
|
||||
* open is silently ignored rather than asserting, and it draws above
|
||||
* every other UI element.
|
||||
*
|
||||
* @param message The error text to display; copied internally, safe to
|
||||
* be transient. Truncated to UI_FATAL_ERROR_MESSAGE_MAX - 1 characters.
|
||||
*/
|
||||
void uiFatalErrorOpen(const char_t *message);
|
||||
|
||||
/**
|
||||
* Returns whether the fatal error overlay is currently open.
|
||||
*
|
||||
* @returns True if open.
|
||||
*/
|
||||
bool_t uiFatalErrorIsOpen(void);
|
||||
|
||||
/**
|
||||
* Draws the fatal error overlay. No-op when not open.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiFatalErrorDraw(void);
|
||||
|
||||
/**
|
||||
* Disposes of the fatal error overlay.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiFatalErrorDispose(void);
|
||||
@@ -16,15 +16,11 @@
|
||||
#include "locale/localemanager.h"
|
||||
#include "asset/loader/locale/assetlocaleloader.h"
|
||||
#include "ui/dialog/uiconfirm.h"
|
||||
#include "ui/dialog/keyboard/uikeyboard.h"
|
||||
#include "scene/mainmenu/scenemainmenu.h"
|
||||
#include "console/console.h"
|
||||
|
||||
#define UI_MAIN_MENU_INDEX_START_GAME 0
|
||||
#define UI_MAIN_MENU_INDEX_OPTIONS 1
|
||||
#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;
|
||||
|
||||
@@ -32,22 +28,6 @@ void uiMainMenuQuitConfirmed(const bool_t result, void *user) {
|
||||
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(
|
||||
const uimenu_t *menu,
|
||||
const uint8_t index,
|
||||
@@ -68,22 +48,6 @@ void uiMainMenuSelected(
|
||||
uiConfirmOpen("main_menu.quit_confirm", uiMainMenuQuitConfirmed, NULL);
|
||||
break;
|
||||
|
||||
case UI_MAIN_MENU_INDEX_LOAD_GAME_TEST: {
|
||||
uikeyboardopen_t open = {
|
||||
.onInput = uiMainMenuKeyboardTestInput,
|
||||
.onKeyPress = uiMainMenuKeyboardTestKeyPress,
|
||||
.cancel = true,
|
||||
.confirm = true,
|
||||
.cancelConfirm = true,
|
||||
.user = NULL,
|
||||
.maxLength = 6,
|
||||
.lineCount = 2,
|
||||
.allowBlank = true,
|
||||
};
|
||||
uiKeyboardOpen(&open);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -120,8 +84,6 @@ errorret_t uiMainMenuInit(void) {
|
||||
MENU_BUTTON(UI_MAIN_MENU.startGameLabel);
|
||||
MENU_BUTTON(UI_MAIN_MENU.optionsLabel);
|
||||
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);
|
||||
|
||||
// The main menu is the root screen once past the initial boot check -
|
||||
|
||||
@@ -9,12 +9,9 @@
|
||||
#include "error/error.h"
|
||||
#include "ui/widget/uimenu.h"
|
||||
|
||||
// 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_ITEM_COUNT 3
|
||||
#define UI_MAIN_MENU_WIDTH 200.0f
|
||||
#define UI_MAIN_MENU_HEIGHT 200.0f
|
||||
#define UI_MAIN_MENU_HEIGHT 160.0f
|
||||
#define UI_MAIN_MENU_LABEL_MAX 32
|
||||
|
||||
typedef struct {
|
||||
@@ -52,29 +49,3 @@ errorret_t uiMainMenuDraw(void);
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
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
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "ui/debug/uifps.h"
|
||||
#include "ui/overlay/uifullbox.h"
|
||||
#include "ui/overlay/uiloading.h"
|
||||
#include "ui/overlay/uifatalerror.h"
|
||||
#include "ui/debug/uiplayerpos.h"
|
||||
#include "ui/overlay/uicrop.h"
|
||||
#include "ui/transition/uitransition.h"
|
||||
@@ -135,6 +136,11 @@ uielement_t UI_ELEMENTS[] = {
|
||||
.update = uiLoadingUpdate,
|
||||
.draw = uiLoadingDraw
|
||||
},
|
||||
{
|
||||
.init = uiFatalErrorInit,
|
||||
.draw = uiFatalErrorDraw,
|
||||
.dispose = uiFatalErrorDispose
|
||||
},
|
||||
|
||||
// ===========================================================================
|
||||
// Important rendering components, lives outside the primary game engine
|
||||
|
||||
Reference in New Issue
Block a user