Files
dusk/archive/dusk/ui/uitextbox.c
2025-08-20 19:18:38 -05:00

191 lines
5.6 KiB
C

/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uitextbox.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "input.h"
uitextbox_t UI_TEXTBOX;
void uiTextboxInit() {
memoryZero(&UI_TEXTBOX, sizeof(uitextbox_t));
}
void uiTextboxUpdate() {
if(UI_TEXTBOX.visible == false) return;
if(UI_TEXTBOX.charsRevealed < UI_TEXTBOX.pageChars[UI_TEXTBOX.page]) {
UI_TEXTBOX.charsRevealed++;
if(inputIsDown(INPUT_BIND_ACTION)) {
UI_TEXTBOX.charsRevealed++;
}
} else {
if(inputPressed(INPUT_BIND_ACTION)) {
if(UI_TEXTBOX.page < UI_TEXTBOX.pageCount - 1) {
UI_TEXTBOX.page++;
UI_TEXTBOX.charsRevealed = 0;
} else {
// Close the textbox
UI_TEXTBOX.visible = false;
UI_TEXTBOX.page = 0;
UI_TEXTBOX.charsRevealed = 0;
}
}
}
}
void uiTextboxSetText(const char_t *text) {
assertNotNull(text, "Text pointer cannot be NULL, call uiTextboxClose()");
memoryZero(UI_TEXTBOX.text, sizeof(UI_TEXTBOX.text));
memoryZero(UI_TEXTBOX.lineLengths, sizeof(UI_TEXTBOX.lineLengths));
memoryZero(UI_TEXTBOX.pageChars, sizeof(UI_TEXTBOX.pageChars));
UI_TEXTBOX.pageCount = 1;// Always at least one page.
UI_TEXTBOX.totalChars = 0;
UI_TEXTBOX.page = 0;
UI_TEXTBOX.charsRevealed = 0;
UI_TEXTBOX.visible = true;
char_t c;// current char
uint16_t i = 0;// Index of character we are pulling
uint16_t lastWordStart = 0;// Index of the last word start (in src string).
uint8_t line = 0;// Which line we are currently writing to.
uint8_t page = 0;
bool_t startOfLine = true;// Are we at the start of a line?
while((c = text[i++]) != '\0') {
// HARD disallowed characters.
assertTrue(c != '\r', "Carriage return characters not allowed.");
assertTrue(c != '\t', "Tab characters not allowed.");
// Is this the beginning of a new line?
if(startOfLine) {
// Yes, we are at the start of a new line.
startOfLine = false;
// Is this the first line?
if(line == 0) {
// nothing to do, just continue.
} else {
// Yes, start of new line. Is this line the first line on a new page?
if((line % UI_TEXTBOX_LINES_PER_PAGE) == 0) {
// Yes, start a new page.
i--;// Rewind so that this character can go through the loop again.
goto newPage;
}
}
}
// Change what we do depending on the character.
if(c == '\n') {
goto newline;
} else if(c == ' ') {
goto whitespace;
} else {
goto character;
}
// Handle whitespace characters (not newlines)
whitespace: {
// Is this whitespace the last char of the line?
if(UI_TEXTBOX.lineLengths[line] == UI_TEXTBOX_CHARS_PER_LINE) {
goto newline;
} else if(UI_TEXTBOX.lineLengths[line] == 0) {
// If this is the first character of the line, we can just ignore it.
continue;
}
lastWordStart = i;
goto appendCharacter;
}
// Handle regular characters
character: {
// Is this character going to cause a wrap to occur?
if(UI_TEXTBOX.lineLengths[line] == UI_TEXTBOX_CHARS_PER_LINE) {
// How long ago was the last whitespace?
uint16_t charsSinceLastSpace = i - lastWordStart;
// Is the word longer than a line can possibly hold?
assertTrue(
charsSinceLastSpace < UI_TEXTBOX_CHARS_PER_LINE,
"Word longer than a line can hold."
);
// Undo appending of all characters since the last whitespace.
UI_TEXTBOX.totalChars -= charsSinceLastSpace;
UI_TEXTBOX.lineLengths[line] -= charsSinceLastSpace;
UI_TEXTBOX.pageChars[page] -= charsSinceLastSpace;
// Rewind the loop so that printing will begin on the new line at the
// start of the last word.
i -= charsSinceLastSpace;
// Newline.
goto newline;
}
// Append the character to the textbox.
goto appendCharacter;
}
// Handle newlines
newline: {
// Ensure we don't exceed the maximum number of lines.
assertTrue(
line < UI_TEXTBOX_LINE_COUNT,
"Exceeded maximum number of lines in textbox."
);
// Add a line to the textbox.
line++;
startOfLine = true;// Next iteration will be a start of line.
lastWordStart = i;// We also mark this as the last word start.
continue;
}
newPage: {
// Make sure we don't exceed the maximum number of pages.
assertTrue(
UI_TEXTBOX.pageCount < UI_TEXTBOX_PAGE_COUNT_MAX,
"Exceeded maximum number of pages in textbox."
);
UI_TEXTBOX.pageCount++;
page++;
continue;
}
appendCharacter: {
assertTrue(
UI_TEXTBOX.totalChars < UI_TEXTBOX_CHARS_MAX,
"Exceeded maximum number of characters in textbox."
);
assertTrue(
line < UI_TEXTBOX_LINE_COUNT,
"Exceeded maximum number of lines in textbox."
);
assertTrue(
UI_TEXTBOX.lineLengths[line] < UI_TEXTBOX_CHARS_PER_LINE,
"Exceeded maximum number of chars per line in textbox."
);
// Push the character to the textbox.
UI_TEXTBOX.text[
line * UI_TEXTBOX_CHARS_PER_LINE + UI_TEXTBOX.lineLengths[line]
] = c;
// Increment the line length and page character count.
UI_TEXTBOX.totalChars++;
UI_TEXTBOX.lineLengths[line]++;
UI_TEXTBOX.pageChars[page]++;
continue;
}
assertUnreachable("Code should not reach here, all cases handled.");
}
}