Slider widget and some cleanup

This commit is contained in:
2026-07-07 09:58:10 -05:00
parent 3bad03afb3
commit 6dee37d8c1
34 changed files with 1502 additions and 152 deletions
+5 -5
View File
@@ -11,7 +11,7 @@
#include "util/memory.h"
#include "time/time.h"
#include "ui/focus/uifocus.h"
#include "ui/frame/uisettings.h"
#include "ui/frame/game/uigamemenu.h"
#include "rpg/cutscene/cutscenesystem.h"
void playerInit(entity_t *entity) {
@@ -27,14 +27,14 @@ void playerInput(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_PLAYER) return;
// Toggle settings on pause
if(uiSettingsIsOpen() && inputPressed(INPUT_ACTION_PAUSE)) {
uiSettingsClose();
// Toggle game menu on pause
if(uiGameMenuIsOpen() && inputPressed(INPUT_ACTION_PAUSE)) {
uiGameMenuClose();
} else if(
inputPressed(INPUT_ACTION_PAUSE) &&
entity->animation == ENTITY_ANIM_IDLE
) {
uiSettingsOpen();
uiGameMenuOpen();
}
// Can player act?
+12 -2
View File
@@ -31,6 +31,7 @@ uifocusitem_t * uiFocusPush(
uifocusitemcallback_t selected,
uifocusitemcallback_t changed,
uifocusitemcallback_t closed,
uifocusitemdirectioncallback_t direction,
void *user
) {
assertTrue(
@@ -47,6 +48,7 @@ uifocusitem_t * uiFocusPush(
item->selected = selected;
item->changed = changed;
item->closed = closed;
item->direction = direction;
item->user = user;
UI_FOCUS.count++;
UI_FOCUS.pushedThisTick = true;
@@ -118,6 +120,14 @@ void uiFocusMoveDirection(
}
}
void uiFocusHandleDirection(
uifocusitem_t *item,
const uifocusdirection_t dir
) {
if(item->direction != NULL && item->direction(item, dir)) return;
uiFocusMoveDirection(item, dir);
}
void uiFocusUpdate(void) {
if(UI_FOCUS.count == 0) return;
@@ -150,7 +160,7 @@ void uiFocusUpdate(void) {
if(!inputPressed(m->action)) continue;
UI_FOCUS.direction = m->direction;
UI_FOCUS.timeHeld = 0.0f;
uiFocusMoveDirection(item, m->direction);
uiFocusHandleDirection(item, m->direction);
return;
}
@@ -176,5 +186,5 @@ void uiFocusUpdate(void) {
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) return;
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) return;
UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY;
uiFocusMoveDirection(item, UI_FOCUS.direction);
uiFocusHandleDirection(item, UI_FOCUS.direction);
}
+16 -8
View File
@@ -22,14 +22,6 @@
*/
#define UI_FOCUS_HOLD_REPEAT 0.1f
typedef enum {
UI_FOCUS_DIRECTION_NONE,
UI_FOCUS_DIRECTION_UP,
UI_FOCUS_DIRECTION_DOWN,
UI_FOCUS_DIRECTION_LEFT,
UI_FOCUS_DIRECTION_RIGHT
} uifocusdirection_t;
typedef struct {
inputaction_t action;
uifocusdirection_t direction;
@@ -72,6 +64,8 @@ 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 direction Called on a direction press/hold before the default
* cell-to-cell movement is applied; may be NULL.
* @param user Arbitrary pointer stored on the item before changed fires.
* @returns Pointer to the newly pushed focus item.
*/
@@ -81,6 +75,7 @@ uifocusitem_t * uiFocusPush(
uifocusitemcallback_t selected,
uifocusitemcallback_t changed,
uifocusitemcallback_t closed,
uifocusitemdirectioncallback_t direction,
void *user
);
@@ -118,6 +113,19 @@ void uiFocusMoveDirection(
const uifocusdirection_t dir
);
/**
* Handles a direction press/hold for the given item: gives the item's
* direction callback (if any) first refusal, falling back to the
* default cell-to-cell movement if it's unset or returns false.
*
* @param item The focus item to move.
* @param dir Direction that was pressed or held.
*/
void uiFocusHandleDirection(
uifocusitem_t *item,
const uifocusdirection_t dir
);
/**
* Updates the focus system. Handles first-press movement and
* held-direction repeating. Called once per game tick.
+24
View File
@@ -10,6 +10,14 @@
typedef struct uifocusitem_s uifocusitem_t;
typedef enum {
UI_FOCUS_DIRECTION_NONE,
UI_FOCUS_DIRECTION_UP,
UI_FOCUS_DIRECTION_DOWN,
UI_FOCUS_DIRECTION_LEFT,
UI_FOCUS_DIRECTION_RIGHT
} uifocusdirection_t;
/**
* Callback invoked when a focus item's selected cell changes.
*
@@ -17,6 +25,21 @@ typedef struct uifocusitem_s uifocusitem_t;
*/
typedef bool_t (*uifocusitemcallback_t)(const uifocusitem_t *item);
/**
* Callback invoked when a direction is pressed or held-repeated while
* this item is focused, before the default cell-to-cell movement is
* applied.
*
* @param item The focus item that is currently focused.
* @param direction The direction that was pressed.
* @returns True if the direction was fully handled and the default
* grid movement should be skipped; false to fall through to it.
*/
typedef bool_t (*uifocusitemdirectioncallback_t)(
const uifocusitem_t *item,
const uifocusdirection_t direction
);
/**
* A single entry on the UI focus stack. Tracks the focused cell
* within a grid of cols x rows, and the current x/y position.
@@ -29,5 +52,6 @@ struct uifocusitem_s {
uifocusitemcallback_t selected;
uifocusitemcallback_t changed;
uifocusitemcallback_t closed;
uifocusitemdirectioncallback_t direction;
void *user;
};
+3 -1
View File
@@ -6,5 +6,7 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
uiframe.c
uisettings.c
)
add_subdirectory(game)
add_subdirectory(settings)
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
uigamemenu.c
)
+85
View File
@@ -0,0 +1,85 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uigamemenu.h"
#include "ui/frame/uiframe.h"
#include "ui/frame/settings/uisettings.h"
#include "util/memory.h"
#include "display/spritebatch/spritebatch.h"
#include "display/screen/screen.h"
#include "assert/assert.h"
#define UI_GAME_MENU_INDEX_CHARACTERS 0
#define UI_GAME_MENU_INDEX_SETTINGS 1
uigamemenu_t UI_GAME_MENU;
void uiGameMenuSelected(
const uimenu_t *menu,
const uint8_t index,
const uimenuitem_t *item
) {
if(index == UI_GAME_MENU_INDEX_SETTINGS) uiSettingsOpen();
}
errorret_t uiGameMenuInit(void) {
memoryZero(&UI_GAME_MENU, sizeof(uigamemenu_t));
uimenu_t *menu = &UI_GAME_MENU.menu;
uiMenuInit(menu, uiGameMenuSelected, NULL, NULL);
menu->items = UI_GAME_MENU.items;
MENU_BEGIN
MENU_BUTTON("Characters");
MENU_BUTTON("Settings");
assertTrue(
menuIndex <= UI_GAME_MENU_ITEM_COUNT,
"Game menu item count mismatch"
);
uiMenuSetItems(menu, UI_GAME_MENU.items, menuIndex, 1);
errorOk();
}
errorret_t uiGameMenuDraw(void) {
if(!uiMenuIsActive(&UI_GAME_MENU.menu)) errorOk();
const float_t width = UI_GAME_MENU_WIDTH;
const float_t height = (float_t)SCREEN.scanHeight;
const float_t x = (float_t)(SCREEN.scanX + SCREEN.scanWidth) - width;
const float_t y = (float_t)SCREEN.scanY;
errorChain(uiFrameDraw(x, y, width, height));
errorChain(uiMenuDraw(
&UI_GAME_MENU.menu,
x + UI_FRAME_START_X,
y + UI_FRAME_START_Y,
width - (UI_FRAME_START_X * 2),
height - (UI_FRAME_START_Y * 2)
));
errorChain(spriteBatchFlush());
errorOk();
}
bool_t uiGameMenuIsOpen(void) {
return uiMenuIsActive(&UI_GAME_MENU.menu);
}
void uiGameMenuOpen() {
uiMenuOpen(&UI_GAME_MENU.menu);
}
void uiGameMenuClose() {
uiMenuClose(&UI_GAME_MENU.menu);
}
errorret_t uiGameMenuDispose(void) {
errorOk();
}
+58
View File
@@ -0,0 +1,58 @@
/**
* 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_GAME_MENU_ITEM_COUNT 2
#define UI_GAME_MENU_WIDTH 150.0f
typedef struct {
uimenu_t menu;
uimenuitem_t items[UI_GAME_MENU_ITEM_COUNT];
} uigamemenu_t;
extern uigamemenu_t UI_GAME_MENU;
/**
* Initializes the game menu panel.
*
* @return Any error that occurs.
*/
errorret_t uiGameMenuInit(void);
/**
* Draws the game menu panel. No-op when not visible.
*
* @return Any error that occurs.
*/
errorret_t uiGameMenuDraw(void);
/**
* Returns true when the game menu panel is currently open.
*
* @returns True if open.
*/
bool_t uiGameMenuIsOpen(void);
/**
* Opens the game menu panel. No-op when already open.
*/
void uiGameMenuOpen();
/**
* Closes the game menu panel. No-op when already closed.
*/
void uiGameMenuClose();
/**
* Disposes of the game menu panel.
*
* @return Any error that occurs.
*/
errorret_t uiGameMenuDispose(void);
+13
View File
@@ -0,0 +1,13 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
uisettings.c
uisettingsgameplay.c
uisettingsinput.c
uisettingsdisplay.c
uisettingsaudio.c
)
+134
View File
@@ -0,0 +1,134 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uisettings.h"
#include "uisettingsgameplay.h"
#include "uisettingsinput.h"
#include "uisettingsdisplay.h"
#include "uisettingsaudio.h"
#include "ui/frame/uiframe.h"
#include "util/memory.h"
#include "display/spritebatch/spritebatch.h"
#include "display/text/text.h"
#include "display/screen/screen.h"
#include "assert/assert.h"
uisettings_t UI_SETTINGS;
void uiSettingsTabSelected(
const uimenu_t *menu,
const uint8_t index,
const uimenuitem_t *item
) {
switch(index) {
case UI_SETTINGS_TAB_GAMEPLAY:
uiSettingsGameplayOpen();
break;
case UI_SETTINGS_TAB_INPUT:
uiSettingsInputOpen();
break;
case UI_SETTINGS_TAB_DISPLAY:
uiSettingsDisplayOpen();
break;
case UI_SETTINGS_TAB_AUDIO:
uiSettingsAudioOpen();
break;
default:
break;
}
}
errorret_t uiSettingsInit(void) {
memoryZero(&UI_SETTINGS, sizeof(uisettings_t));
uimenu_t *menu = &UI_SETTINGS.tabsMenu;
uiMenuInit(menu, uiSettingsTabSelected, NULL, NULL);
menu->items = UI_SETTINGS.tabs;
MENU_BEGIN
MENU_TAB("Gameplay");
MENU_TAB("Input");
MENU_TAB("Display");
MENU_TAB("Audio");
assertTrue(
menuIndex <= UI_SETTINGS_TAB_COUNT,
"Settings tab count mismatch"
);
uiMenuSetItems(menu, UI_SETTINGS.tabs, menuIndex, menuIndex);
errorChain(uiSettingsGameplayInit());
errorChain(uiSettingsInputInit());
errorChain(uiSettingsDisplayInit());
errorChain(uiSettingsAudioInit());
errorOk();
}
errorret_t uiSettingsDraw(void) {
if(!uiMenuIsActive(&UI_SETTINGS.tabsMenu)) errorOk();
const float_t width = 220.0f;
const float_t height = 150.0f;
const float_t x = (float_t)SCREEN.scanX +
((float_t)SCREEN.scanWidth - width) * 0.5f;
const float_t y = (float_t)SCREEN.scanY +
((float_t)SCREEN.scanHeight - height) * 0.5f;
errorChain(uiFrameDraw(x, y, width, height));
const float_t contentX = x + UI_FRAME_START_X;
const float_t contentY = y + UI_FRAME_START_Y;
const float_t contentWidth = width - (UI_FRAME_START_X * 2);
const float_t contentHeight = height - (UI_FRAME_START_Y * 2);
errorChain(uiMenuDraw(
&UI_SETTINGS.tabsMenu,
contentX,
contentY,
contentWidth,
contentHeight
));
const float_t tabsRowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
const float_t pageY = contentY + tabsRowHeight + UI_FRAME_PADDING_Y;
const float_t pageHeight = contentHeight - tabsRowHeight - UI_FRAME_PADDING_Y;
if(uiTabIsActive(&UI_SETTINGS.tabs[UI_SETTINGS_TAB_GAMEPLAY].tab)) {
errorChain(uiSettingsGameplayDraw(contentX, pageY, contentWidth, pageHeight));
} else if(uiTabIsActive(&UI_SETTINGS.tabs[UI_SETTINGS_TAB_INPUT].tab)) {
errorChain(uiSettingsInputDraw(contentX, pageY, contentWidth, pageHeight));
} else if(uiTabIsActive(&UI_SETTINGS.tabs[UI_SETTINGS_TAB_DISPLAY].tab)) {
errorChain(uiSettingsDisplayDraw(contentX, pageY, contentWidth, pageHeight));
} else if(uiTabIsActive(&UI_SETTINGS.tabs[UI_SETTINGS_TAB_AUDIO].tab)) {
errorChain(uiSettingsAudioDraw(contentX, pageY, contentWidth, pageHeight));
}
errorChain(spriteBatchFlush());
errorOk();
}
bool_t uiSettingsIsOpen(void) {
return uiMenuIsActive(&UI_SETTINGS.tabsMenu);
}
void uiSettingsOpen() {
uiMenuOpen(&UI_SETTINGS.tabsMenu);
}
void uiSettingsClose() {
uiMenuClose(&UI_SETTINGS.tabsMenu);
}
errorret_t uiSettingsDispose(void) {
errorOk();
}
@@ -9,17 +9,21 @@
#include "error/error.h"
#include "ui/widget/uimenu.h"
#define UI_SETTINGS_ITEM_COUNT 6
#define UI_SETTINGS_TAB_GAMEPLAY 0
#define UI_SETTINGS_TAB_INPUT 1
#define UI_SETTINGS_TAB_DISPLAY 2
#define UI_SETTINGS_TAB_AUDIO 3
#define UI_SETTINGS_TAB_COUNT 4
typedef struct {
uimenu_t menu;
uimenuitem_t items[UI_SETTINGS_ITEM_COUNT];
uimenu_t tabsMenu;
uimenuitem_t tabs[UI_SETTINGS_TAB_COUNT];
} uisettings_t;
extern uisettings_t UI_SETTINGS;
/**
* Initializes the settings panel.
* Initializes the settings panel and all of its category pages.
*
* @return Any error that occurs.
*/
@@ -0,0 +1,47 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uisettingsaudio.h"
#include "util/memory.h"
uisettingsaudio_t UI_SETTINGS_AUDIO;
errorret_t uiSettingsAudioInit(void) {
memoryZero(&UI_SETTINGS_AUDIO, sizeof(uisettingsaudio_t));
uimenu_t *menu = &UI_SETTINGS_AUDIO.menu;
uiMenuInit(menu, NULL, NULL, NULL);
menu->items = UI_SETTINGS_AUDIO.items;
MENU_BEGIN
MENU_LABEL("No audio settings yet");
uiMenuSetItems(menu, UI_SETTINGS_AUDIO.items, menuIndex, 1);
errorOk();
}
errorret_t uiSettingsAudioDraw(
const float_t x,
const float_t y,
const float_t width,
const float_t height
) {
return uiMenuDraw(&UI_SETTINGS_AUDIO.menu, x, y, width, height);
}
bool_t uiSettingsAudioIsOpen(void) {
return uiMenuIsActive(&UI_SETTINGS_AUDIO.menu);
}
void uiSettingsAudioOpen(void) {
uiMenuOpen(&UI_SETTINGS_AUDIO.menu);
}
void uiSettingsAudioClose(void) {
uiMenuClose(&UI_SETTINGS_AUDIO.menu);
}
@@ -0,0 +1,59 @@
/**
* 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_SETTINGS_AUDIO_ITEM_COUNT 1
typedef struct {
uimenu_t menu;
uimenuitem_t items[UI_SETTINGS_AUDIO_ITEM_COUNT];
} uisettingsaudio_t;
extern uisettingsaudio_t UI_SETTINGS_AUDIO;
/**
* Initializes the audio settings page.
*
* @return Any error that occurs.
*/
errorret_t uiSettingsAudioInit(void);
/**
* Draws the audio settings page at the given content rect.
*
* @param x Screen x position.
* @param y Screen y position.
* @param width Content width.
* @param height Content height.
* @return Any error that occurs.
*/
errorret_t uiSettingsAudioDraw(
const float_t x,
const float_t y,
const float_t width,
const float_t height
);
/**
* Returns true when the audio settings page is currently open.
*
* @returns True if open.
*/
bool_t uiSettingsAudioIsOpen(void);
/**
* Opens the audio settings page. No-op when already open.
*/
void uiSettingsAudioOpen(void);
/**
* Closes the audio settings page. No-op when already closed.
*/
void uiSettingsAudioClose(void);
@@ -0,0 +1,47 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uisettingsdisplay.h"
#include "util/memory.h"
uisettingsdisplay_t UI_SETTINGS_DISPLAY;
errorret_t uiSettingsDisplayInit(void) {
memoryZero(&UI_SETTINGS_DISPLAY, sizeof(uisettingsdisplay_t));
uimenu_t *menu = &UI_SETTINGS_DISPLAY.menu;
uiMenuInit(menu, NULL, NULL, NULL);
menu->items = UI_SETTINGS_DISPLAY.items;
MENU_BEGIN
MENU_LABEL("No display settings yet");
uiMenuSetItems(menu, UI_SETTINGS_DISPLAY.items, menuIndex, 1);
errorOk();
}
errorret_t uiSettingsDisplayDraw(
const float_t x,
const float_t y,
const float_t width,
const float_t height
) {
return uiMenuDraw(&UI_SETTINGS_DISPLAY.menu, x, y, width, height);
}
bool_t uiSettingsDisplayIsOpen(void) {
return uiMenuIsActive(&UI_SETTINGS_DISPLAY.menu);
}
void uiSettingsDisplayOpen(void) {
uiMenuOpen(&UI_SETTINGS_DISPLAY.menu);
}
void uiSettingsDisplayClose(void) {
uiMenuClose(&UI_SETTINGS_DISPLAY.menu);
}
@@ -0,0 +1,59 @@
/**
* 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_SETTINGS_DISPLAY_ITEM_COUNT 1
typedef struct {
uimenu_t menu;
uimenuitem_t items[UI_SETTINGS_DISPLAY_ITEM_COUNT];
} uisettingsdisplay_t;
extern uisettingsdisplay_t UI_SETTINGS_DISPLAY;
/**
* Initializes the display settings page.
*
* @return Any error that occurs.
*/
errorret_t uiSettingsDisplayInit(void);
/**
* Draws the display settings page at the given content rect.
*
* @param x Screen x position.
* @param y Screen y position.
* @param width Content width.
* @param height Content height.
* @return Any error that occurs.
*/
errorret_t uiSettingsDisplayDraw(
const float_t x,
const float_t y,
const float_t width,
const float_t height
);
/**
* Returns true when the display settings page is currently open.
*
* @returns True if open.
*/
bool_t uiSettingsDisplayIsOpen(void);
/**
* Opens the display settings page. No-op when already open.
*/
void uiSettingsDisplayOpen(void);
/**
* Closes the display settings page. No-op when already closed.
*/
void uiSettingsDisplayClose(void);
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uisettingsgameplay.h"
#include "assert/assert.h"
#include "util/memory.h"
uisettingsgameplay_t UI_SETTINGS_GAMEPLAY;
errorret_t uiSettingsGameplayInit(void) {
memoryZero(&UI_SETTINGS_GAMEPLAY, sizeof(uisettingsgameplay_t));
uimenu_t *menu = &UI_SETTINGS_GAMEPLAY.menu;
uiMenuInit(menu, NULL, NULL, NULL);
menu->items = UI_SETTINGS_GAMEPLAY.items;
MENU_BEGIN
MENU_BUTTON("Do thing");
MENU_BUTTON("Do other thing");
MENU_CHECKBOX("Enable thing?");
MENU_SLIDER_FLOAT("test", 1.0f, 1.0f, 4.0f, 0.5f);
assertTrue(
menuIndex <= UI_SETTINGS_GAMEPLAY_ITEM_COUNT,
"Gameplay settings item count mismatch"
);
uiMenuSetItems(menu, UI_SETTINGS_GAMEPLAY.items, menuIndex, 1);
errorOk();
}
errorret_t uiSettingsGameplayDraw(
const float_t x,
const float_t y,
const float_t width,
const float_t height
) {
return uiMenuDraw(&UI_SETTINGS_GAMEPLAY.menu, x, y, width, height);
}
bool_t uiSettingsGameplayIsOpen(void) {
return uiMenuIsActive(&UI_SETTINGS_GAMEPLAY.menu);
}
void uiSettingsGameplayOpen(void) {
uiMenuOpen(&UI_SETTINGS_GAMEPLAY.menu);
}
void uiSettingsGameplayClose(void) {
uiMenuClose(&UI_SETTINGS_GAMEPLAY.menu);
}
@@ -0,0 +1,59 @@
/**
* 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_SETTINGS_GAMEPLAY_ITEM_COUNT 4
typedef struct {
uimenu_t menu;
uimenuitem_t items[UI_SETTINGS_GAMEPLAY_ITEM_COUNT];
} uisettingsgameplay_t;
extern uisettingsgameplay_t UI_SETTINGS_GAMEPLAY;
/**
* Initializes the gameplay settings page.
*
* @return Any error that occurs.
*/
errorret_t uiSettingsGameplayInit(void);
/**
* Draws the gameplay settings page at the given content rect.
*
* @param x Screen x position.
* @param y Screen y position.
* @param width Content width.
* @param height Content height.
* @return Any error that occurs.
*/
errorret_t uiSettingsGameplayDraw(
const float_t x,
const float_t y,
const float_t width,
const float_t height
);
/**
* Returns true when the gameplay settings page is currently open.
*
* @returns True if open.
*/
bool_t uiSettingsGameplayIsOpen(void);
/**
* Opens the gameplay settings page. No-op when already open.
*/
void uiSettingsGameplayOpen(void);
/**
* Closes the gameplay settings page. No-op when already closed.
*/
void uiSettingsGameplayClose(void);
@@ -0,0 +1,47 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uisettingsinput.h"
#include "util/memory.h"
uisettingsinput_t UI_SETTINGS_INPUT;
errorret_t uiSettingsInputInit(void) {
memoryZero(&UI_SETTINGS_INPUT, sizeof(uisettingsinput_t));
uimenu_t *menu = &UI_SETTINGS_INPUT.menu;
uiMenuInit(menu, NULL, NULL, NULL);
menu->items = UI_SETTINGS_INPUT.items;
MENU_BEGIN
MENU_LABEL("No input settings yet");
uiMenuSetItems(menu, UI_SETTINGS_INPUT.items, menuIndex, 1);
errorOk();
}
errorret_t uiSettingsInputDraw(
const float_t x,
const float_t y,
const float_t width,
const float_t height
) {
return uiMenuDraw(&UI_SETTINGS_INPUT.menu, x, y, width, height);
}
bool_t uiSettingsInputIsOpen(void) {
return uiMenuIsActive(&UI_SETTINGS_INPUT.menu);
}
void uiSettingsInputOpen(void) {
uiMenuOpen(&UI_SETTINGS_INPUT.menu);
}
void uiSettingsInputClose(void) {
uiMenuClose(&UI_SETTINGS_INPUT.menu);
}
@@ -0,0 +1,59 @@
/**
* 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_SETTINGS_INPUT_ITEM_COUNT 1
typedef struct {
uimenu_t menu;
uimenuitem_t items[UI_SETTINGS_INPUT_ITEM_COUNT];
} uisettingsinput_t;
extern uisettingsinput_t UI_SETTINGS_INPUT;
/**
* Initializes the input settings page.
*
* @return Any error that occurs.
*/
errorret_t uiSettingsInputInit(void);
/**
* Draws the input settings page at the given content rect.
*
* @param x Screen x position.
* @param y Screen y position.
* @param width Content width.
* @param height Content height.
* @return Any error that occurs.
*/
errorret_t uiSettingsInputDraw(
const float_t x,
const float_t y,
const float_t width,
const float_t height
);
/**
* Returns true when the input settings page is currently open.
*
* @returns True if open.
*/
bool_t uiSettingsInputIsOpen(void);
/**
* Opens the input settings page. No-op when already open.
*/
void uiSettingsInputOpen(void);
/**
* Closes the input settings page. No-op when already closed.
*/
void uiSettingsInputClose(void);
+2 -2
View File
@@ -19,8 +19,8 @@ errorret_t uiFrameInit(void) {
memoryZero(&UI_FRAME, sizeof(uiframe_t));
// Init texture.
color_t border = color4b(0, 100, 220, 255);
color_t center = color4b(0, 100, 220, 200);
color_t border = color4b(0, 50, 255, 255);
color_t center = color4b(0, 100, 220, 240);
for(uint8_t y = 0; y < UI_FRAME_TEXTURE_HEIGHT_POW2; y++) {
for(uint8_t x = 0; x < UI_FRAME_TEXTURE_WIDTH_POW2; x++) {
+4
View File
@@ -12,6 +12,10 @@
#define UI_FRAME_BORDER_WIDTH 6
#define UI_FRAME_BORDER_HEIGHT 6
#define UI_FRAME_PADDING_X 4
#define UI_FRAME_PADDING_Y 4
#define UI_FRAME_START_X (UI_FRAME_BORDER_WIDTH + UI_FRAME_PADDING_X)
#define UI_FRAME_START_Y (UI_FRAME_BORDER_HEIGHT + UI_FRAME_PADDING_Y)
#define UI_FRAME_TILE_WIDTH 1
#define UI_FRAME_TILE_HEIGHT 1
#define UI_FRAME_TEXTURE_WIDTH (UI_FRAME_TILE_WIDTH * 3)
-113
View File
@@ -1,113 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uisettings.h"
#include "ui/frame/uiframe.h"
#include "util/memory.h"
#include "display/spritebatch/spritebatch.h"
#include "display/text/text.h"
#include "display/screen/screen.h"
#include "assert/assert.h"
uisettings_t UI_SETTINGS;
void uiSettingsSelected(
const uimenu_t *menu,
const uint8_t index,
const uimenuitem_t *item
) {
if(item->type == UI_MENU_WIDGET_TYPE_CHECKBOX) {
uiCheckboxToggle(&UI_SETTINGS.items[index].checkbox);
}
}
void uiSettingsClosed(const uimenu_t *menu) {
// nothing extra needed; menu clears focusItem itself
}
errorret_t uiSettingsInit(void) {
memoryZero(&UI_SETTINGS, sizeof(uisettings_t));
uint8_t i = 0;
UI_SETTINGS.items[i] = (uimenuitem_t){
.type = UI_MENU_WIDGET_TYPE_LABEL,
.label = "Settings"
};
++i;
UI_SETTINGS.items[i].type = UI_MENU_WIDGET_TYPE_BUTTON;
uiButtonInit(&UI_SETTINGS.items[i].button, "Do thing");
++i;
UI_SETTINGS.items[i].type = UI_MENU_WIDGET_TYPE_BUTTON;
uiButtonInit(&UI_SETTINGS.items[i].button, "Do other thing");
++i;
UI_SETTINGS.items[i].type = UI_MENU_WIDGET_TYPE_SPACER;
++i;
UI_SETTINGS.items[i] = (uimenuitem_t){
.type = UI_MENU_WIDGET_TYPE_LABEL,
.label = "Options"
};
++i;
UI_SETTINGS.items[i].type = UI_MENU_WIDGET_TYPE_CHECKBOX;
uiCheckboxInit(&UI_SETTINGS.items[i].checkbox, "Enable thing?");
++i;
assertTrue(i <= UI_SETTINGS_ITEM_COUNT, "Settings item count mismatch");
uiMenuInit(&UI_SETTINGS.menu, uiSettingsSelected, uiSettingsClosed, NULL);
uiMenuSetItems(
&UI_SETTINGS.menu,
UI_SETTINGS.items,
i, 1
);
errorOk();
}
errorret_t uiSettingsDraw(void) {
if(!uiMenuIsActive(&UI_SETTINGS.menu)) errorOk();
const float_t width = 300.0f;
const float_t height = 300.0f;
const float_t x = (float_t)SCREEN.scanX +
((float_t)SCREEN.scanWidth - width) * 0.5f;
const float_t y = (float_t)SCREEN.scanY +
((float_t)SCREEN.scanHeight - height) * 0.5f;
errorChain(uiFrameDraw(x, y, width, height));
errorChain(uiMenuDraw(
&UI_SETTINGS.menu,
x + UI_FRAME_BORDER_WIDTH,
y + UI_FRAME_BORDER_HEIGHT,
width - (UI_FRAME_BORDER_WIDTH * 2),
height - (UI_FRAME_BORDER_HEIGHT * 2)
));
errorChain(spriteBatchFlush());
errorOk();
}
bool_t uiSettingsIsOpen(void) {
return uiMenuIsActive(&UI_SETTINGS.menu);
}
void uiSettingsOpen() {
uiMenuOpen(&UI_SETTINGS.menu);
}
void uiSettingsClose() {
uiMenuClose(&UI_SETTINGS.menu);
}
errorret_t uiSettingsDispose(void) {
errorOk();
}
+6 -6
View File
@@ -144,12 +144,12 @@ errorret_t uiTextboxDraw(
) {
assertNotNull(box, "Textbox cannot be NULL");
float_t borderW = (float_t)UI_FRAME_BORDER_WIDTH;
float_t borderH = (float_t)UI_FRAME_BORDER_HEIGHT;
float_t contentX = x + borderW;
float_t contentY = y + borderH;
float_t contentW = width - 2.0f * borderW;
float_t contentH = height - 2.0f * borderH;
float_t startX = (float_t)UI_FRAME_START_X;
float_t startY = (float_t)UI_FRAME_START_Y;
float_t contentX = x + startX;
float_t contentY = y + startY;
float_t contentW = width - 2.0f * startX;
float_t contentH = height - 2.0f * startY;
if(contentW != box->layoutWidth || contentH != box->layoutHeight) {
uiTextboxBuildLayout(box, contentW, contentH);
+2 -1
View File
@@ -27,6 +27,7 @@ void uiTextboxMainSetText(const char_t *text) {
uiTextboxMainFocusSelected,
NULL,
uiTextboxMainFocusClosed,
NULL,
NULL
);
}
@@ -41,7 +42,7 @@ errorret_t uiTextboxMainDraw(void) {
float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
float_t h = (float_t)UI_TEXTBOX_MAIN_LINES * fontH +
(float_t)(UI_TEXTBOX_MAIN_LINES - 1) * UI_TEXTBOX_LINE_SPACING +
2.0f * (float_t)UI_FRAME_BORDER_HEIGHT;
2.0f * (float_t)UI_FRAME_START_Y;
float_t w = (float_t)SCREEN.scanWidth;
float_t x = (float_t)SCREEN.scanX;
float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - h;
+8 -1
View File
@@ -16,7 +16,8 @@
#include "ui/overlay/uicrop.h"
#include "ui/transition/uitransition.h"
#include "ui/debug/uiconsole.h"
#include "ui/frame/uisettings.h"
#include "ui/frame/settings/uisettings.h"
#include "ui/frame/game/uigamemenu.h"
#include "ui/rpg/uitextboxmain.h"
uielement_t UI_ELEMENTS[] = {
@@ -33,6 +34,12 @@ uielement_t UI_ELEMENTS[] = {
.draw = uiFullboxUnderDraw
},
{
.init = uiGameMenuInit,
.draw = uiGameMenuDraw,
.dispose = uiGameMenuDispose
},
{
.init = uiSettingsInit,
.draw = uiSettingsDraw,
+2
View File
@@ -7,5 +7,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
uibutton.c
uicheckbox.c
uitab.c
uislider.c
uimenu.c
)
-1
View File
@@ -8,7 +8,6 @@
#include "uibutton.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "display/text/text.h"
#include "display/color.h"
-1
View File
@@ -7,7 +7,6 @@
#include "uicheckbox.h"
#include "util/memory.h"
#include "util/string.h"
#include "display/text/text.h"
#include "display/color.h"
+64 -6
View File
@@ -11,6 +11,8 @@
#include "display/text/text.h"
#include "display/color.h"
#include "ui/widget/uibutton.h"
#include "ui/widget/uitab.h"
#include "ui/widget/uislider.h"
void uiMenuInit(
uimenu_t *menu,
@@ -61,6 +63,7 @@ void uiMenuOpen(uimenu_t *menu) {
uiMenuFocusSelected,
uiMenuFocusChanged,
uiMenuFocusClosed,
uiMenuFocusDirection,
menu
);
}
@@ -127,6 +130,14 @@ errorret_t uiMenuDraw(
errorChain(uiButtonDraw(&item->button, ix, iy));
break;
case UI_MENU_WIDGET_TYPE_TAB:
errorChain(uiTabDraw(&item->tab, ix, iy));
break;
case UI_MENU_WIDGET_TYPE_SLIDER:
errorChain(uiSliderDraw(&item->slider, ix, iy));
break;
default:
break;
}
@@ -166,12 +177,7 @@ bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem) {
item->type == UI_MENU_WIDGET_TYPE_LABEL ||
item->type == UI_MENU_WIDGET_TYPE_SPACER
) continue;
bool_t isActive = (slot == focusSlot);
if(item->type == UI_MENU_WIDGET_TYPE_CHECKBOX) {
uiCheckboxSetHighlighted(&item->checkbox, isActive);
} else if(item->type == UI_MENU_WIDGET_TYPE_BUTTON) {
uiButtonSetHighlighted(&item->button, isActive);
}
uiMenuItemSetHighlighted(item, slot == focusSlot);
slot++;
}
@@ -188,10 +194,50 @@ bool_t uiMenuFocusClosed(const uifocusitem_t *focusItem) {
assertNotNull(focusItem->user, "Focus item user cannot be NULL");
uimenu_t *menu = (uimenu_t *)focusItem->user;
menu->focusItem = NULL;
for(uint8_t i = 0; i < menu->itemCount; i++) {
uiMenuItemSetHighlighted(&menu->items[i], false);
}
if(menu->closed != NULL) menu->closed(menu);
return true;
}
bool_t uiMenuFocusDirection(
const uifocusitem_t *focusItem,
const uifocusdirection_t direction
) {
assertNotNull(focusItem, "Focus item cannot be NULL");
assertNotNull(focusItem->user, "Focus item user cannot be NULL");
if(direction != UI_FOCUS_DIRECTION_LEFT && direction != UI_FOCUS_DIRECTION_RIGHT) {
return false;
}
uimenu_t *menu = (uimenu_t *)focusItem->user;
uint8_t slot = focusItem->y * menu->columns + focusItem->x;
uint8_t index = uiMenuFocusSlotToIndex(menu, slot);
if(index == 0xFF) return false;
uimenuitem_t *item = &menu->items[index];
bool_t right = direction == UI_FOCUS_DIRECTION_RIGHT;
if(item->type == UI_MENU_WIDGET_TYPE_SLIDER) {
if(right) {
uiSliderStepUp(&item->slider);
} else {
uiSliderStepDown(&item->slider);
}
return true;
}
if(item->type == UI_MENU_WIDGET_TYPE_CHECKBOX) {
uiCheckboxToggle(&item->checkbox);
return true;
}
return false;
}
uint8_t uiMenuFocusableCount(const uimenu_t *menu) {
assertNotNull(menu, "Menu cannot be NULL");
uint8_t count = 0;
@@ -213,3 +259,15 @@ uint8_t uiMenuFocusSlotToIndex(const uimenu_t *menu, const uint8_t slot) {
}
return 0xFF;
}
void uiMenuItemSetHighlighted(uimenuitem_t *item, const bool_t highlighted) {
if(item->type == UI_MENU_WIDGET_TYPE_CHECKBOX) {
uiCheckboxSetHighlighted(&item->checkbox, highlighted);
} else if(item->type == UI_MENU_WIDGET_TYPE_BUTTON) {
uiButtonSetHighlighted(&item->button, highlighted);
} else if(item->type == UI_MENU_WIDGET_TYPE_TAB) {
uiTabSetActive(&item->tab, highlighted);
} else if(item->type == UI_MENU_WIDGET_TYPE_SLIDER) {
uiSliderSetHighlighted(&item->slider, highlighted);
}
}
+72 -1
View File
@@ -10,6 +10,8 @@
#include "ui/focus/uifocus.h"
#include "ui/widget/uibutton.h"
#include "ui/widget/uicheckbox.h"
#include "ui/widget/uitab.h"
#include "ui/widget/uislider.h"
#define UI_MENU_LABEL_MAX UI_CHECKBOX_LABEL_MAX
@@ -21,6 +23,8 @@ typedef enum {
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,
} uimenuwidgettype_t;
typedef struct {
@@ -29,6 +33,8 @@ typedef struct {
char_t *label;
uicheckbox_t checkbox;
uibutton_t button;
uitab_t tab;
uislider_t slider;
};
} uimenuitem_t;
@@ -157,6 +163,16 @@ uint8_t uiMenuFocusableCount(const uimenu_t *menu);
*/
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);
/**
* Internal focus callback - forwards selection to the menu's selected handler.
*
@@ -179,4 +195,59 @@ bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem);
* @param focusItem The active focus item; user field must point to uimenu_t.
* @returns True.
*/
bool_t uiMenuFocusClosed(const uifocusitem_t *focusItem);
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 uint8_t menuIndex = 0;
#define MENU_LABEL(text) \
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_LABEL; \
menu->items[menuIndex].label = text; \
++menuIndex;
#define MENU_SPACER() \
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SPACER; \
++menuIndex;
#define MENU_CHECKBOX(label) \
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_CHECKBOX; \
uiCheckboxInit(&menu->items[menuIndex].checkbox, label); \
++menuIndex;
#define MENU_BUTTON(label) \
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_BUTTON; \
uiButtonInit(&menu->items[menuIndex].button, label); \
++menuIndex;
#define MENU_TAB(label) \
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) \
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) \
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SLIDER; \
uiSliderInitInt( \
&menu->items[menuIndex].slider, label, value, min, max, step \
); \
++menuIndex;
+242
View File
@@ -0,0 +1,242 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uislider.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/math.h"
#include "util/string.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"
void uiSliderInitFloat(
uislider_t *slider,
const char_t *label,
const float_t value,
const float_t min,
const float_t max,
const float_t step
) {
assertNotNull(slider, "Slider cannot be NULL");
assertNotNull(label, "Label cannot be NULL");
assertTrue(max > min, "Slider max must be greater than min");
assertTrue(step > 0.0f, "Slider step must be greater than zero");
memoryZero(slider, sizeof(uislider_t));
slider->label = label;
slider->type = UI_SLIDER_TYPE_FLOAT;
slider->min.f = min;
slider->max.f = max;
slider->step.f = step;
slider->value.f = mathClamp(value, min, max);
}
void uiSliderInitInt(
uislider_t *slider,
const char_t *label,
const int32_t value,
const int32_t min,
const int32_t max,
const int32_t step
) {
assertNotNull(slider, "Slider cannot be NULL");
assertNotNull(label, "Label cannot be NULL");
assertTrue(max > min, "Slider max must be greater than min");
assertTrue(step > 0, "Slider step must be greater than zero");
memoryZero(slider, sizeof(uislider_t));
slider->label = label;
slider->type = UI_SLIDER_TYPE_INT;
slider->min.i = min;
slider->max.i = max;
slider->step.i = step;
slider->value.i = mathClamp(value, min, max);
}
float_t uiSliderGetFloat(const uislider_t *slider) {
assertNotNull(slider, "Slider cannot be NULL");
return slider->type == UI_SLIDER_TYPE_INT ?
(float_t)slider->value.i : slider->value.f;
}
int32_t uiSliderGetInt(const uislider_t *slider) {
assertNotNull(slider, "Slider cannot be NULL");
assertTrue(
slider->type == UI_SLIDER_TYPE_INT, "Slider is not an int slider"
);
return slider->value.i;
}
void uiSliderSetFloat(uislider_t *slider, const float_t value) {
assertNotNull(slider, "Slider cannot be NULL");
assertTrue(
slider->type == UI_SLIDER_TYPE_FLOAT, "Slider is not a float slider"
);
slider->value.f = mathClamp(value, slider->min.f, slider->max.f);
}
void uiSliderSetInt(uislider_t *slider, const int32_t value) {
assertNotNull(slider, "Slider cannot be NULL");
assertTrue(
slider->type == UI_SLIDER_TYPE_INT, "Slider is not an int slider"
);
slider->value.i = mathClamp(value, slider->min.i, slider->max.i);
}
void uiSliderStepUp(uislider_t *slider) {
assertNotNull(slider, "Slider cannot be NULL");
if(slider->type == UI_SLIDER_TYPE_INT) {
int32_t next = slider->value.i + slider->step.i;
uiSliderSetInt(slider, next > slider->max.i ? slider->min.i : next);
} else {
float_t next = slider->value.f + slider->step.f;
uiSliderSetFloat(slider, next > slider->max.f ? slider->min.f : next);
}
}
void uiSliderStepDown(uislider_t *slider) {
assertNotNull(slider, "Slider cannot be NULL");
if(slider->type == UI_SLIDER_TYPE_INT) {
int32_t next = slider->value.i - slider->step.i;
uiSliderSetInt(slider, next < slider->min.i ? slider->max.i : next);
} else {
float_t next = slider->value.f - slider->step.f;
uiSliderSetFloat(slider, next < slider->min.f ? slider->max.f : next);
}
}
float_t uiSliderGetRatio(const uislider_t *slider) {
assertNotNull(slider, "Slider cannot be NULL");
if(slider->type == UI_SLIDER_TYPE_INT) {
if(slider->max.i == slider->min.i) return 0.0f;
return (float_t)(slider->value.i - slider->min.i) /
(float_t)(slider->max.i - slider->min.i);
}
if(slider->max.f == slider->min.f) return 0.0f;
return (slider->value.f - slider->min.f) / (slider->max.f - slider->min.f);
}
int32_t uiSliderGetStepCount(const uislider_t *slider) {
assertNotNull(slider, "Slider cannot be NULL");
if(slider->type != UI_SLIDER_TYPE_INT || slider->step.i <= 0) return 0;
return (slider->max.i - slider->min.i) / slider->step.i;
}
bool_t uiSliderIsHighlighted(const uislider_t *slider) {
assertNotNull(slider, "Slider cannot be NULL");
return slider->highlighted;
}
void uiSliderSetHighlighted(uislider_t *slider, const bool_t highlighted) {
assertNotNull(slider, "Slider cannot be NULL");
slider->highlighted = highlighted;
}
errorret_t uiSliderDraw(
const uislider_t *slider,
const float_t x,
const float_t y
) {
assertNotNull(slider, "Slider cannot be NULL");
color_t color = slider->highlighted ? COLOR_RED : COLOR_WHITE;
errorChain(textDraw(x, y, slider->label, color, &FONT_DEFAULT));
int32_t labelW, labelH;
textMeasure(slider->label, &FONT_DEFAULT, &labelW, &labelH);
float_t trackX = x + (float_t)labelW + UI_SLIDER_GAP;
float_t trackY = y + ((float_t)labelH - UI_SLIDER_TRACK_HEIGHT) * 0.5f;
spritebatchsprite_t trackSprite = {
.min = { trackX, trackY, 0.0f },
.max = { trackX + UI_SLIDER_TRACK_WIDTH, trackY + UI_SLIDER_TRACK_HEIGHT, 0.0f },
.uvMin = { 0.0f, 0.0f },
.uvMax = { 1.0f, 1.0f }
};
shadermaterial_t trackMaterial = {
.unlit = {
.color = COLOR_DARK_GRAY,
.texture = &TEXTURE_WHITE
}
};
errorChain(spriteBatchBuffer(&trackSprite, 1, &SHADER_UNLIT, trackMaterial));
errorChain(spriteBatchFlush());
float_t fillWidth = UI_SLIDER_TRACK_WIDTH * uiSliderGetRatio(slider);
if(fillWidth > 0.0f) {
spritebatchsprite_t fillSprite = {
.min = { trackX, trackY, 0.0f },
.max = { trackX + fillWidth, trackY + UI_SLIDER_TRACK_HEIGHT, 0.0f },
.uvMin = { 0.0f, 0.0f },
.uvMax = { 1.0f, 1.0f }
};
shadermaterial_t fillMaterial = {
.unlit = {
.color = color,
.texture = &TEXTURE_WHITE
}
};
errorChain(spriteBatchBuffer(&fillSprite, 1, &SHADER_UNLIT, fillMaterial));
errorChain(spriteBatchFlush());
}
int32_t stepCount = uiSliderGetStepCount(slider);
if(stepCount > 0 && stepCount < UI_SLIDER_STEP_MARKERS_MAX) {
for(int32_t i = 0; i <= stepCount; i++) {
float_t markerX = trackX +
UI_SLIDER_TRACK_WIDTH * (float_t)i / (float_t)stepCount;
spritebatchsprite_t markerSprite = {
.min = {
markerX - UI_SLIDER_STEP_MARKER_WIDTH * 0.5f,
trackY - UI_SLIDER_STEP_MARKER_OVERHANG,
0.0f
},
.max = {
markerX + UI_SLIDER_STEP_MARKER_WIDTH * 0.5f,
trackY + UI_SLIDER_TRACK_HEIGHT + UI_SLIDER_STEP_MARKER_OVERHANG,
0.0f
},
.uvMin = { 0.0f, 0.0f },
.uvMax = { 1.0f, 1.0f }
};
shadermaterial_t markerMaterial = {
.unlit = {
.color = COLOR_WHITE,
.texture = &TEXTURE_WHITE
}
};
errorChain(
spriteBatchBuffer(&markerSprite, 1, &SHADER_UNLIT, markerMaterial)
);
errorChain(spriteBatchFlush());
}
}
char_t valueText[UI_SLIDER_VALUE_TEXT_MAX];
if(slider->type == UI_SLIDER_TYPE_INT) {
stringFormat(
valueText, UI_SLIDER_VALUE_TEXT_MAX - 1, "%d", slider->value.i
);
} else {
stringFormat(
valueText, UI_SLIDER_VALUE_TEXT_MAX - 1, "%.2f", slider->value.f
);
}
errorChain(textDraw(
trackX + UI_SLIDER_TRACK_WIDTH + UI_SLIDER_GAP, y,
valueText, color, &FONT_DEFAULT
));
errorOk();
}
+185
View File
@@ -0,0 +1,185 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#define UI_SLIDER_TRACK_WIDTH 80.0f
#define UI_SLIDER_TRACK_HEIGHT 4.0f
#define UI_SLIDER_STEP_MARKER_WIDTH 2.0f
#define UI_SLIDER_STEP_MARKER_OVERHANG 1.0f
#define UI_SLIDER_GAP 4.0f
#define UI_SLIDER_VALUE_TEXT_MAX 16
/**
* Number of discrete steps below which an int slider renders individual
* step markers on its track instead of a smooth fill.
*/
#define UI_SLIDER_STEP_MARKERS_MAX 10
typedef enum {
UI_SLIDER_TYPE_FLOAT,
UI_SLIDER_TYPE_INT,
UI_SLIDER_TYPE_COUNT
} uislidertype_t;
typedef union {
float_t f;
int32_t i;
} uislidervalue_t;
typedef struct {
const char_t *label;
uislidertype_t type;
uislidervalue_t value;
uislidervalue_t min;
uislidervalue_t max;
uislidervalue_t step;
bool_t highlighted;
} uislider_t;
/**
* Initializes a floating point slider.
*
* @param slider The slider to initialize.
* @param label Display label.
* @param value Initial value, clamped to [min, max].
* @param min Minimum value.
* @param max Maximum value.
* @param step The amount a single step moves the value by.
*/
void uiSliderInitFloat(
uislider_t *slider,
const char_t *label,
const float_t value,
const float_t min,
const float_t max,
const float_t step
);
/**
* Initializes an integer slider.
*
* @param slider The slider to initialize.
* @param label Display label.
* @param value Initial value, clamped to [min, max].
* @param min Minimum value.
* @param max Maximum value.
* @param step The amount a single step moves the value by.
*/
void uiSliderInitInt(
uislider_t *slider,
const char_t *label,
const int32_t value,
const int32_t min,
const int32_t max,
const int32_t step
);
/**
* Returns the slider's current value as a float. Works for both slider
* types; int values are widened to float.
*
* @param slider The slider to query.
* @returns The current value.
*/
float_t uiSliderGetFloat(const uislider_t *slider);
/**
* Returns the slider's current value as an int. Only valid for
* UI_SLIDER_TYPE_INT sliders.
*
* @param slider The slider to query.
* @returns The current value.
*/
int32_t uiSliderGetInt(const uislider_t *slider);
/**
* Sets the slider's value, clamped to [min, max]. Only valid for
* UI_SLIDER_TYPE_FLOAT sliders.
*
* @param slider The slider to update.
* @param value The new value.
*/
void uiSliderSetFloat(uislider_t *slider, const float_t value);
/**
* Sets the slider's value, clamped to [min, max]. Only valid for
* UI_SLIDER_TYPE_INT sliders.
*
* @param slider The slider to update.
* @param value The new value.
*/
void uiSliderSetInt(uislider_t *slider, const int32_t value);
/**
* Moves the slider's value up by one step, wrapping around to its min
* if the step would exceed its max.
*
* @param slider The slider to update.
*/
void uiSliderStepUp(uislider_t *slider);
/**
* Moves the slider's value down by one step, wrapping around to its
* max if the step would go below its min.
*
* @param slider The slider to update.
*/
void uiSliderStepDown(uislider_t *slider);
/**
* Returns the slider's current value normalized to a 0..1 range based
* on its min/max.
*
* @param slider The slider to query.
* @returns The normalized value.
*/
float_t uiSliderGetRatio(const uislider_t *slider);
/**
* Returns the number of discrete steps between min and max. Always 0
* for float sliders.
*
* @param slider The slider to query.
* @returns The step count.
*/
int32_t uiSliderGetStepCount(const uislider_t *slider);
/**
* Returns whether the slider is highlighted.
*
* @param slider The slider to query.
* @returns True if highlighted.
*/
bool_t uiSliderIsHighlighted(const uislider_t *slider);
/**
* Sets the highlighted state of the slider.
*
* @param slider The slider to update.
* @param highlighted The new highlighted state.
*/
void uiSliderSetHighlighted(uislider_t *slider, const bool_t highlighted);
/**
* Draws the slider at the given screen position: label, then track,
* then the current value as text. Int sliders with fewer than
* UI_SLIDER_STEP_MARKERS_MAX discrete steps render a marker per step
* instead of a plain track.
*
* @param slider The slider to draw.
* @param x Screen x position.
* @param y Screen y position.
* @return Any error that occurs.
*/
errorret_t uiSliderDraw(
const uislider_t *slider,
const float_t x,
const float_t y
);
+62
View File
@@ -0,0 +1,62 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uitab.h"
#include "assert/assert.h"
#include "util/memory.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"
void uiTabInit(uitab_t *tab, const char_t *label) {
assertNotNull(tab, "Tab cannot be NULL");
assertNotNull(label, "Label cannot be NULL");
memoryZero(tab, sizeof(uitab_t));
tab->label = label;
}
bool_t uiTabIsActive(const uitab_t *tab) {
assertNotNull(tab, "Tab cannot be NULL");
return tab->active;
}
void uiTabSetActive(uitab_t *tab, const bool_t active) {
assertNotNull(tab, "Tab cannot be NULL");
tab->active = active;
}
errorret_t uiTabDraw(
const uitab_t *tab,
const float_t x,
const float_t y
) {
assertNotNull(tab, "Tab cannot be NULL");
int32_t labelW, labelH;
textMeasure(tab->label, &FONT_DEFAULT, &labelW, &labelH);
spritebatchsprite_t sprite = {
.min = { x, y, 0.0f },
.max = { x + (float_t)labelW, y + (float_t)labelH, 0.0f },
.uvMin = { 0.0f, 0.0f },
.uvMax = { 1.0f, 1.0f }
};
shadermaterial_t material = {
.unlit = {
.color = tab->active ? COLOR_GREEN : COLOR_RED,
.texture = &TEXTURE_WHITE
}
};
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
errorChain(spriteBatchFlush());
errorChain(textDraw(x, y, tab->label, COLOR_WHITE, &FONT_DEFAULT));
errorOk();
}
+53
View File
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef struct {
const char_t *label;
bool_t active;
} uitab_t;
/**
* Initializes a tab.
*
* @param tab The tab to initialize.
* @param label Display label.
*/
void uiTabInit(uitab_t *tab, const char_t *label);
/**
* Returns whether the tab is active.
*
* @param tab The tab to query.
* @returns True if active.
*/
bool_t uiTabIsActive(const uitab_t *tab);
/**
* Sets the active state of the tab.
*
* @param tab The tab to update.
* @param active The new active state.
*/
void uiTabSetActive(uitab_t *tab, const bool_t active);
/**
* Draws the tab at the given screen position. Active tabs draw with a
* green background; inactive tabs draw with a red background.
*
* @param tab The tab to draw.
* @param x Screen x position.
* @param y Screen y position.
* @return Any error that occurs.
*/
errorret_t uiTabDraw(
const uitab_t *tab,
const float_t x,
const float_t y
);