Simple Feature Gross Code
By Angus Cheng
A few days ago I talked about supporting text events with multiple colours and multiple text speeds. I spent about eight hours writing the code. It took me a lot longer than I expected so I thought I’d share the details here.
Example Data
set_title(data, PHOENIX);
set_text_color(data, SKYBLUE);
add_show_text_V2(data, "They're saying the jay walker was... ");
set_text_color(data, RED);
add_show_text_V2(data, "you.");
set_text_color(data, SKYBLUE);
This is how I was expecting to set up the events. There are four distinct events there, which normally would require me to make four different event lists and four different event counts. Instead I got lazy and merged them into one called SetTextConfig.
typedef struct SetTextConfig {
const char *title;
const char *body;
Color color;
bool setColor;
float secondsPerChar;
enum Justification justification;
bool clearText;
} SetTextConfig;
Handling Events
When we encounter a SET_TEXT_CONFIG event, we add it to a list of active SetTextConfig structs. If the event has a true clearText property, we reset the some text variables and ‘clear the list’.
else if (eventType == SET_TEXT_CONFIG) {
SetTextConfig event = eventData->setTextConfigs[eventIndex];
if (event.clearText) {
data->textEventCount = 0;
data->textTimer = 0.0;
data->previousCharacterCount = 0;
}
else if (event.secondsPerChar > 0) {
data->secondsPerChar = event.secondsPerChar;
}
else {
int i = data->textEventCount++;
data->textEvents[i] = event;
}
}
The second if branch is actually wrong. I haven’t implemented variable text speed yet and quickly wanted those events to have an influence. Later I’ll wipe out that branch and deal with secondsPerChar events somewhere else.
Positioning Text
At this point we have a list of active text events. All we have to do is write some code that will layout the text based on those events. Something that will return us a list structs like this.
typedef struct TextPiece {
char text[300];
Vector2 position;
Color color;
} TextPiece;
Last chance to click away because the following code is gross.
void position_text_pieces(
GamePlayData *data,
Font font,
float fontSize,
float maxWidth,
int *pieceCount,
TextPiece pieces[10]
) {
int pieceIndex = 0;
Color color = WHITE;
float x = 0;
float y = 0;
char line[300];
int lineIndex = 0;
char word[300];
int wordIndex = 0;
memset(line, 0, sizeof(line));
memset(word, 0, sizeof(line));
for (int i = 0; i < data->textEventCount; i++) {
SetTextConfig event = data->textEvents[i];
if (event.setColor) {
color = event.color;
}
if (event.body != NULL) {
int bodyLength = TextLength(event.body);
for (int j = 0; j < bodyLength; j++) {
char c = event.body[j];
if (c == '\n') {
}
else if (c == ' ') {
word[wordIndex++] = c;
Vector2 wordDims = MeasureTextEx(font, word, fontSize, 0);
Vector2 lineDims = MeasureTextEx(font, line, fontSize, 0);
// Exceeded the maxWidth, create a TextPiece
if (x + wordDims.x + lineDims.x > maxWidth) {
add_text_piece(pieces, &pieceIndex, line, (Vector2){x, y}, color);
// Clear out the line
lineIndex = 0;
memset(line, 0, sizeof(line));
x = 0;
y += lineDims.y;
}
// Write the word to the line
for (int k = 0; k < wordIndex; k++) {
line[lineIndex++] = word[k];
}
memset(word, 0, sizeof(word));
wordIndex = 0;
}
else {
word[wordIndex++] = c;
}
}
if (TextLength(word) > 0) {
// Write the word to the line
for (int k = 0; k < wordIndex; k++) {
line[lineIndex++] = word[k];
}
memset(word, 0, sizeof(word));
wordIndex = 0;
}
if (TextLength(line) > 0) {
add_text_piece(pieces, &pieceIndex, line, (Vector2){x, y}, color);
x += MeasureTextEx(font, line, fontSize, 0).x;
// Clear out the line
lineIndex = 0;
memset(line, 0, sizeof(line));
}
}
}
*pieceCount = pieceIndex;
}
The idea is we want to keep track of the current text colour and text position. It also wraps text word by word. Some ideas to clean it up:
- String copying code that be replaced with strcpy calls.
- Last two if statements are duplicated code, try get them into the inner loop
- Replace float x and float y with. Vector2 position.
The code works though! I probably wont change it, but I might write some tests for it because it is tricky.