Mini textboxes
This commit is contained in:
@@ -17,7 +17,7 @@ console_t CONSOLE;
|
||||
|
||||
void consoleInit(void) {
|
||||
memoryZero(&CONSOLE, sizeof(console_t));
|
||||
CONSOLE.visible = true;
|
||||
CONSOLE.visible = false;
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexInit(&CONSOLE.printMutex);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/item/itemgive.h"
|
||||
#include "ui/rpg/uitextboxmain.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
|
||||
void cutsceneItemGiveStart(
|
||||
const cutsceneitem_t *item,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "ui/rpg/uitextboxmain.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
|
||||
void cutsceneTextStart(
|
||||
const cutsceneitem_t *item,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "ui/rpg/uitextboxmain.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
|
||||
void entityInteractWith(entity_t *player, entity_t *target) {
|
||||
assertNotNull(player, "Player entity pointer cannot be NULL");
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "rpg/item/itemgive.h"
|
||||
#include "ui/rpg/uitextboxmain.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
|
||||
void entityItemInit(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include "itemgive.h"
|
||||
#include "rpg/item/backpack.h"
|
||||
#include "ui/rpg/uitextboxmain.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
#include "util/string.h"
|
||||
#include "error/error.h"
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "rpg/cutscene/scene/testcutscene.h"
|
||||
#include "rpg/item/backpack.h"
|
||||
#include "rpg/battle/party.h"
|
||||
#include "ui/rpg/textbox/uitextboxminilist.h"
|
||||
#include "time/time.h"
|
||||
#include "rpgcamera.h"
|
||||
#include "util/memory.h"
|
||||
@@ -64,6 +65,16 @@ errorret_t rpgInit(void) {
|
||||
backpackAdd(ITEM_ID_POTATO, 3);
|
||||
backpackAdd(ITEM_ID_APPLE, 8);
|
||||
|
||||
// TEST: Show a mini textbox at the world origin.
|
||||
uiTextboxMiniShow(
|
||||
&UI_TEXTBOX_MINI_LIST[0],
|
||||
"Hello, World!",
|
||||
(vec3){ 0.0f, 0.0f, 0.0f },
|
||||
100.0f,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
|
||||
// TEST: Create a test map area.
|
||||
uint8_t areaIndex = mapAreaAdd(
|
||||
(worldpos_t){ 11, 3, 0 },
|
||||
|
||||
@@ -56,6 +56,39 @@ void rpgCameraUpdateProjection(void) {
|
||||
);
|
||||
}
|
||||
|
||||
void rpgCameraUpdateEye(void) {
|
||||
float_t fov = glm_rad(RPG_CAMERA_FOV);
|
||||
float_t pixelsPerUnit = TILE_SIZE_PIXELS;
|
||||
float_t worldH = (float_t)(SCREEN.height / SCREEN.scale3d) / pixelsPerUnit;
|
||||
float_t z = (worldH * 0.5f) / tanf(fov * 0.5f);
|
||||
float_t offset = -24.0f * (worldH / TILE_SIZE_PIXELS);
|
||||
|
||||
vec3 target;
|
||||
rpgCameraGetPosition(target);
|
||||
glm_vec3_add(target, (vec3){ 0.5f, 0.5f, 0.5f }, target);
|
||||
|
||||
glm_lookat(
|
||||
(vec3){ target[0], target[1] + offset, target[2] + z },
|
||||
target,
|
||||
(vec3){ 0, 1, 0 }, // up
|
||||
RPG_CAMERA.eye
|
||||
);
|
||||
}
|
||||
|
||||
void rpgCameraToScreen(vec3 worldPos, vec2 out) {
|
||||
mat4 viewProj;
|
||||
glm_mat4_mul(RPG_CAMERA.projection, RPG_CAMERA.eye, viewProj);
|
||||
|
||||
vec4 viewport = {
|
||||
0.0f, 0.0f, (float_t)SCREEN.width, (float_t)SCREEN.height
|
||||
};
|
||||
vec3 window;
|
||||
glm_project(worldPos, viewProj, viewport, window);
|
||||
|
||||
out[0] = window[0];
|
||||
out[1] = (float_t)SCREEN.height - window[1];
|
||||
}
|
||||
|
||||
errorret_t rpgCameraUpdate(void) {
|
||||
if(!mapIsLoaded()) errorOk();
|
||||
|
||||
|
||||
@@ -59,4 +59,21 @@ errorret_t rpgCameraUpdate(void);
|
||||
* matrix is computed once and cached; on dynamic-display platforms it
|
||||
* is recomputed every call.
|
||||
*/
|
||||
void rpgCameraUpdateProjection(void);
|
||||
void rpgCameraUpdateProjection(void);
|
||||
|
||||
/**
|
||||
* Recomputes the camera eye/view matrix from the camera's current mode
|
||||
* and position, and stores it in RPG_CAMERA.eye. Unlike the projection
|
||||
* matrix this is never cached, since the camera position can change
|
||||
* every frame.
|
||||
*/
|
||||
void rpgCameraUpdateEye(void);
|
||||
|
||||
/**
|
||||
* Converts a world-space position to screen-space pixel coordinates,
|
||||
* using the camera's current eye and projection matrices.
|
||||
*
|
||||
* @param worldPos The world-space position to convert.
|
||||
* @param out Output vec2 filled with the screen-space pixel position.
|
||||
*/
|
||||
void rpgCameraToScreen(vec3 worldPos, vec2 out);
|
||||
@@ -44,7 +44,7 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
||||
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
|
||||
}));
|
||||
|
||||
mat4 model, eye;
|
||||
mat4 model;
|
||||
|
||||
errorChain(shaderBind(&SHADER_UNLIT));
|
||||
|
||||
@@ -59,27 +59,10 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
||||
));
|
||||
|
||||
// Camera Eye
|
||||
float_t fov = glm_rad(RPG_CAMERA_FOV);
|
||||
float_t pixelsPerUnit = TILE_SIZE_PIXELS;
|
||||
float_t worldH = (float_t)(SCREEN.height / SCREEN.scale3d) / pixelsPerUnit;
|
||||
float_t z = (worldH * 0.5f) / tanf(fov * 0.5f);
|
||||
vec3 worldPosVec;
|
||||
rpgCameraGetPosition(worldPosVec);
|
||||
float_t offset = -24.0f * (worldH / TILE_SIZE_PIXELS);
|
||||
|
||||
glm_vec3_add(worldPosVec, (vec3){ 0.5f, 0.5f, 0.5f }, worldPosVec);
|
||||
|
||||
glm_lookat(
|
||||
(vec3){
|
||||
worldPosVec[0],
|
||||
worldPosVec[1] + offset,
|
||||
worldPosVec[2] + z
|
||||
},
|
||||
worldPosVec,
|
||||
(vec3){ 0, 1, 0 }, // up
|
||||
eye
|
||||
);
|
||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, eye));
|
||||
rpgCameraUpdateEye();
|
||||
errorChain(shaderSetMatrix(
|
||||
&SHADER_UNLIT, SHADER_UNLIT_VIEW, RPG_CAMERA.eye
|
||||
));
|
||||
|
||||
// Base terrain meshes, drawn with normal depth testing.
|
||||
errorChain(sceneOverworldDrawChunksBase());
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
#define UI_FRAME_BORDER_WIDTH 6
|
||||
#define UI_FRAME_BORDER_HEIGHT 6
|
||||
#define UI_FRAME_PADDING_X 4
|
||||
#define UI_FRAME_PADDING_Y 4
|
||||
#define UI_FRAME_PADDING_X 2
|
||||
#define UI_FRAME_PADDING_Y 2
|
||||
#define UI_FRAME_START_X (UI_FRAME_BORDER_WIDTH + UI_FRAME_PADDING_X)
|
||||
#define UI_FRAME_START_Y (UI_FRAME_BORDER_HEIGHT + UI_FRAME_PADDING_Y)
|
||||
#define UI_FRAME_TILE_WIDTH 1
|
||||
|
||||
@@ -3,8 +3,4 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
uitextbox.c
|
||||
uitextboxmain.c
|
||||
)
|
||||
add_subdirectory(textbox)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
uitextbox.c
|
||||
uitextboxmain.c
|
||||
uitextboxmini.c
|
||||
uitextboxminilist.c
|
||||
)
|
||||
@@ -16,15 +16,29 @@
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "ui/frame/uiframe.h"
|
||||
|
||||
void uiTextboxInit(uitextbox_t *box) {
|
||||
void uiTextboxInit(
|
||||
uitextbox_t *box,
|
||||
char_t *text,
|
||||
const uint32_t maxLength,
|
||||
uitextboxline_t *lines,
|
||||
const uint32_t linesMax
|
||||
) {
|
||||
assertNotNull(box, "Textbox cannot be NULL");
|
||||
assertNotNull(text, "Text buffer cannot be NULL");
|
||||
assertTrue(maxLength >= 1, "maxLength must be at least 1");
|
||||
assertNotNull(lines, "Lines buffer cannot be NULL");
|
||||
assertTrue(linesMax >= 1, "linesMax must be at least 1");
|
||||
memoryZero(box, sizeof(uitextbox_t));
|
||||
box->text = text;
|
||||
box->maxLength = maxLength;
|
||||
box->lines = lines;
|
||||
box->linesMax = linesMax;
|
||||
}
|
||||
|
||||
void uiTextboxSetText(uitextbox_t *box, const char_t *text) {
|
||||
assertNotNull(box, "Textbox cannot be NULL");
|
||||
assertNotNull(text, "Text cannot be NULL");
|
||||
stringCopy(box->text, text, UI_TEXTBOX_TEXT_MAX);
|
||||
stringCopy(box->text, text, box->maxLength);
|
||||
box->currentPage = 0;
|
||||
box->scroll = 0;
|
||||
box->layoutWidth = 0.0f;
|
||||
@@ -60,12 +74,12 @@ void uiTextboxBuildLayout(
|
||||
char_t *src = box->text;
|
||||
int32_t i = 0;
|
||||
|
||||
while(src[i] != '\0' && box->lineCount < UI_TEXTBOX_LINES_MAX) {
|
||||
while(src[i] != '\0' && box->lineCount < (int32_t)box->linesMax) {
|
||||
if(src[i] == '\t') {
|
||||
i++;
|
||||
int32_t rem = box->lineCount % box->linesPerPage;
|
||||
int32_t pad = rem > 0 ? box->linesPerPage - rem : 0;
|
||||
while(pad > 0 && box->lineCount < UI_TEXTBOX_LINES_MAX) {
|
||||
while(pad > 0 && box->lineCount < (int32_t)box->linesMax) {
|
||||
box->lines[box->lineCount].start = i;
|
||||
box->lines[box->lineCount].count = 0;
|
||||
box->lineCount++;
|
||||
@@ -196,18 +210,6 @@ errorret_t uiTextboxDraw(
|
||||
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();
|
||||
}
|
||||
@@ -8,8 +8,6 @@
|
||||
#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
|
||||
@@ -20,9 +18,11 @@ typedef struct {
|
||||
} uitextboxline_t;
|
||||
|
||||
typedef struct {
|
||||
char_t text[UI_TEXTBOX_TEXT_MAX];
|
||||
char_t *text;
|
||||
uint32_t maxLength;
|
||||
|
||||
uitextboxline_t lines[UI_TEXTBOX_LINES_MAX];
|
||||
uitextboxline_t *lines;
|
||||
uint32_t linesMax;
|
||||
int32_t lineCount;
|
||||
int32_t charsPerLine;
|
||||
int32_t linesPerPage;
|
||||
@@ -37,11 +37,22 @@ typedef struct {
|
||||
} uitextbox_t;
|
||||
|
||||
/**
|
||||
* Initializes a textbox, zeroing all state.
|
||||
* Initializes a textbox, zeroing all state and binding it to caller-owned
|
||||
* text and line storage.
|
||||
*
|
||||
* @param box The textbox to initialize.
|
||||
* @param text Caller-owned buffer the textbox copies its text into.
|
||||
* @param maxLength Capacity of text, in characters.
|
||||
* @param lines Caller-owned buffer the textbox lays lines out into.
|
||||
* @param linesMax Capacity of lines, in entries.
|
||||
*/
|
||||
void uiTextboxInit(uitextbox_t *box);
|
||||
void uiTextboxInit(
|
||||
uitextbox_t *box,
|
||||
char_t *text,
|
||||
const uint32_t maxLength,
|
||||
uitextboxline_t *lines,
|
||||
const uint32_t linesMax
|
||||
);
|
||||
|
||||
/**
|
||||
* Copies text into the textbox and marks layout as dirty.
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uitextboxmain.h"
|
||||
#include "ui/focus/uifocus.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/color.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "ui/frame/uiframe.h"
|
||||
|
||||
uitextboxmain_t UI_TEXTBOX_MAIN;
|
||||
static uifocusitem_t *focusItem = NULL;
|
||||
|
||||
errorret_t uiTextboxMainInit(void) {
|
||||
uiTextboxInit(
|
||||
&UI_TEXTBOX_MAIN.box,
|
||||
UI_TEXTBOX_MAIN.text, UI_TEXTBOX_MAIN_TEXT_MAX,
|
||||
UI_TEXTBOX_MAIN.lines, UI_TEXTBOX_MAIN_LINES_MAX
|
||||
);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void uiTextboxMainSetText(const char_t *text) {
|
||||
uiTextboxSetText(&UI_TEXTBOX_MAIN.box, text);
|
||||
if(focusItem != NULL) return;
|
||||
focusItem = uiFocusPush(
|
||||
1, 1,
|
||||
uiTextboxMainFocusSelected,
|
||||
NULL,
|
||||
uiTextboxMainFocusClosed,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
errorret_t uiTextboxMainUpdate(void) {
|
||||
if(focusItem == NULL) errorOk();
|
||||
return uiTextboxUpdate(&UI_TEXTBOX_MAIN.box);
|
||||
}
|
||||
|
||||
errorret_t uiTextboxMainDraw(void) {
|
||||
if(focusItem == NULL) errorOk();
|
||||
float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
||||
float_t h = (float_t)UI_TEXTBOX_MAIN_LINES * fontH +
|
||||
(float_t)(UI_TEXTBOX_MAIN_LINES - 1) * UI_TEXTBOX_LINE_SPACING +
|
||||
2.0f * (float_t)UI_FRAME_START_Y;
|
||||
float_t w = (float_t)SCREEN.scanWidth;
|
||||
float_t x = (float_t)SCREEN.scanX;
|
||||
float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - h;
|
||||
errorChain(uiTextboxDraw(&UI_TEXTBOX_MAIN.box, x, y, w, h));
|
||||
|
||||
if(!uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box)) errorOk();
|
||||
|
||||
float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth;
|
||||
float_t contentX = x + (float_t)UI_FRAME_START_X;
|
||||
float_t contentY = y + (float_t)UI_FRAME_START_Y;
|
||||
float_t contentW = w - 2.0f * (float_t)UI_FRAME_START_X;
|
||||
float_t contentH = h - 2.0f * (float_t)UI_FRAME_START_Y;
|
||||
|
||||
shadermaterial_t material = {
|
||||
.unlit = {
|
||||
.color = COLOR_WHITE,
|
||||
.texture = FONT_DEFAULT.texture
|
||||
}
|
||||
};
|
||||
|
||||
spritebatchsprite_t caret = textGetSprite(
|
||||
(vec2){
|
||||
contentX + contentW - fontW,
|
||||
contentY + contentH - fontH
|
||||
},
|
||||
'v',
|
||||
&FONT_DEFAULT
|
||||
);
|
||||
errorChain(spriteBatchBuffer(&caret, 1, &SHADER_UNLIT, material));
|
||||
errorChain(spriteBatchFlush());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
bool_t uiTextboxMainPageIsComplete(void) {
|
||||
return uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box);
|
||||
}
|
||||
|
||||
bool_t uiTextboxMainHasNextPage(void) {
|
||||
return uiTextboxHasNextPage(&UI_TEXTBOX_MAIN.box);
|
||||
}
|
||||
|
||||
void uiTextboxMainNextPage(void) {
|
||||
uiTextboxNextPage(&UI_TEXTBOX_MAIN.box);
|
||||
}
|
||||
|
||||
bool_t uiTextboxMainIsActive(void) {
|
||||
return focusItem != NULL;
|
||||
}
|
||||
|
||||
bool_t uiTextboxMainFocusSelected(const uifocusitem_t *item) {
|
||||
if(!uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box)) {
|
||||
UI_TEXTBOX_MAIN.box.scroll =
|
||||
uiTextboxGetPageCharCount(&UI_TEXTBOX_MAIN.box);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(uiTextboxHasNextPage(&UI_TEXTBOX_MAIN.box)) {
|
||||
uiTextboxNextPage(&UI_TEXTBOX_MAIN.box);
|
||||
return true;
|
||||
}
|
||||
|
||||
uiFocusPopItem(focusItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool_t uiTextboxMainFocusClosed(const uifocusitem_t *item) {
|
||||
focusItem = NULL;
|
||||
return true;
|
||||
}
|
||||
@@ -6,12 +6,20 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "ui/rpg/uitextbox.h"
|
||||
#include "ui/rpg/textbox/uitextbox.h"
|
||||
#include "ui/focus/uifocusitem.h"
|
||||
|
||||
#define UI_TEXTBOX_MAIN_LINES 4
|
||||
#define UI_TEXTBOX_MAIN_TEXT_MAX 1024
|
||||
#define UI_TEXTBOX_MAIN_LINES_MAX 64
|
||||
|
||||
extern uitextbox_t UI_TEXTBOX_MAIN;
|
||||
typedef struct {
|
||||
uitextbox_t box;
|
||||
char_t text[UI_TEXTBOX_MAIN_TEXT_MAX];
|
||||
uitextboxline_t lines[UI_TEXTBOX_MAIN_LINES_MAX];
|
||||
} uitextboxmain_t;
|
||||
|
||||
extern uitextboxmain_t UI_TEXTBOX_MAIN;
|
||||
|
||||
/**
|
||||
* Initializes UI_TEXTBOX_MAIN.
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uitextboxmini.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "time/time.h"
|
||||
#include "rpg/rpgcamera.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "ui/frame/uiframe.h"
|
||||
|
||||
void uiTextboxMiniInit(uitextboxmini_t *mini) {
|
||||
assertNotNull(mini, "Mini textbox cannot be NULL");
|
||||
memoryZero(mini, sizeof(uitextboxmini_t));
|
||||
uiTextboxInit(
|
||||
&mini->box,
|
||||
mini->text, UI_TEXTBOX_MINI_TEXT_MAX,
|
||||
mini->lines, UI_TEXTBOX_MINI_LINES_MAX
|
||||
);
|
||||
}
|
||||
|
||||
void uiTextboxMiniShow(
|
||||
uitextboxmini_t *mini,
|
||||
const char_t *text,
|
||||
vec3 position,
|
||||
const float_t duration,
|
||||
uitextboxminiclosedcallback_t closed,
|
||||
void *user
|
||||
) {
|
||||
assertNotNull(mini, "Mini textbox cannot be NULL");
|
||||
assertNotNull(text, "Text cannot be NULL");
|
||||
uiTextboxSetText(&mini->box, text);
|
||||
glm_vec3_copy(position, mini->position);
|
||||
|
||||
int32_t textWidth, textHeight;
|
||||
textMeasure(text, &FONT_DEFAULT, &textWidth, &textHeight);
|
||||
float_t width = (float_t)textWidth + 2.0f * (float_t)UI_FRAME_START_X;
|
||||
mini->width = width < UI_TEXTBOX_MINI_WIDTH_MAX
|
||||
? width : UI_TEXTBOX_MINI_WIDTH_MAX;
|
||||
|
||||
float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
||||
float_t maxHeight = (float_t)UI_TEXTBOX_MINI_LINES_MAX * fontH +
|
||||
(float_t)(UI_TEXTBOX_MINI_LINES_MAX - 1) * UI_TEXTBOX_LINE_SPACING +
|
||||
2.0f * (float_t)UI_FRAME_START_Y;
|
||||
uiTextboxBuildLayout(
|
||||
&mini->box,
|
||||
mini->width - 2.0f * (float_t)UI_FRAME_START_X,
|
||||
maxHeight - 2.0f * (float_t)UI_FRAME_START_Y
|
||||
);
|
||||
int32_t lineCount = mini->box.lineCount > 0 ? mini->box.lineCount : 1;
|
||||
mini->height = (float_t)lineCount * fontH +
|
||||
(float_t)(lineCount - 1) * UI_TEXTBOX_LINE_SPACING +
|
||||
2.0f * (float_t)UI_FRAME_START_Y;
|
||||
|
||||
mini->active = true;
|
||||
mini->timer = duration;
|
||||
mini->closed = closed;
|
||||
mini->user = user;
|
||||
}
|
||||
|
||||
errorret_t uiTextboxMiniUpdate(uitextboxmini_t *mini) {
|
||||
assertNotNull(mini, "Mini textbox cannot be NULL");
|
||||
if(!mini->active) errorOk();
|
||||
|
||||
errorChain(uiTextboxUpdate(&mini->box));
|
||||
|
||||
mini->timer -= TIME.delta;
|
||||
if(mini->timer <= 0.0f) uiTextboxMiniClose(mini);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiTextboxMiniDraw(uitextboxmini_t *mini) {
|
||||
assertNotNull(mini, "Mini textbox cannot be NULL");
|
||||
if(!mini->active) errorOk();
|
||||
|
||||
vec2 screenPos;
|
||||
rpgCameraToScreen(mini->position, screenPos);
|
||||
|
||||
if(
|
||||
mini->clamp == UI_TEXTBOX_MINI_CLAMP_X ||
|
||||
mini->clamp == UI_TEXTBOX_MINI_CLAMP_BOTH
|
||||
) {
|
||||
float_t minX = (float_t)SCREEN.scanX;
|
||||
float_t maxX = (float_t)(SCREEN.scanX + SCREEN.scanWidth) - mini->width;
|
||||
if(screenPos[0] < minX) screenPos[0] = minX;
|
||||
if(screenPos[0] > maxX) screenPos[0] = maxX;
|
||||
}
|
||||
|
||||
if(
|
||||
mini->clamp == UI_TEXTBOX_MINI_CLAMP_Y ||
|
||||
mini->clamp == UI_TEXTBOX_MINI_CLAMP_BOTH
|
||||
) {
|
||||
float_t minY = (float_t)SCREEN.scanY;
|
||||
float_t maxY = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - mini->height;
|
||||
if(screenPos[1] < minY) screenPos[1] = minY;
|
||||
if(screenPos[1] > maxY) screenPos[1] = maxY;
|
||||
}
|
||||
|
||||
return uiTextboxDraw(
|
||||
&mini->box, screenPos[0], screenPos[1], mini->width, mini->height
|
||||
);
|
||||
}
|
||||
|
||||
bool_t uiTextboxMiniIsActive(const uitextboxmini_t *mini) {
|
||||
assertNotNull(mini, "Mini textbox cannot be NULL");
|
||||
return mini->active;
|
||||
}
|
||||
|
||||
void uiTextboxMiniClose(uitextboxmini_t *mini) {
|
||||
assertNotNull(mini, "Mini textbox cannot be NULL");
|
||||
if(!mini->active) return;
|
||||
mini->active = false;
|
||||
if(mini->closed != NULL) mini->closed(mini);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "ui/rpg/textbox/uitextbox.h"
|
||||
|
||||
#define UI_TEXTBOX_MINI_TEXT_MAX 128
|
||||
#define UI_TEXTBOX_MINI_LINES_MAX 2
|
||||
#define UI_TEXTBOX_MINI_WIDTH_MAX 160.0f
|
||||
|
||||
typedef struct uitextboxmini_s uitextboxmini_t;
|
||||
typedef void (*uitextboxminiclosedcallback_t)(const uitextboxmini_t *mini);
|
||||
|
||||
/**
|
||||
* Determines whether a mini textbox's screen position is clamped to stay
|
||||
* fully on screen. Defaults to UI_TEXTBOX_MINI_CLAMP_NONE.
|
||||
*/
|
||||
typedef enum {
|
||||
UI_TEXTBOX_MINI_CLAMP_NONE,
|
||||
UI_TEXTBOX_MINI_CLAMP_X,
|
||||
UI_TEXTBOX_MINI_CLAMP_Y,
|
||||
UI_TEXTBOX_MINI_CLAMP_BOTH
|
||||
} uitextboxminiclamp_t;
|
||||
|
||||
typedef struct uitextboxmini_s {
|
||||
uitextbox_t box;
|
||||
char_t text[UI_TEXTBOX_MINI_TEXT_MAX];
|
||||
uitextboxline_t lines[UI_TEXTBOX_MINI_LINES_MAX];
|
||||
|
||||
vec3 position;
|
||||
float_t width;
|
||||
float_t height;
|
||||
uitextboxminiclamp_t clamp;
|
||||
bool_t active;
|
||||
float_t timer;
|
||||
|
||||
uitextboxminiclosedcallback_t closed;
|
||||
void *user;
|
||||
} uitextboxmini_t;
|
||||
|
||||
/**
|
||||
* Initializes a mini textbox, zeroing all state and binding its internal
|
||||
* uitextbox_t to its own fixed-size text and line storage.
|
||||
*
|
||||
* @param mini The mini textbox to initialize.
|
||||
*/
|
||||
void uiTextboxMiniInit(uitextboxmini_t *mini);
|
||||
|
||||
/**
|
||||
* Shows a mini textbox with the given text at the given world position for
|
||||
* the given duration. Resets the typewriter scroll, measures the text to
|
||||
* size the box (width capped at UI_TEXTBOX_MINI_WIDTH_MAX, height derived
|
||||
* from the resulting wrapped line count), and starts the visibility timer.
|
||||
*
|
||||
* @param mini The mini textbox to show.
|
||||
* @param text Null-terminated source string.
|
||||
* @param position World-space position the box is anchored to on screen.
|
||||
* @param duration How long the mini textbox stays visible, in seconds.
|
||||
* @param closed Called once the timer elapses and the box closes. May be
|
||||
* NULL.
|
||||
* @param user Opaque pointer passed back through the closed callback.
|
||||
*/
|
||||
void uiTextboxMiniShow(
|
||||
uitextboxmini_t *mini,
|
||||
const char_t *text,
|
||||
vec3 position,
|
||||
const float_t duration,
|
||||
uitextboxminiclosedcallback_t closed,
|
||||
void *user
|
||||
);
|
||||
|
||||
/**
|
||||
* Advances the typewriter scroll and counts down the visibility timer.
|
||||
* Closes the mini textbox and fires its closed callback once the timer
|
||||
* elapses. Has no effect if not active.
|
||||
*
|
||||
* @param mini The mini textbox to update.
|
||||
* @returns Any error that occurs.
|
||||
*/
|
||||
errorret_t uiTextboxMiniUpdate(uitextboxmini_t *mini);
|
||||
|
||||
/**
|
||||
* Draws the mini textbox at its text-measured size, anchored on screen to
|
||||
* its world-space position via the RPG camera. Has no effect if not
|
||||
* active.
|
||||
*
|
||||
* @param mini The mini textbox to draw.
|
||||
* @returns Any error that occurs.
|
||||
*/
|
||||
errorret_t uiTextboxMiniDraw(uitextboxmini_t *mini);
|
||||
|
||||
/**
|
||||
* Returns true when the mini textbox is currently visible.
|
||||
*
|
||||
* @param mini The mini textbox to query.
|
||||
* @returns True if active.
|
||||
*/
|
||||
bool_t uiTextboxMiniIsActive(const uitextboxmini_t *mini);
|
||||
|
||||
/**
|
||||
* Immediately closes the mini textbox and fires its closed callback.
|
||||
* Has no effect if not active.
|
||||
*
|
||||
* @param mini The mini textbox to close.
|
||||
*/
|
||||
void uiTextboxMiniClose(uitextboxmini_t *mini);
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uitextboxminilist.h"
|
||||
|
||||
uitextboxmini_t UI_TEXTBOX_MINI_LIST[UI_TEXTBOX_MINI_LIST_COUNT];
|
||||
|
||||
errorret_t uiTextboxMiniListInit(void) {
|
||||
for(uint8_t i = 0; i < UI_TEXTBOX_MINI_LIST_COUNT; i++) {
|
||||
uiTextboxMiniInit(&UI_TEXTBOX_MINI_LIST[i]);
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiTextboxMiniListUpdate(void) {
|
||||
for(uint8_t i = 0; i < UI_TEXTBOX_MINI_LIST_COUNT; i++) {
|
||||
errorChain(uiTextboxMiniUpdate(&UI_TEXTBOX_MINI_LIST[i]));
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiTextboxMiniListDraw(void) {
|
||||
for(uint8_t i = 0; i < UI_TEXTBOX_MINI_LIST_COUNT; i++) {
|
||||
errorChain(uiTextboxMiniDraw(&UI_TEXTBOX_MINI_LIST[i]));
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "ui/rpg/textbox/uitextboxmini.h"
|
||||
|
||||
#define UI_TEXTBOX_MINI_LIST_COUNT 4
|
||||
|
||||
extern uitextboxmini_t UI_TEXTBOX_MINI_LIST[UI_TEXTBOX_MINI_LIST_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes all UI_TEXTBOX_MINI_LIST slots.
|
||||
*
|
||||
* @returns Any error that occurs.
|
||||
*/
|
||||
errorret_t uiTextboxMiniListInit(void);
|
||||
|
||||
/**
|
||||
* Updates all UI_TEXTBOX_MINI_LIST slots.
|
||||
*
|
||||
* @returns Any error that occurs.
|
||||
*/
|
||||
errorret_t uiTextboxMiniListUpdate(void);
|
||||
|
||||
/**
|
||||
* Draws all active UI_TEXTBOX_MINI_LIST slots.
|
||||
*
|
||||
* @returns Any error that occurs.
|
||||
*/
|
||||
errorret_t uiTextboxMiniListDraw(void);
|
||||
@@ -1,86 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uitextboxmain.h"
|
||||
#include "ui/focus/uifocus.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/text/text.h"
|
||||
#include "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,
|
||||
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_START_Y;
|
||||
float_t w = (float_t)SCREEN.scanWidth;
|
||||
float_t x = (float_t)SCREEN.scanX;
|
||||
float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - h;
|
||||
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;
|
||||
}
|
||||
@@ -21,7 +21,8 @@
|
||||
#include "ui/frame/battle/uibattlemenu.h"
|
||||
#include "ui/frame/backpack/uibackpack.h"
|
||||
#include "ui/frame/uiconfirm.h"
|
||||
#include "ui/rpg/uitextboxmain.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
#include "ui/rpg/textbox/uitextboxminilist.h"
|
||||
|
||||
uielement_t UI_ELEMENTS[] = {
|
||||
{
|
||||
@@ -74,6 +75,12 @@ uielement_t UI_ELEMENTS[] = {
|
||||
.draw = uiTextboxMainDraw
|
||||
},
|
||||
|
||||
{
|
||||
.init = uiTextboxMiniListInit,
|
||||
.update = uiTextboxMiniListUpdate,
|
||||
.draw = uiTextboxMiniListDraw
|
||||
},
|
||||
|
||||
{
|
||||
.init = uiTransitionInit,
|
||||
.update = uiTransitionUpdate,
|
||||
|
||||
Reference in New Issue
Block a user