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 |
1.1 --- a/src/ascension/datatypes.h Sun Nov 19 13:27:08 2023 +0100 1.2 +++ b/src/ascension/datatypes.h Thu Nov 23 23:08:57 2023 +0100 1.3 @@ -34,6 +34,7 @@ 1.4 1.5 #include <stdbool.h> 1.6 #include <string.h> 1.7 +#include <SDL2/SDL_pixels.h> 1.8 1.9 // -------------------------------------------------------------------------- 1.10 // Datatype Definitions 1.11 @@ -90,6 +91,10 @@ 1.12 return r; 1.13 } 1.14 1.15 +static inline SDL_Color asc_col_sdl(asc_col4i col) { 1.16 + return (SDL_Color) {.r = col.red, .g = col.green, .b =col.blue, .a = col.alpha}; 1.17 +} 1.18 + 1.19 // -------------------------------------------------------------------------- 1.20 // Matrix Functions 1.21 // --------------------------------------------------------------------------
2.1 --- a/src/ascension/text.h Sun Nov 19 13:27:08 2023 +0100 2.2 +++ b/src/ascension/text.h Thu Nov 23 23:08:57 2023 +0100 2.3 @@ -30,74 +30,61 @@ 2.4 2.5 #include "font.h" 2.6 #include "datatypes.h" 2.7 -#include <cx/string.h> 2.8 +#include <cx/buffer.h> 2.9 2.10 typedef struct AscTextNode { 2.11 + CxBuffer text; 2.12 + AscFont const *font; 2.13 asc_vec2i position; 2.14 - asc_vec2i dimension; 2.15 - unsigned tex_id; 2.16 + asc_col4i color; 2.17 + unsigned max_width; 2.18 bool hidden; 2.19 + bool centered; 2.20 + struct { 2.21 + asc_vec2i dimension; 2.22 + unsigned tex_id; 2.23 + } internal; 2.24 } AscTextNode; 2.25 2.26 2.27 /** 2.28 - * Draws text on screen. 2.29 + * Creates a text node. 2.30 * 2.31 * The current context ink and font will be used. 2.32 * 2.33 - * The text must be zero-terminated (use cx_strdup() e.g. to achieve that) and can 2.34 - * be freed after the call. 2.35 + * To allow more adjustments before initializing the internal structure 2.36 + * this function does NOT invoke asc_text_update() automatically. 2.37 * 2.38 - * @param node a previously created node or 2.39 - * @param position the position where to draw the text 2.40 - * @param max_width the maximum width before breaking the text into new lines 2.41 - * @param text the text to draw (must be zero-terminated!) 2.42 + * @param x the position where to draw the text 2.43 + * @param y the position where to draw the text 2.44 + * @param text the text to draw 2.45 * @see asc_ink() 2.46 * @see asc_font() 2.47 */ 2.48 -void asc_text_draw_lb( 2.49 - AscTextNode *node, 2.50 - asc_vec2i position, 2.51 - unsigned max_width, 2.52 - cxmutstr text); 2.53 +AscTextNode *asc_text(int x, int y, char const* text); 2.54 2.55 /** 2.56 - * Draws text on screen. 2.57 + * Updates the internal state of the text node. 2.58 * 2.59 - * The current context ink and font will be used. 2.60 - * 2.61 - * The text must be zero-terminated (use cx_strdup() e.g. to achieve that) and can 2.62 - * be freed after the call. 2.63 - * 2.64 - * @param node a previously created node or 2.65 - * @param position the position where to draw the text 2.66 - * @param text the text to draw (must be zero-terminated!) 2.67 - * @see asc_ink() 2.68 - * @see asc_font() 2.69 - */ 2.70 -void asc_text_draw( 2.71 - AscTextNode *node, 2.72 - asc_vec2i position, 2.73 - cxmutstr text); 2.74 - 2.75 -/** 2.76 - * Just redraw the text node in the current frame. 2.77 - * 2.78 - * Invoke this method, when the text did not change and the texture does not need 2.79 - * to be re-generated. You can change the position of the node. When you change the dimension, 2.80 - * the text will be scaled. 2.81 + * You must invoke this method after changing the state of the struct, 2.82 + * but not in every frame. 2.83 * 2.84 * @param node the text node 2.85 */ 2.86 -void asc_text_redraw(AscTextNode *node); 2.87 +void asc_text_update(AscTextNode *node); 2.88 2.89 /** 2.90 - * Releases the graphics memory for this node. 2.91 - * 2.92 - * The structure can be reused anytime for other draw calls. 2.93 + * Draws the text node in the current frame. 2.94 * 2.95 * @param node the text node 2.96 */ 2.97 -void asc_text_destroy(AscTextNode *node); 2.98 +void asc_text_draw(AscTextNode *node); 2.99 + 2.100 +/** 2.101 + * Releases all the memory of this node. 2.102 + * 2.103 + * @param node the text node 2.104 + */ 2.105 +void asc_text_free(AscTextNode *node); 2.106 2.107 #endif //ASCENSION_TEXT_H
3.1 --- a/src/text.c Sun Nov 19 13:27:08 2023 +0100 3.2 +++ b/src/text.c Thu Nov 23 23:08:57 2023 +0100 3.3 @@ -32,38 +32,49 @@ 3.4 3.5 #include <GL/glew.h> 3.6 3.7 -void asc_text_draw_lb( 3.8 - AscTextNode *node, 3.9 - asc_vec2i position, 3.10 - unsigned max_width, 3.11 - cxmutstr text) { 3.12 +AscTextNode *asc_text(int x, int y, char const *text) { 3.13 + AscTextNode *node = calloc(1, sizeof(AscTextNode)); 3.14 + if (node == NULL) { 3.15 + asc_error("Out of memory."); 3.16 + return NULL; 3.17 + } 3.18 3.19 - // short circuit - if fully transparent, just hide it 3.20 - if (asc_context.ink.alpha == 0) { 3.21 - node->hidden = true; 3.22 + node->position = (asc_vec2i) {x, y}; 3.23 + node->font = asc_context.active_font; 3.24 + node->color = asc_context.ink; 3.25 + cxBufferInit(&node->text, NULL, strlen(text)+8, 3.26 + cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); 3.27 + cxBufferPutString(&node->text, text); 3.28 + 3.29 + return node; 3.30 +} 3.31 + 3.32 +void asc_text_update(AscTextNode *node) { 3.33 + // short circuit if fully transparent or hidden, we don't need anything 3.34 + if (node->color.alpha == 0 || node->hidden) { 3.35 return; 3.36 } 3.37 3.38 // Generate new texture, if required 3.39 - if (node->tex_id == 0) { 3.40 - glGenTextures(1, &node->tex_id); 3.41 - glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id); 3.42 + if (node->internal.tex_id == 0) { 3.43 + glGenTextures(1, &node->internal.tex_id); 3.44 + glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id); 3.45 glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 3.46 glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 3.47 - asc_dprintf("Generated new texture for text node: %u", node->tex_id); 3.48 + asc_dprintf("Generated new texture for text node: %u", node->internal.tex_id); 3.49 } 3.50 3.51 + // ensure the text is zero-terminated 3.52 + CxBuffer* text = &(node->text); 3.53 + cxBufferMinimumCapacity(text, text->size+1); 3.54 + text->space[text->size] = '\0'; 3.55 + 3.56 // Render text onto a surface 3.57 SDL_Surface *surface = TTF_RenderUTF8_Blended_Wrapped( 3.58 - asc_context.active_font->ptr, 3.59 - text.ptr, 3.60 - (SDL_Color) { 3.61 - .r = asc_context.ink.red, 3.62 - .g = asc_context.ink.green, 3.63 - .b = asc_context.ink.blue, 3.64 - .a = asc_context.ink.alpha 3.65 - }, 3.66 - max_width 3.67 + asc_font_cache_validate(node->font)->ptr, 3.68 + text->space, 3.69 + asc_col_sdl(node->color), 3.70 + node->max_width 3.71 ); 3.72 if (surface == NULL) { 3.73 asc_error(SDL_GetError()); 3.74 @@ -71,13 +82,13 @@ 3.75 } 3.76 3.77 // Store basic node information 3.78 - node->position = position; 3.79 - node->dimension.width = surface->w; 3.80 - node->dimension.height = surface->h; 3.81 + node->position = node->position; 3.82 + node->internal.dimension.width = surface->w; 3.83 + node->internal.dimension.height = surface->h; 3.84 3.85 // Transfer Image Data 3.86 // TODO: move the image data transfer to a separate function - we will need it more often 3.87 - glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id); 3.88 + glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id); 3.89 glPixelStorei(GL_UNPACK_ROW_LENGTH, surface->pitch / surface->format->BytesPerPixel); 3.90 glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, 3.91 surface->w, surface->h, 3.92 @@ -85,23 +96,14 @@ 3.93 3.94 // Free the surface 3.95 SDL_FreeSurface(surface); 3.96 - 3.97 - // Redraw the node 3.98 - asc_text_redraw(node); 3.99 } 3.100 3.101 -void asc_text_draw( 3.102 - AscTextNode *node, 3.103 - asc_vec2i position, 3.104 - cxmutstr text) { 3.105 - unsigned max_width = asc_context.active_window->dimensions.width - position.width; 3.106 - asc_text_draw_lb(node, position, max_width, text); 3.107 -} 3.108 +void asc_text_draw(AscTextNode *node) { 3.109 + if (node->color.alpha == 0 || node->hidden) { 3.110 + return; 3.111 + } 3.112 3.113 -void asc_text_redraw(AscTextNode *node) { 3.114 - if (node->hidden) return; 3.115 - 3.116 - if (node->tex_id == 0) { 3.117 + if (node->internal.tex_id == 0) { 3.118 asc_error("Tried to redraw text node after destruction"); 3.119 return; 3.120 } 3.121 @@ -110,12 +112,13 @@ 3.122 3.123 // Upload projection 3.124 // TODO: when we group UI draw calls, we don't need this 3.125 - glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1, GL_FALSE, asc_context.active_window->projection); 3.126 + glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1, 3.127 + GL_FALSE, asc_context.active_window->projection); 3.128 3.129 // Upload model matrix 3.130 asc_mat4f model = {0}; 3.131 - model[asc_mat4_index(0, 0)] = node->dimension.width; 3.132 - model[asc_mat4_index(1, 1)] = node->dimension.height; 3.133 + model[asc_mat4_index(0, 0)] = node->internal.dimension.width; 3.134 + model[asc_mat4_index(1, 1)] = node->internal.dimension.height; 3.135 model[asc_mat4_index(3, 0)] = node->position.x; 3.136 model[asc_mat4_index(3, 1)] = node->position.y; 3.137 model[asc_mat4_index(3, 3)] = 1; 3.138 @@ -123,15 +126,16 @@ 3.139 3.140 // Upload surface 3.141 glActiveTexture(GL_TEXTURE0); 3.142 - glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id); 3.143 + glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id); 3.144 glUniform1i(ASC_SHADER_FONT.surface, 0); 3.145 3.146 // Draw mesh 3.147 asc_primitives_draw_plane(); 3.148 } 3.149 3.150 -void asc_text_destroy(AscTextNode *node) { 3.151 - asc_dprintf("Release text node texture: %u", node->tex_id); 3.152 - glDeleteTextures(1, &node->tex_id); 3.153 - node->tex_id = 0; 3.154 +void asc_text_free(AscTextNode *node) { 3.155 + asc_dprintf("Release text node texture: %u", node->internal.tex_id); 3.156 + glDeleteTextures(1, &node->internal.tex_id); 3.157 + cxBufferDestroy(&node->text); 3.158 + free(node); 3.159 } 3.160 \ No newline at end of file
4.1 --- a/test/sandbox.c Sun Nov 19 13:27:08 2023 +0100 4.2 +++ b/test/sandbox.c Thu Nov 23 23:08:57 2023 +0100 4.3 @@ -52,7 +52,9 @@ 4.4 AscWindow *window = asc_window_initialize(0, &settings); 4.5 asc_shader_initialize_predefined(); 4.6 4.7 - AscTextNode fps_counter = {0}; 4.8 + asc_set_font(asc_font(ASC_FONT_REGULAR, 24)); 4.9 + asc_ink_rgb(255, 0, 0); 4.10 + AscTextNode *fps_counter = asc_text(50, 50, "60 FPS"); 4.11 unsigned last_fps = 0; 4.12 4.13 while (asc_loop_next()) { 4.14 @@ -65,19 +67,16 @@ 4.15 unsigned fps = 1000u / asc_context.elapsed_millis; 4.16 if (fps != last_fps) { 4.17 last_fps = fps; 4.18 - asc_set_font(asc_font(ASC_FONT_REGULAR, 24)); 4.19 - asc_ink_rgb(255, 0, 0); 4.20 - cxmutstr fpstext = cx_asprintf("%u FPS", fps); 4.21 - asc_text_draw(&fps_counter, (asc_vec2i) {50, 50}, fpstext); 4.22 - cx_strfree(&fpstext); 4.23 - } else { 4.24 - asc_text_redraw(&fps_counter); 4.25 + cxBufferClear(&fps_counter->text); 4.26 + cx_bprintf(&fps_counter->text, "%u FPS", fps); 4.27 + asc_text_update(fps_counter); 4.28 } 4.29 } 4.30 + asc_text_draw(fps_counter); 4.31 } 4.32 4.33 // TODO: maybe nodes should also be "garbage collected" 4.34 - asc_text_destroy(&fps_counter); 4.35 + asc_text_free(fps_counter); 4.36 4.37 asc_context_destroy(); 4.38 return 0;