diff --git a/game/res/textures/font_9x20.png b/game/res/textures/font_9x20.png index 7a60e0e..a005c67 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 fda6bd8..ba88e62 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 fb9ee32..1d0c7e5 100644 --- a/game/src/ui/uiman.c +++ b/game/src/ui/uiman.c @@ -57,19 +57,20 @@ uiman_init () Widget_add_child_weighed (chkboxes, Widget_composite_checkbox_make ("Flight", "flight_enabled"), 1); Widget_add_child_weighed (debug_menu, chkboxes, 1); - Widget *window2 = Widget_container_new (TRUE); + Widget *window2 = Widget_flowbox_new (FALSE); window2->style->layout_padding = 8; window2->style->background_image = RESTEX ("terrain0"); VEC2_SETI (window2->position, 414, 32); - VEC2_SETI (window2->size, 400, 200); - Widget_add_child (ui_root, window2); - - Widget *scrtest = Widget_scrollable_new (); - scrtest->scrollable.layout_horizontal = TRUE; + VEC2_SETI (window2->size, 400, 300); - 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); + Widget_add_child_weighed (window2, Widget_test_new (100), 1); + Widget_add_child_weighed (window2, Widget_test_new (100), 1); + Widget_add_child_weighed (window2, Widget_test_new (100), 1); + Widget_add_child_weighed (window2, Widget_test_new (100), 1); + Widget_add_child_weighed (window2, Widget_test_new (100), 1); + Widget_add_child_weighed (window2, Widget_test_new (100), 1); + + Widget_add_child (ui_root, window2); } void diff --git a/game/src/ui/widget_core.c b/game/src/ui/widget_core.c index 8b633ea..6a60cd7 100644 --- a/game/src/ui/widget_core.c +++ b/game/src/ui/widget_core.c @@ -60,6 +60,16 @@ Widget_plain_new () return ui_el_new_internal (WIDGET_PLAIN); } +Widget * +Widget_test_new (float size) +{ + Widget *e = Widget_plain_new (); + e->size[0] = size; + e->size[1] = size; + VEC4_SETI (e->style->background_color, 1, 0, 0, 1); + return e; +} + Widget * Widget_label_new (const char *text, TextAlignment alignment) { @@ -135,6 +145,15 @@ Widget_scrollable_new () return e; } +Widget * +Widget_flowbox_new (gboolean horizontal) +{ + Widget *e = ui_el_new_internal (WIDGET_FLOWBOX); + e->do_layout = Widget_layout_flowbox; + e->flowbox.horizontal = horizontal; + return e; +} + void Widget_add_child (Widget *parent, Widget *child) { diff --git a/game/src/ui/widget_core.h b/game/src/ui/widget_core.h index 971367f..a50a2e7 100644 --- a/game/src/ui/widget_core.h +++ b/game/src/ui/widget_core.h @@ -29,6 +29,7 @@ typedef enum WidgetKind WIDGET_CONTAINER, WIDGET_SCROLLABLE, + WIDGET_FLOWBOX } WidgetKind; typedef enum WidgetIcon @@ -111,6 +112,11 @@ typedef struct ScrollableWidget vec2 content_size; } ScrollableWidget; +typedef struct FlowBoxWidget +{ + gboolean horizontal; +} FlowBoxWidget; + typedef struct Widget { struct Widget *parent; @@ -125,6 +131,7 @@ typedef struct Widget ContainerWidget container; ScrollableWidget scrollable; + FlowBoxWidget flowbox; }; gboolean visible; @@ -150,12 +157,14 @@ typedef struct Widget } Widget; Widget *Widget_plain_new (); +Widget *Widget_test_new (float size); 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 (); +Widget *Widget_flowbox_new (gboolean horizontal); void Widget_add_child (Widget *parent, Widget *child); void Widget_add_child_weighed (Widget *parent, Widget *child, diff --git a/game/src/ui/widget_layout.c b/game/src/ui/widget_layout.c index 0d787e9..3e48e52 100644 --- a/game/src/ui/widget_layout.c +++ b/game/src/ui/widget_layout.c @@ -113,103 +113,174 @@ void Widget_layout_multiline_text (Widget *self) /* Vibe coded. Dunno what the fuck it does. */ { - if (self->multiline_text.lines) - { - for (unsigned int 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++; - } - - gsize wb = end - p; - guint wc = g_utf8_strlen(p, wb); - float w = wc * char_w; - - gboolean too_long = (w > usable); - - if (cur_w > 0 && (*end == '\n' || (!too_long && cur_w + char_w + w > usable))) - { - g_array_append_val (lines, cur); - cur = g_string_new (NULL); - cur_w = 0.0; - } - - if (too_long) - { - const char *q = p; - while (q < end) - { - gunichar ch = g_utf8_get_char (q); - const char *next = g_utf8_next_char (q); - if (cur_w + char_w > usable) - { - g_array_append_val (lines, cur); - cur = g_string_new (NULL); - cur_w = 0.0f; - } - g_string_append_unichar (cur, ch); - cur_w += char_w; - q = next; - } - } - else - { - 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 (self->multiline_text.lines) + { + for (unsigned int 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); } - if (cur->len > 0) { - g_array_append_val(lines, cur); - } else { - g_string_free(cur, 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++; + } + + gsize wb = end - p; + guint wc = g_utf8_strlen(p, wb); + float w = wc * char_w; + + gboolean too_long = (w > usable); + + if (cur_w > 0 && (*end == '\n' || (!too_long && cur_w + char_w + w > usable))) + { + g_array_append_val (lines, cur); + cur = g_string_new (NULL); + cur_w = 0.0; + } + + if (too_long) + { + const char *q = p; + while (q < end) + { + gunichar ch = g_utf8_get_char (q); + const char *next = g_utf8_next_char (q); + if (cur_w + char_w > usable) + { + g_array_append_val (lines, cur); + cur = g_string_new (NULL); + cur_w = 0.0f; + } + g_string_append_unichar (cur, ch); + cur_w += char_w; + q = next; + } + } + else + { + 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++; + } + } } - self->size[1] = pad*2 + self->multiline_text.lines->len * row_h; - self->layout_dirty = FALSE; + if (cur->len > 0) + { + g_array_append_val(lines, cur); + } + else + { + g_string_free(cur, TRUE); + } - if (self->parent) { - self->parent->layout_dirty = TRUE; + self->size[1] = pad*2 + self->multiline_text.lines->len * row_h; + self->layout_dirty = FALSE; + + if (self->parent) + { + self->parent->layout_dirty = TRUE; } } + +void +Widget_layout_flowbox (Widget *self) +/* This widget organizes it's children so they're put into rows or + columns, and expands either horizontally or vertically. */ +{ + gboolean horizontal = self->flowbox.horizontal; + int main_axis = horizontal ? 0 : 1; + int cross_axis = 1 - main_axis; + float pad = self->style->layout_padding; + float space = self->style->layout_child_spacing; + + /* The biggest widght/height of the widget on the cross axis. */ + float last_cross_max = 0.0; + /* Where we position the widget on the main axis. Indeed it also is + the total size of the widgets we put on the main axis. */ + float offset_main = pad; + /* Where we position the widget on the cross axis. */ + float offset_cross = pad; + + /* The widgets have their position be relative to the parent + container. */ + float logical_main = self->position[main_axis]; + float logical_cross = self->position[cross_axis]; + + Widget *child; + WIDGET_FOREACH_CHILD (self, i, child) + { + float size_main = child->size[main_axis]; + float size_cross = child->size[cross_axis]; + + /* If our current width/height on the main axis and the + width/height of the widget are bigger than the container size + (minus the padding), then we move on to the next row. */ + if (offset_main + size_main > self->size[main_axis] - pad * 2) + { + /* The offset on the cross axis is incremented by the + biggest element in the last row (so that the elements are + on an equal axis), plus the space between elements. */ + offset_cross += last_cross_max + space; + /* The offset on the main axis is reset to the container + padding. */ + offset_main = pad; + /* Since we're on the next row/column, we reset the maximum + encountered widget size. */ + last_cross_max = 0.0; + } + + /* If the current widget's cross axis size is bigger than the + biggest one we encountered, we set it to be the biggest. */ + if (size_cross > last_cross_max) + { + last_cross_max = size_cross; + } + + child->position[main_axis] = logical_main + offset_main; + child->position[cross_axis] = logical_cross + offset_cross; + + offset_main += size_main + space; + } + + self->size[cross_axis] = offset_cross + last_cross_max + pad; + self->layout_dirty = FALSE; +} diff --git a/game/src/ui/widget_layout.h b/game/src/ui/widget_layout.h index 9bcc3e7..baf7f01 100644 --- a/game/src/ui/widget_layout.h +++ b/game/src/ui/widget_layout.h @@ -6,3 +6,4 @@ void Widget_layout_container (Widget *self); void Widget_layout_scrollable (Widget *self); void Widget_layout_multiline_text (Widget *self); +void Widget_layout_flowbox (Widget *self); diff --git a/game/src/ui/widget_style.h b/game/src/ui/widget_style.h index ec9cea0..f329916 100644 --- a/game/src/ui/widget_style.h +++ b/game/src/ui/widget_style.h @@ -31,5 +31,8 @@ typedef struct WidgetStyle unsigned int background_image; unsigned int font_bitmap; + + /* Unused for now. */ + unsigned int font_char_w, font_char_h; } WidgetStyle;