UI Frame finally doing things

This commit is contained in:
2026-06-25 20:58:44 -05:00
parent 722fe2ccfb
commit 3f35d56be4
16 changed files with 608 additions and 451 deletions
+1 -1
View File
@@ -61,10 +61,10 @@ errorret_t engineUpdate(void) {
inputUpdate();
consoleUpdate();
errorChain(rpgUpdate());
errorChain(uiUpdate());
errorChain(cutsceneUpdate());
errorChain(sceneUpdate());
errorChain(assetUpdate());
errorChain(uiUpdate());
// Render
errorChain(displayUpdate());
+2 -2
View File
@@ -7,8 +7,8 @@
#include "entity.h"
#include "assert/assert.h"
#include "console/console.h"
#include "rpg/cutscene/cutscenesystem.h"
#include "ui/rpg/uitextboxmain.h"
void entityInteractWith(entity_t *player, entity_t *target) {
assertNotNull(player, "Player entity pointer cannot be NULL");
@@ -24,7 +24,7 @@ void entityInteractWith(entity_t *player, entity_t *target) {
return;
case ENTITY_INTERACT_PRINT:
consolePrint(target->interact.data.message);
uiTextboxMainSetText(target->interact.data.message);
return;
case ENTITY_INTERACT_NULL:
+1
View File
@@ -7,6 +7,7 @@ add_subdirectory(debug)
add_subdirectory(frame)
add_subdirectory(focus)
add_subdirectory(overlay)
add_subdirectory(rpg)
add_subdirectory(widget)
# Sources
+16 -20
View File
@@ -49,13 +49,14 @@ uifocusitem_t * uiFocusPush(
item->closed = closed;
item->user = user;
UI_FOCUS.count++;
UI_FOCUS.pushedThisTick = true;
if(item->changed != NULL) item->changed(item);
return item;
}
void uiFocusPop(void) {
assertTrue(UI_FOCUS.count > 0, "UI focus stack underflow");
uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1];
if(item->closed != NULL) item->closed(item);
UI_FOCUS.count--;
@@ -120,36 +121,39 @@ void uiFocusMoveDirection(
void uiFocusUpdate(void) {
if(UI_FOCUS.count == 0) return;
// Current item
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return;
#endif
if(UI_FOCUS.pushedThisTick) {
UI_FOCUS.pushedThisTick = false;
return;
}
uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1];
// Accept
if(inputPressedIfDynamic(INPUT_ACTION_ACCEPT)) {
if(inputPressed(INPUT_ACTION_ACCEPT)) {
if(item->selected != NULL) item->selected(item);
return;
}
// Return
if(inputPressedIfDynamic(INPUT_ACTION_CANCEL)) {
if(inputPressed(INPUT_ACTION_CANCEL)) {
uiFocusPop();
return;
}
// Handle pressed directions
for(
const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP;
m->action != INPUT_ACTION_NULL;
m++
) {
if(!inputPressedIfDynamic(m->action)) continue;
if(!inputPressed(m->action)) continue;
UI_FOCUS.direction = m->direction;
UI_FOCUS.timeHeld = 0.0f;
uiFocusMoveDirection(item, m->direction);
return;
}
// Handle held directions
bool_t held = false;
for(
const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP;
@@ -157,26 +161,18 @@ void uiFocusUpdate(void) {
m++
) {
if(m->direction != UI_FOCUS.direction) continue;
held = inputDownIfDynamic(m->action);
held = inputIsDown(m->action);
break;
}
// Are we still holding?
if(!held) {
UI_FOCUS.direction = UI_FOCUS_DIRECTION_NONE;
UI_FOCUS.timeHeld = 0.0f;
return;
}
UI_FOCUS.timeHeld += TIME.delta;
#ifdef DUSK_TIME_DYNAMIC
UI_FOCUS.timeHeld += TIME.dynamicDelta;
#else
UI_FOCUS.timeHeld += TIME.delta;
#endif
// Can tick?
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) return;
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) return;
UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY;
+1
View File
@@ -53,6 +53,7 @@ typedef struct {
uint8_t count;
uifocusdirection_t direction;
float_t timeHeld;
bool_t pushedThisTick;
} uifocus_t;
extern uifocus_t UI_FOCUS;
+18 -12
View File
@@ -22,33 +22,39 @@ errorret_t uiFrameInit(void) {
color_t border = color4b(0, 100, 220, 255);
color_t center = color4b(0, 100, 220, 200);
for(uint8_t y = 0; y < UIFRAME_BORDER_HEIGHT; y++) {
for(uint8_t x = 0; x < UIFRAME_BORDER_WIDTH; x++) {
for(uint8_t y = 0; y < UI_FRAME_TEXTURE_HEIGHT_POW2; y++) {
for(uint8_t x = 0; x < UI_FRAME_TEXTURE_WIDTH_POW2; x++) {
color_t c;
if(x >= 12 || y >= 12) {// Because power of two is required.
if(x >= UI_FRAME_TEXTURE_WIDTH || y >= UI_FRAME_TEXTURE_HEIGHT) {
c = COLOR_TRANSPARENT;
} else if(y < 4 || y >= 8 || x < 4 || x >= 8) {
} else if(
y < UI_FRAME_TILE_HEIGHT || y >= UI_FRAME_TILE_HEIGHT * 2 ||
x < UI_FRAME_TILE_WIDTH || x >= UI_FRAME_TILE_WIDTH * 2
) {
c = border;
} else {
c = center;
}
UI_FRAME.pixels[y * UIFRAME_BORDER_WIDTH + x] = c;
UI_FRAME.pixels[y * UI_FRAME_TEXTURE_WIDTH_POW2 + x] = c;
}
}
errorChain(textureInit(
&UI_FRAME.texture, UIFRAME_BORDER_WIDTH, UIFRAME_BORDER_HEIGHT,
&UI_FRAME.texture,
UI_FRAME_TEXTURE_WIDTH_POW2, UI_FRAME_TEXTURE_HEIGHT_POW2,
TEXTURE_FORMAT_RGBA,
(texturedata_t){ .rgbaColors = UI_FRAME.pixels }
));
UI_FRAME.tileset.tileWidth = 4;
UI_FRAME.tileset.tileHeight = 4;
UI_FRAME.tileset.tileWidth = UI_FRAME_TILE_WIDTH;
UI_FRAME.tileset.tileHeight = UI_FRAME_TILE_HEIGHT;
UI_FRAME.tileset.columns = 3;
UI_FRAME.tileset.rows = 3;
UI_FRAME.tileset.tileCount = 9;
UI_FRAME.tileset.uv[0] = 4.0f / UIFRAME_BORDER_WIDTH;
UI_FRAME.tileset.uv[1] = 4.0f / UIFRAME_BORDER_HEIGHT;
UI_FRAME.tileset.uv[0] =
(float_t)UI_FRAME_TILE_WIDTH / UI_FRAME_TEXTURE_WIDTH_POW2;
UI_FRAME.tileset.uv[1] =
(float_t)UI_FRAME_TILE_HEIGHT / UI_FRAME_TEXTURE_HEIGHT_POW2;
errorOk();
}
@@ -66,8 +72,8 @@ errorret_t uiFrameDraw(
}
};
spritebatchsprite_t sprites[9];
float_t tileW = (float_t)UI_FRAME.tileset.tileWidth;
float_t tileH = (float_t)UI_FRAME.tileset.tileHeight;
float_t tileW = (float_t)UI_FRAME_BORDER_WIDTH;
float_t tileH = (float_t)UI_FRAME_BORDER_HEIGHT;
sprites[0] = spriteBatchSpriteTilesetPosition(
&UI_FRAME.tileset, 0, 0,
+9 -3
View File
@@ -10,13 +10,19 @@
#include "display/texture/texture.h"
#include "display/texture/tileset.h"
#define UIFRAME_BORDER_WIDTH 16
#define UIFRAME_BORDER_HEIGHT 16
#define UI_FRAME_BORDER_WIDTH 6
#define UI_FRAME_BORDER_HEIGHT 6
#define UI_FRAME_TILE_WIDTH 1
#define UI_FRAME_TILE_HEIGHT 1
#define UI_FRAME_TEXTURE_WIDTH (UI_FRAME_TILE_WIDTH * 3)
#define UI_FRAME_TEXTURE_HEIGHT (UI_FRAME_TILE_HEIGHT * 3)
#define UI_FRAME_TEXTURE_WIDTH_POW2 4
#define UI_FRAME_TEXTURE_HEIGHT_POW2 4
typedef struct {
tileset_t tileset;
texture_t texture;
color_t pixels[UIFRAME_BORDER_WIDTH * UIFRAME_BORDER_HEIGHT];
color_t pixels[UI_FRAME_BORDER_WIDTH * UI_FRAME_BORDER_HEIGHT];
} uiframe_t;
extern uiframe_t UI_FRAME;
+4 -4
View File
@@ -79,10 +79,10 @@ errorret_t uiSettingsDraw(void) {
errorChain(uiFrameDraw(0.0f, 0.0f, 300.0f, 300));
errorChain(uiMenuDraw(
&UI_SETTINGS.menu,
UIFRAME_BORDER_WIDTH,
UIFRAME_BORDER_HEIGHT,
300.0f - (UIFRAME_BORDER_WIDTH * 2),
300.0f - (UIFRAME_BORDER_HEIGHT * 2)
UI_FRAME_BORDER_WIDTH,
UI_FRAME_BORDER_HEIGHT,
300.0f - (UI_FRAME_BORDER_WIDTH * 2),
300.0f - (UI_FRAME_BORDER_HEIGHT * 2)
));
errorChain(spriteBatchFlush());
+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
uitextbox.c
uitextboxmain.c
)
+242
View File
@@ -0,0 +1,242 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "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) {
assertNotNull(box, "Textbox cannot be NULL");
memoryZero(box, sizeof(uitextbox_t));
}
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, UI_TEXTBOX_TEXT_MAX);
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 < UI_TEXTBOX_LINES_MAX) {
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 < UI_TEXTBOX_LINES_MAX) {
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 borderW = (float_t)UI_FRAME_BORDER_WIDTH;
float_t borderH = (float_t)UI_FRAME_BORDER_HEIGHT;
float_t contentX = x + borderW;
float_t contentY = y + borderH;
float_t contentW = width - 2.0f * borderW;
float_t contentH = height - 2.0f * borderH;
if(contentW != box->layoutWidth || contentH != box->layoutHeight) {
uiTextboxBuildLayout(box, contentW, contentH);
}
errorChain(uiFrameDraw(x, y, width, height));
errorChain(spriteBatchFlush());
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;
}
if(uiTextboxPageIsComplete(box)) {
spritebatchsprite_t caret = textGetSprite(
(vec2){
contentX + contentW - fontW,
contentY + contentH - fontH
},
'v',
&FONT_DEFAULT
);
errorChain(spriteBatchBuffer(&caret, 1, &SHADER_UNLIT, material));
}
errorChain(spriteBatchFlush());
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;
}
+127
View File
@@ -0,0 +1,127 @@
/**
* 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_TEXT_MAX 1024
#define UI_TEXTBOX_LINES_MAX 64
#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[UI_TEXTBOX_TEXT_MAX];
uitextboxline_t lines[UI_TEXTBOX_LINES_MAX];
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.
*
* @param box The textbox to initialize.
*/
void uiTextboxInit(uitextbox_t *box);
/**
* 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);
+85
View File
@@ -0,0 +1,85 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uitextboxmain.h"
#include "ui/focus/uifocus.h"
#include "display/screen/screen.h"
#include "display/text/text.h"
#include "ui/frame/uiframe.h"
uitextbox_t UI_TEXTBOX_MAIN;
static uifocusitem_t *focusItem = NULL;
errorret_t uiTextboxMainInit(void) {
uiTextboxInit(&UI_TEXTBOX_MAIN);
errorOk();
}
void uiTextboxMainSetText(const char_t *text) {
uiTextboxSetText(&UI_TEXTBOX_MAIN, text);
if(focusItem != NULL) return;
focusItem = uiFocusPush(
1, 1,
uiTextboxMainFocusSelected,
NULL,
uiTextboxMainFocusClosed,
NULL
);
}
errorret_t uiTextboxMainUpdate(void) {
if(focusItem == NULL) errorOk();
return uiTextboxUpdate(&UI_TEXTBOX_MAIN);
}
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_BORDER_HEIGHT;
float_t w = (float_t)SCREEN.scanWidth;
float_t x = (float_t)SCREEN.scanX;
float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - h;
return uiTextboxDraw(&UI_TEXTBOX_MAIN, x, y, w, h);
}
bool_t uiTextboxMainPageIsComplete(void) {
return uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN);
}
bool_t uiTextboxMainHasNextPage(void) {
return uiTextboxHasNextPage(&UI_TEXTBOX_MAIN);
}
void uiTextboxMainNextPage(void) {
uiTextboxNextPage(&UI_TEXTBOX_MAIN);
}
bool_t uiTextboxMainIsActive(void) {
return focusItem != NULL;
}
bool_t uiTextboxMainFocusSelected(const uifocusitem_t *item) {
if(!uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN)) {
UI_TEXTBOX_MAIN.scroll = uiTextboxGetPageCharCount(&UI_TEXTBOX_MAIN);
return true;
}
if(uiTextboxHasNextPage(&UI_TEXTBOX_MAIN)) {
uiTextboxNextPage(&UI_TEXTBOX_MAIN);
return true;
}
uiFocusPopItem(focusItem);
return true;
}
bool_t uiTextboxMainFocusClosed(const uifocusitem_t *item) {
focusItem = NULL;
return true;
}
+86
View File
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "ui/rpg/uitextbox.h"
#include "ui/focus/uifocusitem.h"
#define UI_TEXTBOX_MAIN_LINES 4
extern uitextbox_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);
+6 -10
View File
@@ -10,14 +10,13 @@
#include "ui/frame/uiframe.h"
#include "ui/debug/uifps.h"
#include "engine/engine.h"
#include "ui/uitextbox.h"
#include "ui/overlay/uifullbox.h"
#include "ui/overlay/uiloading.h"
#include "ui/debug/uiplayerpos.h"
#include "ui/overlay/uicrop.h"
#include "ui/debug/uiconsole.h"
#include "ui/frame/uiframe.h"
#include "ui/frame/uisettings.h"
#include "ui/rpg/uitextboxmain.h"
uielement_t UI_ELEMENTS[] = {
{
@@ -39,14 +38,11 @@ uielement_t UI_ELEMENTS[] = {
.dispose = uiSettingsDispose
},
// { .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } },
// {
// .init = uiTextboxInit,
// .update = uiTextboxUpdate,
// .draw = uiTextboxDraw,
// .dispose = uiTextboxDispose
// },
{
.init = uiTextboxMainInit,
.update = uiTextboxMainUpdate,
.draw = uiTextboxMainDraw
},
// Fullbox over: above absolutely everything.
{
-278
View File
@@ -1,278 +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 "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;
// 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;
// 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;
// eventInit(
// &UI_TEXTBOX.onPageComplete,
// UI_TEXTBOX.onPageCompleteCallbacks,
// UI_TEXTBOX.onPageCompleteUsers,
// 4
// );
// eventInit(
// &UI_TEXTBOX.onLastPage,
// UI_TEXTBOX.onLastPageCallbacks,
// UI_TEXTBOX.onLastPageUsers,
// 4
// );
// errorOk();
// }
// 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;
// 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;
// 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;
// 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;
// }
// 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 > UI_TEXTBOX.charsPerLine
// ) {
// i++;
// break;
// }
// 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++;
// }
// 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");
// 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
// bool_t wasComplete = uiTextboxPageIsComplete();
// 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(!wasComplete && uiTextboxPageIsComplete()) {
// eventInvoke(&UI_TEXTBOX.onPageComplete, &UI_TEXTBOX);
// if(!uiTextboxHasNextPage()) {
// eventInvoke(&UI_TEXTBOX.onLastPage, &UI_TEXTBOX);
// }
// }
// 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;
// }
// bool_t uiTextboxPageIsComplete(void) {
// return UI_TEXTBOX.scroll >= uiTextboxGetPageCharCount();
// }
// 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;
// }
// 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());
// 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;
// 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;
// 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));
// }
// charsLeft -= visible;
// }
// errorChain(spriteBatchFlush());
// errorOk();
// }
// errorret_t uiTextboxDispose(void) {
// uiFrameDispose(&UI_TEXTBOX.frame);
// UI_TEXTBOX.font = NULL;
// errorOk();
// }
-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
// */
// #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
// typedef struct {
// int32_t start;
// int32_t count;
// } uitextboxline_t;
// typedef struct {
// uiframe_t frame;
// font_t *font;
// color_t textColor;
// float_t x, y, width, height;
// 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;
// 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;
// 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);
// /**
// * 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);
// /**
// * 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);
// /**
// * 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 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);
// /**
// * Disposes of UI_TEXTBOX, nulling out texture pointers.
// *
// * @return Any error that occurs.
// */
// errorret_t uiTextboxDispose(void);