Flowbox layout
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
+10
-9
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user