From 2dcf0d0f0dd01d45d48e4db8af1572c828f3b700 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 7 Jul 2026 10:12:22 -0500 Subject: [PATCH] UI Confirm --- src/dusk/ui/frame/CMakeLists.txt | 1 + .../ui/frame/settings/uisettingsgameplay.c | 20 ++- .../ui/frame/settings/uisettingsgameplay.h | 3 +- src/dusk/ui/frame/uiconfirm.c | 135 ++++++++++++++++++ src/dusk/ui/frame/uiconfirm.h | 94 ++++++++++++ src/dusk/ui/uielement.c | 7 + 6 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 src/dusk/ui/frame/uiconfirm.c create mode 100644 src/dusk/ui/frame/uiconfirm.h diff --git a/src/dusk/ui/frame/CMakeLists.txt b/src/dusk/ui/frame/CMakeLists.txt index d23136c4..758ad917 100644 --- a/src/dusk/ui/frame/CMakeLists.txt +++ b/src/dusk/ui/frame/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC uiframe.c + uiconfirm.c ) add_subdirectory(game) diff --git a/src/dusk/ui/frame/settings/uisettingsgameplay.c b/src/dusk/ui/frame/settings/uisettingsgameplay.c index 7f6329be..1d5db3a4 100644 --- a/src/dusk/ui/frame/settings/uisettingsgameplay.c +++ b/src/dusk/ui/frame/settings/uisettingsgameplay.c @@ -8,14 +8,31 @@ #include "uisettingsgameplay.h" #include "assert/assert.h" #include "util/memory.h" +#include "ui/frame/uiconfirm.h" +#include "console/console.h" uisettingsgameplay_t UI_SETTINGS_GAMEPLAY; +void uiSettingsGameplayShowConfirmResult(const bool_t result, void *user) { + consolePrint("Confirm dialog result: %s", result ? "confirmed" : "cancelled"); +} + +void uiSettingsGameplaySelected( + const uimenu_t *menu, + const uint8_t index, + const uimenuitem_t *item +) { + if(index != UI_SETTINGS_GAMEPLAY_INDEX_SHOW_CONFIRM) return; + uiConfirmOpen( + "Are you sure?", uiSettingsGameplayShowConfirmResult, NULL + ); +} + errorret_t uiSettingsGameplayInit(void) { memoryZero(&UI_SETTINGS_GAMEPLAY, sizeof(uisettingsgameplay_t)); uimenu_t *menu = &UI_SETTINGS_GAMEPLAY.menu; - uiMenuInit(menu, NULL, NULL, NULL); + uiMenuInit(menu, uiSettingsGameplaySelected, NULL, NULL); menu->items = UI_SETTINGS_GAMEPLAY.items; MENU_BEGIN @@ -23,6 +40,7 @@ errorret_t uiSettingsGameplayInit(void) { MENU_BUTTON("Do other thing"); MENU_CHECKBOX("Enable thing?"); MENU_SLIDER_FLOAT("test", 1.0f, 1.0f, 4.0f, 0.5f); + MENU_BUTTON("Show confirm"); assertTrue( menuIndex <= UI_SETTINGS_GAMEPLAY_ITEM_COUNT, diff --git a/src/dusk/ui/frame/settings/uisettingsgameplay.h b/src/dusk/ui/frame/settings/uisettingsgameplay.h index d7f35390..8d7af472 100644 --- a/src/dusk/ui/frame/settings/uisettingsgameplay.h +++ b/src/dusk/ui/frame/settings/uisettingsgameplay.h @@ -9,7 +9,8 @@ #include "error/error.h" #include "ui/widget/uimenu.h" -#define UI_SETTINGS_GAMEPLAY_ITEM_COUNT 4 +#define UI_SETTINGS_GAMEPLAY_ITEM_COUNT 5 +#define UI_SETTINGS_GAMEPLAY_INDEX_SHOW_CONFIRM 4 typedef struct { uimenu_t menu; diff --git a/src/dusk/ui/frame/uiconfirm.c b/src/dusk/ui/frame/uiconfirm.c new file mode 100644 index 00000000..a735c00a --- /dev/null +++ b/src/dusk/ui/frame/uiconfirm.c @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uiconfirm.h" +#include "uiframe.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "util/string.h" +#include "util/math.h" +#include "display/screen/screen.h" +#include "display/text/text.h" +#include "display/color.h" +#include "display/spritebatch/spritebatch.h" +#include "display/texture/texture.h" +#include "display/shader/shaderunlit.h" + +#define UI_CONFIRM_BACKDROP_COLOR color4b(0, 0, 0, 160) + +uiconfirm_t UI_CONFIRM; + +void uiConfirmSelected( + const uimenu_t *menu, + const uint8_t index, + const uimenuitem_t *item +) { + UI_CONFIRM.result = index == UI_CONFIRM_INDEX_CONFIRM; + uiConfirmClose(); +} + +void uiConfirmClosed(const uimenu_t *menu) { + if(UI_CONFIRM.callback != NULL) { + UI_CONFIRM.callback(UI_CONFIRM.result, UI_CONFIRM.user); + } +} + +errorret_t uiConfirmInit(void) { + memoryZero(&UI_CONFIRM, sizeof(uiconfirm_t)); + + uimenu_t *menu = &UI_CONFIRM.menu; + uiMenuInit(menu, uiConfirmSelected, uiConfirmClosed, NULL); + menu->items = UI_CONFIRM.items; + + MENU_BEGIN + MENU_BUTTON("Confirm"); + MENU_BUTTON("Cancel"); + + assertTrue( + menuIndex <= UI_CONFIRM_ITEM_COUNT, + "Confirm item count mismatch" + ); + + uiMenuSetItems(menu, UI_CONFIRM.items, menuIndex, menuIndex); + + errorOk(); +} + +errorret_t uiConfirmDraw(void) { + if(!uiMenuIsActive(&UI_CONFIRM.menu)) 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_CONFIRM_BACKDROP_COLOR, + .texture = &TEXTURE_WHITE + } + }; + errorChain( + spriteBatchBuffer(&backdropSprite, 1, &SHADER_UNLIT, backdropMaterial) + ); + errorChain(spriteBatchFlush()); + + int32_t textW, textH; + textMeasure(UI_CONFIRM.text, &FONT_DEFAULT, &textW, &textH); + + float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; + float_t width = mathMax( + (float_t)textW + (UI_FRAME_START_X * 2), UI_CONFIRM_MIN_WIDTH + ); + float_t height = (UI_FRAME_START_Y * 2) + rowHeight + UI_FRAME_PADDING_Y + + 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; + float_t contentWidth = width - (UI_FRAME_START_X * 2); + + errorChain(textDraw(contentX, contentY, UI_CONFIRM.text, COLOR_WHITE, &FONT_DEFAULT)); + + float_t buttonsY = contentY + rowHeight + UI_FRAME_PADDING_Y; + errorChain(uiMenuDraw(&UI_CONFIRM.menu, contentX, buttonsY, contentWidth, rowHeight)); + + errorChain(spriteBatchFlush()); + errorOk(); +} + +bool_t uiConfirmIsOpen(void) { + return uiMenuIsActive(&UI_CONFIRM.menu); +} + +bool_t uiConfirmGetResult(void) { + return UI_CONFIRM.result; +} + +void uiConfirmOpen( + const char_t *question, + uiconfirmcallback_t callback, + void *user +) { + assertNotNull(question, "Question cannot be NULL"); + stringCopy(UI_CONFIRM.text, question, UI_CONFIRM_TEXT_MAX); + UI_CONFIRM.callback = callback; + UI_CONFIRM.user = user; + UI_CONFIRM.result = false; + uiMenuOpen(&UI_CONFIRM.menu); +} + +void uiConfirmClose(void) { + uiMenuClose(&UI_CONFIRM.menu); +} + +errorret_t uiConfirmDispose(void) { + errorOk(); +} diff --git a/src/dusk/ui/frame/uiconfirm.h b/src/dusk/ui/frame/uiconfirm.h new file mode 100644 index 00000000..38697048 --- /dev/null +++ b/src/dusk/ui/frame/uiconfirm.h @@ -0,0 +1,94 @@ +/** + * 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/uimenu.h" + +#define UI_CONFIRM_TEXT_MAX 256 +#define UI_CONFIRM_MIN_WIDTH 160.0f +#define UI_CONFIRM_INDEX_CONFIRM 0 +#define UI_CONFIRM_INDEX_CANCEL 1 +#define UI_CONFIRM_ITEM_COUNT 2 + +/** + * Callback invoked once a confirm dialog is dismissed. + * + * @param result True if Confirm was selected, false if Cancel was + * selected or the dialog was backed out of. + * @param user Arbitrary pointer passed to uiConfirmOpen. + */ +typedef void (*uiconfirmcallback_t)(const bool_t result, void *user); + +typedef struct { + char_t text[UI_CONFIRM_TEXT_MAX]; + uimenu_t menu; + uimenuitem_t items[UI_CONFIRM_ITEM_COUNT]; + uiconfirmcallback_t callback; + void *user; + bool_t result; +} uiconfirm_t; + +extern uiconfirm_t UI_CONFIRM; + +/** + * Initializes the confirm dialog. + * + * @return Any error that occurs. + */ +errorret_t uiConfirmInit(void); + +/** + * Draws the confirm dialog: a semi-transparent black backdrop covering + * the whole screen, then its own centered frame with the question text + * and Confirm/Cancel buttons. No-op when not open. + * + * @return Any error that occurs. + */ +errorret_t uiConfirmDraw(void); + +/** + * Returns true when the confirm dialog is currently open. + * + * @returns True if open. + */ +bool_t uiConfirmIsOpen(void); + +/** + * Returns the result of the most recently dismissed confirm dialog. + * + * @returns True if Confirm was selected, false otherwise. + */ +bool_t uiConfirmGetResult(void); + +/** + * Opens the confirm dialog with the given question text. callback is + * invoked exactly once with the result, whether the dialog was + * dismissed by selecting a button or by pressing cancel/back. + * + * @param question Display text; copied internally, safe to be transient. + * @param callback Called with the result once the dialog closes. May be + * NULL. + * @param user Arbitrary pointer passed through to callback. + */ +void uiConfirmOpen( + const char_t *question, + uiconfirmcallback_t callback, + void *user +); + +/** + * Closes the confirm dialog. No-op when already closed. + */ +void uiConfirmClose(void); + +/** + * Disposes of the confirm dialog. + * + * @return Any error that occurs. + */ +errorret_t uiConfirmDispose(void); diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index 943ea614..b75d9a7d 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -18,6 +18,7 @@ #include "ui/debug/uiconsole.h" #include "ui/frame/settings/uisettings.h" #include "ui/frame/game/uigamemenu.h" +#include "ui/frame/uiconfirm.h" #include "ui/rpg/uitextboxmain.h" uielement_t UI_ELEMENTS[] = { @@ -46,6 +47,12 @@ uielement_t UI_ELEMENTS[] = { .dispose = uiSettingsDispose }, + { + .init = uiConfirmInit, + .draw = uiConfirmDraw, + .dispose = uiConfirmDispose + }, + { .init = uiTextboxMainInit, .update = uiTextboxMainUpdate,