From dd0eb68d4633ccfb9bb3f13dff828259b953ebe5 Mon Sep 17 00:00:00 2001 From: Emil Kosz Date: Mon, 16 Jun 2025 12:59:59 +0200 Subject: [PATCH] Vibe coded multiline text --- game/res/textures/font_10x20.png | Bin 2159 -> 0 bytes game/res/textures/font_10x20.xcf | Bin 23743 -> 0 bytes game/res/textures/font_9x20.png | Bin 2552 -> 2545 bytes game/res/textures/font_9x20.xcf | Bin 26381 -> 26422 bytes game/src/ui/uiman.c | 15 +++-- game/src/ui/widget_core.c | 13 +++++ game/src/ui/widget_core.h | 17 +++++- game/src/ui/widget_layout.c | 95 +++++++++++++++++++++++++++++++ game/src/ui/widget_layout.h | 1 + game/src/ui/widget_render.c | 72 ++++++++++++++++++++++- game/src/ui/widget_render.h | 1 + 11 files changed, 204 insertions(+), 10 deletions(-) delete mode 100644 game/res/textures/font_10x20.png delete mode 100644 game/res/textures/font_10x20.xcf diff --git a/game/res/textures/font_10x20.png b/game/res/textures/font_10x20.png deleted file mode 100644 index e7d76c69850b2dcb7e3fa85fc9567df611ff002e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2159 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 Y0gEyH5I&v+L`=dNAT`j0h&N#Z09a_V!2kdN diff --git a/game/res/textures/font_9x20.png b/game/res/textures/font_9x20.png index 5f7a52d2f502d771d7968290001d4f98d44b5bf7..df17ae699e32fcf787276a9546d5a717c5d105bc 100644 GIT binary patch delta 1748 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 delta 754 zcmdmXj_5Q-s10K!O7fYJs~+F`O6_gYRChX4PW82ib zCMXSZr!B+({~%n?C@`5(Ok0+f;s1XY2B!b4V0Fw;n(03~!~g#|#Hoze#7%>w|u C4!-mN diff --git a/game/src/ui/uiman.c b/game/src/ui/uiman.c index c38cafa..feb6d2e 100644 --- a/game/src/ui/uiman.c +++ b/game/src/ui/uiman.c @@ -16,6 +16,13 @@ static mat4 projection; static Widget *hovered_widget = NULL; +const char *lorem_ipsum = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut " + "labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit " + "esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " + "culpa qui officia deserunt mollit anim id est laborum."; + void uiman_init () { @@ -52,17 +59,13 @@ uiman_init () window2->style->layout_padding = 8; window2->style->background_image = RESTEX ("terrain0"); VEC2_SETI (window2->position, 414, 32); - VEC2_SETI (window2->size, 350, 200); + VEC2_SETI (window2->size, 400, 200); Widget_add_child (ui_root, window2); Widget *scrtest = Widget_scrollable_new (); scrtest->scrollable.layout_horizontal = TRUE; - Widget *test1 = Widget_plain_new (); - test1->style->background_image = RESTEX ("terrain1"); - test1->size[1] = 500; - Widget_add_child_weighed (scrtest, test1, 1); - + Widget_add_child_weighed (scrtest, Widget_multiline_text_new (lorem_ipsum, 21, TEXT_ALIGN_LEFT), 1); Widget_add_child_weighed (window2, scrtest, 0.95); Widget_add_child_weighed (window2, Widget_scrollbar_new (scrtest, FALSE), 0.05); } diff --git a/game/src/ui/widget_core.c b/game/src/ui/widget_core.c index 6f2dc29..8b633ea 100644 --- a/game/src/ui/widget_core.c +++ b/game/src/ui/widget_core.c @@ -100,6 +100,19 @@ Widget_progress_new (gboolean horizontal, float min, float max, float *ptr) return e; } +Widget * +Widget_multiline_text_new (const char *text, float line_height, TextAlignment alignment) +{ + Widget *e = ui_el_new_internal (WIDGET_MULTILINE_TEXT); + e->do_layout = Widget_layout_multiline_text; + e->do_render = Widget_render_multiline_text; + e->multiline_text.text = g_string_new (text); + e->multiline_text.lines = g_array_new (FALSE, FALSE, sizeof (GString)); + e->multiline_text.line_height = line_height; + e->multiline_text.text_alignment = alignment; + return e; +} + Widget * Widget_container_new (gboolean horizontal) { diff --git a/game/src/ui/widget_core.h b/game/src/ui/widget_core.h index 126cf8d..971367f 100644 --- a/game/src/ui/widget_core.h +++ b/game/src/ui/widget_core.h @@ -21,11 +21,14 @@ typedef enum WidgetKind { WIDGET_PLAIN, - WIDGET_CONTAINER, WIDGET_LABEL, - WIDGET_SCROLLABLE, WIDGET_SCROLLBAR, WIDGET_PROGRESS, + + WIDGET_MULTILINE_TEXT, + + WIDGET_CONTAINER, + WIDGET_SCROLLABLE, } WidgetKind; typedef enum WidgetIcon @@ -85,6 +88,14 @@ typedef struct ProgressWidget float min, max, *ptr; } ProgressWidget; +typedef struct MultilineTextWidget +{ + GString *text; + GArray *lines; + float line_height; + TextAlignment text_alignment; +} MultilineTextWidget; + typedef struct ContainerWidget { gboolean horizontal; @@ -110,6 +121,7 @@ typedef struct Widget LabelWidget label; ScrollbarWidget scrollbar; ProgressWidget progress; + MultilineTextWidget multiline_text; ContainerWidget container; ScrollableWidget scrollable; @@ -141,6 +153,7 @@ Widget *Widget_plain_new (); Widget *Widget_label_new (const char *text, TextAlignment alignment); Widget *Widget_scrollbar_new (Widget *target, gboolean horizontal); Widget *Widget_progress_new (gboolean horizontal, float min, float max, float *ptr); +Widget *Widget_multiline_text_new (const char *text, float line_height, TextAlignment alignment); Widget *Widget_container_new (gboolean horizontal); Widget *Widget_scrollable_new (); diff --git a/game/src/ui/widget_layout.c b/game/src/ui/widget_layout.c index 46454d9..0f70b7a 100644 --- a/game/src/ui/widget_layout.c +++ b/game/src/ui/widget_layout.c @@ -108,3 +108,98 @@ Widget_layout_scrollable (Widget *self) glm_vec2_copy (content_size, self->scrollable.content_size); self->layout_dirty = FALSE; } + +void +Widget_layout_multiline_text (Widget *self) +/* Vibe coded. Dunno what the fuck it does. */ +{ + if (self->multiline_text.lines) + { + for (guint i = 0; i < self->multiline_text.lines->len; ++i) + { + GString *old = g_array_index (self->multiline_text.lines, GString *, i); + g_string_free (old, TRUE); + } + + g_array_free (self->multiline_text.lines, TRUE); + } + + GArray *lines = g_array_new (FALSE, FALSE, sizeof (GString *)); + self->multiline_text.lines = lines; + + float pad = self->style->layout_padding; + float row_h = self->multiline_text.line_height; + float char_w = row_h * (9.0 / 20.0); + float usable = self->size[0] - 2 * pad; + + const char *p = self->multiline_text.text->str; + GString *cur = g_string_new (NULL); + float cur_w = 0.0; + + while (*p) + { + const char *end = p; + + while (*end && *end != ' ' && *end != '\n') + { + end++; + } + + size_t wb = end - p; + size_t wc = g_utf8_strlen (p, wb); + float w = wc * char_w; + + if (*end == '\n' || (cur_w > 0 && cur_w + char_w + w > usable)) + { + g_array_append_val (lines, cur); + cur = g_string_new (NULL); + cur_w = 0.0f; + } + + if (cur->len > 0 && *p != '\n') + { + g_string_append_c (cur, ' '); + cur_w += char_w; + } + + g_string_append_len (cur, p, wb); + cur_w += w; + + if (*end == '\n') + { + g_array_append_val (lines, cur); + cur = g_string_new (NULL); + cur_w = 0.0; + p = end + 1; + } + else + { + p = end; + while (*p == ' ') + { + p++; + } + } + } + + if (cur->len > 0) + { + g_array_append_val (lines, cur); + } + else + { + g_string_free (cur, TRUE); + } + + self->size[1] = pad * 2 + lines->len * row_h; + self->layout_dirty = FALSE; + + /* Just a hack. In case the multiline text is inside a scrollable + (which, most of the time, would be the case) we have to update + the scrollable, so it can set it's content_size properly and the + scrollbar can be the right size. */ + if (self->parent) + { + self->parent->layout_dirty = TRUE; + } +} diff --git a/game/src/ui/widget_layout.h b/game/src/ui/widget_layout.h index 1f54b87..9bcc3e7 100644 --- a/game/src/ui/widget_layout.h +++ b/game/src/ui/widget_layout.h @@ -5,3 +5,4 @@ void Widget_layout_container (Widget *self); void Widget_layout_scrollable (Widget *self); +void Widget_layout_multiline_text (Widget *self); diff --git a/game/src/ui/widget_render.c b/game/src/ui/widget_render.c index 106f3d9..2dba062 100644 --- a/game/src/ui/widget_render.c +++ b/game/src/ui/widget_render.c @@ -98,7 +98,7 @@ Widget_render_label (Widget *self, mat4 projection, int window_height) program_use (RESPRG ("uitext")); program_set_mat4 (RESPRG ("uitext"), "projection", projection); - texture_use (self->style->font_bitmap, 0); + texture_use (style->font_bitmap, 0); program_set_vec4 (RESPRG ("uitext"), "color", style->text_color); @@ -111,7 +111,7 @@ Widget_render_label (Widget *self, mat4 projection, int window_height) program_set_vec4 (RESPRG ("uitext"), "color", style->text_color_hovered); } - float char_width = self->size[1] * 0.5; + float char_width = self->size[1] * (9.0 / 20.0); float total_width = g_utf8_strlen (self->label.text->str, -1) * char_width; float offset = 0.0; @@ -208,3 +208,71 @@ Widget_render_progress (Widget *self, mat4 projection, int window_height) Widget_render_base (self, projection, window_height); program_set_int (RESPRG ("uiwidget"), "has_progress", FALSE); } + +void +Widget_render_multiline_text (Widget *self, mat4 projection, int window_height) +{ + Widget_render_base(self, projection, window_height); + + WidgetStyle *style = self->style; + + program_use (RESPRG ("uitext")); + program_set_mat4 (RESPRG ("uitext"), "projection", projection); + texture_use (style->font_bitmap, 0); + program_set_vec4 (RESPRG ("uitext"), "color", style->text_color); + + float pad = style->layout_padding; + guint lines = self->multiline_text.lines->len; + + if (lines == 0) + { + return; + } + + float row_h = self->multiline_text.line_height; + float char_w = row_h * (9.0 / 20.0); + float x0 = self->position[0] + pad; + float y0 = self->position[1] + pad; + float usable = self->size[0] - 2 * pad; + + for (guint i = 0; i < lines; ++i) + { + GString *line = g_array_index (self->multiline_text.lines, GString *, i); + guint nchars = g_utf8_strlen (line->str, -1); + float line_w = nchars * char_w; + + float y = y0 + i * row_h; + float x; + + switch (self->multiline_text.text_alignment) + { + case TEXT_ALIGN_CENTER: + x = x0 + (usable - line_w) * 0.5; + break; + case TEXT_ALIGN_RIGHT: + x = x0 + (usable - line_w); + break; + case TEXT_ALIGN_LEFT: + default: + x = x0; + break; + } + + const char *p = line->str; + while (*p) + { + gunichar ch = g_utf8_get_char (p); + program_set_float (RESPRG ("uitext"), "character", ch); + + mat3 model; + glm_translate2d_make (model, (vec2) { x, y }); + glm_scale2d (model, (vec2) { char_w, row_h }); + program_set_mat3 (RESPRG ("uitext"), "model", model); + + SimpleMesh_render (&quad, FALSE); + + x += char_w; + p = g_utf8_next_char (p); + } + } +} diff --git a/game/src/ui/widget_render.h b/game/src/ui/widget_render.h index 3a44dad..a4f2dac 100644 --- a/game/src/ui/widget_render.h +++ b/game/src/ui/widget_render.h @@ -11,3 +11,4 @@ void Widget_render_base (Widget *self, mat4 projection, int window_height); void Widget_render_label (Widget *self, mat4 projection, int window_height); void Widget_render_scrollbar (Widget *self, mat4 projection, int window_height); void Widget_render_progress (Widget *self, mat4 projection, int window_height); +void Widget_render_multiline_text (Widget *self, mat4 projection, int window_height);