Flowbox layout

This commit is contained in:
2025-06-16 21:20:54 +02:00
parent 79f735e7b5
commit fd1133f54f
8 changed files with 206 additions and 102 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.
+10 -9
View File
@@ -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
+19
View File
@@ -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)
{
+9
View File
@@ -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,
+164 -93
View File
@@ -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;
}
+1
View File
@@ -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);
+3
View File
@@ -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;