textbox
This commit is contained in:
@@ -19,7 +19,7 @@ CubeEntity.prototype.update = function() {
|
|||||||
var move = Input.axis2D(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT, INPUT_ACTION_UP, INPUT_ACTION_DOWN);
|
var move = Input.axis2D(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT, INPUT_ACTION_UP, INPUT_ACTION_DOWN);
|
||||||
this.position.position.x += move.x * speed * TIME.delta;
|
this.position.position.x += move.x * speed * TIME.delta;
|
||||||
this.position.position.z += move.y * speed * TIME.delta;
|
this.position.position.z += move.y * speed * TIME.delta;
|
||||||
this.material.setColor(Color.rainbow());
|
this.material.color = Color.rainbow();
|
||||||
};
|
};
|
||||||
|
|
||||||
module = CubeEntity;
|
module = CubeEntity;
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ function CubeScene() {
|
|||||||
Cutscene.play(new MoveCubeCutscene({ cube: this.cube })).then(function() {
|
Cutscene.play(new MoveCubeCutscene({ cube: this.cube })).then(function() {
|
||||||
scene.inputEnabled = true;
|
scene.inputEnabled = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Textbox.setText(
|
||||||
|
"Hello! This is a visual novel textbox. It automatically " +
|
||||||
|
"wraps long lines and splits into pages when the content " +
|
||||||
|
"is too tall to fit. Press advance to continue...\t" +
|
||||||
|
"This is a second paragraph on a new page."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
CubeScene.prototype = Object.create(Scene.prototype);
|
CubeScene.prototype = Object.create(Scene.prototype);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "cutscene/cutscene.h"
|
#include "cutscene/cutscene.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
|
#include "ui/uitextbox.h"
|
||||||
#include "script/scriptmanager.h"
|
#include "script/scriptmanager.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "entity/entitymanager.h"
|
#include "entity/entitymanager.h"
|
||||||
@@ -53,6 +54,8 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
errorChain(scriptManagerInit());
|
errorChain(scriptManagerInit());
|
||||||
errorChain(displayInit());
|
errorChain(displayInit());
|
||||||
errorChain(uiInit());
|
errorChain(uiInit());
|
||||||
|
errorChain(uiTextboxInit());
|
||||||
|
|
||||||
errorChain(cutsceneInit());
|
errorChain(cutsceneInit());
|
||||||
errorChain(sceneInit());
|
errorChain(sceneInit());
|
||||||
entityManagerInit();
|
entityManagerInit();
|
||||||
@@ -74,6 +77,7 @@ errorret_t engineUpdate(void) {
|
|||||||
inputUpdate();
|
inputUpdate();
|
||||||
consoleUpdate();
|
consoleUpdate();
|
||||||
uiUpdate();
|
uiUpdate();
|
||||||
|
errorChain(uiTextboxUpdate());
|
||||||
physicsManagerUpdate();
|
physicsManagerUpdate();
|
||||||
errorChain(displayUpdate());
|
errorChain(displayUpdate());
|
||||||
|
|
||||||
@@ -92,6 +96,7 @@ void engineExit(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t engineDispose(void) {
|
errorret_t engineDispose(void) {
|
||||||
|
uiTextboxDispose();
|
||||||
cutsceneDispose();
|
cutsceneDispose();
|
||||||
sceneDispose();
|
sceneDispose();
|
||||||
errorChain(networkDispose());
|
errorChain(networkDispose());
|
||||||
@@ -101,6 +106,6 @@ errorret_t engineDispose(void) {
|
|||||||
consoleDispose();
|
consoleDispose();
|
||||||
errorChain(displayDispose());
|
errorChain(displayDispose());
|
||||||
errorChain(assetDispose());
|
errorChain(assetDispose());
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,4 +35,5 @@ errorret_t engineUpdate(void);
|
|||||||
/**
|
/**
|
||||||
* Shuts down the engine.
|
* Shuts down the engine.
|
||||||
*/
|
*/
|
||||||
errorret_t engineDispose(void);
|
errorret_t engineDispose(void);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "script/module/engine/moduleengine.h"
|
#include "script/module/engine/moduleengine.h"
|
||||||
#include "script/module/item/moduleitem.h"
|
#include "script/module/item/moduleitem.h"
|
||||||
#include "script/module/story/modulestory.h"
|
#include "script/module/story/modulestory.h"
|
||||||
|
#include "script/module/ui/moduletextbox.h"
|
||||||
|
|
||||||
static void moduleRegister(void) {
|
static void moduleRegister(void) {
|
||||||
moduleInclude();
|
moduleInclude();
|
||||||
@@ -46,4 +47,5 @@ static void moduleRegister(void) {
|
|||||||
moduleEngine();
|
moduleEngine();
|
||||||
moduleItem();
|
moduleItem();
|
||||||
moduleStory();
|
moduleStory();
|
||||||
|
moduleTextbox();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
// Copyright (c) 2026 Dominic Masters
|
||||||
|
//
|
||||||
|
// This software is released under the MIT License.
|
||||||
|
// https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "ui/uitextbox.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_TEXTBOX_PROTO;
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxSetText) {
|
||||||
|
if(argc < 1 || !jerry_value_is_string(args[0])) {
|
||||||
|
return moduleBaseThrow("Textbox.setText: expected string");
|
||||||
|
}
|
||||||
|
char_t buf[UI_TEXTBOX_TEXT_MAX];
|
||||||
|
moduleBaseToString(args[0], buf, sizeof(buf));
|
||||||
|
uiTextboxSetText(buf);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxNextPage) {
|
||||||
|
uiTextboxNextPage();
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxUpdate) {
|
||||||
|
uiTextboxUpdate();
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxDraw) {
|
||||||
|
uiTextboxDraw();
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxGetScroll) {
|
||||||
|
return jerry_number((double)UI_TEXTBOX.scroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxSetScroll) {
|
||||||
|
if(argc < 1 || !jerry_value_is_number(args[0])) {
|
||||||
|
return moduleBaseThrow("Textbox.scroll: expected number");
|
||||||
|
}
|
||||||
|
UI_TEXTBOX.scroll = (int32_t)jerry_value_as_number(args[0]);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxGetAdvanceAction) {
|
||||||
|
return jerry_number((double)UI_TEXTBOX.advanceAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxSetAdvanceAction) {
|
||||||
|
if(argc < 1 || !jerry_value_is_number(args[0])) {
|
||||||
|
return moduleBaseThrow("Textbox.advanceAction: expected number");
|
||||||
|
}
|
||||||
|
UI_TEXTBOX.advanceAction = (inputaction_t)(
|
||||||
|
(int32_t)jerry_value_as_number(args[0])
|
||||||
|
);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxGetPageComplete) {
|
||||||
|
return jerry_boolean(uiTextboxPageIsComplete());
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxGetHasNextPage) {
|
||||||
|
return jerry_boolean(uiTextboxHasNextPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxGetCurrentPage) {
|
||||||
|
return jerry_number((double)UI_TEXTBOX.currentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextboxGetPageCount) {
|
||||||
|
return jerry_number((double)UI_TEXTBOX.pageCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleTextbox(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "Textbox", sizeof(uint8_t), NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
scriptProtoDefineStaticFunc(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "setText", moduleTextboxSetText
|
||||||
|
);
|
||||||
|
scriptProtoDefineStaticFunc(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "nextPage", moduleTextboxNextPage
|
||||||
|
);
|
||||||
|
scriptProtoDefineStaticFunc(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "update", moduleTextboxUpdate
|
||||||
|
);
|
||||||
|
scriptProtoDefineStaticFunc(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "draw", moduleTextboxDraw
|
||||||
|
);
|
||||||
|
|
||||||
|
scriptProtoDefineStaticProp(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "scroll",
|
||||||
|
moduleTextboxGetScroll, moduleTextboxSetScroll
|
||||||
|
);
|
||||||
|
scriptProtoDefineStaticProp(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "advanceAction",
|
||||||
|
moduleTextboxGetAdvanceAction, moduleTextboxSetAdvanceAction
|
||||||
|
);
|
||||||
|
scriptProtoDefineStaticProp(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "pageComplete",
|
||||||
|
moduleTextboxGetPageComplete, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineStaticProp(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "hasNextPage",
|
||||||
|
moduleTextboxGetHasNextPage, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineStaticProp(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "currentPage",
|
||||||
|
moduleTextboxGetCurrentPage, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineStaticProp(
|
||||||
|
&MODULE_TEXTBOX_PROTO, "pageCount",
|
||||||
|
moduleTextboxGetPageCount, NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,4 +9,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
ui.c
|
ui.c
|
||||||
uifps.c
|
uifps.c
|
||||||
uielement.c
|
uielement.c
|
||||||
|
uiframe.c
|
||||||
|
uitextbox.c
|
||||||
)
|
)
|
||||||
@@ -10,10 +10,13 @@
|
|||||||
#include "script/scriptmanager.h"
|
#include "script/scriptmanager.h"
|
||||||
#include "console/console.h"
|
#include "console/console.h"
|
||||||
#include "ui/uifps.h"
|
#include "ui/uifps.h"
|
||||||
|
#include "engine/engine.h"
|
||||||
|
#include "ui/uitextbox.h"
|
||||||
|
|
||||||
uielement_t UI_ELEMENTS[] = {
|
uielement_t UI_ELEMENTS[] = {
|
||||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .native = { .draw = consoleDraw } },
|
{ .type = UI_ELEMENT_TYPE_NATIVE, .native = { .draw = consoleDraw } },
|
||||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .native = { .draw = uiFPSDraw } },
|
{ .type = UI_ELEMENT_TYPE_NATIVE, .native = { .draw = uiFPSDraw } },
|
||||||
|
{ .type = UI_ELEMENT_TYPE_NATIVE, .native = { .draw = uiTextboxDraw } },
|
||||||
{ .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } },
|
{ .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } },
|
||||||
|
|
||||||
{ .type = UI_ELEMENT_TYPE_NULL },
|
{ .type = UI_ELEMENT_TYPE_NULL },
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
// 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/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));
|
||||||
|
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");
|
||||||
|
|
||||||
|
errorChain(shaderSetTexture(
|
||||||
|
&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, frame->texture
|
||||||
|
));
|
||||||
|
|
||||||
|
float_t tileW = (float_t)frame->tileset.tileWidth;
|
||||||
|
float_t tileH = (float_t)frame->tileset.tileHeight;
|
||||||
|
|
||||||
|
vec4 uv;
|
||||||
|
|
||||||
|
tilesetPositionGetUV(&frame->tileset, 0, 0, uv);
|
||||||
|
errorChain(spriteBatchPush(
|
||||||
|
x, y, x + tileW, y + tileH,
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
COLOR_WHITE,
|
||||||
|
#endif
|
||||||
|
uv[0], uv[1], uv[2], uv[3]
|
||||||
|
));
|
||||||
|
|
||||||
|
tilesetPositionGetUV(&frame->tileset, 1, 0, uv);
|
||||||
|
errorChain(spriteBatchPush(
|
||||||
|
x + tileW, y, x + width - tileW, y + tileH,
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
COLOR_WHITE,
|
||||||
|
#endif
|
||||||
|
uv[0], uv[1], uv[2], uv[3]
|
||||||
|
));
|
||||||
|
|
||||||
|
tilesetPositionGetUV(&frame->tileset, 2, 0, uv);
|
||||||
|
errorChain(spriteBatchPush(
|
||||||
|
x + width - tileW, y, x + width, y + tileH,
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
COLOR_WHITE,
|
||||||
|
#endif
|
||||||
|
uv[0], uv[1], uv[2], uv[3]
|
||||||
|
));
|
||||||
|
|
||||||
|
tilesetPositionGetUV(&frame->tileset, 0, 1, uv);
|
||||||
|
errorChain(spriteBatchPush(
|
||||||
|
x, y + tileH, x + tileW, y + height - tileH,
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
COLOR_WHITE,
|
||||||
|
#endif
|
||||||
|
uv[0], uv[1], uv[2], uv[3]
|
||||||
|
));
|
||||||
|
|
||||||
|
tilesetPositionGetUV(&frame->tileset, 1, 1, uv);
|
||||||
|
errorChain(spriteBatchPush(
|
||||||
|
x + tileW, y + tileH, x + width - tileW, y + height - tileH,
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
COLOR_WHITE,
|
||||||
|
#endif
|
||||||
|
uv[0], uv[1], uv[2], uv[3]
|
||||||
|
));
|
||||||
|
|
||||||
|
tilesetPositionGetUV(&frame->tileset, 2, 1, uv);
|
||||||
|
errorChain(spriteBatchPush(
|
||||||
|
x + width - tileW, y + tileH, x + width, y + height - tileH,
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
COLOR_WHITE,
|
||||||
|
#endif
|
||||||
|
uv[0], uv[1], uv[2], uv[3]
|
||||||
|
));
|
||||||
|
|
||||||
|
tilesetPositionGetUV(&frame->tileset, 0, 2, uv);
|
||||||
|
errorChain(spriteBatchPush(
|
||||||
|
x, y + height - tileH, x + tileW, y + height,
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
COLOR_WHITE,
|
||||||
|
#endif
|
||||||
|
uv[0], uv[1], uv[2], uv[3]
|
||||||
|
));
|
||||||
|
|
||||||
|
tilesetPositionGetUV(&frame->tileset, 1, 2, uv);
|
||||||
|
errorChain(spriteBatchPush(
|
||||||
|
x + tileW, y + height - tileH, x + width - tileW, y + height,
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
COLOR_WHITE,
|
||||||
|
#endif
|
||||||
|
uv[0], uv[1], uv[2], uv[3]
|
||||||
|
));
|
||||||
|
|
||||||
|
tilesetPositionGetUV(&frame->tileset, 2, 2, uv);
|
||||||
|
errorChain(spriteBatchPush(
|
||||||
|
x + width - tileW, y + height - tileH, x + width, y + height,
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
COLOR_WHITE,
|
||||||
|
#endif
|
||||||
|
uv[0], uv[1], uv[2], uv[3]
|
||||||
|
));
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiFrameDispose(uiframe_t *frame) {
|
||||||
|
assertNotNull(frame, "frame must not be NULL");
|
||||||
|
frame->texture = NULL;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "display/texture/texture.h"
|
||||||
|
#include "display/texture/tileset.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
tileset_t tileset;
|
||||||
|
texture_t *texture;
|
||||||
|
} uiframe_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a UI frame element.
|
||||||
|
*
|
||||||
|
* @param frame The frame to initialize.
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiFrameInit(uiframe_t *frame);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws a UI frame using 9-slice rendering from a 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.
|
||||||
|
* @param height Total height of the 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,
|
||||||
|
const float_t height
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of a UI frame element. Does not dispose the texture.
|
||||||
|
*
|
||||||
|
* @param frame The frame to dispose.
|
||||||
|
*/
|
||||||
|
void uiFrameDispose(uiframe_t *frame);
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
// 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 "display/screen/screen.h"
|
||||||
|
#include "display/texture/texture.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;
|
||||||
|
|
||||||
|
float_t fontW = (float_t)FONT_TILESET_DEFAULT.tileWidth;
|
||||||
|
float_t fontH = (float_t)FONT_TILESET_DEFAULT.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 = 10.0f;
|
||||||
|
UI_TEXTBOX.y = (float_t)SCREEN.height - tbHeight - 10.0f;
|
||||||
|
UI_TEXTBOX.width = (float_t)SCREEN.width - 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_TILESET_DEFAULT.tileWidth;
|
||||||
|
UI_TEXTBOX.frame.tileset.tileHeight = FONT_TILESET_DEFAULT.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.textTileset = FONT_TILESET_DEFAULT;
|
||||||
|
UI_TEXTBOX.textTexture = &FONT_TEXTURE_DEFAULT;
|
||||||
|
|
||||||
|
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.textTileset.tileWidth;
|
||||||
|
float_t fontH = (float_t)UI_TEXTBOX.textTileset.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;
|
||||||
|
(void)contentH;
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if(!uiTextboxPageIsComplete()) {
|
||||||
|
UI_TEXTBOX.scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(inputPressed(UI_TEXTBOX.advanceAction)) {
|
||||||
|
if(!uiTextboxPageIsComplete()) {
|
||||||
|
UI_TEXTBOX.scroll = uiTextboxGetPageCharCount();
|
||||||
|
} else if(uiTextboxHasNextPage()) {
|
||||||
|
uiTextboxNextPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
errorChain(uiFrameDraw(
|
||||||
|
&UI_TEXTBOX.frame,
|
||||||
|
UI_TEXTBOX.x, UI_TEXTBOX.y,
|
||||||
|
UI_TEXTBOX.width, UI_TEXTBOX.height
|
||||||
|
));
|
||||||
|
errorChain(spriteBatchFlush());
|
||||||
|
|
||||||
|
if(UI_TEXTBOX.lineCount == 0 || UI_TEXTBOX.text[0] == '\0') errorOk();
|
||||||
|
|
||||||
|
errorChain(shaderSetTexture(
|
||||||
|
&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, UI_TEXTBOX.textTexture
|
||||||
|
));
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
#else
|
||||||
|
errorChain(shaderSetColor(
|
||||||
|
&SHADER_UNLIT, SHADER_UNLIT_COLOR, UI_TEXTBOX.textColor
|
||||||
|
));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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.textTileset.tileWidth;
|
||||||
|
float_t fontH = (float_t)UI_TEXTBOX.textTileset.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;
|
||||||
|
lineY += (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;
|
||||||
|
errorChain(textDrawChar(
|
||||||
|
contentX + (float_t)ci * fontW,
|
||||||
|
lineY,
|
||||||
|
c,
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
UI_TEXTBOX.textColor,
|
||||||
|
#endif
|
||||||
|
&UI_TEXTBOX.textTileset,
|
||||||
|
UI_TEXTBOX.textTexture
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
charsLeft -= visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorChain(spriteBatchFlush());
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiTextboxDispose(void) {
|
||||||
|
uiFrameDispose(&UI_TEXTBOX.frame);
|
||||||
|
UI_TEXTBOX.textTexture = NULL;
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
#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;
|
||||||
|
|
||||||
|
tileset_t textTileset;
|
||||||
|
texture_t *textTexture;
|
||||||
|
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;
|
||||||
|
} 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.
|
||||||
|
*/
|
||||||
|
void uiTextboxDispose(void);
|
||||||
Reference in New Issue
Block a user