diff --git a/game/res/textures/font_10x20.png b/game/res/textures/font_10x20.png deleted file mode 100644 index e7d76c6..0000000 Binary files a/game/res/textures/font_10x20.png and /dev/null differ diff --git a/game/res/textures/font_10x20.xcf b/game/res/textures/font_10x20.xcf deleted file mode 100644 index 293da6e..0000000 Binary files a/game/res/textures/font_10x20.xcf and /dev/null differ diff --git a/game/res/textures/font_9x20.png b/game/res/textures/font_9x20.png index 5f7a52d..df17ae6 100644 Binary files a/game/res/textures/font_9x20.png and b/game/res/textures/font_9x20.png differ diff --git a/game/res/textures/font_9x20.xcf b/game/res/textures/font_9x20.xcf index caaa7a0..c41d918 100644 Binary files a/game/res/textures/font_9x20.xcf and b/game/res/textures/font_9x20.xcf differ 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);