Cleanup more.

This commit is contained in:
2026-07-18 17:38:25 -05:00
parent 9f86c20129
commit 11e5d37563
45 changed files with 6 additions and 3022 deletions
-1
View File
@@ -15,5 +15,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
add_subdirectory(display) add_subdirectory(display)
add_subdirectory(locale) add_subdirectory(locale)
add_subdirectory(json) add_subdirectory(json)
add_subdirectory(chunk)
add_subdirectory(dmf) add_subdirectory(dmf)
-6
View File
@@ -45,10 +45,4 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = {
.loadAsync = assetJsonLoaderAsync, .loadAsync = assetJsonLoaderAsync,
.dispose = assetJsonDispose .dispose = assetJsonDispose
}, },
[ASSET_LOADER_TYPE_CHUNK] = {
.loadSync = assetChunkLoaderSync,
.loadAsync = assetChunkLoaderAsync,
.dispose = assetChunkDispose
},
}; };
-5
View File
@@ -12,7 +12,6 @@
#include "asset/loader/display/assettilesetloader.h" #include "asset/loader/display/assettilesetloader.h"
#include "asset/loader/locale/assetlocaleloader.h" #include "asset/loader/locale/assetlocaleloader.h"
#include "asset/loader/json/assetjsonloader.h" #include "asset/loader/json/assetjsonloader.h"
#include "asset/loader/chunk/assetchunkloader.h"
typedef enum { typedef enum {
ASSET_LOADER_TYPE_NULL, ASSET_LOADER_TYPE_NULL,
@@ -23,7 +22,6 @@ typedef enum {
ASSET_LOADER_TYPE_TILESET, ASSET_LOADER_TYPE_TILESET,
ASSET_LOADER_TYPE_LOCALE, ASSET_LOADER_TYPE_LOCALE,
ASSET_LOADER_TYPE_JSON, ASSET_LOADER_TYPE_JSON,
ASSET_LOADER_TYPE_CHUNK,
ASSET_LOADER_TYPE_COUNT ASSET_LOADER_TYPE_COUNT
} assetloadertype_t; } assetloadertype_t;
@@ -35,7 +33,6 @@ typedef union {
assettilesetloaderloading_t tileset; assettilesetloaderloading_t tileset;
assetlocaleloaderloading_t locale; assetlocaleloaderloading_t locale;
assetjsonloaderloading_t json; assetjsonloaderloading_t json;
assetchunkloaderloading_t chunk;
} assetloaderloading_t; } assetloaderloading_t;
typedef union { typedef union {
@@ -45,7 +42,6 @@ typedef union {
assettilesetoutput_t tileset; assettilesetoutput_t tileset;
assetlocaleoutput_t locale; assetlocaleoutput_t locale;
assetjsonoutput_t json; assetjsonoutput_t json;
assetchunkoutput_t chunk;
} assetloaderoutput_t; } assetloaderoutput_t;
typedef union { typedef union {
@@ -53,7 +49,6 @@ typedef union {
assettilesetloaderinput_t tileset; assettilesetloaderinput_t tileset;
assetlocaleloaderinput_t locale; assetlocaleloaderinput_t locale;
assetjsonloaderinput_t json; assetjsonloaderinput_t json;
assetchunkloaderinput_t chunk;
} assetloaderinput_t; } assetloaderinput_t;
typedef struct assetloading_s assetloading_t; typedef struct assetloading_s assetloading_t;
@@ -1,10 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
assetchunkloader.c
)
@@ -1,181 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetchunkloader.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/endian.h"
#include "asset/loader/assetloading.h"
#include "asset/loader/assetentry.h"
#include "asset/loader/assetloader.h"
#include "asset/asset.h"
errorret_t assetChunkLoaderAsync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertNotMainThread("Should be called from an async thread.");
if(loading->loading.chunk.state != ASSET_CHUNK_LOADING_STATE_READ_FILE) {
errorOk();
}
assertNull(loading->loading.chunk.data, "Data already defined?");
assetfile_t *file = &loading->loading.chunk.file;
assetLoaderErrorChain(loading,
assetFileInit(file, loading->entry->name, NULL, NULL)
);
uint8_t *data = memoryAllocate(file->size);
assetLoaderErrorChain(loading, assetFileOpen(file));
assetLoaderErrorChain(loading, assetFileRead(file, data, file->size));
assetLoaderErrorChain(loading, assetFileClose(file));
assetLoaderErrorChain(loading, assetFileDispose(file));
assertTrue(
file->lastRead == file->size,
"Failed to read entire chunk file."
);
loading->loading.chunk.data = data;
loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_PARSE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
errorret_t assetChunkLoaderSync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertTrue(loading->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type.");
assertIsMainThread("Must be called from the main thread.");
assetchunkoutput_t *out = &loading->entry->data.chunk;
switch(loading->loading.chunk.state) {
case ASSET_CHUNK_LOADING_STATE_INITIAL:
loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_READ_FILE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
errorOk();
case ASSET_CHUNK_LOADING_STATE_PARSE:
break;
case ASSET_CHUNK_LOADING_STATE_LOAD_MODELS:
while(loading->loading.chunk.modelIndex < out->meshCount) {
uint8_t m = loading->loading.chunk.modelIndex;
if(out->modelEntries[m] == NULL) {
out->modelEntries[m] = assetLock(
out->modelNames[m], ASSET_LOADER_TYPE_MODEL, NULL
);
assertNotNull(
out->modelEntries[m], "Failed to lock model."
);
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
if(out->modelEntries[m]->state == ASSET_ENTRY_STATE_ERROR) {
assetLoaderErrorThrow(
loading, "Model failed to load: %s", out->modelNames[m]
);
}
if(out->modelEntries[m]->state != ASSET_ENTRY_STATE_LOADED) {
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
loading->loading.chunk.modelIndex++;
}
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
errorOk();
default:
errorOk();
}
uint8_t *data = loading->loading.chunk.data;
assertNotNull(data, "Chunk data should have been loaded by now.");
if(data[0] != 'D' || data[1] != 'C' || data[2] != 'F') {
memoryFree(data);
assetLoaderErrorThrow(loading, "Invalid chunk file header");
}
uint32_t version = endianLittleToHost32(*(uint32_t *)(data + 4));
if(version != ASSET_CHUNK_FILE_VERSION) {
memoryFree(data);
assetLoaderErrorThrow(
loading, "Unsupported chunk file version %u", version
);
}
size_t offset = 8;
size_t tileSize = CHUNK_TILE_COUNT * sizeof(tile_t);
out->tiles = memoryAllocate(tileSize);
memoryCopy(out->tiles, data + offset, tileSize);
offset += tileSize;
for(size_t t = 0; t < CHUNK_TILE_COUNT; t++) {
uint32_t *shape = (uint32_t *)&out->tiles[t].shape;
*shape = endianLittleToHost32(*shape);
}
out->meshCount = data[offset];
offset += sizeof(uint8_t);
assertTrue(
out->meshCount <= CHUNK_MESH_COUNT_MAX,
"Chunk mesh count exceeds maximum."
);
for(uint8_t m = 0; m < out->meshCount; m++) {
uint8_t nameLen = 0;
while(
data[offset + nameLen] != '\0' &&
nameLen < CHUNK_MESH_NAME_MAX - 1
) {
nameLen++;
}
memoryCopy(out->modelNames[m], data + offset, nameLen);
out->modelNames[m][nameLen] = '\0';
offset += nameLen + 1;
memoryCopy(out->meshOffsets[m], data + offset, sizeof(vec3));
offset += sizeof(vec3);
out->meshOffsets[m][0] = endianLittleToHostFloat(out->meshOffsets[m][0]);
out->meshOffsets[m][1] = endianLittleToHostFloat(out->meshOffsets[m][1]);
out->meshOffsets[m][2] = endianLittleToHostFloat(out->meshOffsets[m][2]);
}
memoryFree(data);
loading->loading.chunk.data = NULL;
if(out->meshCount == 0) {
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
errorOk();
}
loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_LOAD_MODELS;
loading->loading.chunk.modelIndex = 0;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
errorret_t assetChunkDispose(assetentry_t *entry) {
assertNotNull(entry, "Entry cannot be NULL");
assertTrue(entry->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type.");
assertIsMainThread("Must be called from the main thread.");
assetchunkoutput_t *out = &entry->data.chunk;
if(out->tiles != NULL) {
memoryFree(out->tiles);
out->tiles = NULL;
}
for(uint8_t m = 0; m < out->meshCount; m++) {
if(out->modelEntries[m] == NULL) continue;
assetUnlockEntry(out->modelEntries[m]);
out->modelEntries[m] = NULL;
}
errorOk();
}
@@ -1,70 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "asset/assetfile.h"
#include "rpg/overworld/chunk.h"
#define ASSET_CHUNK_FILE_VERSION 4
typedef struct assetloading_s assetloading_t;
typedef struct assetentry_s assetentry_t;
typedef struct {
void *nothing;
} assetchunkloaderinput_t;
typedef enum {
ASSET_CHUNK_LOADING_STATE_INITIAL,
ASSET_CHUNK_LOADING_STATE_READ_FILE,
ASSET_CHUNK_LOADING_STATE_PARSE,
ASSET_CHUNK_LOADING_STATE_LOAD_MODELS,
ASSET_CHUNK_LOADING_STATE_DONE
} assetchunkloadingstate_t;
typedef struct {
assetfile_t file;
assetchunkloadingstate_t state;
uint8_t *data;
uint8_t modelIndex;
} assetchunkloaderloading_t;
typedef struct {
tile_t *tiles;
uint8_t meshCount;
char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX];
vec3 meshOffsets[CHUNK_MESH_COUNT_MAX];
assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX];
} assetchunkoutput_t;
/**
* Asynchronous loader for chunk assets. Reads the raw DCF file bytes into
* the loading buffer so the sync phase can parse without blocking the
* main thread on I/O.
*
* @param loading Loading information for the asset being loaded.
* @return Error code indicating success or failure of the load operation.
*/
errorret_t assetChunkLoaderAsync(assetloading_t *loading);
/**
* Synchronous loader for chunk assets. Validates the DCF binary previously
* read by the async phase and populates the output assetchunkoutput_t with
* tile data and model paths.
*
* @param loading Loading information for the asset being loaded.
* @return Error code indicating success or failure of the load operation.
*/
errorret_t assetChunkLoaderSync(assetloading_t *loading);
/**
* Disposer for chunk assets.
*
* @param entry Asset entry containing the chunk data to dispose.
* @return Error code indicating success or failure of the dispose operation.
*/
errorret_t assetChunkDispose(assetentry_t *entry);
+1 -5
View File
@@ -10,7 +10,6 @@
#include "time/time.h" #include "time/time.h"
#include "input/input.h" #include "input/input.h"
#include "locale/localemanager.h" #include "locale/localemanager.h"
#include "rpg/rpg.h"
#include "display/display.h" #include "display/display.h"
#include "scene/scene.h" #include "scene/scene.h"
#include "asset/asset.h" #include "asset/asset.h"
@@ -44,7 +43,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
errorChain(localeManagerInit()); errorChain(localeManagerInit());
errorChain(displayInit()); errorChain(displayInit());
errorChain(uiInit()); errorChain(uiInit());
errorChain(rpgInit());
errorChain(networkInit()); errorChain(networkInit());
errorChain(sceneInit()); errorChain(sceneInit());
@@ -63,7 +61,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
consolePrint("Assertions real"); consolePrint("Assertions real");
#endif #endif
sceneSet(SCENE_TYPE_OVERWORLD); // sceneSet(SCENE_TYPE_OVERWORLD);
errorOk(); errorOk();
} }
@@ -85,7 +83,6 @@ errorret_t engineUpdate(void) {
if(dialogType == SYSTEM_DIALOG_TYPE_NONE) { if(dialogType == SYSTEM_DIALOG_TYPE_NONE) {
inputUpdate(); inputUpdate();
consoleUpdate(); consoleUpdate();
errorChain(rpgUpdate());
errorChain(sceneUpdate()); errorChain(sceneUpdate());
errorChain(assetUpdate()); errorChain(assetUpdate());
errorChain(uiUpdate()); errorChain(uiUpdate());
@@ -109,7 +106,6 @@ void engineExit(void) {
errorret_t engineDispose(void) { errorret_t engineDispose(void) {
errorChain(sceneDispose()); errorChain(sceneDispose());
errorChain(networkDispose()); errorChain(networkDispose());
errorChain(rpgDispose());
localeManagerDispose(); localeManagerDispose();
errorChain(uiDispose()); errorChain(uiDispose());
consoleDispose(); consoleDispose();
+1 -5
View File
@@ -6,8 +6,4 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
scene.c scene.c
scenetype.c )
)
# Subdirs
add_subdirectory(overworld)
-9
View File
@@ -1,9 +0,0 @@
# 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
sceneoverworld.c
)
-262
View File
@@ -1,262 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scene/scene.h"
#include "console/console.h"
#include "assert/assert.h"
#include "display/display.h"
#include "display/displaystate.h"
#include "display/shader/shader.h"
#include "display/screen/screen.h"
#include "display/shader/shaderunlit.h"
#include "display/spritebatch/spritebatch.h"
#include "display/texture/texture.h"
#include "rpg/overworld/map.h"
#include "rpg/entity/entity.h"
#include "rpg/rpgcamera.h"
#include "asset/loader/assetloader.h"
#include "asset/loader/assetentry.h"
#include "util/memory.h"
errorret_t sceneOverworldInit(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null");
errorOk();
}
errorret_t sceneOverworldUpdate(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null");
errorOk();
}
errorret_t sceneOverworldRender(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null");
sceneoverworld_t *overworld = &sceneData->overworld;
sceneOverworldCullUpdate(overworld);
// Overworld camera
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(
&SHADER_UNLIT, SHADER_UNLIT_MODEL, SCENE.screenIdentity
));
rpgCameraUpdateProjection();
errorChain(shaderSetMatrix(
&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, RPG_CAMERA.projection
));
rpgCameraUpdateEye();
errorChain(shaderSetMatrix(
&SHADER_UNLIT, SHADER_UNLIT_VIEW, RPG_CAMERA.eye
));
// Chunk tiles
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
}));
errorChain(sceneOverworldDrawChunksBase(overworld));
// Entities
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_CULL
}));
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
entity_t *ent = &ENTITIES[i];
if(ent->type == ENTITY_TYPE_NULL) continue;
errorChain(sceneOverworldDrawEntity(overworld, ent));
}
// Chunk props
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
}));
errorChain(sceneOverworldDrawChunksProps(overworld));
// Weather effects
// errorChain(shaderSetMatrix(
// &SHADER_UNLIT, SHADER_UNLIT_PROJECTION, SCENE.screenProj
// ));
// errorChain(shaderSetMatrix(
// &SHADER_UNLIT, SHADER_UNLIT_VIEW, SCENE.screenView
// ));
// errorChain(displaySetState((displaystate_t){
// .flags = DISPLAY_STATE_FLAG_BLEND
// }));
// spritebatchsprite_t skyboxSprite;
// memoryZero(&skyboxSprite, sizeof(spritebatchsprite_t));
// skyboxSprite.max[0] = (float_t)(SCREEN.width / SCREEN.scaleUi);
// skyboxSprite.max[1] = (float_t)(SCREEN.height / SCREEN.scaleUi);
// shadermaterial_t skyboxMaterial = {
// .unlit = { .color = color(0xFF, 0x00, 0x00, 0xFF/2), .texture = NULL }
// };
// errorChain(spriteBatchBuffer(&skyboxSprite, 1, &SHADER_UNLIT, skyboxMaterial));
// errorChain(spriteBatchFlush());
errorOk();
}
void sceneOverworldCullUpdate(sceneoverworld_t *overworld) {
rpgCameraGetPosition(overworld->cullTarget);
const float_t worldH =
(float_t)(SCREEN.height / SCREEN.scale3d) / TILE_SIZE_PIXELS;
const float_t worldW = worldH * SCREEN.aspect;
overworld->cullHalfRangeX =
(worldW * 0.5f) + SCENE_OVERWORLD_CHUNK_CULL_SKIN_X;
overworld->cullHalfRangeY =
(worldH * 0.5f) + SCENE_OVERWORLD_CHUNK_CULL_SKIN_Y;
}
bool_t sceneOverworldChunkShouldRender(
const sceneoverworld_t *overworld,
const chunk_t *chunk
) {
worldpos_t worldPos;
chunkPosToWorldPos(&chunk->position, &worldPos);
const float_t chunkCenterX = (float_t)worldPos.x + (CHUNK_WIDTH * 0.5f);
const float_t chunkCenterY = (float_t)worldPos.y + (CHUNK_HEIGHT * 0.5f);
if(fabsf(overworld->cullTarget[0] - chunkCenterX) >
overworld->cullHalfRangeX) return false;
if(fabsf(overworld->cullTarget[1] - chunkCenterY) >
overworld->cullHalfRangeY) return false;
return true;
}
bool_t sceneOverworldEntityShouldRender(
const sceneoverworld_t *overworld,
const entity_t *ent
) {
if(fabsf(overworld->cullTarget[0] - ent->renderPosition[0]) >
overworld->cullHalfRangeX) return false;
if(fabsf(overworld->cullTarget[1] - ent->renderPosition[1]) >
overworld->cullHalfRangeY) return false;
return true;
}
errorret_t sceneOverworldDrawEntity(
const sceneoverworld_t *overworld,
entity_t *ent
) {
if(!sceneOverworldEntityShouldRender(overworld, ent)) errorOk();
spritebatchsprite_t sprite;
glm_vec3_copy(ent->renderPosition, sprite.min);
glm_vec3_add(sprite.min, (vec3){ 0, -0.05f, 0.05f }, sprite.min);// Stop Fight
glm_vec3_copy(sprite.min, sprite.max);
glm_vec3_add(sprite.max, (vec3){ 1, 1, 0 }, sprite.max);
glm_vec2_copy((vec2){ 0, 0 }, sprite.uvMin);
glm_vec2_copy((vec2){ 1, 1 }, sprite.uvMax);
color_t color;
switch(ent->direction) {
case ENTITY_DIR_NORTH: color = COLOR_YELLOW; break;
case ENTITY_DIR_EAST: color = COLOR_RED; break;
case ENTITY_DIR_SOUTH: color = COLOR_GREEN; break;
case ENTITY_DIR_WEST: color = COLOR_BLUE; break;
default: color = COLOR_CYAN; break;
}
shadermaterial_t material = {
.unlit = { .color = color, .texture = NULL }
};
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
errorChain(spriteBatchFlush());
errorOk();
}
errorret_t sceneOverworldDrawChunksBase(const sceneoverworld_t *overworld) {
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
chunk_t *chunk = MAP.chunkOrder[i];
if(chunk == NULL) continue;
if(!sceneOverworldChunkShouldRender(overworld, chunk)) continue;
if(chunk->meshCount == 0) continue;
if(chunk->modelEntries[0] == NULL) continue;
if(chunk->modelEntries[0]->state != ASSET_ENTRY_STATE_LOADED) continue;
assetmodeloutput_t *model = &chunk->modelEntries[0]->data.model;
if(model->meshEntry == NULL) continue;
texture_t *tex = model->texEntry != NULL
? &model->texEntry->data.texture
: NULL;
shadermaterial_t mat = {
.unlit = { .color = model->color, .texture = tex }
};
errorChain(shaderSetMatrix(
&SHADER_UNLIT, SHADER_UNLIT_MODEL, chunk->meshModels[0]
));
errorChain(shaderSetMaterial(&SHADER_UNLIT, &mat));
errorChain(meshDraw(
&model->meshEntry->data.mesh.mesh, 0, -1
));
}
// Restore identity model so subsequent renders (e.g. entities) are
// not affected by the last chunk transform.
mat4 identity;
glm_mat4_identity(identity);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, identity));
errorOk();
}
errorret_t sceneOverworldDrawChunksProps(const sceneoverworld_t *overworld) {
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
chunk_t *chunk = MAP.chunkOrder[i];
if(chunk == NULL) continue;
if(!sceneOverworldChunkShouldRender(overworld, chunk)) continue;
for(uint8_t m = 1; m < chunk->meshCount; m++) {
if(chunk->modelEntries[m] == NULL) continue;
if(chunk->modelEntries[m]->state != ASSET_ENTRY_STATE_LOADED) continue;
assetmodeloutput_t *model = &chunk->modelEntries[m]->data.model;
if(model->meshEntry == NULL) continue;
texture_t *tex = model->texEntry != NULL
? &model->texEntry->data.texture
: NULL;
shadermaterial_t mat = {
.unlit = { .color = model->color, .texture = tex }
};
errorChain(shaderSetMatrix(
&SHADER_UNLIT, SHADER_UNLIT_MODEL, chunk->meshModels[m]
));
errorChain(shaderSetMaterial(&SHADER_UNLIT, &mat));
errorChain(meshDraw(
&model->meshEntry->data.mesh.mesh, 0, -1
));
}
}
// Restore identity model in case anything renders after props.
mat4 identity;
glm_mat4_identity(identity);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, identity));
errorOk();
}
errorret_t sceneOverworldDispose(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null");
errorOk();
}
-149
View File
@@ -1,149 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "scene/scenebase.h"
#include "rpg/overworld/chunk.h"
#include "rpg/entity/entity.h"
typedef struct {
// Cached per-frame chunk culling bounds, refreshed by
// sceneOverworldCullUpdate() so sceneOverworldChunkShouldRender()
// does not need to recompute them for every chunk.
vec3 cullTarget;
float_t cullHalfRangeX;
float_t cullHalfRangeY;
} sceneoverworld_t;
// Extra world-space margin, in tiles, added on top of the rough
// screen-size skin below - cheap insurance against the approximation
// culling a chunk that is actually still just in view. Y gets its own
// value since the camera's tilt makes the vertical check less exact.
#define SCENE_OVERWORLD_CHUNK_CULL_PADDING_X 8.0f
#define SCENE_OVERWORLD_CHUNK_CULL_PADDING_Y 4.0f
// The camera is tilted between the ground-depth (Y) and height (Z)
// axes, so a chunk's on-screen vertical extent also depends on its Z
// layers. Rather than projecting every corner, fold that into a
// generous skin margin on the depth check.
#define SCENE_OVERWORLD_CHUNK_CULL_SKIN_X \
((CHUNK_WIDTH * 0.5f) + SCENE_OVERWORLD_CHUNK_CULL_PADDING_X)
#define SCENE_OVERWORLD_CHUNK_CULL_SKIN_Y \
((CHUNK_HEIGHT * 0.5f) + (CHUNK_DEPTH * WORLD_LAYER_HEIGHT) + \
SCENE_OVERWORLD_CHUNK_CULL_PADDING_Y)
/**
* Initialises the overworld scene.
*
* @param sceneData The scene data used for this scene.
* @return An error if the init failed, or errorOk() if it succeeded.
*/
errorret_t sceneOverworldInit(scenedata_t *sceneData);
/**
* Updates the overworld scene.
*
* @param sceneData The scene data used for this scene.
* @return An error if the update failed, or errorOk() if it succeeded.
*/
errorret_t sceneOverworldUpdate(scenedata_t *sceneData);
/**
* Refreshes the chunk culling bounds cached on the overworld scene data,
* from the current camera position and screen dimensions. Must be
* called once per frame before sceneOverworldChunkShouldRender().
*
* @param overworld The overworld scene data to update.
*/
void sceneOverworldCullUpdate(sceneoverworld_t *overworld);
/**
* Roughly checks whether a chunk falls within the visible screen area,
* based on chunk size and screen dimensions. Cheap approximation, not
* an exact frustum check - includes a generous skin margin to account
* for the camera's angle tilting height/depth onto the screen's
* vertical axis, so it may pass some chunks that are actually just out
* of view but will never wrongly cull one that is visible.
*
* @param overworld The overworld scene data holding the cached culling
* bounds, as refreshed by sceneOverworldCullUpdate().
* @param chunk The chunk to check.
* @return true if the chunk should be rendered, false otherwise.
*/
bool_t sceneOverworldChunkShouldRender(
const sceneoverworld_t *overworld,
const chunk_t *chunk
);
/**
* Draws the base (tile) mesh of every loaded chunk, with normal depth
* testing. Must be called before entities are rendered.
*
* @param overworld The overworld scene data holding the cached culling
* bounds, as refreshed by sceneOverworldCullUpdate().
* @return An error if drawing failed, or errorOk() on success.
*/
errorret_t sceneOverworldDrawChunksBase(const sceneoverworld_t *overworld);
/**
* Draws every loaded chunk's additional meshes (trees, buildings, etc),
* with normal depth testing so they correctly occlude entities standing
* beneath them. Must be called after entities are rendered.
*
* @param overworld The overworld scene data holding the cached culling
* bounds, as refreshed by sceneOverworldCullUpdate().
* @return An error if drawing failed, or errorOk() on success.
*/
errorret_t sceneOverworldDrawChunksProps(const sceneoverworld_t *overworld);
/**
* Roughly checks whether an entity falls within the visible screen
* area. Reuses the same cached chunk culling bounds, since an entity
* is just a point within that same space - deliberately cheap, a
* couple of subtractions and comparisons, since it runs per entity
* every frame and it is not worth spending more math than that to
* avoid drawing one that is slightly off-screen.
*
* @param overworld The overworld scene data holding the cached culling
* bounds, as refreshed by sceneOverworldCullUpdate().
* @param ent The entity to check.
* @return true if the entity should be rendered, false otherwise.
*/
bool_t sceneOverworldEntityShouldRender(
const sceneoverworld_t *overworld,
const entity_t *ent
);
/**
* Draws a single entity as a sprite, colored by its facing direction.
* Skips drawing (without error) if the entity should not render.
*
* @param overworld The overworld scene data holding the cached culling
* bounds, as refreshed by sceneOverworldCullUpdate().
* @param ent The entity to draw.
* @return An error if drawing failed, or errorOk() on success.
*/
errorret_t sceneOverworldDrawEntity(
const sceneoverworld_t *overworld,
entity_t *ent
);
/**
* Renders the overworld scene.
*
* @param sceneData The scene data used for this scene.
* @return An error if the render failed, or errorOk() if it succeeded.
*/
errorret_t sceneOverworldRender(scenedata_t *sceneData);
/**
* Disposes the overworld scene.
*
* @param sceneData The scene data used for this scene.
* @return An error if the dispose failed, or errorOk() if it succeeded.
*/
errorret_t sceneOverworldDispose(scenedata_t *sceneData);
-85
View File
@@ -23,98 +23,13 @@ errorret_t sceneInit(void) {
} }
errorret_t sceneUpdate(void) { errorret_t sceneUpdate(void) {
// Handle scene change.
if(SCENE.next != SCENE_TYPE_NULL) {
if(
SCENE.current != SCENE_TYPE_NULL &&
SCENE_TYPES[SCENE.current].dispose != NULL
) {
errorChain(SCENE_TYPES[SCENE.current].dispose(&SCENE.data));
}
SCENE.current = SCENE.next;
SCENE.next = SCENE_TYPE_NULL;
if(
SCENE.current != SCENE_TYPE_NULL &&
SCENE_TYPES[SCENE.current].init != NULL
) {
errorChain(SCENE_TYPES[SCENE.current].init(&SCENE.data));
}
}
#if DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) errorOk();
#endif
if(
SCENE.current != SCENE_TYPE_NULL &&
SCENE_TYPES[SCENE.current].update != NULL
) {
errorChain(SCENE_TYPES[SCENE.current].update(&SCENE.data));
}
errorOk(); errorOk();
} }
errorret_t sceneRender(void) { errorret_t sceneRender(void) {
// Setup screen matrices for 3D rendering.
glm_mat4_identity(SCENE.screenIdentity);
glm_ortho(
0.0f, (float_t)(SCREEN.width / SCREEN.scaleUi),
(float_t)(SCREEN.height / SCREEN.scaleUi), 0.0f,
0.1f, 100.0f,
SCENE.screenProj
);
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
SCENE.screenView
);
// Scene rendering
if(
SCENE.current != SCENE_TYPE_NULL &&
SCENE_TYPES[SCENE.current].render != NULL
) {
errorChain(SCENE_TYPES[SCENE.current].render(&SCENE.data));
}
// UI Rendering
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(
&SHADER_UNLIT, SHADER_UNLIT_MODEL, SCENE.screenIdentity
));
errorChain(shaderSetMatrix(
&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, SCENE.screenProj
));
errorChain(shaderSetMatrix(
&SHADER_UNLIT, SHADER_UNLIT_VIEW, SCENE.screenView
));
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_BLEND
}));
errorChain(uiRender());
errorOk(); errorOk();
} }
void sceneSet(const scenetype_t type) {
assertTrue(type < SCENE_TYPE_COUNT, "Invalid scene type.");
SCENE.next = type;
}
errorret_t sceneDispose(void) { errorret_t sceneDispose(void) {
if(
SCENE.current != SCENE_TYPE_NULL &&
SCENE_TYPES[SCENE.current].dispose != NULL
) {
errorChain(SCENE_TYPES[SCENE.current].dispose(&SCENE.data));
}
errorOk(); errorOk();
} }
+3 -9
View File
@@ -6,16 +6,10 @@
*/ */
#pragma once #pragma once
#include "dusk.h" #include "error/error.h"
#include "scenetype.h"
typedef struct { typedef struct {
scenetype_t current; void *nothing;
scenetype_t next;
scenedata_t data;
mat4 screenProj;
mat4 screenView;
mat4 screenIdentity;
} scene_t; } scene_t;
extern scene_t SCENE; extern scene_t SCENE;
@@ -47,7 +41,7 @@ errorret_t sceneRender(void);
* *
* @param type The scene type to change to. * @param type The scene type to change to.
*/ */
void sceneSet(const scenetype_t type); // void sceneSet(const scenetype_t type);
/** /**
* Disposes the active scene immediately. * Disposes the active scene immediately.
-11
View File
@@ -1,11 +0,0 @@
/**
* 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 union scenedata_u scenedata_t;
-20
View File
@@ -1,20 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scenetype.h"
scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT] = {
[SCENE_TYPE_NULL] = { 0 },
[SCENE_TYPE_OVERWORLD] = {
.init = sceneOverworldInit,
.update = sceneOverworldUpdate,
.render = sceneOverworldRender,
.dispose = sceneOverworldDispose
},
};
-33
View File
@@ -1,33 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "scene/scenebase.h"
#include "scene/overworld/sceneoverworld.h"
typedef union scenedata_u {
sceneoverworld_t overworld;
} scenedata_t;
typedef errorret_t (*scenecallback_t)(scenedata_t *);
typedef struct {
scenecallback_t init;
scenecallback_t update;
scenecallback_t render;
scenecallback_t dispose;
} scenecallbacks_t;
typedef enum {
SCENE_TYPE_NULL,
SCENE_TYPE_OVERWORLD,
SCENE_TYPE_COUNT
} scenetype_t;
extern scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT];
-1
View File
@@ -7,7 +7,6 @@ add_subdirectory(debug)
add_subdirectory(frame) add_subdirectory(frame)
add_subdirectory(focus) add_subdirectory(focus)
add_subdirectory(overlay) add_subdirectory(overlay)
add_subdirectory(rpg)
add_subdirectory(transition) add_subdirectory(transition)
add_subdirectory(widget) add_subdirectory(widget)
-1
View File
@@ -7,5 +7,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
uiconsole.c uiconsole.c
uifps.c uifps.c
uiplayerpos.c
) )
-63
View File
@@ -1,63 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uiplayerpos.h"
#include "display/screen/screen.h"
#include "display/text/text.h"
#include "rpg/entity/entity.h"
#include "rpg/entity/entitytype.h"
#include "rpg/overworld/worldpos.h"
errorret_t uiPlayerPosDraw() {
entity_t *player = NULL;
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
if(ENTITIES[i].type == ENTITY_TYPE_PLAYER) {
player = &ENTITIES[i];
break;
}
}
if(!player) errorOk();
chunkpos_t chunkPos;
worldPosToChunkPos(&player->position, &chunkPos);
char_t text[64];
snprintf(
text,
sizeof(text),
"%d,%d,%d[%d,%d,%d]",
(int_t)player->position.x,
(int_t)player->position.y,
(int_t)player->position.z,
(int_t)chunkPos.x,
(int_t)chunkPos.y,
(int_t)chunkPos.z
);
float_t y = (float_t)SCREEN.scanY +
(float_t)FONT_DEFAULT.tileset->tileHeight;
errorChain(textDraw(
(float_t)SCREEN.scanX, y, text, COLOR_GREEN, &FONT_DEFAULT
));
char_t velocityText[64];
snprintf(
velocityText,
sizeof(velocityText),
"vel %.2f,%.2f,%.2f",
player->body.velocity[0],
player->body.velocity[1],
player->body.velocity[2]
);
float_t velocityY = y + (float_t)FONT_DEFAULT.tileset->tileHeight;
errorChain(textDraw(
(float_t)SCREEN.scanX, velocityY, velocityText, COLOR_GREEN, &FONT_DEFAULT
));
errorOk();
}
-18
View File
@@ -1,18 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
/**
* Draws the player world and chunk position on screen.
* Searches for the first ENTITY_TYPE_PLAYER and renders:
* WORLDX,WORLDY,WORLDZ[chunkx,chunky,chunkz]
*
* @return Any error that occurs.
*/
errorret_t uiPlayerPosDraw();
+1 -3
View File
@@ -9,6 +9,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
uiconfirm.c uiconfirm.c
) )
add_subdirectory(game) add_subdirectory(settings)
add_subdirectory(settings)
add_subdirectory(backpack)
@@ -1,9 +0,0 @@
# 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
uibackpack.c
)
-114
View File
@@ -1,114 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uibackpack.h"
#include "ui/frame/uiframe.h"
#include "rpg/item/backpack.h"
#include "util/memory.h"
#include "util/string.h"
#include "display/screen/screen.h"
#include "display/text/text.h"
#include "assert/assert.h"
uibackpack_t UI_BACKPACK;
void uiBackpackTabChanged(
const uimenu_t *menu,
const uint8_t index,
const uimenuitem_t *item
) {
const itemtype_t type = (itemtype_t)(index + 1);
const inventory_t *inventory = backpackGetInventory(type);
errorCatch(uiItemListSetItemStacks(
&UI_BACKPACK.itemList, inventory->storage, inventory->storageSize
));
}
void uiBackpackTabSelected(
const uimenu_t *menu,
const uint8_t index,
const uimenuitem_t *item
) {
uiItemListOpen(&UI_BACKPACK.itemList);
}
errorret_t uiBackpackInit(void) {
memoryZero(&UI_BACKPACK, sizeof(uibackpack_t));
MENU_BEGIN(
&UI_BACKPACK.tabsMenu, UI_BACKPACK.tabs,
uiBackpackTabSelected, NULL, uiBackpackTabChanged
);
for(uint8_t i = 0; i < UI_BACKPACK_TAB_COUNT; i++) {
stringFormat(
UI_BACKPACK.tabLabels[i], UI_BACKPACK_TAB_LABEL_MAX - 1,
"Category %u", i + 1
);
MENU_TAB(UI_BACKPACK.tabLabels[i]);
}
MENU_END(UI_BACKPACK.tabs, menuIndex);
uiItemListInit(
&UI_BACKPACK.itemList,
UI_BACKPACK_ITEM_LIST_COLUMNS, UI_BACKPACK_ITEM_LIST_ROWS, 1,
NULL, NULL, NULL, NULL
);
errorOk();
}
errorret_t uiBackpackDraw(void) {
if(!uiMenuIsActive(&UI_BACKPACK.tabsMenu)) errorOk();
const float_t width = SCREEN.width;
const float_t height = SCREEN.height;
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_BACKPACK.tabsMenu,
contentX,
contentY,
contentWidth,
contentHeight
));
const float_t tabsRowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
const float_t listY = contentY + tabsRowHeight + UI_FRAME_PADDING_Y;
errorChain(
uiItemListDraw(&UI_BACKPACK.itemList, contentX, listY, contentWidth)
);
errorOk();
}
bool_t uiBackpackIsOpen(void) {
return uiMenuIsActive(&UI_BACKPACK.tabsMenu);
}
void uiBackpackOpen(void) {
uiMenuOpen(&UI_BACKPACK.tabsMenu);
}
void uiBackpackClose(void) {
uiMenuClose(&UI_BACKPACK.tabsMenu);
}
errorret_t uiBackpackDispose(void) {
errorOk();
}
-65
View File
@@ -1,65 +0,0 @@
/**
* 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"
#include "ui/widget/uiitemlist.h"
#include "rpg/item/item.h"
#define UI_BACKPACK_TAB_COUNT (ITEM_TYPE_COUNT - 1)
#define UI_BACKPACK_TAB_LABEL_MAX 32
#define UI_BACKPACK_ITEM_LIST_COLUMNS 4
#define UI_BACKPACK_ITEM_LIST_ROWS 5
typedef struct {
uimenu_t tabsMenu;
uimenuitem_t tabs[UI_BACKPACK_TAB_COUNT];
char_t tabLabels[UI_BACKPACK_TAB_COUNT][UI_BACKPACK_TAB_LABEL_MAX];
uiitemlist_t itemList;
} uibackpack_t;
extern uibackpack_t UI_BACKPACK;
/**
* Initializes the backpack panel and its item type tabs.
*
* @return Any error that occurs.
*/
errorret_t uiBackpackInit(void);
/**
* Draws the backpack panel. No-op when not visible.
*
* @return Any error that occurs.
*/
errorret_t uiBackpackDraw(void);
/**
* Returns true when the backpack panel is currently open.
*
* @returns True if open.
*/
bool_t uiBackpackIsOpen(void);
/**
* Opens the backpack panel. No-op when already open.
*/
void uiBackpackOpen(void);
/**
* Closes the backpack panel. No-op when already closed.
*/
void uiBackpackClose(void);
/**
* Disposes of the backpack panel.
*
* @return Any error that occurs.
*/
errorret_t uiBackpackDispose(void);
-9
View File
@@ -1,9 +0,0 @@
# 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
)
-104
View File
@@ -1,104 +0,0 @@
/**
* 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 "ui/frame/backpack/uibackpack.h"
#include "util/memory.h"
#include "display/screen/screen.h"
#include "assert/assert.h"
#include "locale/localemanager.h"
#include "asset/loader/locale/assetlocaleloader.h"
#define UI_GAME_MENU_INDEX_CHARACTERS 0
#define UI_GAME_MENU_INDEX_ITEMS 1
#define UI_GAME_MENU_INDEX_SETTINGS 2
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_ITEMS) uiBackpackOpen();
if(index == UI_GAME_MENU_INDEX_SETTINGS) uiSettingsOpen();
}
errorret_t uiGameMenuInit(void) {
memoryZero(&UI_GAME_MENU, sizeof(uigamemenu_t));
errorChain(assetLocaleGetString(
&LOCALE.entry->data.locale,
"ui.game_menu.characters",
0,
UI_GAME_MENU.charactersLabel,
UI_GAME_MENU_LABEL_MAX
));
errorChain(assetLocaleGetString(
&LOCALE.entry->data.locale,
"ui.game_menu.items",
0,
UI_GAME_MENU.itemsLabel,
UI_GAME_MENU_LABEL_MAX
));
errorChain(assetLocaleGetString(
&LOCALE.entry->data.locale,
"ui.game_menu.settings",
0,
UI_GAME_MENU.settingsLabel,
UI_GAME_MENU_LABEL_MAX
));
MENU_BEGIN(
&UI_GAME_MENU.menu, UI_GAME_MENU.items, uiGameMenuSelected, NULL, NULL
);
MENU_BUTTON(UI_GAME_MENU.charactersLabel);
MENU_BUTTON(UI_GAME_MENU.itemsLabel);
MENU_BUTTON(UI_GAME_MENU.settingsLabel);
MENU_END(UI_GAME_MENU.items, 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)
));
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();
}
-62
View File
@@ -1,62 +0,0 @@
/**
* 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 3
#define UI_GAME_MENU_WIDTH 150.0f
#define UI_GAME_MENU_LABEL_MAX 32
typedef struct {
uimenu_t menu;
uimenuitem_t items[UI_GAME_MENU_ITEM_COUNT];
char_t charactersLabel[UI_GAME_MENU_LABEL_MAX];
char_t itemsLabel[UI_GAME_MENU_LABEL_MAX];
char_t settingsLabel[UI_GAME_MENU_LABEL_MAX];
} 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);
-12
View File
@@ -1,12 +0,0 @@
# 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
uiemoji.c
)
add_subdirectory(textbox)
-12
View File
@@ -1,12 +0,0 @@
# 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
uitextbox.c
uitextboxmain.c
uitextboxmini.c
uitextboxminilist.c
)
-242
View File
@@ -1,242 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uitextbox.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "time/time.h"
#include "display/text/text.h"
#include "display/color.h"
#include "display/spritebatch/spritebatch.h"
#include "display/shader/shaderunlit.h"
#include "ui/frame/uiframe.h"
void uiTextboxInit(
uitextbox_t *box,
char_t *text,
const uint32_t maxLength,
uitextboxline_t *lines,
const uint32_t linesMax
) {
assertNotNull(box, "Textbox cannot be NULL");
assertNotNull(text, "Text buffer cannot be NULL");
assertTrue(maxLength >= 1, "maxLength must be at least 1");
assertNotNull(lines, "Lines buffer cannot be NULL");
assertTrue(linesMax >= 1, "linesMax must be at least 1");
memoryZero(box, sizeof(uitextbox_t));
box->text = text;
box->maxLength = maxLength;
box->lines = lines;
box->linesMax = linesMax;
}
void uiTextboxSetText(uitextbox_t *box, const char_t *text) {
assertNotNull(box, "Textbox cannot be NULL");
assertNotNull(text, "Text cannot be NULL");
stringCopy(box->text, text, box->maxLength);
box->currentPage = 0;
box->scroll = 0;
box->layoutWidth = 0.0f;
box->layoutHeight = 0.0f;
}
void uiTextboxBuildLayout(
uitextbox_t *box,
const float_t width,
const float_t height
) {
assertNotNull(box, "Textbox cannot be NULL");
box->layoutWidth = width;
box->layoutHeight = height;
box->lineCount = 0;
box->pageCount = 1;
float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth;
float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
if(fontW <= 0.0f || fontH <= 0.0f) return;
box->charsPerLine = (int32_t)(width / fontW);
box->linesPerPage = (int32_t)(height / (fontH + UI_TEXTBOX_LINE_SPACING));
if(box->linesPerPage > UI_TEXTBOX_LINES_PER_PAGE_MAX) {
box->linesPerPage = UI_TEXTBOX_LINES_PER_PAGE_MAX;
}
if(box->charsPerLine <= 0 || box->linesPerPage <= 0) return;
if(box->text[0] == '\0') return;
char_t *src = box->text;
int32_t i = 0;
while(src[i] != '\0' && box->lineCount < (int32_t)box->linesMax) {
if(src[i] == '\t') {
i++;
int32_t rem = box->lineCount % box->linesPerPage;
int32_t pad = rem > 0 ? box->linesPerPage - rem : 0;
while(pad > 0 && box->lineCount < (int32_t)box->linesMax) {
box->lines[box->lineCount].start = i;
box->lines[box->lineCount].count = 0;
box->lineCount++;
pad--;
}
continue;
}
int32_t lineStart = i;
int32_t lineWidth = 0;
while(src[i] != '\0') {
char_t c = src[i];
if(c == '\n') { i++; break; }
if(c == '\t') break;
if(c == ' ') {
int32_t wordLen = 0;
int32_t j = i + 1;
while(
src[j] != ' ' && src[j] != '\n' &&
src[j] != '\t' && src[j] != '\0'
) {
wordLen++;
j++;
}
if(lineWidth > 0 && lineWidth + 1 + wordLen > box->charsPerLine) {
i++;
break;
}
lineWidth++;
i++;
} else {
if(lineWidth >= box->charsPerLine) break;
lineWidth++;
i++;
}
}
box->lines[box->lineCount].start = lineStart;
box->lines[box->lineCount].count = lineWidth;
box->lineCount++;
}
if(box->lineCount == 0) {
box->pageCount = 1;
} else {
box->pageCount =
(box->lineCount + box->linesPerPage - 1) / box->linesPerPage;
}
}
errorret_t uiTextboxUpdate(uitextbox_t *box) {
assertNotNull(box, "Textbox cannot be NULL");
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) errorOk();
#endif
if(!uiTextboxPageIsComplete(box)) {
box->scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK;
}
errorOk();
}
errorret_t uiTextboxDraw(
uitextbox_t *box,
const float_t x,
const float_t y,
const float_t width,
const float_t height
) {
assertNotNull(box, "Textbox cannot be NULL");
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);
}
errorChain(uiFrameDraw(x, y, width, height));
if(box->lineCount == 0 || box->text[0] == '\0') errorOk();
float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth;
float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
shadermaterial_t material = {
.unlit = {
.color = COLOR_WHITE,
.texture = FONT_DEFAULT.texture
}
};
int32_t pageFirst = box->currentPage * box->linesPerPage;
int32_t pageLast = pageFirst + box->linesPerPage;
if(pageLast > box->lineCount) pageLast = box->lineCount;
int32_t charsLeft = box->scroll;
for(int32_t li = pageFirst; li < pageLast && charsLeft > 0; li++) {
uitextboxline_t *line = &box->lines[li];
int32_t visible = line->count < charsLeft ? line->count : charsLeft;
float_t lineY = contentY +
(float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING);
for(int32_t ci = 0; ci < visible; ci++) {
char_t c = box->text[line->start + ci];
if(c == ' ') continue;
spritebatchsprite_t sprite = textGetSprite(
(vec2){ contentX + (float_t)ci * fontW, lineY },
c,
&FONT_DEFAULT
);
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
}
charsLeft -= visible;
}
errorOk();
}
int32_t uiTextboxGetPageCharCount(const uitextbox_t *box) {
assertNotNull(box, "Textbox cannot be NULL");
int32_t first = box->currentPage * box->linesPerPage;
int32_t last = first + box->linesPerPage;
if(last > box->lineCount) last = box->lineCount;
int32_t total = 0;
for(int32_t i = first; i < last; i++) {
total += box->lines[i].count;
}
return total;
}
bool_t uiTextboxPageIsComplete(const uitextbox_t *box) {
assertNotNull(box, "Textbox cannot be NULL");
return box->scroll >= uiTextboxGetPageCharCount(box);
}
bool_t uiTextboxHasNextPage(const uitextbox_t *box) {
assertNotNull(box, "Textbox cannot be NULL");
return box->currentPage + 1 < box->pageCount;
}
void uiTextboxNextPage(uitextbox_t *box) {
assertNotNull(box, "Textbox cannot be NULL");
if(!uiTextboxHasNextPage(box)) return;
box->currentPage++;
box->scroll = 0;
}
-138
View File
@@ -1,138 +0,0 @@
/**
* 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_TEXTBOX_LINES_PER_PAGE_MAX 4
#define UI_TEXTBOX_SCROLL_CHARS_PER_TICK 1
#define UI_TEXTBOX_LINE_SPACING 0.0f
typedef struct {
int32_t start;
int32_t count;
} uitextboxline_t;
typedef struct {
char_t *text;
uint32_t maxLength;
uitextboxline_t *lines;
uint32_t linesMax;
int32_t lineCount;
int32_t charsPerLine;
int32_t linesPerPage;
int32_t pageCount;
// last dimensions used for layout; rebuild triggers when these change
float_t layoutWidth;
float_t layoutHeight;
int32_t currentPage;
int32_t scroll;
} uitextbox_t;
/**
* Initializes a textbox, zeroing all state and binding it to caller-owned
* text and line storage.
*
* @param box The textbox to initialize.
* @param text Caller-owned buffer the textbox copies its text into.
* @param maxLength Capacity of text, in characters.
* @param lines Caller-owned buffer the textbox lays lines out into.
* @param linesMax Capacity of lines, in entries.
*/
void uiTextboxInit(
uitextbox_t *box,
char_t *text,
const uint32_t maxLength,
uitextboxline_t *lines,
const uint32_t linesMax
);
/**
* Copies text into the textbox and marks layout as dirty.
* Resets currentPage and scroll to 0.
*
* @param box The textbox to update.
* @param text Null-terminated source string.
*/
void uiTextboxSetText(uitextbox_t *box, const char_t *text);
/**
* Rebuilds word-wrap and page layout for the given draw dimensions.
* Called automatically by uiTextboxDraw when width or height changes.
*
* @param box The textbox to rebuild.
* @param width Available content width in pixels.
* @param height Available content height in pixels.
*/
void uiTextboxBuildLayout(
uitextbox_t *box,
const float_t width,
const float_t height
);
/**
* Advances the typewriter scroll by UI_TEXTBOX_SCROLL_CHARS_PER_TICK.
* Skipped on dynamic ticks.
*
* @param box The textbox to update.
* @returns Any error that occurs.
*/
errorret_t uiTextboxUpdate(uitextbox_t *box);
/**
* Draws the textbox frame and visible text. Rebuilds layout automatically
* if width or height differs from the last draw call.
*
* @param box The textbox to draw.
* @param x Screen x position.
* @param y Screen y position.
* @param width Draw width in pixels.
* @param height Draw height in pixels.
* @returns Any error that occurs.
*/
errorret_t uiTextboxDraw(
uitextbox_t *box,
const float_t x,
const float_t y,
const float_t width,
const float_t height
);
/**
* Returns the total visible char count for the current page.
*
* @param box The textbox to query.
* @returns Total chars on the current page.
*/
int32_t uiTextboxGetPageCharCount(const uitextbox_t *box);
/**
* Returns true when scroll has fully revealed the current page.
*
* @param box The textbox to query.
* @returns True if the current page is fully visible.
*/
bool_t uiTextboxPageIsComplete(const uitextbox_t *box);
/**
* Returns true when there is at least one more page after the current one.
*
* @param box The textbox to query.
* @returns True if a next page exists.
*/
bool_t uiTextboxHasNextPage(const uitextbox_t *box);
/**
* Advances to the next page and resets scroll to 0.
* Has no effect if already on the last page.
*
* @param box The textbox to advance.
*/
void uiTextboxNextPage(uitextbox_t *box);
-121
View File
@@ -1,121 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uitextboxmain.h"
#include "ui/focus/uifocus.h"
#include "display/screen/screen.h"
#include "display/text/text.h"
#include "display/color.h"
#include "display/spritebatch/spritebatch.h"
#include "display/shader/shaderunlit.h"
#include "ui/frame/uiframe.h"
uitextboxmain_t UI_TEXTBOX_MAIN;
static uifocusitem_t *focusItem = NULL;
errorret_t uiTextboxMainInit(void) {
uiTextboxInit(
&UI_TEXTBOX_MAIN.box,
UI_TEXTBOX_MAIN.text, UI_TEXTBOX_MAIN_TEXT_MAX,
UI_TEXTBOX_MAIN.lines, UI_TEXTBOX_MAIN_LINES_MAX
);
errorOk();
}
void uiTextboxMainSetText(const char_t *text) {
uiTextboxSetText(&UI_TEXTBOX_MAIN.box, text);
if(focusItem != NULL) return;
focusItem = uiFocusPush(
1, 1,
uiTextboxMainFocusSelected,
NULL,
uiTextboxMainFocusClosed,
NULL,
NULL
);
}
errorret_t uiTextboxMainUpdate(void) {
if(focusItem == NULL) errorOk();
return uiTextboxUpdate(&UI_TEXTBOX_MAIN.box);
}
errorret_t uiTextboxMainDraw(void) {
if(focusItem == NULL) errorOk();
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_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;
errorChain(uiTextboxDraw(&UI_TEXTBOX_MAIN.box, x, y, w, h));
if(!uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box)) errorOk();
float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth;
float_t contentX = x + (float_t)UI_FRAME_START_X;
float_t contentY = y + (float_t)UI_FRAME_START_Y;
float_t contentW = w - 2.0f * (float_t)UI_FRAME_START_X;
float_t contentH = h - 2.0f * (float_t)UI_FRAME_START_Y;
shadermaterial_t material = {
.unlit = {
.color = COLOR_WHITE,
.texture = FONT_DEFAULT.texture
}
};
spritebatchsprite_t caret = textGetSprite(
(vec2){
contentX + contentW - fontW,
contentY + contentH - fontH
},
'v',
&FONT_DEFAULT
);
errorChain(spriteBatchBuffer(&caret, 1, &SHADER_UNLIT, material));
errorOk();
}
bool_t uiTextboxMainPageIsComplete(void) {
return uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box);
}
bool_t uiTextboxMainHasNextPage(void) {
return uiTextboxHasNextPage(&UI_TEXTBOX_MAIN.box);
}
void uiTextboxMainNextPage(void) {
uiTextboxNextPage(&UI_TEXTBOX_MAIN.box);
}
bool_t uiTextboxMainIsActive(void) {
return focusItem != NULL;
}
bool_t uiTextboxMainFocusSelected(const uifocusitem_t *item) {
if(!uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box)) {
UI_TEXTBOX_MAIN.box.scroll =
uiTextboxGetPageCharCount(&UI_TEXTBOX_MAIN.box);
return true;
}
if(uiTextboxHasNextPage(&UI_TEXTBOX_MAIN.box)) {
uiTextboxNextPage(&UI_TEXTBOX_MAIN.box);
return true;
}
uiFocusPopItem(focusItem);
return true;
}
bool_t uiTextboxMainFocusClosed(const uifocusitem_t *item) {
focusItem = NULL;
return true;
}
-94
View File
@@ -1,94 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "ui/rpg/textbox/uitextbox.h"
#include "ui/focus/uifocusitem.h"
#define UI_TEXTBOX_MAIN_LINES 4
#define UI_TEXTBOX_MAIN_TEXT_MAX 1024
#define UI_TEXTBOX_MAIN_LINES_MAX 64
typedef struct {
uitextbox_t box;
char_t text[UI_TEXTBOX_MAIN_TEXT_MAX];
uitextboxline_t lines[UI_TEXTBOX_MAIN_LINES_MAX];
} uitextboxmain_t;
extern uitextboxmain_t UI_TEXTBOX_MAIN;
/**
* Initializes UI_TEXTBOX_MAIN.
*
* @returns Any error that occurs.
*/
errorret_t uiTextboxMainInit(void);
/**
* Copies text into UI_TEXTBOX_MAIN and resets page and scroll.
*
* @param text Null-terminated source string.
*/
void uiTextboxMainSetText(const char_t *text);
/**
* Advances the typewriter scroll for UI_TEXTBOX_MAIN.
*
* @returns Any error that occurs.
*/
errorret_t uiTextboxMainUpdate(void);
/**
* Draws UI_TEXTBOX_MAIN full-width at the bottom of the screen.
* Position and size are derived from SCREEN each call.
*
* @returns Any error that occurs.
*/
errorret_t uiTextboxMainDraw(void);
/**
* Returns true when the current page is fully scrolled in.
*
* @returns True if the current page is complete.
*/
bool_t uiTextboxMainPageIsComplete(void);
/**
* Returns true when at least one more page follows the current one.
*
* @returns True if a next page exists.
*/
bool_t uiTextboxMainHasNextPage(void);
/**
* Advances UI_TEXTBOX_MAIN to the next page and resets scroll.
* Has no effect if already on the last page.
*/
void uiTextboxMainNextPage(void);
/**
* Returns true when UI_TEXTBOX_MAIN has focus (is visible and active).
*
* @returns True if the textbox is currently active.
*/
bool_t uiTextboxMainIsActive(void);
/**
* Internal focus callback - skip scroll or advance page or dismiss.
*
* @param item The active focus item.
* @returns True.
*/
bool_t uiTextboxMainFocusSelected(const uifocusitem_t *item);
/**
* Internal focus callback - clears the focus item pointer on dismiss.
*
* @param item The focus item being closed.
* @returns True.
*/
bool_t uiTextboxMainFocusClosed(const uifocusitem_t *item);
-122
View File
@@ -1,122 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uitextboxmini.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "time/time.h"
#include "rpg/rpgcamera.h"
#include "display/text/text.h"
#include "display/screen/screen.h"
#include "ui/frame/uiframe.h"
void uiTextboxMiniInit(uitextboxmini_t *mini) {
assertNotNull(mini, "Mini textbox cannot be NULL");
memoryZero(mini, sizeof(uitextboxmini_t));
uiTextboxInit(
&mini->box,
mini->text, UI_TEXTBOX_MINI_TEXT_MAX,
mini->lines, UI_TEXTBOX_MINI_LINES_MAX
);
}
void uiTextboxMiniShow(
uitextboxmini_t *mini,
const char_t *text,
vec3 position,
const float_t duration,
uitextboxminiclosedcallback_t closed,
void *user
) {
assertNotNull(mini, "Mini textbox cannot be NULL");
assertNotNull(text, "Text cannot be NULL");
uiTextboxSetText(&mini->box, text);
glm_vec3_copy(position, mini->position);
int32_t textWidth, textHeight;
textMeasure(text, &FONT_DEFAULT, &textWidth, &textHeight);
float_t width = (float_t)textWidth + 2.0f * (float_t)UI_FRAME_START_X;
mini->width = width < UI_TEXTBOX_MINI_WIDTH_MAX
? width : UI_TEXTBOX_MINI_WIDTH_MAX;
float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
float_t maxHeight = (float_t)UI_TEXTBOX_MINI_LINES_MAX * fontH +
(float_t)(UI_TEXTBOX_MINI_LINES_MAX - 1) * UI_TEXTBOX_LINE_SPACING +
2.0f * (float_t)UI_FRAME_START_Y;
uiTextboxBuildLayout(
&mini->box,
mini->width - 2.0f * (float_t)UI_FRAME_START_X,
maxHeight - 2.0f * (float_t)UI_FRAME_START_Y
);
int32_t lineCount = mini->box.lineCount > 0 ? mini->box.lineCount : 1;
mini->height = (float_t)lineCount * fontH +
(float_t)(lineCount - 1) * UI_TEXTBOX_LINE_SPACING +
2.0f * (float_t)UI_FRAME_START_Y;
mini->active = true;
mini->timer = duration;
mini->closed = closed;
mini->user = user;
}
errorret_t uiTextboxMiniUpdate(uitextboxmini_t *mini) {
assertNotNull(mini, "Mini textbox cannot be NULL");
if(!mini->active) errorOk();
errorChain(uiTextboxUpdate(&mini->box));
if(mini->timer != UI_TEXTBOX_MINI_DURATION_INFINITE) {
mini->timer -= TIME.delta;
if(mini->timer <= 0.0f) uiTextboxMiniClose(mini);
}
errorOk();
}
errorret_t uiTextboxMiniDraw(uitextboxmini_t *mini) {
assertNotNull(mini, "Mini textbox cannot be NULL");
if(!mini->active) errorOk();
vec2 screenPos;
rpgCameraToScreen(mini->position, screenPos);
if(
mini->clamp == UI_TEXTBOX_MINI_CLAMP_X ||
mini->clamp == UI_TEXTBOX_MINI_CLAMP_BOTH
) {
float_t minX = (float_t)SCREEN.scanX;
float_t maxX = (float_t)(SCREEN.scanX + SCREEN.scanWidth) - mini->width;
if(screenPos[0] < minX) screenPos[0] = minX;
if(screenPos[0] > maxX) screenPos[0] = maxX;
}
if(
mini->clamp == UI_TEXTBOX_MINI_CLAMP_Y ||
mini->clamp == UI_TEXTBOX_MINI_CLAMP_BOTH
) {
float_t minY = (float_t)SCREEN.scanY;
float_t maxY = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - mini->height;
if(screenPos[1] < minY) screenPos[1] = minY;
if(screenPos[1] > maxY) screenPos[1] = maxY;
}
return uiTextboxDraw(
&mini->box, screenPos[0], screenPos[1], mini->width, mini->height
);
}
bool_t uiTextboxMiniIsActive(const uitextboxmini_t *mini) {
assertNotNull(mini, "Mini textbox cannot be NULL");
return mini->active;
}
void uiTextboxMiniClose(uitextboxmini_t *mini) {
assertNotNull(mini, "Mini textbox cannot be NULL");
if(!mini->active) return;
mini->active = false;
if(mini->closed != NULL) mini->closed(mini);
}
-116
View File
@@ -1,116 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "ui/rpg/textbox/uitextbox.h"
#define UI_TEXTBOX_MINI_TEXT_MAX 128
#define UI_TEXTBOX_MINI_LINES_MAX 2
#define UI_TEXTBOX_MINI_WIDTH_MAX 160.0f
// Magic duration value that keeps a mini textbox visible indefinitely
// instead of counting down and auto-closing.
#define UI_TEXTBOX_MINI_DURATION_INFINITE -1.0f
typedef struct uitextboxmini_s uitextboxmini_t;
typedef void (*uitextboxminiclosedcallback_t)(const uitextboxmini_t *mini);
/**
* Determines whether a mini textbox's screen position is clamped to stay
* fully on screen. Defaults to UI_TEXTBOX_MINI_CLAMP_NONE.
*/
typedef enum {
UI_TEXTBOX_MINI_CLAMP_NONE,
UI_TEXTBOX_MINI_CLAMP_X,
UI_TEXTBOX_MINI_CLAMP_Y,
UI_TEXTBOX_MINI_CLAMP_BOTH
} uitextboxminiclamp_t;
typedef struct uitextboxmini_s {
uitextbox_t box;
char_t text[UI_TEXTBOX_MINI_TEXT_MAX];
uitextboxline_t lines[UI_TEXTBOX_MINI_LINES_MAX];
vec3 position;
float_t width;
float_t height;
uitextboxminiclamp_t clamp;
bool_t active;
float_t timer;
uitextboxminiclosedcallback_t closed;
void *user;
} uitextboxmini_t;
/**
* Initializes a mini textbox, zeroing all state and binding its internal
* uitextbox_t to its own fixed-size text and line storage.
*
* @param mini The mini textbox to initialize.
*/
void uiTextboxMiniInit(uitextboxmini_t *mini);
/**
* Shows a mini textbox with the given text at the given world position for
* the given duration. Resets the typewriter scroll, measures the text to
* size the box (width capped at UI_TEXTBOX_MINI_WIDTH_MAX, height derived
* from the resulting wrapped line count), and starts the visibility timer.
*
* @param mini The mini textbox to show.
* @param text Null-terminated source string.
* @param position World-space position the box is anchored to on screen.
* @param duration How long the mini textbox stays visible, in seconds, or
* UI_TEXTBOX_MINI_DURATION_INFINITE to never auto-close.
* @param closed Called once the timer elapses and the box closes. May be
* NULL.
* @param user Opaque pointer passed back through the closed callback.
*/
void uiTextboxMiniShow(
uitextboxmini_t *mini,
const char_t *text,
vec3 position,
const float_t duration,
uitextboxminiclosedcallback_t closed,
void *user
);
/**
* Advances the typewriter scroll and counts down the visibility timer.
* Closes the mini textbox and fires its closed callback once the timer
* elapses. Has no effect if not active or if the duration was set to
* UI_TEXTBOX_MINI_DURATION_INFINITE.
*
* @param mini The mini textbox to update.
* @returns Any error that occurs.
*/
errorret_t uiTextboxMiniUpdate(uitextboxmini_t *mini);
/**
* Draws the mini textbox at its text-measured size, anchored on screen to
* its world-space position via the RPG camera. Has no effect if not
* active.
*
* @param mini The mini textbox to draw.
* @returns Any error that occurs.
*/
errorret_t uiTextboxMiniDraw(uitextboxmini_t *mini);
/**
* Returns true when the mini textbox is currently visible.
*
* @param mini The mini textbox to query.
* @returns True if active.
*/
bool_t uiTextboxMiniIsActive(const uitextboxmini_t *mini);
/**
* Immediately closes the mini textbox and fires its closed callback.
* Has no effect if not active.
*
* @param mini The mini textbox to close.
*/
void uiTextboxMiniClose(uitextboxmini_t *mini);
@@ -1,40 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uitextboxminilist.h"
uitextboxmini_t UI_TEXTBOX_MINI_LIST[UI_TEXTBOX_MINI_LIST_COUNT];
uint8_t UI_TEXTBOX_MINI_LIST_NEXT;
errorret_t uiTextboxMiniListInit(void) {
for(uint8_t i = 0; i < UI_TEXTBOX_MINI_LIST_COUNT; i++) {
uiTextboxMiniInit(&UI_TEXTBOX_MINI_LIST[i]);
}
UI_TEXTBOX_MINI_LIST_NEXT = 0;
errorOk();
}
uint8_t uiTextboxMiniListGetNext(void) {
uint8_t index = UI_TEXTBOX_MINI_LIST_NEXT;
UI_TEXTBOX_MINI_LIST_NEXT =
(UI_TEXTBOX_MINI_LIST_NEXT + 1) % UI_TEXTBOX_MINI_LIST_COUNT;
return index;
}
errorret_t uiTextboxMiniListUpdate(void) {
for(uint8_t i = 0; i < UI_TEXTBOX_MINI_LIST_COUNT; i++) {
errorChain(uiTextboxMiniUpdate(&UI_TEXTBOX_MINI_LIST[i]));
}
errorOk();
}
errorret_t uiTextboxMiniListDraw(void) {
for(uint8_t i = 0; i < UI_TEXTBOX_MINI_LIST_COUNT; i++) {
errorChain(uiTextboxMiniDraw(&UI_TEXTBOX_MINI_LIST[i]));
}
errorOk();
}
@@ -1,44 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "ui/rpg/textbox/uitextboxmini.h"
#define UI_TEXTBOX_MINI_LIST_COUNT 4
extern uitextboxmini_t UI_TEXTBOX_MINI_LIST[UI_TEXTBOX_MINI_LIST_COUNT];
extern uint8_t UI_TEXTBOX_MINI_LIST_NEXT;
/**
* Initializes all UI_TEXTBOX_MINI_LIST slots and resets
* UI_TEXTBOX_MINI_LIST_NEXT.
*
* @returns Any error that occurs.
*/
errorret_t uiTextboxMiniListInit(void);
/**
* Returns the index of the next UI_TEXTBOX_MINI_LIST slot to use, cycling
* through all slots in round-robin order via UI_TEXTBOX_MINI_LIST_NEXT.
*
* @returns The next slot index.
*/
uint8_t uiTextboxMiniListGetNext(void);
/**
* Updates all UI_TEXTBOX_MINI_LIST slots.
*
* @returns Any error that occurs.
*/
errorret_t uiTextboxMiniListUpdate(void);
/**
* Draws all active UI_TEXTBOX_MINI_LIST slots.
*
* @returns Any error that occurs.
*/
errorret_t uiTextboxMiniListDraw(void);
-118
View File
@@ -1,118 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uiemoji.h"
#include "display/spritebatch/spritebatch.h"
#include "time/time.h"
#include "util/memory.h"
#include "display/shader/shaderunlit.h"
#include "rpg/entity/entity.h"
#include "rpg/rpgcamera.h"
uiemoji_t UI_EMOJI;
errorret_t uiEmojiInit(void) {
memoryZero(&UI_EMOJI, sizeof(UI_EMOJI));
errorOk();
}
void uiEmojiAdd(
const uint8_t entity,
const float_t duration,
const uiemojitype_t type
) {
uint8_t slot = 0xFF;
// Find an available slot if any
for(uint8_t i = 0; i < UI_EMOJI_ACTIVE_COUNT; i++) {
if(UI_EMOJI.items[i].time <= 0.0f) {
slot = i;
break;
}
if(UI_EMOJI.items[i].entity == entity) {
slot = i;
break;
}
if(UI_EMOJI.items[i].type == UI_EMOJI_NULL) {
slot = i;
break;
}
}
if(slot == 0xFF) {
// Whichever ends soonest
float_t minTime = UI_EMOJI.items[0].time;
slot = 0;
for(uint8_t i = 1; i < UI_EMOJI_ACTIVE_COUNT; i++) {
if(UI_EMOJI.items[i].time >= minTime) continue;
minTime = UI_EMOJI.items[i].time;
slot = i;
}
}
uiemojiitem_t *item = &UI_EMOJI.items[slot];
item->entity = entity;
item->time = duration;
item->type = type;
}
errorret_t uiEmojiUpdate(void) {
for(uint8_t i = 0; i < UI_EMOJI_ACTIVE_COUNT; i++) {
uiemojiitem_t *item = &UI_EMOJI.items[i];
if(item->type == UI_EMOJI_NULL) continue;
item->time -= TIME.delta;
if(item->time <= 0.0f) {
item->type = UI_EMOJI_NULL;
item->entity = 0xFF;
}
}
errorOk();
}
errorret_t uiEmojiDraw(void) {
spritebatchsprite_t sprites[UI_EMOJI_ACTIVE_COUNT];
uint8_t count = 0;
for(uint8_t i = 0; i < UI_EMOJI_ACTIVE_COUNT; i++) {
uiemojiitem_t *item = &UI_EMOJI.items[i];
if(item->type == UI_EMOJI_NULL) continue;
vec2 screenPos;
rpgCameraToScreen(ENTITIES[item->entity].renderPosition, screenPos);
spritebatchsprite_t *sprite = &sprites[count++];
sprite->min[0] = screenPos[0];
sprite->min[1] = screenPos[1] - UI_EMOJI_SIZE;
sprite->min[2] = 0.0f;
sprite->max[0] = screenPos[0] + UI_EMOJI_SIZE;
sprite->max[1] = screenPos[1];
sprite->max[2] = 0.0f;
sprite->uvMin[0] = 0.0f;
sprite->uvMin[1] = 0.0f;
sprite->uvMax[0] = 1.0f;
sprite->uvMax[1] = 1.0f;
}
if(count == 0) errorOk();
shadermaterial_t mat = {
.unlit = {
.color = COLOR_WHITE,
.texture = NULL
}
};
errorChain(spriteBatchBuffer(sprites, count, &SHADER_UNLIT, mat));
errorOk();
}
errorret_t uiEmojiDispose(void) {
errorOk();
}
-66
View File
@@ -1,66 +0,0 @@
/**
* 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_EMOJI_ACTIVE_COUNT 4
#define UI_EMOJI_SIZE 32.0f
typedef enum {
UI_EMOJI_NULL,
UI_EMOJI_QUESTION_MARK,
UI_EMOJI_EXCLAMATION_MARK,
UI_EMOJI_COUNT,
} uiemojitype_t;
typedef struct {
uint8_t entity;
float_t time;
uiemojitype_t type;
} uiemojiitem_t;
typedef struct {
uiemojiitem_t items[UI_EMOJI_ACTIVE_COUNT];
} uiemoji_t;
extern uiemoji_t UI_EMOJI;
/**
* Initialize the UI emoji.
*/
errorret_t uiEmojiInit(void);
/**
* Show a UI emoji for a specific entity.
*
* @param entity The entity to show the emoji for.
* @param duration The duration to display the emoji.
* @param type The type of emoji to display.
*/
void uiEmojiAdd(
const uint8_t entity,
const float_t duration,
const uiemojitype_t type
);
/**
* Update the UI emoji.
*/
errorret_t uiEmojiUpdate(void);
/**
* Draw the UI emoji.
*/
errorret_t uiEmojiDraw(void);
/**
* Dispose of the UI emoji.
*/
errorret_t uiEmojiDispose(void);
-40
View File
@@ -12,17 +12,11 @@
#include "engine/engine.h" #include "engine/engine.h"
#include "ui/overlay/uifullbox.h" #include "ui/overlay/uifullbox.h"
#include "ui/overlay/uiloading.h" #include "ui/overlay/uiloading.h"
#include "ui/debug/uiplayerpos.h"
#include "ui/overlay/uicrop.h" #include "ui/overlay/uicrop.h"
#include "ui/transition/uitransition.h" #include "ui/transition/uitransition.h"
#include "ui/debug/uiconsole.h" #include "ui/debug/uiconsole.h"
#include "ui/frame/settings/uisettings.h" #include "ui/frame/settings/uisettings.h"
#include "ui/frame/game/uigamemenu.h"
#include "ui/frame/backpack/uibackpack.h"
#include "ui/frame/uiconfirm.h" #include "ui/frame/uiconfirm.h"
#include "ui/rpg/textbox/uitextboxmain.h"
#include "ui/rpg/textbox/uitextboxminilist.h"
#include "ui/rpg/uiemoji.h"
uielement_t UI_ELEMENTS[] = { uielement_t UI_ELEMENTS[] = {
{ {
@@ -37,27 +31,6 @@ uielement_t UI_ELEMENTS[] = {
.draw = uiFullboxUnderDraw .draw = uiFullboxUnderDraw
}, },
// in world stuff
{
.init = uiEmojiInit,
.update = uiEmojiUpdate,
.draw = uiEmojiDraw,
.dispose = uiEmojiDispose
},
// Ingame menus
{
.init = uiGameMenuInit,
.draw = uiGameMenuDraw,
.dispose = uiGameMenuDispose
},
{
.init = uiBackpackInit,
.draw = uiBackpackDraw,
.dispose = uiBackpackDispose
},
{ {
.init = uiSettingsInit, .init = uiSettingsInit,
.update = uiSettingsUpdate, .update = uiSettingsUpdate,
@@ -72,18 +45,6 @@ uielement_t UI_ELEMENTS[] = {
.dispose = uiConfirmDispose .dispose = uiConfirmDispose
}, },
{
.init = uiTextboxMainInit,
.update = uiTextboxMainUpdate,
.draw = uiTextboxMainDraw
},
{
.init = uiTextboxMiniListInit,
.update = uiTextboxMiniListUpdate,
.draw = uiTextboxMiniListDraw
},
{ {
.init = uiTransitionInit, .init = uiTransitionInit,
.update = uiTransitionUpdate, .update = uiTransitionUpdate,
@@ -111,7 +72,6 @@ uielement_t UI_ELEMENTS[] = {
// Debug items // Debug items
{ .draw = uiConsoleDraw, .dispose = uiConsoleDispose }, { .draw = uiConsoleDraw, .dispose = uiConsoleDispose },
{ .draw = uiFPSDraw }, { .draw = uiFPSDraw },
{ .draw = uiPlayerPosDraw },
{ 0 } // Null terminator { 0 } // Null terminator
}; };
-2
View File
@@ -11,7 +11,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
uislider.c uislider.c
uidropdown.c uidropdown.c
uiscrolling.c uiscrolling.c
uiitem.c
uiitemlist.c
uimenu.c uimenu.c
) )
-77
View File
@@ -1,77 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uiitem.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "display/text/text.h"
#include "display/color.h"
#define UI_ITEM_LABEL_MAX 48
errorret_t uiItemInit(
uiitem_t *item,
const itemid_t itemId,
const uint8_t quantity
) {
assertNotNull(item, "Item cannot be NULL");
memoryZero(item, sizeof(uiitem_t));
item->item = itemId;
item->quantity = quantity;
if(itemId == ITEM_ID_NULL) errorOk();
errorChain(itemGetName(itemId, item->nameLabel, UI_ITEM_NAME_LABEL_MAX));
errorOk();
}
itemid_t uiItemGetItem(const uiitem_t *item) {
assertNotNull(item, "Item cannot be NULL");
return item->item;
}
uint8_t uiItemGetQuantity(const uiitem_t *item) {
assertNotNull(item, "Item cannot be NULL");
return item->quantity;
}
void uiItemSetQuantity(uiitem_t *item, const uint8_t quantity) {
assertNotNull(item, "Item cannot be NULL");
item->quantity = quantity;
}
bool_t uiItemIsHighlighted(const uiitem_t *item) {
assertNotNull(item, "Item cannot be NULL");
return item->highlighted;
}
void uiItemSetHighlighted(uiitem_t *item, const bool_t highlighted) {
assertNotNull(item, "Item cannot be NULL");
item->highlighted = highlighted;
}
errorret_t uiItemDraw(
const uiitem_t *item,
const float_t x,
const float_t y
) {
assertNotNull(item, "Item cannot be NULL");
if(item->item == ITEM_ID_NULL) errorOk();
const color_t color = item->highlighted ? COLOR_RED : COLOR_WHITE;
char_t text[UI_ITEM_LABEL_MAX];
stringFormat(
text, UI_ITEM_LABEL_MAX - 1,
"%s x%u", item->nameLabel, item->quantity
);
errorChain(textDraw(x, y, text, color, &FONT_DEFAULT));
errorOk();
}
-89
View File
@@ -1,89 +0,0 @@
/**
* 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 "rpg/item/item.h"
#define UI_ITEM_NAME_LABEL_MAX 32
typedef struct {
itemid_t item;
uint8_t quantity;
bool_t highlighted;
char_t nameLabel[UI_ITEM_NAME_LABEL_MAX];
} uiitem_t;
/**
* Initializes an item widget, resolving the item's localized name.
*
* @param item The item widget to initialize.
* @param itemId The item ID to display. ITEM_ID_NULL renders as an
* empty slot.
* @param quantity The stack quantity to display.
* @return Any error that occurs.
*/
errorret_t uiItemInit(
uiitem_t *item,
const itemid_t itemId,
const uint8_t quantity
);
/**
* Returns the item ID this widget displays.
*
* @param item The item widget to query.
* @returns The item ID.
*/
itemid_t uiItemGetItem(const uiitem_t *item);
/**
* Returns the stack quantity this widget displays.
*
* @param item The item widget to query.
* @returns The quantity.
*/
uint8_t uiItemGetQuantity(const uiitem_t *item);
/**
* Sets the stack quantity this widget displays.
*
* @param item The item widget to update.
* @param quantity The new quantity.
*/
void uiItemSetQuantity(uiitem_t *item, const uint8_t quantity);
/**
* Returns whether the item widget is highlighted.
*
* @param item The item widget to query.
* @returns True if highlighted.
*/
bool_t uiItemIsHighlighted(const uiitem_t *item);
/**
* Sets the highlighted state of the item widget.
*
* @param item The item widget to update.
* @param highlighted The new highlighted state.
*/
void uiItemSetHighlighted(uiitem_t *item, const bool_t highlighted);
/**
* Draws the item widget at the given screen position: item name and
* quantity. No-op for an empty (ITEM_ID_NULL) slot.
*
* @param item The item widget to draw.
* @param x Screen x position.
* @param y Screen y position.
* @return Any error that occurs.
*/
errorret_t uiItemDraw(
const uiitem_t *item,
const float_t x,
const float_t y
);
-179
View File
@@ -1,179 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uiitemlist.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "display/text/text.h"
void uiItemListInit(
uiitemlist_t *list,
const uint8_t columns,
const uint8_t rows,
const uint8_t itemColumns,
uiitemlistselectedcallback_t selected,
uiitemlistchangedcallback_t changed,
uiitemlistclosedcallback_t closed,
uiitemlistcolumncallback_t columnDraw
) {
assertNotNull(list, "Item list cannot be NULL");
assertTrue(columns > 0, "Item list columns must be > 0");
assertTrue(rows > 0, "Item list rows must be > 0");
assertTrue(itemColumns > 0, "Item list itemColumns must be > 0");
memoryZero(list, sizeof(uiitemlist_t));
list->columns = columns;
list->rows = rows;
list->itemColumns = itemColumns;
list->selected = selected;
list->changed = changed;
list->closed = closed;
list->columnDraw = columnDraw;
uiScrollingInit(&list->scroll);
}
void uiItemListSetItems(
uiitemlist_t *list,
const uiitem_t *items,
const uint8_t itemCount
) {
assertNotNull(list, "Item list cannot be NULL");
assertTrue(
itemCount <= UI_ITEM_LIST_CAPACITY_MAX, "Too many items for list"
);
memoryCopy(list->items, items, sizeof(uiitem_t) * itemCount);
list->itemCount = itemCount;
}
errorret_t uiItemListSetItemStacks(
uiitemlist_t *list,
const inventorystack_t *stacks,
const uint8_t stackCount
) {
assertNotNull(list, "Item list cannot be NULL");
assertTrue(
stackCount <= UI_ITEM_LIST_CAPACITY_MAX, "Too many items for list"
);
for(uint8_t i = 0; i < stackCount; i++) {
errorChain(
uiItemInit(&list->items[i], stacks[i].item, stacks[i].quantity)
);
}
list->itemCount = stackCount;
errorOk();
}
void uiItemListOpen(uiitemlist_t *list) {
assertNotNull(list, "Item list cannot be NULL");
if(list->focusItem != NULL) return;
list->focusItem = uiFocusPush(
list->columns, list->rows,
uiItemListFocusSelected,
uiItemListFocusChanged,
uiItemListFocusClosed,
NULL,
list
);
}
void uiItemListClose(uiitemlist_t *list) {
assertNotNull(list, "Item list cannot be NULL");
if(list->focusItem == NULL) return;
uiFocusPopItem(list->focusItem);
list->focusItem = NULL;
}
bool_t uiItemListIsActive(const uiitemlist_t *list) {
assertNotNull(list, "Item list cannot be NULL");
return list->focusItem != NULL;
}
errorret_t uiItemListDraw(
const uiitemlist_t *list,
const float_t x,
const float_t y,
const float_t width
) {
assertNotNull(list, "Item list cannot be NULL");
if(list->itemCount == 0) errorOk();
const float_t colStep = width / (float_t)list->columns;
const float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
const float_t itemColStep = colStep / (float_t)list->itemColumns;
const uint8_t visibleMax = list->columns * list->rows;
const uint8_t drawCount =
list->itemCount < visibleMax ? list->itemCount : visibleMax;
for(uint8_t i = 0; i < drawCount; i++) {
const uint8_t col = i % list->columns;
const uint8_t row = i / list->columns;
const float_t ix = x + (float_t)col * colStep;
const float_t iy = y + (float_t)row * rowHeight;
errorChain(uiItemDraw(&list->items[i], ix, iy));
for(uint8_t c = 1; c < list->itemColumns; c++) {
if(list->columnDraw == NULL) continue;
errorChain(list->columnDraw(
list, &list->items[i], c, ix + (float_t)c * itemColStep, iy
));
}
}
errorOk();
}
bool_t uiItemListFocusSelected(const uifocusitem_t *focusItem) {
assertNotNull(focusItem, "Focus item cannot be NULL");
assertNotNull(focusItem->user, "Focus item user cannot be NULL");
uiitemlist_t *list = (uiitemlist_t *)focusItem->user;
if(list->selected == NULL) return true;
const uint8_t index = focusItem->y * list->columns + focusItem->x;
if(index >= list->itemCount) return true;
list->selected(list, index, &list->items[index]);
return true;
}
bool_t uiItemListFocusChanged(const uifocusitem_t *focusItem) {
assertNotNull(focusItem, "Focus item cannot be NULL");
assertNotNull(focusItem->user, "Focus item user cannot be NULL");
uiitemlist_t *list = (uiitemlist_t *)focusItem->user;
const uint8_t focusIndex = focusItem->y * list->columns + focusItem->x;
for(uint8_t i = 0; i < list->itemCount; i++) {
uiItemSetHighlighted(&list->items[i], i == focusIndex);
}
if(list->changed == NULL) return true;
if(focusIndex >= list->itemCount) return true;
list->changed(list, focusIndex, &list->items[focusIndex]);
return true;
}
bool_t uiItemListFocusClosed(const uifocusitem_t *focusItem) {
assertNotNull(focusItem, "Focus item cannot be NULL");
assertNotNull(focusItem->user, "Focus item user cannot be NULL");
uiitemlist_t *list = (uiitemlist_t *)focusItem->user;
list->focusItem = NULL;
for(uint8_t i = 0; i < list->itemCount; i++) {
uiItemSetHighlighted(&list->items[i], false);
}
if(list->closed != NULL) list->closed(list);
return true;
}
-200
View File
@@ -1,200 +0,0 @@
/**
* 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/uiitem.h"
#include "ui/widget/uiscrolling.h"
#include "ui/focus/uifocus.h"
#include "rpg/item/inventory.h"
#define UI_ITEM_LIST_CAPACITY_MAX 40
typedef struct uiitemlist_s uiitemlist_t;
typedef void (*uiitemlistselectedcallback_t)(
const uiitemlist_t *list,
const uint8_t index,
const uiitem_t *item
);
typedef void (*uiitemlistchangedcallback_t)(
const uiitemlist_t *list,
const uint8_t index,
const uiitem_t *item
);
typedef void (*uiitemlistclosedcallback_t)(const uiitemlist_t *list);
/**
* Called to draw an extra per-item info column (columnIndex >= 1)
* beyond the item's own default uiItemDraw rendering -- e.g. a shop
* menu drawing a price alongside the item name.
*
* @param list The item list being drawn.
* @param item The item slot being drawn.
* @param columnIndex The info column being drawn (>= 1).
* @param x Screen x position for this column.
* @param y Screen y position for this column.
* @return Any error that occurs.
*/
typedef errorret_t (*uiitemlistcolumncallback_t)(
const uiitemlist_t *list,
const uiitem_t *item,
const uint8_t columnIndex,
const float_t x,
const float_t y
);
struct uiitemlist_s {
uiitem_t items[UI_ITEM_LIST_CAPACITY_MAX];
uint8_t itemCount;
// Grid layout: how many item slots wide/tall the list displays.
uint8_t columns;
uint8_t rows;
// How many info columns are rendered per item slot. 1 means only the
// item's own default name/quantity is drawn; anything beyond that is
// rendered via columnDraw.
uint8_t itemColumns;
uiscrolling_t scroll;
uifocusitem_t *focusItem;
uiitemlistselectedcallback_t selected;
uiitemlistchangedcallback_t changed;
uiitemlistclosedcallback_t closed;
uiitemlistcolumncallback_t columnDraw;
void *user;
};
/**
* Initializes an item list.
*
* @param list The item list to initialize.
* @param columns Number of item slots per row.
* @param rows Number of item slot rows.
* @param itemColumns Number of info columns rendered per item slot.
* @param selected Called when an item slot is selected.
* @param changed Called when the focused item slot changes.
* @param closed Called when the item list is closed.
* @param columnDraw Called to draw each extra info column (index >= 1);
* may be NULL if itemColumns is 1.
*/
void uiItemListInit(
uiitemlist_t *list,
const uint8_t columns,
const uint8_t rows,
const uint8_t itemColumns,
uiitemlistselectedcallback_t selected,
uiitemlistchangedcallback_t changed,
uiitemlistclosedcallback_t closed,
uiitemlistcolumncallback_t columnDraw
);
/**
* Sets the items displayed by the list directly, copying them into
* the list's own storage.
*
* @param list The item list to update.
* @param items The items to display.
* @param itemCount Number of entries in items. Must be <=
* UI_ITEM_LIST_CAPACITY_MAX.
*/
void uiItemListSetItems(
uiitemlist_t *list,
const uiitem_t *items,
const uint8_t itemCount
);
/**
* Sets the items displayed by the list from an array of item stacks,
* internally creating a uiitem_t for each stack.
*
* @param list The item list to update.
* @param stacks The item stacks to display.
* @param stackCount Number of entries in stacks. Must be <=
* UI_ITEM_LIST_CAPACITY_MAX.
* @return Any error that occurs.
*/
errorret_t uiItemListSetItemStacks(
uiitemlist_t *list,
const inventorystack_t *stacks,
const uint8_t stackCount
);
/**
* Pushes the item list onto the UI focus stack, making it navigable.
* No-op if already open.
*
* @param list The item list to open.
*/
void uiItemListOpen(uiitemlist_t *list);
/**
* Pops the item list from the UI focus stack. No-op if already closed.
*
* @param list The item list to close.
*/
void uiItemListClose(uiitemlist_t *list);
/**
* Returns whether the item list is currently on the UI focus stack.
*
* @param list The item list to query.
* @returns True if active.
*/
bool_t uiItemListIsActive(const uiitemlist_t *list);
/**
* Draws the item list's grid of item slots at the given position.
* Only the first columns * rows items are drawn.
*
* @param list The item list to draw.
* @param x Screen x position.
* @param y Screen y position.
* @param width Content width, used to lay out columns.
* @return Any error that occurs.
*/
errorret_t uiItemListDraw(
const uiitemlist_t *list,
const float_t x,
const float_t y,
const float_t width
);
/**
* Internal focus callback -- forwards selection to the list's selected
* handler.
*
* @param focusItem The active focus item; user field must point to
* uiitemlist_t.
* @returns True.
*/
bool_t uiItemListFocusSelected(const uifocusitem_t *focusItem);
/**
* Internal focus callback -- updates item highlights and fires
* changed.
*
* @param focusItem The active focus item; user field must point to
* uiitemlist_t.
* @returns True.
*/
bool_t uiItemListFocusChanged(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
* uiitemlist_t.
* @returns True.
*/
bool_t uiItemListFocusClosed(const uifocusitem_t *focusItem);