diff --git a/assets/locale/en_US.po b/assets/locale/en_US.po index e48db954..0c0599c7 100644 --- a/assets/locale/en_US.po +++ b/assets/locale/en_US.po @@ -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" diff --git a/src/dusk/rpg/cutscene/cutscene.h b/src/dusk/rpg/cutscene/cutscene.h index 7c379dee..17643179 100644 --- a/src/dusk/rpg/cutscene/cutscene.h +++ b/src/dusk/rpg/cutscene/cutscene.h @@ -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 diff --git a/src/dusk/rpg/cutscene/cutscenesystem.c b/src/dusk/rpg/cutscene/cutscenesystem.c index 7ccd101b..1abe3b57 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.c +++ b/src/dusk/rpg/cutscene/cutscenesystem.c @@ -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; } diff --git a/src/dusk/rpg/cutscene/cutscenesystem.h b/src/dusk/rpg/cutscene/cutscenesystem.h index 8c9ed702..823285d0 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.h +++ b/src/dusk/rpg/cutscene/cutscenesystem.h @@ -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. */ diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index 7e38413b..5061c144 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -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 } }; diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.h b/src/dusk/rpg/cutscene/item/cutsceneitem.h index 53b4baf7..7817983b 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.h @@ -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; }; }; diff --git a/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt b/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt index b359087a..fe4708bd 100644 --- a/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt +++ b/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt @@ -13,4 +13,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} cutsceneshake.c cutscenemodal.c cutscenemodaloptionsmarkers.c + cutscenekeyboard.c ) diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.c b/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.c new file mode 100644 index 00000000..79dd0323 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/ui/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; +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h b/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h new file mode 100644 index 00000000..6a7c7a98 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h @@ -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 +); diff --git a/src/dusk/ui/dialog/keyboard/uikeyboard.c b/src/dusk/ui/dialog/keyboard/uikeyboard.c index 97f5fe8c..51cfd369 100644 --- a/src/dusk/ui/dialog/keyboard/uikeyboard.c +++ b/src/dusk/ui/dialog/keyboard/uikeyboard.c @@ -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; diff --git a/src/dusk/ui/dialog/keyboard/uikeyboard.h b/src/dusk/ui/dialog/keyboard/uikeyboard.h index ce1d00c7..565d7cba 100644 --- a/src/dusk/ui/dialog/keyboard/uikeyboard.h +++ b/src/dusk/ui/dialog/keyboard/uikeyboard.h @@ -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; diff --git a/src/dusk/ui/dialog/save/uiselectsave.c b/src/dusk/ui/dialog/save/uiselectsave.c index 1674879c..12179ff8 100644 --- a/src/dusk/ui/dialog/save/uiselectsave.c +++ b/src/dusk/ui/dialog/save/uiselectsave.c @@ -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(); } diff --git a/src/dusk/ui/dialog/save/uiselectsave.h b/src/dusk/ui/dialog/save/uiselectsave.h index 4997767d..d916c016 100644 --- a/src/dusk/ui/dialog/save/uiselectsave.h +++ b/src/dusk/ui/dialog/save/uiselectsave.h @@ -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; diff --git a/src/dusk/ui/overlay/CMakeLists.txt b/src/dusk/ui/overlay/CMakeLists.txt index d6c051b3..2c49abf3 100644 --- a/src/dusk/ui/overlay/CMakeLists.txt +++ b/src/dusk/ui/overlay/CMakeLists.txt @@ -8,4 +8,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} uicrop.c uifullbox.c uiloading.c + uifatalerror.c ) diff --git a/src/dusk/ui/overlay/uifatalerror.c b/src/dusk/ui/overlay/uifatalerror.c new file mode 100644 index 00000000..4f619de7 --- /dev/null +++ b/src/dusk/ui/overlay/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(); +} diff --git a/src/dusk/ui/overlay/uifatalerror.h b/src/dusk/ui/overlay/uifatalerror.h new file mode 100644 index 00000000..c392960c --- /dev/null +++ b/src/dusk/ui/overlay/uifatalerror.h @@ -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); diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.c b/src/dusk/ui/screen/mainmenu/uimainmenu.c index 57508b85..4dd78f3d 100644 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.c +++ b/src/dusk/ui/screen/mainmenu/uimainmenu.c @@ -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 - diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.h b/src/dusk/ui/screen/mainmenu/uimainmenu.h index 8c2178d7..3a53c9ee 100644 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.h +++ b/src/dusk/ui/screen/mainmenu/uimainmenu.h @@ -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 -); diff --git a/src/dusk/ui/uielementlist.c b/src/dusk/ui/uielementlist.c index 68228361..3a0426a5 100644 --- a/src/dusk/ui/uielementlist.c +++ b/src/dusk/ui/uielementlist.c @@ -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