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
     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;

mercurial