text_nextframe now takes deltatime instead of recomputing it, the scene command struct got an overhaul and now it's much easier to write and read
This commit is contained in:
1 parent
e56d88ad08
commit
612c4e694b
3 files changed
+204
-172
No files matched your search
+1
-1
@@ -21,7 +21,7 @@ void text_push(text_t* text,
|
||||
void text_show_all_current(text_t* text);
|
||||
bool text_is_animating(text_t* text);
|
||||
// advance, call every frame, returns false when done
|
||||
bool text_nextframe(text_t* text);
|
||||
bool text_nextframe(text_t* text, float deltatime);
|
||||
uint32_t* text_getfb(text_t* text);
|
||||
void text_clear(text_t* text);
|
||||
}
|
||||
|
||||
+118
-105
@@ -4,7 +4,7 @@
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <vector> // <-- NEW: For tracking multiple animations
|
||||
#include <vector>
|
||||
#include <window.h>
|
||||
#include <text.h>
|
||||
#include <compositor.h>
|
||||
@@ -22,6 +22,7 @@ enum CommandType {
|
||||
CMD_SET_Z,
|
||||
CMD_SET_SCALE,
|
||||
CMD_SPEAK,
|
||||
CMD_CLEAR,
|
||||
CMD_WAIT,
|
||||
CMD_BACKGROUND,
|
||||
};
|
||||
@@ -33,23 +34,74 @@ enum TransitionType {
|
||||
TRANS_SLIDE_RIGHT,
|
||||
};
|
||||
|
||||
struct Command {
|
||||
CommandType type;
|
||||
const char* character_name;
|
||||
|
||||
int x, y;
|
||||
int z;
|
||||
float scale;
|
||||
|
||||
const char* sprite_name;
|
||||
|
||||
const char* speaker;
|
||||
const char* text;
|
||||
|
||||
struct CmdShow {
|
||||
const char* character;
|
||||
const char* sprite;
|
||||
int x, y, z;
|
||||
TransitionType transition;
|
||||
float duration_ms;
|
||||
};
|
||||
|
||||
bool blocking; // NEW: If true, wait for this animation to complete before next command
|
||||
struct CmdHide {
|
||||
const char* character;
|
||||
TransitionType transition;
|
||||
float duration_ms;
|
||||
};
|
||||
|
||||
struct CmdMove {
|
||||
const char* character;
|
||||
int x, y;
|
||||
TransitionType transition;
|
||||
float duration_ms;
|
||||
};
|
||||
|
||||
struct CmdChangeSprite {
|
||||
const char* character;
|
||||
const char* sprite;
|
||||
};
|
||||
|
||||
struct CmdSetZ {
|
||||
const char* character;
|
||||
int z;
|
||||
};
|
||||
|
||||
struct CmdSetScale {
|
||||
const char* character;
|
||||
float scale;
|
||||
TransitionType transition;
|
||||
float duration_ms;
|
||||
};
|
||||
|
||||
struct CmdSpeak {
|
||||
const char* speaker;
|
||||
const char* text;
|
||||
float text_speed;
|
||||
};
|
||||
|
||||
struct CmdWait {};
|
||||
struct CmdClear {};
|
||||
struct CmdBackground {
|
||||
const char* filename;
|
||||
TransitionType transition;
|
||||
float duration_ms;
|
||||
};
|
||||
|
||||
struct Command {
|
||||
CommandType type;
|
||||
bool blocking;
|
||||
|
||||
union {
|
||||
CmdShow show;
|
||||
CmdHide hide;
|
||||
CmdMove move;
|
||||
CmdChangeSprite change_sprite;
|
||||
CmdSetZ set_z;
|
||||
CmdSetScale set_scale;
|
||||
CmdSpeak speak;
|
||||
CmdWait wait;
|
||||
CmdClear clear;
|
||||
CmdBackground background;
|
||||
};
|
||||
};
|
||||
|
||||
struct Scene {
|
||||
@@ -364,47 +416,50 @@ public:
|
||||
float execute(const Command* cmd) {
|
||||
switch (cmd->type) {
|
||||
case CMD_SHOW:
|
||||
char_manager->show_character(cmd->character_name, cmd->x, cmd->y,
|
||||
cmd->z, cmd->sprite_name,
|
||||
cmd->transition, cmd->duration_ms);
|
||||
return cmd->duration_ms / 1000.0f;
|
||||
char_manager->show_character(cmd->show.character, cmd->show.x, cmd->show.y,
|
||||
cmd->show.z, cmd->show.sprite,
|
||||
cmd->show.transition, cmd->show.duration_ms);
|
||||
return cmd->show.duration_ms / 1000.0f;
|
||||
|
||||
case CMD_HIDE:
|
||||
char_manager->hide_character(cmd->character_name,
|
||||
cmd->transition, cmd->duration_ms);
|
||||
return cmd->duration_ms / 1000.0f;
|
||||
char_manager->hide_character(cmd->hide.character,
|
||||
cmd->hide.transition, cmd->hide.duration_ms);
|
||||
return cmd->hide.duration_ms / 1000.0f;
|
||||
|
||||
case CMD_MOVE:
|
||||
char_manager->move_character(cmd->character_name, cmd->x, cmd->y,
|
||||
cmd->transition, cmd->duration_ms);
|
||||
return cmd->duration_ms / 1000.0f;
|
||||
char_manager->move_character(cmd->move.character, cmd->move.x, cmd->move.y,
|
||||
cmd->move.transition, cmd->move.duration_ms);
|
||||
return cmd->move.duration_ms / 1000.0f;
|
||||
|
||||
case CMD_CHANGE_SPRITE:
|
||||
char_manager->change_sprite(cmd->character_name, cmd->sprite_name);
|
||||
char_manager->change_sprite(cmd->change_sprite.character, cmd->change_sprite.sprite);
|
||||
break;
|
||||
|
||||
case CMD_SET_Z:
|
||||
char_manager->set_z(cmd->character_name, cmd->z);
|
||||
char_manager->set_z(cmd->set_z.character, cmd->set_z.z);
|
||||
break;
|
||||
|
||||
case CMD_SET_SCALE:
|
||||
char_manager->set_scale(cmd->character_name, cmd->scale,
|
||||
cmd->transition, cmd->duration_ms);
|
||||
return cmd->duration_ms / 1000.0f;
|
||||
char_manager->set_scale(cmd->set_scale.character, cmd->set_scale.scale,
|
||||
cmd->set_scale.transition, cmd->set_scale.duration_ms);
|
||||
return cmd->set_scale.duration_ms / 1000.0f;
|
||||
|
||||
case CMD_SPEAK:
|
||||
text_clear(name_text);
|
||||
text_clear(dialogue_text);
|
||||
if (cmd->speaker) {
|
||||
text_push(name_text, cmd->speaker, 34.0f, 0.0f, "assets/Vera.ttf", true);
|
||||
if (cmd->speak.speaker) {
|
||||
text_push(name_text, cmd->speak.speaker, 34.0f, 0.0f, "assets/Vera.ttf", true);
|
||||
}
|
||||
if (cmd->text) {
|
||||
text_push(dialogue_text, cmd->text, 24.0f, 2.0f, "assets/Vera.ttf", true);
|
||||
if (cmd->speak.text) {
|
||||
text_push(dialogue_text, cmd->speak.text, 24.0f, cmd->speak.text_speed, "assets/Vera.ttf", true);
|
||||
}
|
||||
compositor_mark_content_dirty(compositor, name_id);
|
||||
compositor_mark_content_dirty(compositor, dialogue_id);
|
||||
break;
|
||||
|
||||
case CMD_CLEAR:
|
||||
text_clear(name_text);
|
||||
text_clear(dialogue_text);
|
||||
break;
|
||||
|
||||
case CMD_WAIT:
|
||||
case CMD_BACKGROUND:
|
||||
break;
|
||||
@@ -477,71 +532,29 @@ int main() {
|
||||
|
||||
// --- Scene Script ---
|
||||
Command scene_commands[] = {
|
||||
{CMD_SHOW, "maksw", 100, POS_Y, 2, 1.0f, "neutral", nullptr, nullptr, TRANS_SLIDE_LEFT, 500.0f, false},
|
||||
{CMD_SHOW, "wikor", 800, POS_Y, 1, 1.0f, "neutral", nullptr, nullptr, TRANS_SLIDE_RIGHT, 500.0f, true},
|
||||
{.type = CMD_SHOW, .blocking = false, .show = {.character = "maksw", .sprite = "neutral", .x = 100, .y = POS_Y, .z = 2, .transition = TRANS_SLIDE_LEFT, .duration_ms = 500.0f } },
|
||||
{.type = CMD_SHOW, .blocking = true, .show = {.character = "wikor", .sprite = "neutral", .x = 800, .y = POS_Y, .z = 1, .transition = TRANS_SLIDE_RIGHT, .duration_ms = 500.0f } },
|
||||
|
||||
// Maksw starts talking
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
|
||||
"Wow, it's been a really long week, hasn't it? The sky looks different today.", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
{.type = CMD_SPEAK, .blocking = true, .speak = {.speaker = "Maksw", .text = "Wow, it's been a really long week, hasn't it? The sky looks different today.", .text_speed = 30.0f } },
|
||||
{.type = CMD_SPEAK, .blocking = true, .speak = {.speaker = "", .text = "\nline 2", .text_speed = 0.0f } },
|
||||
{.type = CMD_WAIT, .blocking = true, .wait = {} },
|
||||
{.type = CMD_CLEAR, .blocking = true, .clear = {} },
|
||||
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
|
||||
"I mean, just look at how the clouds are stretching all the way to the horizon. Feels almost cinematic.", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
{.type = CMD_SPEAK, .blocking = true, .speak = {.speaker = "Maksw", .text = "I mean, just look at how the clouds are stretching all the way to the horizon. Feels almost cinematic.", .text_speed = 30.0f } },
|
||||
{.type = CMD_WAIT, .blocking = true, .wait = {} },
|
||||
{.type = CMD_CLEAR, .blocking = true, .clear = {} },
|
||||
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
|
||||
"And the air, it smells like rain... or maybe just damp leaves? Either way, kinda refreshing.", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
{.type = CMD_SET_SCALE, .blocking = false, .set_scale = {.character = "maksw", .scale = 1.0f, .transition = TRANS_FADE, .duration_ms = 200.0f } },
|
||||
{.type = CMD_SET_Z, .blocking = false, .set_z = {.character = "maksw", .z = 1 } },
|
||||
{.type = CMD_SET_Z, .blocking = false, .set_z = {.character = "wikor", .z = 3 } },
|
||||
{.type = CMD_SET_SCALE, .blocking = false, .set_scale = {.character = "wikor", .scale = 1.05f, .transition = TRANS_FADE, .duration_ms = 200.0f } },
|
||||
|
||||
// Wikor responds
|
||||
{CMD_SET_SCALE, "maksw", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
|
||||
{CMD_SET_Z, "maksw", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
|
||||
{CMD_SET_Z, "wikor", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
|
||||
{CMD_SET_SCALE, "wikor", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, u8"admirał wiktorov",
|
||||
"I see what you mean. It's almost like the clouds are in slow motion today. Peaceful, but a bit eerie too.", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
{.type = CMD_SPEAK, .blocking = true, .speak = {.speaker = u8"admirał wiktorov", .text = "I see what you mean. It's almost like the clouds are in slow motion today. Peaceful, but a bit eerie too.", .text_speed = 30.0f } },
|
||||
{.type = CMD_WAIT, .blocking = true, .wait = {} },
|
||||
{.type = CMD_CLEAR, .blocking = true, .clear = {} },
|
||||
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, u8"admirał wiktorov",
|
||||
"I almost feel like grabbing a cup of tea and just watching the sky for hours. But I have a feeling that won't happen.", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, u8"admirał wiktorov",
|
||||
"Also, did you notice that weird bird earlier? It looked like it was practicing aerobatics or something.", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
|
||||
// Maksw continues
|
||||
{CMD_SET_SCALE, "wikor", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
|
||||
{CMD_SET_Z, "wikor", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
|
||||
{CMD_SET_Z, "maksw", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
|
||||
{CMD_SET_SCALE, "maksw", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
|
||||
"Yeah, that bird was definitely showing off. Maybe it's training for some secret bird Olympics.", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
|
||||
"Honestly, between the weather, the clouds, and the bird, it feels like the whole world hit the pause button for a moment.", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
|
||||
"But then reality hits. Bugs to fix, emails to answer, the usual chaos. Can't just float in a dream forever.", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
|
||||
"Still, I like moments like this. Makes me appreciate the little things more. You know?", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
|
||||
// Wikor closes it
|
||||
{CMD_SET_SCALE, "maksw", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
|
||||
{CMD_SET_Z, "maksw", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
|
||||
{CMD_SET_Z, "wikor", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
|
||||
{CMD_SET_SCALE, "wikor", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
|
||||
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, u8"admirał wiktorov",
|
||||
"Absolutely. Alright, back to work. But next time the sky looks like this, we should take a proper break.", TRANS_NONE, 0, true},
|
||||
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
|
||||
|
||||
{CMD_HIDE, "maksw", 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_FADE, 500.0f, false},
|
||||
{CMD_HIDE, "wikor", 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_FADE, 500.0f, true},
|
||||
{.type = CMD_HIDE, .blocking = false, .hide = {.character = "maksw", .transition = TRANS_FADE, .duration_ms = 500.0f } },
|
||||
{.type = CMD_HIDE, .blocking = true, .hide = {.character = "wikor", .transition = TRANS_FADE, .duration_ms = 500.0f } },
|
||||
};
|
||||
|
||||
Scene scene = { scene_commands, sizeof(scene_commands) / sizeof(Command), 0 };
|
||||
@@ -551,7 +564,7 @@ int main() {
|
||||
bool running = true;
|
||||
|
||||
AppState current_state = STATE_IDLE;
|
||||
AnimationTracker anim_tracker; // NEW: Track multiple animations
|
||||
AnimationTracker anim_tracker; // Track multiple animations
|
||||
|
||||
auto last_frame_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
@@ -580,13 +593,6 @@ int main() {
|
||||
frame_time_sum = 0.0f;
|
||||
}
|
||||
|
||||
compositor_tick(compositor, delta_time_sec);
|
||||
|
||||
text_nextframe(name_text);
|
||||
text_nextframe(dialogue_text);
|
||||
compositor_mark_content_dirty(compositor, name_id);
|
||||
compositor_mark_content_dirty(compositor, dialogue_id);
|
||||
|
||||
// Update animation tracker
|
||||
anim_tracker.update(delta_time_sec);
|
||||
|
||||
@@ -640,7 +646,7 @@ int main() {
|
||||
|
||||
scene.next();
|
||||
|
||||
// NEW: Check blocking flag
|
||||
// Check blocking flag
|
||||
if (cmd->blocking) {
|
||||
if (cmd->type == CMD_WAIT) {
|
||||
current_state = STATE_WAITING_FOR_INPUT;
|
||||
@@ -655,6 +661,13 @@ int main() {
|
||||
// If non-blocking, loop continues immediately to next command
|
||||
}
|
||||
|
||||
compositor_tick(compositor, delta_time_sec);
|
||||
|
||||
text_nextframe(name_text, delta_time_sec);
|
||||
text_nextframe(dialogue_text, delta_time_sec);
|
||||
compositor_mark_content_dirty(compositor, name_id);
|
||||
compositor_mark_content_dirty(compositor, dialogue_id);
|
||||
|
||||
const uint32_t* fb = compositor_compose(compositor);
|
||||
window_blit(window, fb);
|
||||
}
|
||||
|
||||
+85
-66
@@ -670,8 +670,9 @@ extern "C" {
|
||||
|
||||
// --- Update & Animation ---
|
||||
|
||||
// Advances the text animation and renders new characters.
|
||||
bool text_nextframe(text_t* text) {
|
||||
// Advances the text animation and renders new characters.
|
||||
// deltatime is the time elapsed since the last frame (in seconds).
|
||||
bool text_nextframe(text_t* text, float deltatime) {
|
||||
if (!text) return true;
|
||||
if (text->queue_index >= text->text_queue.size()) {
|
||||
return true; // Nothing to do
|
||||
@@ -704,94 +705,112 @@ extern "C" {
|
||||
stbtt_GetFontVMetrics(info, &ascent, &descent, &line_gap);
|
||||
float baseline = ascent * scale;
|
||||
|
||||
// Update animation progress
|
||||
// --- Update animation progress using deltatime ---
|
||||
size_t total_cp = utf8_count_codepoints(params->text);
|
||||
if (params->speed > 0.0f && text->char_index < (int)total_cp) {
|
||||
text->char_progress += params->speed;
|
||||
text->char_index = static_cast<int>(text->char_progress);
|
||||
// The animation speed (params->speed) is now characters per second.
|
||||
// Multiply by deltatime to get the progress to advance this frame.
|
||||
text->char_progress += params->speed * deltatime;
|
||||
|
||||
// Only update char_index if it has advanced past the current index.
|
||||
// This prevents the animation from advancing multiple characters in a single frame
|
||||
// if the deltatime is large, but still allows continuous float progress.
|
||||
int new_char_index = static_cast<int>(text->char_progress);
|
||||
|
||||
// We only want to draw characters *up to* the new index, but we
|
||||
// still need to check if the new index is greater than the last frame's index.
|
||||
if (new_char_index > text->char_index) {
|
||||
text->char_index = new_char_index;
|
||||
}
|
||||
}
|
||||
else if (params->speed <= 0.0f) {
|
||||
text->char_index = (int)total_cp;
|
||||
}
|
||||
// Cap char_index to the total number of codepoints
|
||||
text->char_index = (int)std::min((size_t)text->char_index, total_cp);
|
||||
|
||||
// Only draw NEW characters
|
||||
size_t draw_start = text->last_rendered_char; // cp index
|
||||
size_t draw_end = text->char_index; // cp index
|
||||
size_t draw_end = text->char_index; // cp index
|
||||
|
||||
// Drawing loop - only for NEW characters
|
||||
for (const auto& line : params->lines) {
|
||||
// Skip lines that are completely rendered
|
||||
if (line.end_cp_index <= draw_start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip lines that haven't started yet
|
||||
if (line.start_cp_index >= draw_end) {
|
||||
break;
|
||||
}
|
||||
|
||||
text->current_x = text->padding_x;
|
||||
text->current_y = line.y_offset;
|
||||
|
||||
// Calculate x position up to where we need to draw
|
||||
// Iterate through the line bytes and sum advances for cps before draw_start
|
||||
size_t byte_i = line.start_byte_index;
|
||||
size_t cp_i = line.start_cp_index;
|
||||
|
||||
while (byte_i < line.end_byte_index && cp_i < draw_start) {
|
||||
uint32_t cp = utf8_next(params->text, byte_i);
|
||||
const cached_glyph_t* glyph = get_glyph(text, cp, params->size);
|
||||
if (glyph) {
|
||||
text->current_x += glyph->advance;
|
||||
}
|
||||
++cp_i;
|
||||
}
|
||||
|
||||
// Draw only NEW characters on this line
|
||||
byte_i = line.start_byte_index;
|
||||
cp_i = line.start_cp_index;
|
||||
|
||||
// advance byte_i to the first drawable codepoint (max(line.start_cp_index, draw_start))
|
||||
while (byte_i < line.end_byte_index && cp_i < std::max(line.start_cp_index, draw_start)) {
|
||||
utf8_next(params->text, byte_i);
|
||||
++cp_i;
|
||||
}
|
||||
|
||||
// Now iterate and draw until cp reaches min(line.end_cp_index, draw_end)
|
||||
while (byte_i < line.end_byte_index && cp_i < std::min(line.end_cp_index, draw_end)) {
|
||||
uint32_t cp = utf8_next(params->text, byte_i);
|
||||
if (cp == (uint32_t)'\n') { ++cp_i; continue; }
|
||||
|
||||
const cached_glyph_t* glyph = get_glyph(text, cp, params->size);
|
||||
if (!glyph) {
|
||||
++cp_i;
|
||||
// Check if any new characters should be drawn
|
||||
if (draw_start < draw_end) {
|
||||
// Drawing loop - only for NEW characters
|
||||
for (const auto& line : params->lines) {
|
||||
// Skip lines that are completely rendered
|
||||
if (line.end_cp_index <= draw_start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int x_base = static_cast<int>(text->current_x + glyph->xoff);
|
||||
int y_base = static_cast<int>(text->current_y + baseline + glyph->yoff);
|
||||
|
||||
if (params->outline) {
|
||||
draw_glyph(text, x_base, y_base, params->color_outline, glyph, params->size, true);
|
||||
// Skip lines that haven't started yet
|
||||
if (line.start_cp_index >= draw_end) {
|
||||
break;
|
||||
}
|
||||
draw_glyph(text, x_base, y_base, params->color_main, glyph, params->size, false);
|
||||
|
||||
text->current_x += glyph->advance;
|
||||
++cp_i;
|
||||
text->current_x = text->padding_x;
|
||||
text->current_y = line.y_offset;
|
||||
|
||||
// Calculate x position up to where we need to draw
|
||||
// Iterate through the line bytes and sum advances for cps before draw_start
|
||||
size_t byte_i = line.start_byte_index;
|
||||
size_t cp_i = line.start_cp_index;
|
||||
|
||||
while (byte_i < line.end_byte_index && cp_i < draw_start) {
|
||||
uint32_t cp = utf8_next(params->text, byte_i);
|
||||
const cached_glyph_t* glyph = get_glyph(text, cp, params->size);
|
||||
if (glyph) {
|
||||
text->current_x += glyph->advance;
|
||||
}
|
||||
++cp_i;
|
||||
}
|
||||
|
||||
// Draw only NEW characters on this line
|
||||
byte_i = line.start_byte_index;
|
||||
cp_i = line.start_cp_index;
|
||||
|
||||
// advance byte_i to the first drawable codepoint (max(line.start_cp_index, draw_start))
|
||||
while (byte_i < line.end_byte_index && cp_i < std::max(line.start_cp_index, draw_start)) {
|
||||
utf8_next(params->text, byte_i);
|
||||
++cp_i;
|
||||
}
|
||||
|
||||
// Now iterate and draw until cp reaches min(line.end_cp_index, draw_end)
|
||||
while (byte_i < line.end_byte_index && cp_i < std::min(line.end_cp_index, draw_end)) {
|
||||
uint32_t cp = utf8_next(params->text, byte_i);
|
||||
if (cp == (uint32_t)'\n') { ++cp_i; continue; }
|
||||
|
||||
const cached_glyph_t* glyph = get_glyph(text, cp, params->size);
|
||||
if (!glyph) {
|
||||
++cp_i;
|
||||
continue;
|
||||
}
|
||||
|
||||
int x_base = static_cast<int>(text->current_x + glyph->xoff);
|
||||
int y_base = static_cast<int>(text->current_y + baseline + glyph->yoff);
|
||||
|
||||
if (params->outline) {
|
||||
draw_glyph(text, x_base, y_base, params->color_outline, glyph, params->size, true);
|
||||
}
|
||||
draw_glyph(text, x_base, y_base, params->color_main, glyph, params->size, false);
|
||||
|
||||
text->current_x += glyph->advance;
|
||||
++cp_i;
|
||||
}
|
||||
}
|
||||
// Update tracking only if we drew something new
|
||||
text->last_rendered_char = (int)text->char_index;
|
||||
}
|
||||
|
||||
// Update tracking
|
||||
text->last_rendered_char = (int)text->char_index;
|
||||
|
||||
// Handle block transition
|
||||
if (text->char_index >= (int)utf8_count_codepoints(params->text)) {
|
||||
if (text->char_index >= (int)total_cp) {
|
||||
text->queue_index++;
|
||||
text->char_index = 0;
|
||||
text->char_progress = 0.0f;
|
||||
text->last_rendered_char = 0; // Reset for next block
|
||||
text->current_y += text->last_line_height; // Advance y-pos
|
||||
// Only advance Y if there was a previous block rendered (to prevent double-spacing on first block)
|
||||
if (text->queue_index > 1) {
|
||||
text->current_y += text->last_line_height; // Advance y-pos
|
||||
}
|
||||
}
|
||||
|
||||
// Check if queue is finished
|
||||
|
||||
Reference in new issue
Block a user