improve text node API

Thu, 23 Nov 2023 23:08:57 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 23 Nov 2023 23:08:57 +0100
changeset 19
d0e88022e209
parent 18
00c0632f0f40
child 20
b101c1ef13c7

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;

mercurial