This commit is contained in:
2026-06-25 19:35:33 -05:00
parent ce29435831
commit d85737cc08
8 changed files with 381 additions and 54 deletions
+3 -1
View File
@@ -30,7 +30,8 @@ uifocusitem_t * uiFocusPush(
const uint8_t rows,
uifocusitemcallback_t selected,
uifocusitemcallback_t changed,
uifocusitemcallback_t closed
uifocusitemcallback_t closed,
void *user
) {
assertTrue(
UI_FOCUS.count < UI_FOCUS_STACK_MAX,
@@ -46,6 +47,7 @@ uifocusitem_t * uiFocusPush(
item->selected = selected;
item->changed = changed;
item->closed = closed;
item->user = user;
UI_FOCUS.count++;
if(item->changed != NULL) item->changed(item);
return item;
+3 -1
View File
@@ -71,6 +71,7 @@ void uiFocusInit(void);
* @param selected Called when the user selects the focused cell.
* @param changed Called when the focused cell position changes.
* @param closed Called when this focus item is popped.
* @param user Arbitrary pointer stored on the item before changed fires.
* @returns Pointer to the newly pushed focus item.
*/
uifocusitem_t * uiFocusPush(
@@ -78,7 +79,8 @@ uifocusitem_t * uiFocusPush(
const uint8_t rows,
uifocusitemcallback_t selected,
uifocusitemcallback_t changed,
uifocusitemcallback_t closed
uifocusitemcallback_t closed,
void *user
);
/**
+1
View File
@@ -29,4 +29,5 @@ struct uifocusitem_s {
uifocusitemcallback_t selected;
uifocusitemcallback_t changed;
uifocusitemcallback_t closed;
void *user;
};
+45 -48
View File
@@ -7,63 +7,67 @@
#include "uisettings.h"
#include "ui/frame/uiframe.h"
#include "ui/widget/uicheckbox.h"
#include "util/memory.h"
#include "display/spritebatch/spritebatch.h"
#include "display/text/text.h"
uisettings_t UI_SETTINGS;
bool_t uiSettingsSelected(const uifocusitem_t *item) {
if(item->y == 3) uiCheckboxToggle(&UI_SETTINGS.checkboxTest);
return true;
void uiSettingsSelected(
const uimenu_t *menu,
const uint8_t index,
const uimenuitem_t *item
) {
if(item->type == UIMENU_WIDGET_TYPE_CHECKBOX) {
uiCheckboxToggle(&UI_SETTINGS.items[index].checkbox);
}
}
bool_t uiSettingsChanged(const uifocusitem_t *item) {
uiCheckboxSetHighlighted(&UI_SETTINGS.checkboxTest, item->y == 3);
return true;
}
bool_t uiSettingsClosed(const uifocusitem_t *item) {
UI_SETTINGS.item = NULL;
return true;
void uiSettingsClosed(const uimenu_t *menu) {
// nothing extra needed; menu clears focusItem itself
}
errorret_t uiSettingsInit(void) {
memoryZero(&UI_SETTINGS, sizeof(uisettings_t));
uiCheckboxInit(&UI_SETTINGS.checkboxTest, "Enable thing?");
UI_SETTINGS.items[0] = (uimenuitem_t){
.type = UIMENU_WIDGET_TYPE_LABEL,
.label = "Settings"
};
UI_SETTINGS.items[1] = (uimenuitem_t){
.type = UIMENU_WIDGET_TYPE_LABEL,
.label = "Settings two"
};
UI_SETTINGS.items[2] = (uimenuitem_t){
.type = UIMENU_WIDGET_TYPE_LABEL,
.label = "Settings three"
};
UI_SETTINGS.items[3].type = UIMENU_WIDGET_TYPE_CHECKBOX;
uiCheckboxInit(&UI_SETTINGS.items[3].checkbox, "Enable thing?");
uiMenuInit(&UI_SETTINGS.menu, uiSettingsSelected, uiSettingsClosed, NULL);
uiMenuSetItems(
&UI_SETTINGS.menu,
UI_SETTINGS.items,
UI_SETTINGS_ITEM_COUNT,
2
);
errorOk();
}
errorret_t uiSettingsDraw(void) {
if(UI_SETTINGS.item == NULL) errorOk();
if(!uiMenuIsActive(&UI_SETTINGS.menu)) errorOk();
const char_t *texts[] = {
"Settings",
"Settings two",
"Settings three"
};
const uint8_t textCount = sizeof(texts) / sizeof(texts[0]);
const uint8_t totalRows = textCount + 1;
uint8_t rows = UI_SETTINGS_ITEM_COUNT;
errorChain(uiFrameDraw(
0.0f, 0.0f, 100.0f,
FONT_DEFAULT.tileset->tileHeight * totalRows + (UIFRAME_BORDER_HEIGHT * 2)
));
for(uint8_t i = 0; i < textCount; i++) {
errorChain(textDraw(
UIFRAME_BORDER_WIDTH,
UIFRAME_BORDER_HEIGHT + (i * FONT_DEFAULT.tileset->tileHeight),
texts[i],
UI_SETTINGS.item->y == i ? COLOR_RED : COLOR_WHITE, NULL
));
}
errorChain(uiCheckboxDraw(
&UI_SETTINGS.checkboxTest,
errorChain(uiFrameDraw(0.0f, 0.0f, 300.0f, 300));
errorChain(uiMenuDraw(
&UI_SETTINGS.menu,
UIFRAME_BORDER_WIDTH,
UIFRAME_BORDER_HEIGHT + (textCount * FONT_DEFAULT.tileset->tileHeight)
UIFRAME_BORDER_HEIGHT,
300.0f - (UIFRAME_BORDER_WIDTH * 2),
300.0f - (UIFRAME_BORDER_HEIGHT * 2)
));
errorChain(spriteBatchFlush());
@@ -71,22 +75,15 @@ errorret_t uiSettingsDraw(void) {
}
bool_t uiSettingsIsOpen(void) {
return UI_SETTINGS.item != NULL;
return uiMenuIsActive(&UI_SETTINGS.menu);
}
void uiSettingsOpen() {
if(UI_SETTINGS.item != NULL) return;
UI_SETTINGS.item = uiFocusPush(
1, 4, uiSettingsSelected, uiSettingsChanged, uiSettingsClosed
);
uiMenuOpen(&UI_SETTINGS.menu);
}
void uiSettingsClose() {
if(UI_SETTINGS.item == NULL) return;
uiFocusPopItem(UI_SETTINGS.item);
UI_SETTINGS.item = NULL;
uiMenuClose(&UI_SETTINGS.menu);
}
errorret_t uiSettingsDispose(void) {
+5 -4
View File
@@ -7,12 +7,13 @@
#pragma once
#include "error/error.h"
#include "ui/focus/uifocus.h"
#include "ui/widget/uicheckbox.h"
#include "ui/widget/uimenu.h"
#define UI_SETTINGS_ITEM_COUNT 4
typedef struct {
uifocusitem_t *item;
uicheckbox_t checkboxTest;
uimenu_t menu;
uimenuitem_t items[UI_SETTINGS_ITEM_COUNT];
} uisettings_t;
extern uisettings_t UI_SETTINGS;
+1
View File
@@ -6,4 +6,5 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
uicheckbox.c
uimenu.c
)
+163
View File
@@ -0,0 +1,163 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uimenu.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "display/text/text.h"
#include "display/color.h"
void uiMenuInit(
uimenu_t *menu,
uimenuselectedcallback_t selected,
uimenuclosedcallback_t closed,
uimenuchangedcallback_t changed
) {
assertNotNull(menu, "Menu cannot be NULL");
memoryZero(menu, sizeof(uimenu_t));
menu->selected = selected;
menu->closed = closed;
menu->changed = changed;
}
void uiMenuSetItems(
uimenu_t *menu,
const uimenuitem_t *items,
const uint8_t itemCount,
const uint8_t columns
) {
assertNotNull(menu, "Menu cannot be NULL");
assertNotNull(items, "Items cannot be NULL");
assertTrue(itemCount > 0, "Item count must be > 0");
assertTrue(columns > 0, "Columns must be > 0");
menu->items = (uimenuitem_t *)items;
menu->itemCount = itemCount;
menu->columns = columns;
}
void uiMenuSetPosition(uimenu_t *menu, const uint8_t x, const uint8_t y) {
assertNotNull(menu, "Menu cannot be NULL");
if(menu->focusItem == NULL) return;
uiFocusSetPosition(menu->focusItem, x, y);
}
void uiMenuOpen(uimenu_t *menu) {
assertNotNull(menu, "Menu cannot be NULL");
assertNotNull(menu->items, "Menu items cannot be NULL");
assertTrue(menu->itemCount > 0, "Menu item count must be > 0");
assertTrue(menu->columns > 0, "Menu columns must be > 0");
if(menu->focusItem != NULL) return;
uint8_t rows = (menu->itemCount + menu->columns - 1) / menu->columns;
menu->focusItem = uiFocusPush(
menu->columns, rows,
uiMenuFocusSelected,
uiMenuFocusChanged,
uiMenuFocusClosed,
menu
);
}
void uiMenuClose(uimenu_t *menu) {
assertNotNull(menu, "Menu cannot be NULL");
if(menu->focusItem == NULL) return;
uiFocusPopItem(menu->focusItem);
menu->focusItem = NULL;
}
bool_t uiMenuIsActive(const uimenu_t *menu) {
assertNotNull(menu, "Menu cannot be NULL");
return menu->focusItem != NULL;
}
errorret_t uiMenuDraw(
const uimenu_t *menu,
const float_t x,
const float_t y,
const float_t width,
const float_t height
) {
assertNotNull(menu, "Menu cannot be NULL");
if(menu->itemCount == 0) errorOk();
float_t colStep = width / (float_t)menu->columns;
float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
uint8_t focusIndex = menu->focusItem != NULL ?
menu->focusItem->y * menu->columns + menu->focusItem->x :
0xFF;
for(uint8_t i = 0; i < menu->itemCount; i++) {
const uimenuitem_t *item = &menu->items[i];
uint8_t col = i % menu->columns;
uint8_t row = i / menu->columns;
float_t ix = x + (float_t)col * colStep;
float_t iy = y + (float_t)row * rowHeight;
bool_t highlighted = i == focusIndex;
switch(item->type) {
case UIMENU_WIDGET_TYPE_LABEL:
errorChain(textDraw(
ix, iy, item->label,
highlighted ? COLOR_RED : COLOR_WHITE,
&FONT_DEFAULT
));
break;
case UIMENU_WIDGET_TYPE_CHECKBOX:
errorChain(uiCheckboxDraw(&item->checkbox, ix, iy));
break;
default:
break;
}
}
errorOk();
}
bool_t uiMenuFocusSelected(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->selected == NULL) return true;
uint8_t index = focusItem->y * menu->columns + focusItem->x;
if(index >= menu->itemCount) return true;
menu->selected(menu, index, &menu->items[index]);
return true;
}
bool_t uiMenuFocusChanged(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;
uint8_t index = focusItem->y * menu->columns + focusItem->x;
for(uint8_t i = 0; i < menu->itemCount; i++) {
uimenuitem_t *item = &menu->items[i];
if(item->type != UIMENU_WIDGET_TYPE_CHECKBOX) continue;
uiCheckboxSetHighlighted(&item->checkbox, i == index);
}
if(menu->changed == NULL) return true;
if(index >= menu->itemCount) return true;
menu->changed(menu, index, &menu->items[index]);
return true;
}
bool_t uiMenuFocusClosed(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;
menu->focusItem = NULL;
if(menu->closed != NULL) menu->closed(menu);
return true;
}
+160
View File
@@ -0,0 +1,160 @@
/**
* 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/focus/uifocus.h"
#include "ui/widget/uicheckbox.h"
#define UI_MENU_LABEL_MAX UI_CHECKBOX_LABEL_MAX
typedef struct uimenu_s uimenu_t;
typedef enum {
UIMENU_WIDGET_TYPE_NONE,
UIMENU_WIDGET_TYPE_LABEL,
UIMENU_WIDGET_TYPE_CHECKBOX,
} uimenuwidgettype_t;
typedef struct {
uimenuwidgettype_t type;
union {
char_t label[UI_MENU_LABEL_MAX];
uicheckbox_t checkbox;
};
} uimenuitem_t;
typedef void (*uimenuselectedcallback_t)(
const uimenu_t *menu,
const uint8_t index,
const uimenuitem_t *item
);
typedef void (*uimenuchangedcallback_t)(
const uimenu_t *menu,
const uint8_t index,
const uimenuitem_t *item
);
typedef void (*uimenuclosedcallback_t)(const uimenu_t *menu);
typedef struct uimenu_s {
uimenuitem_t *items;
uint8_t itemCount;
uint8_t columns;
uifocusitem_t *focusItem;
uimenuselectedcallback_t selected;
uimenuclosedcallback_t closed;
uimenuchangedcallback_t changed;
} uimenu_t;
/**
* Initializes a menu, clearing all items and focus state.
*
* @param menu The menu to initialize.
* @param items The list of items to display in the menu.
* @param itemCount The number of items in the list.
* @param columns The number of columns to display the items in.
* @param selected The callback to invoke when an item is selected.
* @param closed The callback to invoke when the menu is closed.
* @param changed The callback to invoke when the menu changes.
*/
void uiMenuInit(
uimenu_t *menu,
uimenuselectedcallback_t selected,
uimenuclosedcallback_t closed,
uimenuchangedcallback_t changed
);
/**
* Sets the items to display in the menu.
*
* @param menu The menu to update.
* @param items The list of items to display in the menu.
* @param itemCount The number of items in the list.
* @param columns The number of columns to display the items in.
*/
void uiMenuSetItems(
uimenu_t *menu,
const uimenuitem_t *items,
const uint8_t itemCount,
const uint8_t columns
);
/**
* Sets the position of the menu on the screen.
*
* @param menu The menu to position.
* @param x The x-coordinate to position the menu at.
* @param y The y-coordinate to position the menu at.
*/
void uiMenuSetPosition(uimenu_t *menu, const uint8_t x, const uint8_t y);
/**
* Pushes a menu onto the UI focus stack, making it the active menu.
*
* @param menu The menu to push.
*/
void uiMenuOpen(uimenu_t *menu);
/**
* Pops a menu from the UI focus stack, removing it from the active menu.
*
* @param menu The menu to pop.
*/
void uiMenuClose(uimenu_t *menu);
/**
* Returns whether the menu is currently active (on the UI focus stack).
*
* @param menu The menu to query.
* @returns True if the menu is active.
*/
bool_t uiMenuIsActive(const uimenu_t *menu);
/**
* Draws the menu at the specified position and size.
*
* @param menu The menu to draw.
* @param x The x-coordinate to draw the menu at.
* @param y The y-coordinate to draw the menu at.
* @param width The width of the menu.
* @param height The height of the menu.
* @returns An error code indicating success or failure.
*/
errorret_t uiMenuDraw(
const uimenu_t *menu,
const float_t x,
const float_t y,
const float_t width,
const float_t height
);
/**
* Internal focus callback — forwards selection to the menu's selected handler.
*
* @param focusItem The active focus item; user field must point to uimenu_t.
* @returns True.
*/
bool_t uiMenuFocusSelected(const uifocusitem_t *focusItem);
/**
* Internal focus callback — updates checkbox highlights and fires changed.
*
* @param focusItem The active focus item; user field must point to uimenu_t.
* @returns True.
*/
bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem);
/**
* Internal focus callback — clears focusItem and fires the closed handler.
*
* @param focusItem The active focus item; user field must point to uimenu_t.
* @returns True.
*/
bool_t uiMenuFocusClosed(const uifocusitem_t *focusItem);