diff --git a/assets/minesweeper/CMakeLists.txt b/assets/minesweeper/CMakeLists.txt index 1f51776..4dc5bc9 100644 --- a/assets/minesweeper/CMakeLists.txt +++ b/assets/minesweeper/CMakeLists.txt @@ -5,6 +5,7 @@ set(DUSK_GAME_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL ${DUSK_CACHE_TARGET}) -add_subdirectory(palette)# Palette asset needs to be added before any images. +# Palette asset needs to be added before any images. +add_subdirectory(palette) add_subdirectory(config) add_subdirectory(ui) \ No newline at end of file diff --git a/assets/minesweeper/config/init.dcf b/assets/minesweeper/config/init.dcf index 714d2c7..30edff1 100644 --- a/assets/minesweeper/config/init.dcf +++ b/assets/minesweeper/config/init.dcf @@ -14,4 +14,6 @@ bind enter accept; bind q cancel; bind esc quit; -fps 1; \ No newline at end of file +fps 1; + +scene sweep; \ No newline at end of file diff --git a/assets/minesweeper/config/init_psp.dcf b/assets/minesweeper/config/init_psp.dcf index 18162c5..802b20a 100644 --- a/assets/minesweeper/config/init_psp.dcf +++ b/assets/minesweeper/config/init_psp.dcf @@ -14,4 +14,6 @@ bind lstick_positive_y down; bind lstick_negative_x left; bind lstick_positive_x right; -fps 1; \ No newline at end of file +fps 1; + +scene sweep; \ No newline at end of file diff --git a/assets/minesweeper/palette/palette0.png b/assets/minesweeper/palette/palette0.png index e60ee7f..51dbd9f 100644 Binary files a/assets/minesweeper/palette/palette0.png and b/assets/minesweeper/palette/palette0.png differ diff --git a/assets/minesweeper/palette/palette0.pxo b/assets/minesweeper/palette/palette0.pxo index 070274a..f314160 100644 Binary files a/assets/minesweeper/palette/palette0.pxo and b/assets/minesweeper/palette/palette0.pxo differ diff --git a/assets/minesweeper/sweep/CMakeLists.txt b/assets/minesweeper/sweep/CMakeLists.txt index d8eda53..45c387e 100644 --- a/assets/minesweeper/sweep/CMakeLists.txt +++ b/assets/minesweeper/sweep/CMakeLists.txt @@ -2,5 +2,3 @@ # # This software is released under the MIT License. # https://opensource.org/licenses/MIT - -add_asset(PALETTE palette0.png) \ No newline at end of file diff --git a/assets/minesweeper/sweep/ui.png b/assets/minesweeper/sweep/ui.png deleted file mode 100644 index 28b2df5..0000000 Binary files a/assets/minesweeper/sweep/ui.png and /dev/null differ diff --git a/assets/minesweeper/ui/CMakeLists.txt b/assets/minesweeper/ui/CMakeLists.txt index ffe622f..a2ded2e 100644 --- a/assets/minesweeper/ui/CMakeLists.txt +++ b/assets/minesweeper/ui/CMakeLists.txt @@ -3,4 +3,6 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -add_asset(TILESET minogram.png type=ALPHA tileWidth=6 tileHeight=10 columns=16 rows=6) \ No newline at end of file +add_asset(TILESET minogram.png type=ALPHA tileWidth=6 tileHeight=10 columns=16 rows=6) +add_asset(TILESET ui.png type=PALETTIZED tileWidth=16 tileHeight=16) +add_asset(TILESET ui_frame.png type=PALETTIZED tileWidth=16 tileHeight=16) \ No newline at end of file diff --git a/assets/minesweeper/ui/ui.png b/assets/minesweeper/ui/ui.png new file mode 100644 index 0000000..74832f9 Binary files /dev/null and b/assets/minesweeper/ui/ui.png differ diff --git a/assets/minesweeper/ui/ui.pxo b/assets/minesweeper/ui/ui.pxo new file mode 100644 index 0000000..1ff4fcf Binary files /dev/null and b/assets/minesweeper/ui/ui.pxo differ diff --git a/assets/minesweeper/ui/ui_frame b/assets/minesweeper/ui/ui_frame new file mode 100644 index 0000000..3f3c281 Binary files /dev/null and b/assets/minesweeper/ui/ui_frame differ diff --git a/assets/minesweeper/ui/ui_frame.png b/assets/minesweeper/ui/ui_frame.png new file mode 100644 index 0000000..5815eb1 Binary files /dev/null and b/assets/minesweeper/ui/ui_frame.png differ diff --git a/src/asset/assetmanager.h b/src/asset/assetmanager.h index d0f0d46..14984fb 100644 --- a/src/asset/assetmanager.h +++ b/src/asset/assetmanager.h @@ -8,7 +8,7 @@ #pragma once #include "asset.h" -#define ASSET_MANAGER_ASSET_COUNT_MAX 256 +#define ASSET_MANAGER_ASSET_COUNT_MAX 32 #if ASSET_TYPE == wad #else diff --git a/src/asset/type/assetpaletteimage.h b/src/asset/type/assetpaletteimage.h index 2c27898..37a595e 100644 --- a/src/asset/type/assetpaletteimage.h +++ b/src/asset/type/assetpaletteimage.h @@ -9,8 +9,8 @@ #include "error/error.h" #include "display/texture/texture.h" -#define ASSET_PALETTE_IMAGE_WIDTH_MAX 256 -#define ASSET_PALETTE_IMAGE_HEIGHT_MAX 256 +#define ASSET_PALETTE_IMAGE_WIDTH_MAX 128 +#define ASSET_PALETTE_IMAGE_HEIGHT_MAX 128 #define ASSET_PALETTE_IMAGE_SIZE_MAX ( \ ASSET_PALETTE_IMAGE_WIDTH_MAX * ASSET_PALETTE_IMAGE_HEIGHT_MAX \ ) diff --git a/src/console/cmd/cmdscene.h b/src/console/cmd/cmdscene.h new file mode 100644 index 0000000..ea8fb3d --- /dev/null +++ b/src/console/cmd/cmdscene.h @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "console/console.h" +#include "engine/engine.h" +#include "scene/scenemanager.h" + +void cmdScene(const consolecmdexec_t *exec) { + if(exec->argc < 1) { + consolePrint("Usage: scene "); + return; + } + + scene_t *scene = sceneManagerGetSceneByName(exec->argv[0]); + if(scene == NULL) { + consolePrint("Error: Scene '%s' not found.", exec->argv[0]); + return; + } + + if((scene->flags & SCENE_FLAG_INITIALIZED) == 0) { + if(scene->init) { + errorret_t ret = errorPrint(scene->init()); + if(ret.code != ERROR_OK) { + errorCatch(ret); + consolePrint("Error: Failed to initialize scene '%s'.", exec->argv[0]); + return; + } + } + scene->flags |= SCENE_FLAG_INITIALIZED; + } + + sceneManagerSetScene(scene); +} \ No newline at end of file diff --git a/src/console/console.c b/src/console/console.c index 0decd27..18fd874 100644 --- a/src/console/console.c +++ b/src/console/console.c @@ -19,6 +19,7 @@ #include "console/cmd/cmdbind.h" #include "console/cmd/cmdtoggleconsole.h" #include "console/cmd/cmdalias.h" +#include "console/cmd/cmdscene.h" console_t CONSOLE; @@ -34,6 +35,7 @@ void consoleInit() { consoleRegCmd("bind", cmdBind); consoleRegCmd("toggleconsole", cmdToggleConsole); consoleRegCmd("alias", cmdAlias); + consoleRegCmd("scene", cmdScene); #if CONSOLE_POSIX threadInit(&CONSOLE.thread, consoleInputThread); diff --git a/src/engine/engine.c b/src/engine/engine.c index abad84d..ee9814a 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -58,6 +58,7 @@ errorret_t engineUpdate(void) { errorret_t engineDispose(void) { gameDispose(); + sceneManagerDispose(); errorChain(displayDispose()); assetManagerDispose(); consoleDispose(); diff --git a/src/error/error.c b/src/error/error.c index f9bab6a..11f9ad9 100644 --- a/src/error/error.c +++ b/src/error/error.c @@ -57,6 +57,11 @@ errorret_t errorThrowImpl( } errorret_t errorOkImpl() { + assertTrue( + ERROR_STATE.code == ERROR_OK, + "Global error state is not OK (Likely missing errorCatch)" + ); + return (errorret_t) { .code = ERROR_OK, .state = NULL diff --git a/src/game/minesweeper/CMakeLists.txt b/src/game/minesweeper/CMakeLists.txt index 86f2d8e..459d03e 100644 --- a/src/game/minesweeper/CMakeLists.txt +++ b/src/game/minesweeper/CMakeLists.txt @@ -10,4 +10,5 @@ target_sources(${DUSK_TARGET_NAME} ) # Subdirs +add_subdirectory(scene) add_subdirectory(ui) \ No newline at end of file diff --git a/src/game/minesweeper/game.c b/src/game/minesweeper/game.c index b030180..605aa39 100644 --- a/src/game/minesweeper/game.c +++ b/src/game/minesweeper/game.c @@ -7,9 +7,14 @@ #include "game/game.h" #include "game/minesweeper/ui/ui.h" +#include "game/minesweeper/scene/scenesweep.h" +#include "scene/scenemanager.h" +#include "console/console.h" errorret_t gameInit(void) { - uiInit(); + errorChain(uiInit()); + + sceneManagerRegisterScene(&SCENE_SWEEP); errorOk(); } diff --git a/src/game/minesweeper/scene/CMakeLists.txt b/src/game/minesweeper/scene/CMakeLists.txt new file mode 100644 index 0000000..cb19f3a --- /dev/null +++ b/src/game/minesweeper/scene/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_TARGET_NAME} + PRIVATE + scenesweep.c +) + +# Subdirs \ No newline at end of file diff --git a/src/game/minesweeper/scene/scenesweep.c b/src/game/minesweeper/scene/scenesweep.c index bd92583..ad877c6 100644 --- a/src/game/minesweeper/scene/scenesweep.c +++ b/src/game/minesweeper/scene/scenesweep.c @@ -6,22 +6,20 @@ */ #include "scenesweep.h" +#include "game/minesweeper/ui/ui.h" scene_t SCENE_SWEEP = { + .name = "sweep", .init = sceneSweepInit, .update = sceneSweepUpdate, .render = sceneSweepRender, .dispose = sceneSweepDispose, - .active = NULL, - .sleep = NULL, - .flags = 0 }; scenesweep_t SCENE_SWEEP_DATA; errorret_t sceneSweepInit(void) { // Initialize scene data here - errorOk(); } @@ -31,8 +29,10 @@ void sceneSweepUpdate(void) { void sceneSweepRender(void) { // Render scene here + uiRender(); } void sceneSweepDispose(void) { // Clean up scene resources here + printf("Disposing sweep scene\n"); } \ No newline at end of file diff --git a/src/game/minesweeper/scene/scenesweep.h b/src/game/minesweeper/scene/scenesweep.h index 4285098..6a4fbf8 100644 --- a/src/game/minesweeper/scene/scenesweep.h +++ b/src/game/minesweeper/scene/scenesweep.h @@ -9,7 +9,7 @@ #include "scene/scene.h" typedef struct { - + int32_t x; } scenesweep_t; extern scene_t SCENE_SWEEP; diff --git a/src/game/minesweeper/ui/ui.c b/src/game/minesweeper/ui/ui.c index c64435d..4b62020 100644 --- a/src/game/minesweeper/ui/ui.c +++ b/src/game/minesweeper/ui/ui.c @@ -8,22 +8,41 @@ #include "ui.h" #include "ui/uifps.h" #include "ui/uiconsole.h" +#include "ui/uiframe.h" #include "display/framebuffer/framebuffer.h" +#include "display/spritebatch/spritebatch.h" #include "display/tileset/tileset_minogram.h" +#include "display/tileset/tileset_ui.h" +#include "display/tileset/tileset_ui_frame.h" ui_t UI; -void uiInit(void) { +errorret_t uiInit(void) { cameraInitOrthographic(&UI.camera); UI.minogramTileset = (tileset_t*)&TILESET_MINOGRAM; - - assetManagerLoadAsset( + errorChain(assetManagerLoadAsset( UI.minogramTileset->image, &UI.minogramAsset, &UI.minogramRef - ); + )); + + UI.uiTileset = (tileset_t*)&TILESET_UI; + errorChain(assetManagerLoadAsset( + UI.uiTileset->image, + &UI.uiAsset, + &UI.uiRef + )); + + UI.frameTileset = (tileset_t*)&TILESET_UI_FRAME; + errorChain(assetManagerLoadAsset( + UI.frameTileset->image, + &UI.frameAsset, + &UI.frameRef + )); + + errorOk(); } void uiRender(void) { @@ -38,7 +57,22 @@ void uiRender(void) { cameraPushMatrix(&UI.camera); uiFPSRender(UI.minogramTileset, &UI.minogramAsset->alphaImage.texture); - uiConsoleRender(UI.minogramTileset, &UI.minogramAsset->alphaImage.texture); + + float_t x, y; + x = 32, y = 32; + uiFrameDrawTiled( + x, y, + 256, 256, + UI.frameTileset, + 0, 0, + &UI.frameAsset->paletteImage.texture + ); + + uiConsoleRender( + 0, 0, + UI.minogramTileset, &UI.minogramAsset->alphaImage.texture + ); + spriteBatchFlush(); cameraPopMatrix(); } diff --git a/src/game/minesweeper/ui/ui.h b/src/game/minesweeper/ui/ui.h index 0283751..8f7b321 100644 --- a/src/game/minesweeper/ui/ui.h +++ b/src/game/minesweeper/ui/ui.h @@ -17,14 +17,24 @@ typedef struct { tileset_t *minogramTileset; asset_t *minogramAsset; ref_t minogramRef; + + tileset_t *uiTileset; + asset_t *uiAsset; + ref_t uiRef; + + tileset_t *frameTileset; + asset_t *frameAsset; + ref_t frameRef; } ui_t; extern ui_t UI; /** * Initializes the Minesweeper UI. + * + * @return Error code indicating success or failure. */ -void uiInit(void); +errorret_t uiInit(void); /** * Renders the Minesweeper UI. diff --git a/src/scene/scene.h b/src/scene/scene.h index f17a7dd..d45a239 100644 --- a/src/scene/scene.h +++ b/src/scene/scene.h @@ -10,8 +10,11 @@ #include "error/error.h" #define SCENE_FLAG_ACTIVE (1 << 0) +#define SCENE_FLAG_INITIALIZED (1 << 1) typedef struct { + const char_t *name; + errorret_t (*init)(void); void (*update)(void); void (*render)(void); diff --git a/src/scene/scenemanager.c b/src/scene/scenemanager.c index f486d52..ae44643 100644 --- a/src/scene/scenemanager.c +++ b/src/scene/scenemanager.c @@ -8,47 +8,103 @@ #include "scenemanager.h" #include "util/memory.h" #include "assert/assert.h" +#include "console/console.h" +#include "util/string.h" scenemanager_t SCENE_MANAGER; errorret_t sceneManagerInit(void) { memoryZero(&SCENE_MANAGER, sizeof(scenemanager_t)); - + errorOk(); } +scene_t * sceneManagerGetSceneByName(const char_t *name) { + assertNotNull(name, "Name is null"); + + for(uint8_t i = 0; i < SCENE_MANAGER.sceneCount; i++) { + if(strcmp(SCENE_MANAGER.scenes[i]->name, name) != 0) continue; + return SCENE_MANAGER.scenes[i]; + } + + return NULL; +} + +void sceneManagerRegisterScene(scene_t *scene) { + assertNotNull(scene, "Scene is null"); + assertTrue( + SCENE_MANAGER.sceneCount < SCENE_MANAGER_SCENE_COUNT_MAX, + "Scene count exceeded max" + ); + assertNotNull(scene->name, "Scene name is null"); + assertNull( + sceneManagerGetSceneByName(scene->name), "Scene name already registered" + ); + + SCENE_MANAGER.scenes[SCENE_MANAGER.sceneCount++] = scene; +} + void sceneManagerSetScene(scene_t *scene) { if(SCENE_MANAGER.current) { - SCENE_MANAGER.current->sleep(); - SCENE_MANAGER.current->flags &= ~SCENE_FLAG_ACTIVE; + if(SCENE_MANAGER.current->sleep) SCENE_MANAGER.current->sleep(); + + // TODO: Should dispose? + SCENE_MANAGER.current->flags &= ~( + SCENE_FLAG_INITIALIZED | SCENE_FLAG_ACTIVE + ); + if(SCENE_MANAGER.current->dispose) SCENE_MANAGER.current->dispose(); } SCENE_MANAGER.current = scene; - if(SCENE_MANAGER.current) { - SCENE_MANAGER.current->active(); - SCENE_MANAGER.current->flags |= SCENE_FLAG_ACTIVE; + if(scene) { + assertTrue( + scene->flags & SCENE_FLAG_INITIALIZED, + "Scene not initialized" + ); + + if(scene->active) scene->active(); + scene->flags |= SCENE_FLAG_ACTIVE; } } void sceneManagerUpdate(void) { if(!SCENE_MANAGER.current) return; + assertTrue( SCENE_MANAGER.current->flags & SCENE_FLAG_ACTIVE, "Current scene not active" ); - SCENE_MANAGER.current->update(); + assertTrue( + SCENE_MANAGER.current->flags & SCENE_FLAG_INITIALIZED, + "Current scene not initialized" + ); + + if(SCENE_MANAGER.current->update) SCENE_MANAGER.current->update(); } void sceneManagerRender(void) { if(!SCENE_MANAGER.current) return; + assertTrue( SCENE_MANAGER.current->flags & SCENE_FLAG_ACTIVE, "Current scene not active" ); - SCENE_MANAGER.current->render(); + assertTrue( + SCENE_MANAGER.current->flags & SCENE_FLAG_INITIALIZED, + "Current scene not initialized" + ); + + if(SCENE_MANAGER.current->render) SCENE_MANAGER.current->render(); } void sceneManagerDispose(void) { - assertNull(SCENE_MANAGER.current, "Current scene not null"); + for(uint8_t i = 0; i < SCENE_MANAGER.sceneCount; i++) { + scene_t *scene = SCENE_MANAGER.scenes[i]; + if(scene->flags & SCENE_FLAG_INITIALIZED) { + scene->dispose(); + } + } + + SCENE_MANAGER.sceneCount = 0; } diff --git a/src/scene/scenemanager.h b/src/scene/scenemanager.h index 138c977..5b39dc9 100644 --- a/src/scene/scenemanager.h +++ b/src/scene/scenemanager.h @@ -8,8 +8,12 @@ #pragma once #include "scene.h" +#define SCENE_MANAGER_SCENE_COUNT_MAX 32 + typedef struct { scene_t *current; + scene_t *scenes[SCENE_MANAGER_SCENE_COUNT_MAX]; + uint8_t sceneCount; } scenemanager_t; extern scenemanager_t SCENE_MANAGER; @@ -19,6 +23,21 @@ extern scenemanager_t SCENE_MANAGER; */ errorret_t sceneManagerInit(void); +/** + * Retrieves a registered scene by its name. + * + * @param name The name of the scene to retrieve. + * @return The scene with the specified name, or NULL if not found. + */ +scene_t * sceneManagerGetSceneByName(const char_t *name); + +/** + * Registers a scene with the scene manager. + * + * @param scene The scene to register. + */ +void sceneManagerRegisterScene(scene_t *scene); + /** * Sets the current active scene. * diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index b0d132a..14dadcf 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -8,5 +8,6 @@ target_sources(${DUSK_TARGET_NAME} PRIVATE uitext.c uifps.c + uiframe.c uiconsole.c ) \ No newline at end of file diff --git a/src/ui/uiconsole.c b/src/ui/uiconsole.c index a678b66..00865c8 100644 --- a/src/ui/uiconsole.c +++ b/src/ui/uiconsole.c @@ -9,7 +9,12 @@ #include "uitext.h" #include "console/console.h" -void uiConsoleRender(const tileset_t *tileset, texture_t *texture) { +void uiConsoleRender( + const float_t x, + const float_t y, + const tileset_t *tileset, + texture_t *texture +) { if(!CONSOLE.visible) return; int32_t i = CONSOLE_HISTORY_MAX - 1; @@ -21,7 +26,7 @@ void uiConsoleRender(const tileset_t *tileset, texture_t *texture) { continue; } uiTextDraw( - 0, i * TILESET_MINOGRAM.tileHeight, + x, y + (i * TILESET_MINOGRAM.tileHeight), line, COLOR_WHITE, tileset, texture ); diff --git a/src/ui/uiconsole.h b/src/ui/uiconsole.h index 6c4953b..a1a1260 100644 --- a/src/ui/uiconsole.h +++ b/src/ui/uiconsole.h @@ -12,7 +12,14 @@ /** * Renders the console UI. * + * @param x The x-coordinate to start rendering the console. + * @param y The y-coordinate to start rendering the console. * @param tileset The tileset to use for rendering text. * @param texture The texture associated with the tileset. */ -void uiConsoleRender(const tileset_t *tileset, texture_t *texture); \ No newline at end of file +void uiConsoleRender( + const float_t x, + const float_t y, + const tileset_t *tileset, + texture_t *texture +); \ No newline at end of file diff --git a/src/ui/uiframe.c b/src/ui/uiframe.c new file mode 100644 index 0000000..648d735 --- /dev/null +++ b/src/ui/uiframe.c @@ -0,0 +1,384 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uiframe.h" +#include "display/spritebatch/spritebatch.h" +#include "assert/assert.h" +#include + +void uiFrameDraw( + const float_t x, + const float_t y, + const float_t width, + const float_t height, + const tileset_t *tileset, + const uint16_t column, + const uint16_t row, + texture_t *texture +) { + assertNotNull(tileset, "Tileset cannot be NULL"); + assertNotNull(texture, "Texture cannot be NULL"); + + if(height <= 0 || width <= 0) return; + + // Top-Left + vec4 uv; + + tilesetPositionGetUV( + tileset, + column, + row, + uv + ); + spriteBatchPush( + texture, + x, y, + x + tileset->tileWidth, + y + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Top-Center + tilesetPositionGetUV( + tileset, + column + 1, + row, + uv + ); + spriteBatchPush( + texture, + x + tileset->tileWidth, y, + x + width - tileset->tileWidth, y + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Top-Right + tilesetPositionGetUV( + tileset, + column + 2, + row, + uv + ); + spriteBatchPush( + texture, + x + width - tileset->tileWidth, y, + x + width, y + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Middle-Left + tilesetPositionGetUV( + tileset, + column, + row + 1, + uv + ); + spriteBatchPush( + texture, + x, y + tileset->tileHeight, + x + tileset->tileWidth, y + height - tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Middle-Center + tilesetPositionGetUV( + tileset, + column + 1, + row + 1, + uv + ); + spriteBatchPush( + texture, + x + tileset->tileWidth, y + tileset->tileHeight, + x + width - tileset->tileWidth, y + height - tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Middle-Right + tilesetPositionGetUV( + tileset, + column + 2, + row + 1, + uv + ); + spriteBatchPush( + texture, + x + width - tileset->tileWidth, y + tileset->tileHeight, + x + width, y + height - tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Bottom-Left + tilesetPositionGetUV( + tileset, + column, + row + 2, + uv + ); + spriteBatchPush( + texture, + x, y + height - tileset->tileHeight, + x + tileset->tileWidth, y + height, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Bottom-Center + tilesetPositionGetUV( + tileset, + column + 1, + row + 2, + uv + ); + spriteBatchPush( + texture, + x + tileset->tileWidth, y + height - tileset->tileHeight, + x + width - tileset->tileWidth, y + height, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Bottom-Right + tilesetPositionGetUV( + tileset, + column + 2, + row + 2, + uv + ); + spriteBatchPush( + texture, + x + width - tileset->tileWidth, y + height - tileset->tileHeight, + x + width, y + height, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); +} + +void uiFrameDrawTiled( + const float_t x, + const float_t y, + const float_t width, + const float_t height, + const tileset_t *tileset, + const uint16_t column, + const uint16_t row, + texture_t *texture +) { + assertNotNull(tileset, "Tileset cannot be NULL"); + assertNotNull(texture, "Texture cannot be NULL"); + + if(height <= 0 || width <= 0) return; + + uint32_t segmentsX, segmentsY; + float_t remainderX, remainderY; + segmentsX = (width - (tileset->tileWidth * 2)) / tileset->tileWidth; + segmentsY = (height - (tileset->tileHeight * 2)) / tileset->tileHeight; + remainderX = fmodf(width - (tileset->tileWidth * 2), tileset->tileWidth); + remainderY = fmodf(height - (tileset->tileHeight * 2), tileset->tileHeight); + + // Corners + vec4 uv; + + // Top-Left + tilesetPositionGetUV( + tileset, + column, + row, + uv + ); + spriteBatchPush( + texture, + x, y, + x + tileset->tileWidth, + y + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Top-Right + tilesetPositionGetUV( + tileset, + column + 2, + row, + uv + ); + spriteBatchPush( + texture, + x + width - tileset->tileWidth, y, + x + width, y + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Bottom-Left + tilesetPositionGetUV( + tileset, + column, + row + 2, + uv + ); + spriteBatchPush( + texture, + x, y + height - tileset->tileHeight, + x + tileset->tileWidth, y + height, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Bottom-Right + tilesetPositionGetUV( + tileset, + column + 2, + row + 2, + uv + ); + spriteBatchPush( + texture, + x + width - tileset->tileWidth, y + height - tileset->tileHeight, + x + width, y + height, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + + // Top and Bottom Edges (Tiled) + tilesetPositionGetUV(tileset, column + 1, row, uv); // Top edge + float_t edgeX = x + tileset->tileWidth; + for(uint32_t i = 0; i < segmentsX; ++i, edgeX += tileset->tileWidth) { + spriteBatchPush( + texture, + edgeX, y, + edgeX + tileset->tileWidth, y + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + if(remainderX) { + spriteBatchPush( + texture, + edgeX, y, + edgeX + remainderX, y + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + + tilesetPositionGetUV(tileset, column + 1, row + 2, uv); // Bottom edge + edgeX = x + tileset->tileWidth; + for(uint32_t i = 0; i < segmentsX; ++i, edgeX += tileset->tileWidth) { + spriteBatchPush( + texture, + edgeX, y + height - tileset->tileHeight, + edgeX + tileset->tileWidth, y + height, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + if(remainderX) { + spriteBatchPush( + texture, + edgeX, y + height - tileset->tileHeight, + edgeX + remainderX, y + height, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + + // Left and Right Edges (Tiled) + tilesetPositionGetUV(tileset, column, row + 1, uv); // Left edge + float_t edgeY = y + tileset->tileHeight; + for(uint32_t i = 0; i < segmentsY; ++i, edgeY += tileset->tileHeight) { + spriteBatchPush( + texture, + x, edgeY, + x + tileset->tileWidth, edgeY + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + if(remainderY) { + spriteBatchPush( + texture, + x, edgeY, + x + tileset->tileWidth, edgeY + remainderY, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + + tilesetPositionGetUV(tileset, column + 2, row + 1, uv); // Right edge + edgeY = y + tileset->tileHeight; + for(uint32_t i = 0; i < segmentsY; ++i, edgeY += tileset->tileHeight) { + spriteBatchPush( + texture, + x + width - tileset->tileWidth, edgeY, + x + width, edgeY + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + if(remainderY) { + spriteBatchPush( + texture, + x + width - tileset->tileWidth, edgeY, + x + width, edgeY + remainderY, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + + // Center (Tiled) + tilesetPositionGetUV(tileset, column + 1, row + 1, uv); // Center tile + float_t centerY = y + tileset->tileHeight; + for(uint32_t j = 0; j < segmentsY; ++j, centerY += tileset->tileHeight) { + float_t centerX = x + tileset->tileWidth; + for(uint32_t i = 0; i < segmentsX; ++i, centerX += tileset->tileWidth) { + spriteBatchPush( + texture, + centerX, centerY, + centerX + tileset->tileWidth, centerY + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + if(remainderX) { + spriteBatchPush( + texture, + centerX, centerY, + centerX + remainderX, centerY + tileset->tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + } + if(remainderY) { + float_t centerX = x + tileset->tileWidth; + for(uint32_t i = 0; i < segmentsX; ++i, centerX += tileset->tileWidth) { + spriteBatchPush( + texture, + centerX, centerY, + centerX + tileset->tileWidth, centerY + remainderY, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + if(remainderX) { + spriteBatchPush( + texture, + centerX, centerY, + centerX + remainderX, centerY + remainderY, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] + ); + } + } +} \ No newline at end of file diff --git a/src/ui/uiframe.h b/src/ui/uiframe.h new file mode 100644 index 0000000..b5d844a --- /dev/null +++ b/src/ui/uiframe.h @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "display/tileset.h" +#include "display/texture/texture.h" + +/** + * Draw a UI Frame (using the 9-slice technique). + * + * @param x The x position to draw the frame at. + * @param y The y position to draw the frame at. + * @param width The width of the frame. + * @param height The height of the frame. + * @param tileset The tileset to use for rendering the frame. + * @param column The column in the tileset to use for the frame. + * @param row The row in the tileset to use for the frame. + * @param texture The texture associated with the tileset. + */ +void uiFrameDraw( + const float_t x, + const float_t y, + const float_t width, + const float_t height, + const tileset_t *tileset, + const uint16_t column, + const uint16_t row, + texture_t *texture +); + +/** + * Draw a tiled UI Frame (using the 9-slice technique). This will tile the + * center components to allow them to repeat without distortion. + * + * @param x The x position to draw the frame at. + * @param y The y position to draw the frame at. + * @param width The width of the frame. + * @param height The height of the frame. + * @param tileset The tileset to use for rendering the frame. + * @param column The column in the tileset to use for the frame. + * @param row The row in the tileset to use for the frame. + * @param texture The texture associated with the tileset. + */ +void uiFrameDrawTiled( + const float_t x, + const float_t y, + const float_t width, + const float_t height, + const tileset_t *tileset, + const uint16_t column, + const uint16_t row, + texture_t *texture +); \ No newline at end of file diff --git a/tools/assetstool/processimage.py b/tools/assetstool/processimage.py index 7ed0ec6..8038cd7 100644 --- a/tools/assetstool/processimage.py +++ b/tools/assetstool/processimage.py @@ -39,11 +39,15 @@ def processPalettizedImage(asset): if all(color in palette['pixels'] for color in imagePalette): break else: + palette = palettes[0] # Just to avoid reference error print(f"No matching palette found for {assetPath}!") # Find which pixel is missing for color in imagePalette: - if color not in palette: - print(f"Missing color: {color}") + if color in palette['pixels']: + continue + # Convert to hex (with alpha) + hexColor = '#{:02x}{:02x}{:02x}{:02x}'.format(color[0], color[1], color[2], color[3]) + print(f"Missing color: {hexColor} in palette {palette['paletteName']}") sys.exit(1) print(f"Converting image {assetPath} to use palette")