7ee04c78cd
The old UI system (X-macro static element list, hand-authored C screens/widgets/focus stack) is archived under archive/ rather than deleted, since it's a useful reference during the rewrite. New native UI element pool (src/dusk/ui): a flat UI_ELEMENTS[128] pool of tagged-union elements (Label, Rectangle, Scripted), a real persistent parent/child tree (children[8] per element, cycle- and capacity-checked uiElementSetParent), always-fresh worldX/worldY (cheap enough to recompute on every read, no dirty-flag cache needed), and cascading dispose. Rendering stays manual/immediate: a scripted element with no render() override auto-renders its children by default, but overriding render() takes full control (an override must call renderChildren() itself to opt back in) -- this is deliberately preserved end to end via a render()-shadow trampoline so overriding render() always keeps working the same way regardless of how a node is reached. New scripting layer (src/dusk/script/module/ui): UIElement/Label/ Rectangle JS classes (Label/Rectangle share UIElement's prototype via manual chaining, not JS `extends`), exposing x/y/worldX/worldY/parent/ add()/remove()/render()/renderChildren()/dispose(). UI.add()/ UI.remove() manage top-level render roots, mutually exclusive with being someone's child. Also: Scene gains a lateUpdate() hook (called once per frame after every other update, for things like camera-follow that need to react to where everything else ended up); several duskrpg call sites (cutscene items, entityinteractable, entityplayer) that depended on the now-archived RPG textbox are stubbed to console output instead of a dialogue box, pending the new UI reaching that far. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
293 lines
8.5 KiB
C
293 lines
8.5 KiB
C
/**
|
|
* 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/uiwidgetlabel.h"
|
|
#include "ui/widget/uibutton.h"
|
|
#include "ui/widget/uicheckbox.h"
|
|
#include "ui/widget/uitab.h"
|
|
#include "ui/widget/uislider.h"
|
|
#include "ui/widget/uidropdown.h"
|
|
|
|
typedef struct uimenu_s uimenu_t;
|
|
|
|
typedef enum {
|
|
UI_MENU_WIDGET_TYPE_NONE,
|
|
UI_MENU_WIDGET_TYPE_LABEL,
|
|
UI_MENU_WIDGET_TYPE_SPACER,
|
|
UI_MENU_WIDGET_TYPE_CHECKBOX,
|
|
UI_MENU_WIDGET_TYPE_BUTTON,
|
|
UI_MENU_WIDGET_TYPE_TAB,
|
|
UI_MENU_WIDGET_TYPE_SLIDER,
|
|
UI_MENU_WIDGET_TYPE_DROPDOWN,
|
|
} uimenuwidgettype_t;
|
|
|
|
typedef struct {
|
|
uimenuwidgettype_t type;
|
|
union {
|
|
uiwidgetlabel_t label;
|
|
uicheckbox_t checkbox;
|
|
uibutton_t button;
|
|
uitab_t tab;
|
|
uislider_t slider;
|
|
uidropdown_t dropdown;
|
|
};
|
|
} 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;
|
|
|
|
void *user;
|
|
} 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
|
|
);
|
|
|
|
/**
|
|
* Returns the number of focusable (non-label) items in the menu.
|
|
*
|
|
* @param menu The menu to query.
|
|
* @returns Count of non-label items.
|
|
*/
|
|
uint8_t uiMenuFocusableCount(const uimenu_t *menu);
|
|
|
|
/**
|
|
* Maps a flat focus slot index to the corresponding item array index,
|
|
* skipping over label items which are not focusable.
|
|
*
|
|
* @param menu The menu to query.
|
|
* @param slot The focus slot index (y * columns + x).
|
|
* @returns The item array index, or 0xFF if out of range.
|
|
*/
|
|
uint8_t uiMenuFocusSlotToIndex(const uimenu_t *menu, const uint8_t slot);
|
|
|
|
/**
|
|
* Sets the highlighted/active state on a menu item, dispatching to the
|
|
* matching widget's setter based on its type. No-op for label/spacer
|
|
* items and any other type with no highlight concept.
|
|
*
|
|
* @param item The item to update.
|
|
* @param highlighted The new highlighted/active state.
|
|
*/
|
|
void uiMenuItemSetHighlighted(uimenuitem_t *item, const bool_t highlighted);
|
|
|
|
/**
|
|
* Initializes a menu item as a standalone label, caching its text.
|
|
* Used internally by the MENU_LABEL helper macro.
|
|
*
|
|
* @param item The item to initialize.
|
|
* @param text Display text.
|
|
*/
|
|
void uiMenuLabelInit(uimenuitem_t *item, const char_t *text);
|
|
|
|
/**
|
|
* 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 widget 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);
|
|
|
|
/**
|
|
* Internal focus callback - gives the focused item's widget a chance to
|
|
* handle LEFT/RIGHT itself (e.g. a slider adjusting its value) before
|
|
* falling back to the default cell-to-cell movement.
|
|
*
|
|
* @param focusItem The active focus item; user field must point to uimenu_t.
|
|
* @param direction The direction that was pressed or held.
|
|
* @returns True if the focused widget handled the direction.
|
|
*/
|
|
bool_t uiMenuFocusDirection(
|
|
const uifocusitem_t *focusItem,
|
|
const uifocusdirection_t direction
|
|
);
|
|
|
|
// Helper macros
|
|
#define MENU_BEGIN(menuPtr, itemsArray, selected, closed, changed) \
|
|
uimenu_t *menu = (menuPtr); \
|
|
uiMenuInit(menu, selected, closed, changed); \
|
|
menu->items = (itemsArray); \
|
|
uint8_t menuIndex = 0; \
|
|
uint8_t menuCapacity = sizeof(itemsArray) / sizeof((itemsArray)[0])
|
|
|
|
#define MENU_LABEL(text) \
|
|
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_LABEL; \
|
|
uiMenuLabelInit(&menu->items[menuIndex], text); \
|
|
++menuIndex
|
|
|
|
#define MENU_SPACER() \
|
|
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SPACER; \
|
|
++menuIndex
|
|
|
|
#define MENU_CHECKBOX(label) \
|
|
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_CHECKBOX; \
|
|
uiCheckboxInit(&menu->items[menuIndex].checkbox, label); \
|
|
++menuIndex
|
|
|
|
#define MENU_BUTTON(label) \
|
|
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_BUTTON; \
|
|
uiButtonInit(&menu->items[menuIndex].button, label); \
|
|
++menuIndex
|
|
|
|
#define MENU_TAB(label) \
|
|
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_TAB; \
|
|
uiTabInit(&menu->items[menuIndex].tab, label); \
|
|
++menuIndex
|
|
|
|
#define MENU_SLIDER_FLOAT(label, value, min, max, step) \
|
|
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SLIDER; \
|
|
uiSliderInitFloat( \
|
|
&menu->items[menuIndex].slider, label, value, min, max, step \
|
|
); \
|
|
++menuIndex
|
|
|
|
#define MENU_SLIDER_INT(label, value, min, max, step) \
|
|
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SLIDER; \
|
|
uiSliderInitInt( \
|
|
&menu->items[menuIndex].slider, label, value, min, max, step \
|
|
); \
|
|
++menuIndex
|
|
|
|
#define MENU_DROPDOWN(label, options, optionCount, selectedIndex) \
|
|
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_DROPDOWN; \
|
|
uiDropdownInit( \
|
|
&menu->items[menuIndex].dropdown, label, options, optionCount, \
|
|
selectedIndex \
|
|
); \
|
|
++menuIndex
|
|
|
|
#define MENU_END(itemsArray, columns) \
|
|
uiMenuSetItems(menu, (itemsArray), menuIndex, (columns))
|
|
|
|
//EOF
|