This commit is contained in:
2026-06-25 15:33:17 -05:00
parent 8faf881399
commit a9e33660cb
22 changed files with 563 additions and 398 deletions
+4 -6
View File
@@ -4,16 +4,14 @@
# https://opensource.org/licenses/MIT
add_subdirectory(debug)
add_subdirectory(uifocus)
add_subdirectory(frame)
add_subdirectory(focus)
add_subdirectory(overlay)
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
ui.c
uicrop.c
uielement.c
uiframe.c
uifullbox.c
uiloading.c
uitextbox.c
# uitextbox.c
)
+10
View File
@@ -0,0 +1,10 @@
# 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
uiframe.c
uisettings.c
)
@@ -1,43 +1,76 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uiframe.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "display/texture/texture.h"
#include "display/spritebatch/spritebatch.h"
#include "display/shader/shaderunlit.h"
#include "display/color.h"
errorret_t uiFrameInit(uiframe_t *frame) {
assertNotNull(frame, "frame must not be NULL");
memoryZero(frame, sizeof(uiframe_t));
uiframe_t UI_FRAME;
errorret_t uiFrameInit(void) {
memoryZero(&UI_FRAME, sizeof(uiframe_t));
// Init texture.
color_t border = color4b(0, 100, 220, 200);
color_t center = color4b(0, 100, 220, 50);
for(uint8_t y = 0; y < UIFRAME_DUMMY_HEIGHT; y++) {
for(uint8_t x = 0; x < UIFRAME_DUMMY_WIDTH; x++) {
color_t c;
if(x >= 12 || y >= 12) {
c = COLOR_TRANSPARENT;
} else if(y < 4 || y >= 8 || x < 4 || x >= 8) {
c = border;
} else {
c = center;
}
UI_FRAME.pixels[y * UIFRAME_DUMMY_WIDTH + x] = c;
}
}
errorChain(textureInit(
&UI_FRAME.texture, UIFRAME_DUMMY_WIDTH, UIFRAME_DUMMY_HEIGHT,
TEXTURE_FORMAT_RGBA,
(texturedata_t){ .rgbaColors = UI_FRAME.pixels }
));
UI_FRAME.tileset.tileWidth = 4;
UI_FRAME.tileset.tileHeight = 4;
UI_FRAME.tileset.columns = 3;
UI_FRAME.tileset.rows = 3;
UI_FRAME.tileset.tileCount = 9;
UI_FRAME.tileset.uv[0] = 4.0f / UIFRAME_DUMMY_WIDTH;
UI_FRAME.tileset.uv[1] = 4.0f / UIFRAME_DUMMY_HEIGHT;
errorOk();
}
errorret_t uiFrameDraw(
const uiframe_t *frame,
const float_t x,
const float_t y,
const float_t width,
const float_t height
) {
assertNotNull(frame, "frame must not be NULL");
assertNotNull(frame->texture, "frame texture must not be NULL");
shadermaterial_t material = {
.unlit = {
.color = COLOR_WHITE,
.texture = frame->texture
.texture = &UI_FRAME.texture
}
};
spritebatchsprite_t sprites[9];
float_t tileW = (float_t)frame->tileset.tileWidth;
float_t tileH = (float_t)frame->tileset.tileHeight;
float_t tileW = (float_t)UI_FRAME.tileset.tileWidth;
float_t tileH = (float_t)UI_FRAME.tileset.tileHeight;
vec4 uv;
tilesetPositionGetUV(&frame->tileset, 0, 0, uv);
tilesetPositionGetUV(&UI_FRAME.tileset, 0, 0, uv);
sprites[0].min[0] = x;
sprites[0].min[1] = y;
sprites[0].min[2] = 0.0f;
@@ -49,7 +82,7 @@ errorret_t uiFrameDraw(
sprites[0].uvMax[0] = uv[2];
sprites[0].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 1, 0, uv);
tilesetPositionGetUV(&UI_FRAME.tileset, 1, 0, uv);
sprites[1].min[0] = x + tileW;
sprites[1].min[1] = y;
sprites[1].min[2] = 0.0f;
@@ -61,7 +94,7 @@ errorret_t uiFrameDraw(
sprites[1].uvMax[0] = uv[2];
sprites[1].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 2, 0, uv);
tilesetPositionGetUV(&UI_FRAME.tileset, 2, 0, uv);
sprites[2].min[0] = x + width - tileW;
sprites[2].min[1] = y;
sprites[2].min[2] = 0.0f;
@@ -73,7 +106,7 @@ errorret_t uiFrameDraw(
sprites[2].uvMax[0] = uv[2];
sprites[2].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 0, 1, uv);
tilesetPositionGetUV(&UI_FRAME.tileset, 0, 1, uv);
sprites[3].min[0] = x;
sprites[3].min[1] = y + tileH;
sprites[3].min[2] = 0.0f;
@@ -85,7 +118,7 @@ errorret_t uiFrameDraw(
sprites[3].uvMax[0] = uv[2];
sprites[3].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 1, 1, uv);
tilesetPositionGetUV(&UI_FRAME.tileset, 1, 1, uv);
sprites[4].min[0] = x + tileW;
sprites[4].min[1] = y + tileH;
sprites[4].min[2] = 0.0f;
@@ -97,7 +130,7 @@ errorret_t uiFrameDraw(
sprites[4].uvMax[0] = uv[2];
sprites[4].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 2, 1, uv);
tilesetPositionGetUV(&UI_FRAME.tileset, 2, 1, uv);
sprites[5].min[0] = x + width - tileW;
sprites[5].min[1] = y + tileH;
sprites[5].min[2] = 0.0f;
@@ -109,7 +142,7 @@ errorret_t uiFrameDraw(
sprites[5].uvMax[0] = uv[2];
sprites[5].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 0, 2, uv);
tilesetPositionGetUV(&UI_FRAME.tileset, 0, 2, uv);
sprites[6].min[0] = x;
sprites[6].min[1] = y + height - tileH;
sprites[6].min[2] = 0.0f;
@@ -121,7 +154,7 @@ errorret_t uiFrameDraw(
sprites[6].uvMax[0] = uv[2];
sprites[6].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 1, 2, uv);
tilesetPositionGetUV(&UI_FRAME.tileset, 1, 2, uv);
sprites[7].min[0] = x + tileW;
sprites[7].min[1] = y + height - tileH;
sprites[7].min[2] = 0.0f;
@@ -133,7 +166,7 @@ errorret_t uiFrameDraw(
sprites[7].uvMax[0] = uv[2];
sprites[7].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 2, 2, uv);
tilesetPositionGetUV(&UI_FRAME.tileset, 2, 2, uv);
sprites[8].min[0] = x + width - tileW;
sprites[8].min[1] = y + height - tileH;
sprites[8].min[2] = 0.0f;
@@ -148,7 +181,6 @@ errorret_t uiFrameDraw(
return spriteBatchBuffer(sprites, 9, &SHADER_UNLIT, material);
}
void uiFrameDispose(uiframe_t *frame) {
assertNotNull(frame, "frame must not be NULL");
frame->texture = NULL;
errorret_t uiFrameDispose(void) {
return textureDispose(&UI_FRAME.texture);
}
@@ -10,24 +10,29 @@
#include "display/texture/texture.h"
#include "display/texture/tileset.h"
#define UIFRAME_DUMMY_WIDTH 16
#define UIFRAME_DUMMY_HEIGHT 16
typedef struct {
tileset_t tileset;
texture_t *texture;
texture_t texture;
color_t pixels[UIFRAME_DUMMY_WIDTH * UIFRAME_DUMMY_HEIGHT];
} uiframe_t;
/**
* Initializes a UI frame element.
*
* @param frame The frame to initialize.
* @return Any error that occurs.
*/
errorret_t uiFrameInit(uiframe_t *frame);
extern uiframe_t UI_FRAME;
/**
* Draws a UI frame using 9-slice rendering from a 3x3 tileset.
* Initializes the global UI_FRAME: builds the dummy texture and
* configures the tileset. Call once after displayInit().
*
* @return Any error that occurs.
*/
errorret_t uiFrameInit(void);
/**
* Draws UI_FRAME using 9-slice rendering from its 3x3 tileset.
* Pushes quads to the sprite batch without flushing.
*
* @param frame The frame to draw.
* @param x Screen x position.
* @param y Screen y position.
* @param width Total width of the frame.
@@ -35,7 +40,6 @@ errorret_t uiFrameInit(uiframe_t *frame);
* @return Any error that occurs.
*/
errorret_t uiFrameDraw(
const uiframe_t *frame,
const float_t x,
const float_t y,
const float_t width,
@@ -43,8 +47,8 @@ errorret_t uiFrameDraw(
);
/**
* Disposes of a UI frame element. Does not dispose the texture.
* Disposes of the global UI_FRAME, releasing its GPU texture.
*
* @param frame The frame to dispose.
* @return Any error that occurs.
*/
void uiFrameDispose(uiframe_t *frame);
errorret_t uiFrameDispose(void);
+34
View File
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uisettings.h"
#include "ui/frame/uiframe.h"
#include "util/memory.h"
uisettings_t UI_SETTINGS;
errorret_t uiSettingsInit(void) {
memoryZero(&UI_SETTINGS, sizeof(uisettings_t));
errorOk();
}
errorret_t uiSettingsUpdate(void) {
// if(!UI_SETTINGS.visible) errorOk();
errorOk();
}
errorret_t uiSettingsDraw(void) {
// if(!UI_SETTINGS.visible) errorOk();
errorChain(uiFrameDraw(
100.0f, 100.0f, 1.0f, 1.0f
));
errorOk();
}
errorret_t uiSettingsDispose(void) {
errorOk();
}
+43
View File
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef struct {
bool_t visible;
} uisettings_t;
extern uisettings_t UI_SETTINGS;
/**
* Initializes the settings panel.
*
* @return Any error that occurs.
*/
errorret_t uiSettingsInit(void);
/**
* Updates the settings panel. Handles input when visible.
*
* @return Any error that occurs.
*/
errorret_t uiSettingsUpdate(void);
/**
* Draws the settings panel. No-op when not visible.
*
* @return Any error that occurs.
*/
errorret_t uiSettingsDraw(void);
/**
* Disposes of the settings panel.
*
* @return Any error that occurs.
*/
errorret_t uiSettingsDispose(void);
+11
View File
@@ -0,0 +1,11 @@
# 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
uicrop.c
uifullbox.c
uiloading.c
)
@@ -12,7 +12,7 @@
#include "display/text/text.h"
#include "display/screen/screen.h"
#include "display/spritebatch/spritebatch.h"
#include "ui/uifullbox.h"
#include "uifullbox.h"
#define UI_LOADING_TEXT "loading"
+5 -5
View File
@@ -11,7 +11,7 @@
#include "display/spritebatch/spritebatch.h"
#include "display/screen/screen.h"
#include "ui/uielement.h"
#include "ui/uifocus/uifocus.h"
#include "ui/focus/uifocus.h"
#include "log/log.h"
ui_t UI;
@@ -21,7 +21,7 @@ errorret_t uiInit(void) {
uiFocusInit();
uielement_t *element = &UI_ELEMENTS[0];
while(element->draw != NULL) {
while(!uiElementIsNull(element)) {
errorChain(uiElementInit(element));
element++;
}
@@ -33,7 +33,7 @@ errorret_t uiUpdate(void) {
uiFocusUpdate();
uielement_t *element = &UI_ELEMENTS[0];
while(element->draw != NULL) {
while(!uiElementIsNull(element)) {
errorChain(uiElementUpdate(element));
element++;
}
@@ -43,7 +43,7 @@ errorret_t uiUpdate(void) {
errorret_t uiRender(void) {
const uielement_t *element = &UI_ELEMENTS[0];
while(element->draw != NULL) {
while(!uiElementIsNull(element)) {
errorChain(uiElementDraw(element));
if(SPRITEBATCH.spriteCount > 0) {
@@ -58,7 +58,7 @@ errorret_t uiRender(void) {
errorret_t uiDispose(void) {
uielement_t *element = &UI_ELEMENTS[0];
while(element->draw != NULL) {
while(!uiElementIsNull(element)) {
errorChain(uiElementDispose(element));
element++;
}
+35 -11
View File
@@ -7,16 +7,24 @@
#include "uielement.h"
#include "assert/assert.h"
#include "ui/frame/uiframe.h"
#include "ui/debug/uifps.h"
#include "engine/engine.h"
#include "ui/uitextbox.h"
#include "ui/uifullbox.h"
#include "ui/uiloading.h"
#include "ui/overlay/uifullbox.h"
#include "ui/overlay/uiloading.h"
#include "ui/debug/uiplayerpos.h"
#include "ui/uicrop.h"
#include "ui/overlay/uicrop.h"
#include "ui/debug/uiconsole.h"
#include "ui/frame/uiframe.h"
#include "ui/frame/uisettings.h"
uielement_t UI_ELEMENTS[] = {
{
.init = uiFrameInit,
.dispose = uiFrameDispose
},
// Fullbox under: above scene, below system UI.
{
@@ -25,14 +33,21 @@ uielement_t UI_ELEMENTS[] = {
.draw = uiFullboxUnderDraw
},
{
.init = uiSettingsInit,
.update = uiSettingsUpdate,
.draw = uiSettingsDraw,
.dispose = uiSettingsDispose
},
// { .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } },
{
.init = uiTextboxInit,
.update = uiTextboxUpdate,
.draw = uiTextboxDraw,
.dispose = uiTextboxDispose
},
// {
// .init = uiTextboxInit,
// .update = uiTextboxUpdate,
// .draw = uiTextboxDraw,
// .dispose = uiTextboxDispose
// },
// Fullbox over: above absolutely everything.
{
@@ -60,8 +75,16 @@ uielement_t UI_ELEMENTS[] = {
{ 0 } // Null terminator
};
bool_t uiElementIsNull(const uielement_t *element) {
return element->init == NULL &&
element->update == NULL &&
element->draw == NULL &&
element->dispose == NULL;
}
errorret_t uiElementInit(uielement_t *element) {
assertNotNull(element, "element must not be NULL");
errorChain(uiFrameInit());
if(element->init != NULL) errorChain(element->init());
errorOk();
}
@@ -74,12 +97,13 @@ errorret_t uiElementUpdate(uielement_t *element) {
errorret_t uiElementDraw(const uielement_t *element) {
assertNotNull(element, "element must not be NULL");
assertNotNull(element->draw, "element draw callback must not be NULL");
return element->draw();
if(element->draw != NULL) errorChain(element->draw());
errorOk();
}
errorret_t uiElementDispose(uielement_t *element) {
assertNotNull(element, "element must not be NULL");
if(element->dispose != NULL) errorChain(element->dispose());
errorChain(uiFrameDispose());
errorOk();
}
+11 -2
View File
@@ -18,7 +18,16 @@ typedef struct {
extern uielement_t UI_ELEMENTS[];
/**
* Initializes a UI element, calling its init callback if set.
* Returns true when all four callbacks on the element are NULL,
* which marks the end of the UI_ELEMENTS array.
*
* @param element The element to test.
* @returns True if the element is the null terminator.
*/
bool_t uiElementIsNull(const uielement_t *element);
/**
* Initializes a UI element, invoking its init callback if set.
*
* @param element The element to initialize.
* @return Any error that occurs.
@@ -42,7 +51,7 @@ errorret_t uiElementUpdate(uielement_t *element);
errorret_t uiElementDraw(const uielement_t *element);
/**
* Disposes of a UI element, calling its dispose callback if set.
* Disposes of a UI element, invoking its dispose callback if set.
*
* @param element The element to dispose.
* @return Any error that occurs.
+230 -230
View File
@@ -1,278 +1,278 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
// // 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 "input/input.h"
#include "event/event.h"
#include "display/screen/screen.h"
#include "display/texture/texture.h"
#include "display/text/text.h"
#include "display/spritebatch/spritebatch.h"
#include "display/shader/shaderunlit.h"
// #include "uitextbox.h"
// #include "assert/assert.h"
// #include "util/memory.h"
// #include "util/string.h"
// #include "time/time.h"
// #include "input/input.h"
// #include "event/event.h"
// #include "display/screen/screen.h"
// #include "display/texture/texture.h"
// #include "display/text/text.h"
// #include "display/spritebatch/spritebatch.h"
// #include "display/shader/shaderunlit.h"
uitextbox_t UI_TEXTBOX;
// uitextbox_t UI_TEXTBOX;
errorret_t uiTextboxInit(void) {
memoryZero(&UI_TEXTBOX, sizeof(uitextbox_t));
UI_TEXTBOX.textColor = COLOR_WHITE;
UI_TEXTBOX.advanceAction = INPUT_ACTION_ACCEPT;
// errorret_t uiTextboxInit(void) {
// memoryZero(&UI_TEXTBOX, sizeof(uitextbox_t));
// UI_TEXTBOX.textColor = COLOR_WHITE;
// UI_TEXTBOX.advanceAction = INPUT_ACTION_ACCEPT;
UI_TEXTBOX.font = &FONT_DEFAULT;
// UI_TEXTBOX.font = &FONT_DEFAULT;
float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth;
float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
float_t tbHeight = (
(float_t)UI_TEXTBOX_LINES_PER_PAGE_MAX * fontH +
(float_t)(UI_TEXTBOX_LINES_PER_PAGE_MAX - 1) * UI_TEXTBOX_LINE_SPACING +
2.0f * fontH
);
UI_TEXTBOX.x = (float_t)SCREEN.scanX + 10.0f;
UI_TEXTBOX.y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) -
tbHeight - 10.0f;
UI_TEXTBOX.width = (float_t)SCREEN.scanWidth - 20.0f;
UI_TEXTBOX.height = tbHeight;
// float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth;
// float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
// float_t tbHeight = (
// (float_t)UI_TEXTBOX_LINES_PER_PAGE_MAX * fontH +
// (float_t)(UI_TEXTBOX_LINES_PER_PAGE_MAX - 1) * UI_TEXTBOX_LINE_SPACING +
// 2.0f * fontH
// );
// UI_TEXTBOX.x = (float_t)SCREEN.scanX + 10.0f;
// UI_TEXTBOX.y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) -
// tbHeight - 10.0f;
// UI_TEXTBOX.width = (float_t)SCREEN.scanWidth - 20.0f;
// UI_TEXTBOX.height = tbHeight;
UI_TEXTBOX.frame.tileset.columns = 3;
UI_TEXTBOX.frame.tileset.rows = 3;
UI_TEXTBOX.frame.tileset.tileCount = 9;
UI_TEXTBOX.frame.tileset.tileWidth = FONT_DEFAULT.tileset->tileWidth;
UI_TEXTBOX.frame.tileset.tileHeight = FONT_DEFAULT.tileset->tileHeight;
UI_TEXTBOX.frame.tileset.uv[0] = 1.0f / 3.0f;
UI_TEXTBOX.frame.tileset.uv[1] = 1.0f / 3.0f;
UI_TEXTBOX.frame.texture = &TEXTURE_WHITE;
// UI_TEXTBOX.frame.tileset.columns = 3;
// UI_TEXTBOX.frame.tileset.rows = 3;
// UI_TEXTBOX.frame.tileset.tileCount = 9;
// UI_TEXTBOX.frame.tileset.tileWidth = FONT_DEFAULT.tileset->tileWidth;
// UI_TEXTBOX.frame.tileset.tileHeight = FONT_DEFAULT.tileset->tileHeight;
// UI_TEXTBOX.frame.tileset.uv[0] = 1.0f / 3.0f;
// UI_TEXTBOX.frame.tileset.uv[1] = 1.0f / 3.0f;
// UI_TEXTBOX.frame.texture = &TEXTURE_WHITE;
eventInit(
&UI_TEXTBOX.onPageComplete,
UI_TEXTBOX.onPageCompleteCallbacks,
UI_TEXTBOX.onPageCompleteUsers,
4
);
eventInit(
&UI_TEXTBOX.onLastPage,
UI_TEXTBOX.onLastPageCallbacks,
UI_TEXTBOX.onLastPageUsers,
4
);
// eventInit(
// &UI_TEXTBOX.onPageComplete,
// UI_TEXTBOX.onPageCompleteCallbacks,
// UI_TEXTBOX.onPageCompleteUsers,
// 4
// );
// eventInit(
// &UI_TEXTBOX.onLastPage,
// UI_TEXTBOX.onLastPageCallbacks,
// UI_TEXTBOX.onLastPageUsers,
// 4
// );
errorOk();
}
// errorOk();
// }
void uiTextboxBuildLayout(void) {
UI_TEXTBOX.lineCount = 0;
UI_TEXTBOX.pageCount = 1;
// void uiTextboxBuildLayout(void) {
// UI_TEXTBOX.lineCount = 0;
// UI_TEXTBOX.pageCount = 1;
float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth;
float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight;
float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth;
float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight;
// float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth;
// float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight;
// float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth;
// float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight;
if(fontW <= 0.0f || fontH <= 0.0f) return;
// if(fontW <= 0.0f || fontH <= 0.0f) return;
float_t contentW = UI_TEXTBOX.width - 2.0f * frameTileW;
float_t contentH = UI_TEXTBOX.height - 2.0f * frameTileH;
// float_t contentW = UI_TEXTBOX.width - 2.0f * frameTileW;
// float_t contentH = UI_TEXTBOX.height - 2.0f * frameTileH;
UI_TEXTBOX.charsPerLine = (int32_t)(contentW / fontW);
UI_TEXTBOX.linesPerPage = UI_TEXTBOX_LINES_PER_PAGE_MAX;
// UI_TEXTBOX.charsPerLine = (int32_t)(contentW / fontW);
// UI_TEXTBOX.linesPerPage = UI_TEXTBOX_LINES_PER_PAGE_MAX;
if(UI_TEXTBOX.charsPerLine <= 0 || UI_TEXTBOX.linesPerPage <= 0) return;
if(UI_TEXTBOX.text[0] == '\0') return;
// if(UI_TEXTBOX.charsPerLine <= 0 || UI_TEXTBOX.linesPerPage <= 0) return;
// if(UI_TEXTBOX.text[0] == '\0') return;
char_t *src = UI_TEXTBOX.text;
int32_t i = 0;
// char_t *src = UI_TEXTBOX.text;
// int32_t i = 0;
while(src[i] != '\0' && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) {
if(src[i] == '\t') {
i++;
int32_t rem = UI_TEXTBOX.lineCount % UI_TEXTBOX.linesPerPage;
int32_t pad = rem > 0 ? UI_TEXTBOX.linesPerPage - rem : 0;
while(pad > 0 && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) {
UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = i;
UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = 0;
UI_TEXTBOX.lineCount++;
pad--;
}
continue;
}
// while(src[i] != '\0' && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) {
// if(src[i] == '\t') {
// i++;
// int32_t rem = UI_TEXTBOX.lineCount % UI_TEXTBOX.linesPerPage;
// int32_t pad = rem > 0 ? UI_TEXTBOX.linesPerPage - rem : 0;
// while(pad > 0 && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) {
// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = i;
// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = 0;
// UI_TEXTBOX.lineCount++;
// pad--;
// }
// continue;
// }
int32_t lineStart = i;
int32_t lineWidth = 0;
// int32_t lineStart = i;
// int32_t lineWidth = 0;
while(src[i] != '\0') {
char_t c = src[i];
// while(src[i] != '\0') {
// char_t c = src[i];
if(c == '\n') {
i++;
break;
}
// if(c == '\n') {
// i++;
// break;
// }
if(c == '\t') 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(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 > UI_TEXTBOX.charsPerLine
) {
i++;
break;
}
// if(
// lineWidth > 0 &&
// lineWidth + 1 + wordLen > UI_TEXTBOX.charsPerLine
// ) {
// i++;
// break;
// }
lineWidth++;
i++;
} else {
if(lineWidth >= UI_TEXTBOX.charsPerLine) break;
lineWidth++;
i++;
}
}
// lineWidth++;
// i++;
// } else {
// if(lineWidth >= UI_TEXTBOX.charsPerLine) break;
// lineWidth++;
// i++;
// }
// }
UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = lineStart;
UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = lineWidth;
UI_TEXTBOX.lineCount++;
}
// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = lineStart;
// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = lineWidth;
// UI_TEXTBOX.lineCount++;
// }
if(UI_TEXTBOX.lineCount == 0) {
UI_TEXTBOX.pageCount = 1;
} else {
int32_t div = UI_TEXTBOX.lineCount + UI_TEXTBOX.linesPerPage - 1;
UI_TEXTBOX.pageCount = div / UI_TEXTBOX.linesPerPage;
}
}
// if(UI_TEXTBOX.lineCount == 0) {
// UI_TEXTBOX.pageCount = 1;
// } else {
// int32_t div = UI_TEXTBOX.lineCount + UI_TEXTBOX.linesPerPage - 1;
// UI_TEXTBOX.pageCount = div / UI_TEXTBOX.linesPerPage;
// }
// }
void uiTextboxSetText(const char_t *text) {
assertNotNull(text, "text must not be NULL");
// void uiTextboxSetText(const char_t *text) {
// assertNotNull(text, "text must not be NULL");
stringCopy(UI_TEXTBOX.text, text, UI_TEXTBOX_TEXT_MAX);
UI_TEXTBOX.currentPage = 0;
UI_TEXTBOX.scroll = 0;
uiTextboxBuildLayout();
}
// stringCopy(UI_TEXTBOX.text, text, UI_TEXTBOX_TEXT_MAX);
// UI_TEXTBOX.currentPage = 0;
// UI_TEXTBOX.scroll = 0;
// uiTextboxBuildLayout();
// }
errorret_t uiTextboxUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) errorOk();
#endif
// errorret_t uiTextboxUpdate(void) {
// #ifdef DUSK_TIME_DYNAMIC
// if(TIME.dynamicUpdate) errorOk();
// #endif
bool_t wasComplete = uiTextboxPageIsComplete();
// bool_t wasComplete = uiTextboxPageIsComplete();
if(!wasComplete) {
UI_TEXTBOX.scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK;
}
// if(!wasComplete) {
// UI_TEXTBOX.scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK;
// }
if(inputPressed(UI_TEXTBOX.advanceAction)) {
if(!uiTextboxPageIsComplete()) {
UI_TEXTBOX.scroll = uiTextboxGetPageCharCount();
} else if(uiTextboxHasNextPage()) {
uiTextboxNextPage();
}
}
// if(inputPressed(UI_TEXTBOX.advanceAction)) {
// if(!uiTextboxPageIsComplete()) {
// UI_TEXTBOX.scroll = uiTextboxGetPageCharCount();
// } else if(uiTextboxHasNextPage()) {
// uiTextboxNextPage();
// }
// }
if(!wasComplete && uiTextboxPageIsComplete()) {
eventInvoke(&UI_TEXTBOX.onPageComplete, &UI_TEXTBOX);
if(!uiTextboxHasNextPage()) {
eventInvoke(&UI_TEXTBOX.onLastPage, &UI_TEXTBOX);
}
}
// if(!wasComplete && uiTextboxPageIsComplete()) {
// eventInvoke(&UI_TEXTBOX.onPageComplete, &UI_TEXTBOX);
// if(!uiTextboxHasNextPage()) {
// eventInvoke(&UI_TEXTBOX.onLastPage, &UI_TEXTBOX);
// }
// }
errorOk();
}
// errorOk();
// }
int32_t uiTextboxGetPageCharCount(void) {
int32_t first = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage;
int32_t last = first + UI_TEXTBOX.linesPerPage;
if(last > UI_TEXTBOX.lineCount) last = UI_TEXTBOX.lineCount;
int32_t total = 0;
for(int32_t i = first; i < last; i++) {
total += UI_TEXTBOX.lines[i].count;
}
return total;
}
// int32_t uiTextboxGetPageCharCount(void) {
// int32_t first = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage;
// int32_t last = first + UI_TEXTBOX.linesPerPage;
// if(last > UI_TEXTBOX.lineCount) last = UI_TEXTBOX.lineCount;
// int32_t total = 0;
// for(int32_t i = first; i < last; i++) {
// total += UI_TEXTBOX.lines[i].count;
// }
// return total;
// }
bool_t uiTextboxPageIsComplete(void) {
return UI_TEXTBOX.scroll >= uiTextboxGetPageCharCount();
}
// bool_t uiTextboxPageIsComplete(void) {
// return UI_TEXTBOX.scroll >= uiTextboxGetPageCharCount();
// }
bool_t uiTextboxHasNextPage(void) {
return UI_TEXTBOX.currentPage + 1 < UI_TEXTBOX.pageCount;
}
// bool_t uiTextboxHasNextPage(void) {
// return UI_TEXTBOX.currentPage + 1 < UI_TEXTBOX.pageCount;
// }
void uiTextboxNextPage(void) {
if(!uiTextboxHasNextPage()) return;
UI_TEXTBOX.currentPage++;
UI_TEXTBOX.scroll = 0;
}
// void uiTextboxNextPage(void) {
// if(!uiTextboxHasNextPage()) return;
// UI_TEXTBOX.currentPage++;
// UI_TEXTBOX.scroll = 0;
// }
errorret_t uiTextboxDraw(void) {
if(UI_TEXTBOX.lineCount == 0 || UI_TEXTBOX.text[0] == '\0') errorOk();
// errorret_t uiTextboxDraw(void) {
// if(UI_TEXTBOX.lineCount == 0 || UI_TEXTBOX.text[0] == '\0') errorOk();
errorChain(uiFrameDraw(
&UI_TEXTBOX.frame,
UI_TEXTBOX.x, UI_TEXTBOX.y,
UI_TEXTBOX.width, UI_TEXTBOX.height
));
errorChain(spriteBatchFlush());
// errorChain(uiFrameDraw(
// &UI_TEXTBOX.frame,
// UI_TEXTBOX.x, UI_TEXTBOX.y,
// UI_TEXTBOX.width, UI_TEXTBOX.height
// ));
// errorChain(spriteBatchFlush());
shadermaterial_t textMaterial = {
.unlit = {
.color = UI_TEXTBOX.textColor,
.texture = UI_TEXTBOX.font->texture
}
};
// shadermaterial_t textMaterial = {
// .unlit = {
// .color = UI_TEXTBOX.textColor,
// .texture = UI_TEXTBOX.font->texture
// }
// };
float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth;
float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight;
float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth;
float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight;
float_t contentX = UI_TEXTBOX.x + frameTileW;
float_t contentY = UI_TEXTBOX.y + frameTileH;
// float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth;
// float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight;
// float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth;
// float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight;
// float_t contentX = UI_TEXTBOX.x + frameTileW;
// float_t contentY = UI_TEXTBOX.y + frameTileH;
int32_t pageFirst = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage;
int32_t pageLast = pageFirst + UI_TEXTBOX.linesPerPage;
if(pageLast > UI_TEXTBOX.lineCount) pageLast = UI_TEXTBOX.lineCount;
// int32_t pageFirst = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage;
// int32_t pageLast = pageFirst + UI_TEXTBOX.linesPerPage;
// if(pageLast > UI_TEXTBOX.lineCount) pageLast = UI_TEXTBOX.lineCount;
int32_t charsLeft = UI_TEXTBOX.scroll;
// int32_t charsLeft = UI_TEXTBOX.scroll;
for(int32_t li = pageFirst; li < pageLast && charsLeft > 0; li++) {
uitextboxline_t *line = &UI_TEXTBOX.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 li = pageFirst; li < pageLast && charsLeft > 0; li++) {
// uitextboxline_t *line = &UI_TEXTBOX.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 = UI_TEXTBOX.text[line->start + ci];
if(c == ' ') continue;
spritebatchsprite_t sprite = textGetSprite(
(vec2){ contentX + (float_t)ci * fontW, lineY },
c,
UI_TEXTBOX.font
);
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, textMaterial));
}
// for(int32_t ci = 0; ci < visible; ci++) {
// char_t c = UI_TEXTBOX.text[line->start + ci];
// if(c == ' ') continue;
// spritebatchsprite_t sprite = textGetSprite(
// (vec2){ contentX + (float_t)ci * fontW, lineY },
// c,
// UI_TEXTBOX.font
// );
// errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, textMaterial));
// }
charsLeft -= visible;
}
// charsLeft -= visible;
// }
errorChain(spriteBatchFlush());
errorOk();
}
// errorChain(spriteBatchFlush());
// errorOk();
// }
errorret_t uiTextboxDispose(void) {
uiFrameDispose(&UI_TEXTBOX.frame);
UI_TEXTBOX.font = NULL;
errorOk();
}
// errorret_t uiTextboxDispose(void) {
// uiFrameDispose(&UI_TEXTBOX.frame);
// UI_TEXTBOX.font = NULL;
// errorOk();
// }
+100 -100
View File
@@ -1,121 +1,121 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
// /**
// * Copyright (c) 2026 Dominic Masters
// *
// * This software is released under the MIT License.
// * https://opensource.org/licenses/MIT
// */
#pragma once
#include "uiframe.h"
#include "display/text/text.h"
#include "input/inputaction.h"
#include "event/event.h"
// #pragma once
// #include "ui/frame/uiframe.h"
// #include "display/text/text.h"
// #include "input/inputaction.h"
// #include "event/event.h"
#define UI_TEXTBOX_TEXT_MAX 1024
#define UI_TEXTBOX_LINES_MAX 64
#define UI_TEXTBOX_SCROLL_CHARS_PER_TICK 1
#define UI_TEXTBOX_LINES_PER_PAGE_MAX 3
#define UI_TEXTBOX_LINE_SPACING 0.0f
// #define UI_TEXTBOX_TEXT_MAX 1024
// #define UI_TEXTBOX_LINES_MAX 64
// #define UI_TEXTBOX_SCROLL_CHARS_PER_TICK 1
// #define UI_TEXTBOX_LINES_PER_PAGE_MAX 3
// #define UI_TEXTBOX_LINE_SPACING 0.0f
typedef struct {
int32_t start;
int32_t count;
} uitextboxline_t;
// typedef struct {
// int32_t start;
// int32_t count;
// } uitextboxline_t;
typedef struct {
uiframe_t frame;
// typedef struct {
// uiframe_t frame;
font_t *font;
color_t textColor;
// font_t *font;
// color_t textColor;
float_t x, y, width, height;
// float_t x, y, width, height;
char_t text[UI_TEXTBOX_TEXT_MAX];
// char_t text[UI_TEXTBOX_TEXT_MAX];
uitextboxline_t lines[UI_TEXTBOX_LINES_MAX];
int32_t lineCount;
int32_t charsPerLine;
int32_t linesPerPage;
int32_t pageCount;
// uitextboxline_t lines[UI_TEXTBOX_LINES_MAX];
// int32_t lineCount;
// int32_t charsPerLine;
// int32_t linesPerPage;
// int32_t pageCount;
int32_t currentPage;
int32_t scroll;
inputaction_t advanceAction;
// int32_t currentPage;
// int32_t scroll;
// inputaction_t advanceAction;
eventcallback_t onPageCompleteCallbacks[4];
void *onPageCompleteUsers[4];
event_t onPageComplete;
eventcallback_t onLastPageCallbacks[4];
void *onLastPageUsers[4];
event_t onLastPage;
} uitextbox_t;
// eventcallback_t onPageCompleteCallbacks[4];
// void *onPageCompleteUsers[4];
// event_t onPageComplete;
// eventcallback_t onLastPageCallbacks[4];
// void *onLastPageUsers[4];
// event_t onLastPage;
// } uitextbox_t;
extern uitextbox_t UI_TEXTBOX;
// extern uitextbox_t UI_TEXTBOX;
/**
* Initializes UI_TEXTBOX. Sets position and size from current screen
* dimensions and wires up the default font and frame textures.
* Call after displayInit().
*
* @return Any error that occurs.
*/
errorret_t uiTextboxInit(void);
// /**
// * Initializes UI_TEXTBOX. Sets position and size from current screen
// * dimensions and wires up the default font and frame textures.
// * Call after displayInit().
// *
// * @return Any error that occurs.
// */
// errorret_t uiTextboxInit(void);
/**
* Rebuilds the word-wrap and page layout from the current text and dimensions.
* Called automatically by uiTextboxSetText.
*/
void uiTextboxBuildLayout(void);
// /**
// * Rebuilds the word-wrap and page layout from the current text and dimensions.
// * Called automatically by uiTextboxSetText.
// */
// void uiTextboxBuildLayout(void);
/**
* Copies text into UI_TEXTBOX and rebuilds the word-wrap / page layout.
* Resets currentPage and scroll to 0.
*
* @param text Null-terminated source string.
*/
void uiTextboxSetText(const char_t *text);
// /**
// * Copies text into UI_TEXTBOX and rebuilds the word-wrap / page layout.
// * Resets currentPage and scroll to 0.
// *
// * @param text Null-terminated source string.
// */
// void uiTextboxSetText(const char_t *text);
/**
* Advances the typewriter scroll by UI_TEXTBOX_SCROLL_CHARS_PER_TICK.
* Skipped on dynamic ticks when DUSK_TIME_DYNAMIC is defined.
*
* @return Any error that occurs.
*/
errorret_t uiTextboxUpdate(void);
// /**
// * Advances the typewriter scroll by UI_TEXTBOX_SCROLL_CHARS_PER_TICK.
// * Skipped on dynamic ticks when DUSK_TIME_DYNAMIC is defined.
// *
// * @return Any error that occurs.
// */
// errorret_t uiTextboxUpdate(void);
/**
* Draws the frame and the currently visible text, including a final flush.
*
* @return Any error that occurs.
*/
errorret_t uiTextboxDraw(void);
// /**
// * Draws the frame and the currently visible text, including a final flush.
// *
// * @return Any error that occurs.
// */
// errorret_t uiTextboxDraw(void);
/**
* Returns the total char count for the current page.
*
* @return Total chars on current page.
*/
int32_t uiTextboxGetPageCharCount(void);
// /**
// * Returns the total char count for the current page.
// *
// * @return Total chars on current page.
// */
// int32_t uiTextboxGetPageCharCount(void);
/**
* Returns true when scroll has fully revealed the current page.
*/
bool_t uiTextboxPageIsComplete(void);
// /**
// * Returns true when scroll has fully revealed the current page.
// */
// bool_t uiTextboxPageIsComplete(void);
/**
* Returns true when there is at least one more page after the current one.
*/
bool_t uiTextboxHasNextPage(void);
// /**
// * Returns true when there is at least one more page after the current one.
// */
// bool_t uiTextboxHasNextPage(void);
/**
* Advances to the next page and resets scroll to 0.
* Has no effect if already on the last page.
*/
void uiTextboxNextPage(void);
// /**
// * Advances to the next page and resets scroll to 0.
// * Has no effect if already on the last page.
// */
// void uiTextboxNextPage(void);
/**
* Disposes of UI_TEXTBOX, nulling out texture pointers.
*
* @return Any error that occurs.
*/
errorret_t uiTextboxDispose(void);
// /**
// * Disposes of UI_TEXTBOX, nulling out texture pointers.
// *
// * @return Any error that occurs.
// */
// errorret_t uiTextboxDispose(void);