Thu, 23 Nov 2023 23:08:57 +0100
improve text node API
src/ascension/datatypes.h | file | annotate | diff | comparison | revisions | |
src/ascension/text.h | file | annotate | diff | comparison | revisions | |
src/text.c | file | annotate | diff | comparison | revisions | |
test/sandbox.c | file | annotate | diff | comparison | revisions |
--- a/src/ascension/datatypes.h Sun Nov 19 13:27:08 2023 +0100 +++ b/src/ascension/datatypes.h Thu Nov 23 23:08:57 2023 +0100 @@ -34,6 +34,7 @@ #include <stdbool.h> #include <string.h> +#include <SDL2/SDL_pixels.h> // -------------------------------------------------------------------------- // Datatype Definitions @@ -90,6 +91,10 @@ return r; } +static inline SDL_Color asc_col_sdl(asc_col4i col) { + return (SDL_Color) {.r = col.red, .g = col.green, .b =col.blue, .a = col.alpha}; +} + // -------------------------------------------------------------------------- // Matrix Functions // --------------------------------------------------------------------------
--- a/src/ascension/text.h Sun Nov 19 13:27:08 2023 +0100 +++ b/src/ascension/text.h Thu Nov 23 23:08:57 2023 +0100 @@ -30,74 +30,61 @@ #include "font.h" #include "datatypes.h" -#include <cx/string.h> +#include <cx/buffer.h> typedef struct AscTextNode { + CxBuffer text; + AscFont const *font; asc_vec2i position; - asc_vec2i dimension; - unsigned tex_id; + asc_col4i color; + unsigned max_width; bool hidden; + bool centered; + struct { + asc_vec2i dimension; + unsigned tex_id; + } internal; } AscTextNode; /** - * Draws text on screen. - * - * The current context ink and font will be used. - * - * The text must be zero-terminated (use cx_strdup() e.g. to achieve that) and can - * be freed after the call. - * - * @param node a previously created node or - * @param position the position where to draw the text - * @param max_width the maximum width before breaking the text into new lines - * @param text the text to draw (must be zero-terminated!) - * @see asc_ink() - * @see asc_font() - */ -void asc_text_draw_lb( - AscTextNode *node, - asc_vec2i position, - unsigned max_width, - cxmutstr text); - -/** - * Draws text on screen. + * Creates a text node. * * The current context ink and font will be used. * - * The text must be zero-terminated (use cx_strdup() e.g. to achieve that) and can - * be freed after the call. + * To allow more adjustments before initializing the internal structure + * this function does NOT invoke asc_text_update() automatically. * - * @param node a previously created node or - * @param position the position where to draw the text - * @param text the text to draw (must be zero-terminated!) + * @param x the position where to draw the text + * @param y the position where to draw the text + * @param text the text to draw * @see asc_ink() * @see asc_font() */ -void asc_text_draw( - AscTextNode *node, - asc_vec2i position, - cxmutstr text); +AscTextNode *asc_text(int x, int y, char const* text); /** - * Just redraw the text node in the current frame. + * Updates the internal state of the text node. * - * Invoke this method, when the text did not change and the texture does not need - * to be re-generated. You can change the position of the node. When you change the dimension, - * the text will be scaled. + * You must invoke this method after changing the state of the struct, + * but not in every frame. * * @param node the text node */ -void asc_text_redraw(AscTextNode *node); +void asc_text_update(AscTextNode *node); /** - * Releases the graphics memory for this node. - * - * The structure can be reused anytime for other draw calls. + * Draws the text node in the current frame. * * @param node the text node */ -void asc_text_destroy(AscTextNode *node); +void asc_text_draw(AscTextNode *node); + +/** + * Releases all the memory of this node. + * + * @param node the text node + */ +void asc_text_free(AscTextNode *node); #endif //ASCENSION_TEXT_H
--- a/src/text.c Sun Nov 19 13:27:08 2023 +0100 +++ b/src/text.c Thu Nov 23 23:08:57 2023 +0100 @@ -32,38 +32,49 @@ #include <GL/glew.h> -void asc_text_draw_lb( - AscTextNode *node, - asc_vec2i position, - unsigned max_width, - cxmutstr text) { +AscTextNode *asc_text(int x, int y, char const *text) { + AscTextNode *node = calloc(1, sizeof(AscTextNode)); + if (node == NULL) { + asc_error("Out of memory."); + return NULL; + } - // short circuit - if fully transparent, just hide it - if (asc_context.ink.alpha == 0) { - node->hidden = true; + node->position = (asc_vec2i) {x, y}; + node->font = asc_context.active_font; + node->color = asc_context.ink; + cxBufferInit(&node->text, NULL, strlen(text)+8, + cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); + cxBufferPutString(&node->text, text); + + return node; +} + +void asc_text_update(AscTextNode *node) { + // short circuit if fully transparent or hidden, we don't need anything + if (node->color.alpha == 0 || node->hidden) { return; } // Generate new texture, if required - if (node->tex_id == 0) { - glGenTextures(1, &node->tex_id); - glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id); + if (node->internal.tex_id == 0) { + glGenTextures(1, &node->internal.tex_id); + glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id); glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - asc_dprintf("Generated new texture for text node: %u", node->tex_id); + asc_dprintf("Generated new texture for text node: %u", node->internal.tex_id); } + // ensure the text is zero-terminated + CxBuffer* text = &(node->text); + cxBufferMinimumCapacity(text, text->size+1); + text->space[text->size] = '\0'; + // Render text onto a surface SDL_Surface *surface = TTF_RenderUTF8_Blended_Wrapped( - asc_context.active_font->ptr, - text.ptr, - (SDL_Color) { - .r = asc_context.ink.red, - .g = asc_context.ink.green, - .b = asc_context.ink.blue, - .a = asc_context.ink.alpha - }, - max_width + asc_font_cache_validate(node->font)->ptr, + text->space, + asc_col_sdl(node->color), + node->max_width ); if (surface == NULL) { asc_error(SDL_GetError()); @@ -71,13 +82,13 @@ } // Store basic node information - node->position = position; - node->dimension.width = surface->w; - node->dimension.height = surface->h; + node->position = node->position; + node->internal.dimension.width = surface->w; + node->internal.dimension.height = surface->h; // Transfer Image Data // TODO: move the image data transfer to a separate function - we will need it more often - glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id); + glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id); glPixelStorei(GL_UNPACK_ROW_LENGTH, surface->pitch / surface->format->BytesPerPixel); glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, surface->w, surface->h, @@ -85,23 +96,14 @@ // Free the surface SDL_FreeSurface(surface); - - // Redraw the node - asc_text_redraw(node); } -void asc_text_draw( - AscTextNode *node, - asc_vec2i position, - cxmutstr text) { - unsigned max_width = asc_context.active_window->dimensions.width - position.width; - asc_text_draw_lb(node, position, max_width, text); -} +void asc_text_draw(AscTextNode *node) { + if (node->color.alpha == 0 || node->hidden) { + return; + } -void asc_text_redraw(AscTextNode *node) { - if (node->hidden) return; - - if (node->tex_id == 0) { + if (node->internal.tex_id == 0) { asc_error("Tried to redraw text node after destruction"); return; } @@ -110,12 +112,13 @@ // Upload projection // TODO: when we group UI draw calls, we don't need this - glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1, GL_FALSE, asc_context.active_window->projection); + glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1, + GL_FALSE, asc_context.active_window->projection); // Upload model matrix asc_mat4f model = {0}; - model[asc_mat4_index(0, 0)] = node->dimension.width; - model[asc_mat4_index(1, 1)] = node->dimension.height; + model[asc_mat4_index(0, 0)] = node->internal.dimension.width; + model[asc_mat4_index(1, 1)] = node->internal.dimension.height; model[asc_mat4_index(3, 0)] = node->position.x; model[asc_mat4_index(3, 1)] = node->position.y; model[asc_mat4_index(3, 3)] = 1; @@ -123,15 +126,16 @@ // Upload surface glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id); + glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id); glUniform1i(ASC_SHADER_FONT.surface, 0); // Draw mesh asc_primitives_draw_plane(); } -void asc_text_destroy(AscTextNode *node) { - asc_dprintf("Release text node texture: %u", node->tex_id); - glDeleteTextures(1, &node->tex_id); - node->tex_id = 0; +void asc_text_free(AscTextNode *node) { + asc_dprintf("Release text node texture: %u", node->internal.tex_id); + glDeleteTextures(1, &node->internal.tex_id); + cxBufferDestroy(&node->text); + free(node); } \ No newline at end of file
--- a/test/sandbox.c Sun Nov 19 13:27:08 2023 +0100 +++ b/test/sandbox.c Thu Nov 23 23:08:57 2023 +0100 @@ -52,7 +52,9 @@ AscWindow *window = asc_window_initialize(0, &settings); asc_shader_initialize_predefined(); - AscTextNode fps_counter = {0}; + asc_set_font(asc_font(ASC_FONT_REGULAR, 24)); + asc_ink_rgb(255, 0, 0); + AscTextNode *fps_counter = asc_text(50, 50, "60 FPS"); unsigned last_fps = 0; while (asc_loop_next()) { @@ -65,19 +67,16 @@ unsigned fps = 1000u / asc_context.elapsed_millis; if (fps != last_fps) { last_fps = fps; - asc_set_font(asc_font(ASC_FONT_REGULAR, 24)); - asc_ink_rgb(255, 0, 0); - cxmutstr fpstext = cx_asprintf("%u FPS", fps); - asc_text_draw(&fps_counter, (asc_vec2i) {50, 50}, fpstext); - cx_strfree(&fpstext); - } else { - asc_text_redraw(&fps_counter); + cxBufferClear(&fps_counter->text); + cx_bprintf(&fps_counter->text, "%u FPS", fps); + asc_text_update(fps_counter); } } + asc_text_draw(fps_counter); } // TODO: maybe nodes should also be "garbage collected" - asc_text_destroy(&fps_counter); + asc_text_free(fps_counter); asc_context_destroy(); return 0;