191 lines
5.6 KiB
C
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.");
|
|
}
|
|
} |