From 0fab830be6d30b60170f91758e080777c1a1ecc0 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Wed, 5 Jul 2023 00:40:29 +0900 Subject: [PATCH] [ui] Implement auto-expand layout By default, horizontal and vertical layouts expand to fill their parent in their on-axis direction (horizontally for horizontal layouts), but fit to their child views in their off-axis. Flexible space views take advantage of auto-expansion, pushing sibling views such that the grandparent view is filled on the parent view's on-axis, and the parent view is filled by the space in the parent view's off-axis. Flexible views currently have a background fill, allowing them to provide background filling of the overall view with minimal overdraw (ancestor views don't need to have any fill at all). --- include/QF/ui/canvas.h | 1 + include/QF/ui/imui.h | 17 +++ libs/console/cl_debug.c | 28 +++-- libs/ui/canvas.c | 3 + libs/ui/imui.c | 273 ++++++++++++++++++++++++++++++++++------ 5 files changed, 278 insertions(+), 44 deletions(-) diff --git a/include/QF/ui/canvas.h b/include/QF/ui/canvas.h index c72637da2..0fa04da9e 100644 --- a/include/QF/ui/canvas.h +++ b/include/QF/ui/canvas.h @@ -68,6 +68,7 @@ typedef struct canvas_system_s { uint32_t base; uint32_t view_base; uint32_t text_base; + uint32_t imui_base; } canvas_system_t; struct view_s; diff --git a/include/QF/ui/imui.h b/include/QF/ui/imui.h index 4e65ac2e4..e7a4de835 100644 --- a/include/QF/ui/imui.h +++ b/include/QF/ui/imui.h @@ -35,12 +35,22 @@ typedef struct imui_ctx_s imui_ctx_t; struct canvas_system_s; struct IE_event_s; +enum { + imui_percent_x, ///< int + imui_percent_y, ///< int + + imui_comp_count +}; + +extern const struct component_s imui_components[imui_comp_count]; + typedef enum IMUI_SizeKind { IMUI_SizeKind_Null, IMUI_SizeKind_Pixels, IMUI_SizeKind_TextContent, IMUI_SizeKind_PercentOfParent, IMUI_SizeKind_ChildrenSum, + IMUI_SizeKind_Expand, } IMUI_SizeKind; imui_ctx_t *IMUI_NewContext (struct canvas_system_s canvas_sys, @@ -61,6 +71,7 @@ bool IMUI_Checkbox (imui_ctx_t *ctx, bool *flag, const char *label); void IMUI_Radio (imui_ctx_t *ctx, int *curvalue, int value, const char *label); void IMUI_Slider (imui_ctx_t *ctx, float *value, float minval, float maxval, const char *label); +void IMUI_FlexibleSpace (imui_ctx_t *ctx); #define IMUI_DeferLoop(begin, end) \ for (int _i_ = ((begin), 0); !_i_; _i_++, (end)) @@ -79,8 +90,14 @@ void IMUI_Slider (imui_ctx_t *ctx, float *value, float minval, float maxval, #define UI_Slider(value, minval, maxval, label) \ IMUI_Slider(IMUI_context, value, minval, maxval, label) +#define UI_FlexibleSpace() \ + IMUI_FlexibleSpace(IMUI_context) + #define UI_Layout(vertical) \ IMUI_DeferLoop (IMUI_PushLayout (IMUI_context, vertical), \ IMUI_PopLayout (IMUI_context )) +#define UI_Horizontal UI_Layout(false) +#define UI_Vertical UI_Layout(true) + #endif//__QF_ui_imui_h diff --git a/libs/console/cl_debug.c b/libs/console/cl_debug.c index 3bd4057d8..0ef2911bf 100644 --- a/libs/console/cl_debug.c +++ b/libs/console/cl_debug.c @@ -5,6 +5,7 @@ #include "QF/cvar.h" #include "QF/keys.h" #include "QF/sys.h" +#include "QF/va.h" #include "QF/input/event.h" @@ -52,6 +53,9 @@ static cvar_t deb_fontsize_cvar = { .value = { .type = &cexpr_float, .value = &deb_fontsize }, }; +static int deb_xlen = -1; +static int deb_ylen = -1; + static void con_debug_f (void *data, const cvar_t *cvar) { @@ -61,12 +65,12 @@ con_debug_f (void *data, const cvar_t *cvar) debug_enable_time = Sys_LongTime (); if (!con_debug) { IE_Set_Focus (debug_saved_focus); + } else { + IMUI_SetSize (debug_imui, deb_xlen, deb_ylen); } } } -static int deb_xlen = -1; -static int deb_ylen = -1; static void debug_app_window (const IE_event_t *ie_event) { @@ -152,26 +156,34 @@ Con_Debug_Draw (void) IMUI_BeginFrame (debug_imui); static int state; static bool flag = true; - UI_Layout(true) { - UI_Layout(false) { + UI_Vertical { + UI_Horizontal { if (UI_Button ("Close Debug")) { close_debug (); } if (flag) { - UI_Button ("_##1"); + UI_FlexibleSpace (); + UI_Button ("abcdefghijklmnopqrstuvwxyza##1"); } } - UI_Layout(false) { + UI_Horizontal { UI_Checkbox (&flag, "hi there"); if (flag) { - UI_Button ("_##2"); + UI_FlexibleSpace (); + UI_Button ("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst##2"); } } - UI_Layout(false) { + UI_Horizontal { UI_Radio (&state, 0, "A"); UI_Radio (&state, 1, "B"); UI_Radio (&state, 2, "C"); + UI_FlexibleSpace (); } + UI_Horizontal { + UI_Button (va(0, "mem: %zd", Sys_CurrentRSS ())); + UI_FlexibleSpace (); + } + UI_FlexibleSpace (); } IMUI_Draw (debug_imui); diff --git a/libs/ui/canvas.c b/libs/ui/canvas.c index 909d54996..6275e7929 100644 --- a/libs/ui/canvas.c +++ b/libs/ui/canvas.c @@ -33,6 +33,7 @@ #define IMPLEMENT_CANVAS_Funcs #include "QF/ui/canvas.h" +#include "QF/ui/imui.h" #include "QF/ui/text.h" #include "QF/ui/view.h" @@ -498,6 +499,8 @@ Canvas_InitSys (canvas_system_t *canvas_sys, ecs_registry_t *reg) view_comp_count), .text_base = ECS_RegisterComponents (reg, text_components, text_comp_count), + .imui_base = ECS_RegisterComponents (reg, imui_components, + imui_comp_count), }; } diff --git a/libs/ui/imui.c b/libs/ui/imui.c index ac2e4e6ea..f257483e8 100644 --- a/libs/ui/imui.c +++ b/libs/ui/imui.c @@ -47,6 +47,17 @@ #include "QF/ui/imui.h" #include "QF/ui/text.h" +const component_t imui_components[imui_comp_count] = { + [imui_percent_x] = { + .size = sizeof (int), + .name = "percent x", + }, + [imui_percent_y] = { + .size = sizeof (int), + .name = "percent y", + }, +}; + typedef struct imui_state_s { struct imui_state_s *next; struct imui_state_s **prev; @@ -234,8 +245,10 @@ void IMUI_BeginFrame (imui_ctx_t *ctx) { uint32_t root_ent = ctx->root_view.id; + auto root_size = View_GetLen (ctx->root_view); Ent_RemoveComponent (root_ent, ctx->root_view.comp, ctx->root_view.reg); ctx->root_view = View_AddToEntity (root_ent, ctx->vsys, nullview); + View_SetLen (ctx->root_view, root_size.x, root_size.y); ctx->frame_start = Sys_LongTime (); ctx->frame_count++; ctx->current_parent = ctx->root_view; @@ -256,47 +269,118 @@ prune_objects (imui_ctx_t *ctx) } } -//FIXME currently works properly only for grav_northwest -static void -layout_objects (imui_ctx_t *ctx) -{ - auto ref = View_GetRef (ctx->root_view); - auto h = ref->hierarchy; +#define DFL "\e[39;49m" +#define BLK "\e[30;40m" +#define RED "\e[31;40m" +#define GRN "\e[32;40m" +#define ONG "\e[33;40m" +#define BLU "\e[34;40m" +#define MAG "\e[35;40m" +#define CYN "\e[36;40m" +#define WHT "\e[37;40m" - byte *modified = h->components[view_modified]; - view_pos_t *pos = h->components[view_pos]; +static const char * +view_color (hierarchy_t *h, uint32_t ind, imui_ctx_t *ctx) +{ + auto reg = h->reg; + uint32_t e = h->ent[ind]; + viewcont_t *cont = h->components[view_control]; + + switch (cont[ind].semantic_x) { + case IMUI_SizeKind_Null: + if (Ent_HasComponent (e, ctx->csys.base + canvas_glyphs, reg)) { + return CYN; + } + return DFL; + case IMUI_SizeKind_Pixels: return WHT; + case IMUI_SizeKind_TextContent: return CYN; + case IMUI_SizeKind_PercentOfParent: return ONG; + case IMUI_SizeKind_ChildrenSum: return MAG; + case IMUI_SizeKind_Expand: return RED; + } + return DFL; +} + +static void __attribute__((used)) +dump_tree (hierarchy_t *h, uint32_t ind, int level, imui_ctx_t *ctx) +{ + view_pos_t *len = h->components[view_len]; + auto c = ((viewcont_t *)h->components[view_control])[ind]; + uint32_t e = h->ent[ind]; + printf ("%2d: %*s%s[%d %d] %c %d %d", ind, + level * 3, "", view_color (h, ind, ctx), + len[ind].x, len[ind].y, + c.vertical ? 'v' : 'h', c.semantic_x, c.semantic_y); + for (uint32_t j = 0; j < h->reg->components.size; j++) { + if (Ent_HasComponent (e, j, h->reg)) { + printf (", %s", h->reg->components.a[j].name); + } + } + printf (DFL"\n"); + + if (h->childIndex[ind] > ind) { + for (uint32_t i = 0; i < h->childCount[ind]; i++) { + if (h->childIndex[ind] + i >= h->num_objects) { + break; + } + dump_tree (h, h->childIndex[ind] + i, level + 1, ctx); + } + } + if (!level) { + puts (""); + } +} + +typedef struct { + bool x, y; +} boolpair_t; + +static void +calc_upwards_dependent (imui_ctx_t *ctx, hierarchy_t *h, + boolpair_t *down_depend) +{ + auto reg = ctx->csys.reg; + uint32_t *ent = h->ent; view_pos_t *len = h->components[view_len]; viewcont_t *cont = h->components[view_control]; uint32_t *parent = h->parentIndex; - struct boolpair { - bool x, y; - } down_depend[h->num_objects]; - - // the root view size is always explicity - down_depend[0] = (struct boolpair) { false, false }; + uint32_t c_percent_x = ctx->csys.imui_base + imui_percent_x; + uint32_t c_percent_y = ctx->csys.imui_base + imui_percent_y; for (uint32_t i = 1; i < h->num_objects; i++) { -// printf ("%d %d %d [%d %d] [%d %d]\n", i, parent[i], h->childCount[i], -// pos[i].x, pos[i].y, len[i].x, len[i].y); - if (cont[i].semantic_x == IMUI_SizeKind_ChildrenSum) { - down_depend[i].x = 1; - } else if (!(down_depend[i].x = down_depend[parent[i]].x) + if (down_depend + && (cont[i].semantic_x == IMUI_SizeKind_ChildrenSum + || cont[i].semantic_x == IMUI_SizeKind_Expand)) { + down_depend[i].x = true; + } else if ((!down_depend + || !(down_depend[i].x = down_depend[parent[i]].x)) && cont[i].semantic_x == IMUI_SizeKind_PercentOfParent) { - int x = (len[parent[i]].x * 100) / 100; //FIXME precent - modified[i] |= len[i].x != x; + int *percent = Ent_GetComponent (ent[i], c_percent_x, reg); + int x = (len[parent[i]].x * *percent) / 100; len[i].x = x; } - if (cont[i].semantic_y == IMUI_SizeKind_ChildrenSum) { - down_depend[i].y = 1; - } else if (!(down_depend[i].y = down_depend[parent[i]].y) + if (down_depend + && (cont[i].semantic_y == IMUI_SizeKind_ChildrenSum + || cont[i].semantic_y == IMUI_SizeKind_Expand)) { + down_depend[i].y = true; + } else if ((!down_depend + || !(down_depend[i].y = down_depend[parent[i]].y)) && cont[i].semantic_y == IMUI_SizeKind_PercentOfParent) { - int y = (len[parent[i]].y * 100) / 100; //FIXME precent - modified[i] |= len[i].y != y; + int *percent = Ent_GetComponent (ent[i], c_percent_y, reg); + int y = (len[parent[i]].y * *percent) / 100; len[i].y = y; } } +} + +static void +calc_downwards_dependent (hierarchy_t *h) +{ + view_pos_t *len = h->components[view_len]; + viewcont_t *cont = h->components[view_control]; for (uint32_t i = h->num_objects; --i > 0; ) { view_pos_t clen = len[i]; - if (cont[i].semantic_x == IMUI_SizeKind_ChildrenSum) { + if (cont[i].semantic_x == IMUI_SizeKind_ChildrenSum + || cont[i].semantic_x == IMUI_SizeKind_Expand) { clen.x = 0; if (cont[i].vertical) { for (uint32_t j = 0; j < h->childCount[i]; j++) { @@ -310,7 +394,8 @@ layout_objects (imui_ctx_t *ctx) } } } - if (cont[i].semantic_y == IMUI_SizeKind_ChildrenSum) { + if (cont[i].semantic_y == IMUI_SizeKind_ChildrenSum + || cont[i].semantic_y == IMUI_SizeKind_Expand) { clen.y = 0; if (!cont[i].vertical) { for (uint32_t j = 0; j < h->childCount[i]; j++) { @@ -324,9 +409,98 @@ layout_objects (imui_ctx_t *ctx) } } } - modified[i] |= (len[i].x != clen.x) | (len[i].y != clen.y); len[i] = clen; } +} + +static void +calc_expansions (imui_ctx_t *ctx, hierarchy_t *h) +{ + auto reg = ctx->csys.reg; + + uint32_t *ent = h->ent; + view_pos_t *len = h->components[view_len]; + viewcont_t *cont = h->components[view_control]; + uint32_t c_percent_x = ctx->csys.imui_base + imui_percent_x; + uint32_t c_percent_y = ctx->csys.imui_base + imui_percent_y; + + for (uint32_t i = 0; i < h->num_objects; i++) { + view_pos_t tlen = {}; + view_pos_t elen = {}; + view_pos_t ecount = {}; + for (uint32_t j = 0; j < h->childCount[i]; j++) { + uint32_t child = h->childIndex[i] + j; + tlen.x += len[child].x; + tlen.y += len[child].y; + if (cont[child].semantic_x == IMUI_SizeKind_Expand) { + int *p = Ent_GetComponent (ent[child], c_percent_x, reg); + elen.x += *p; + ecount.x++; + } + if (cont[child].semantic_y == IMUI_SizeKind_Expand) { + int *p = Ent_GetComponent (ent[child], c_percent_y, reg); + elen.y += *p; + ecount.y++; + } + } +// printf ("i:%d t:[%d %d] e:[%d %d] ec:[%d %d]\n", i, tlen.x, tlen.y, +// elen.x, elen.y, ecount.x, ecount.y); + for (uint32_t j = 0; ecount.x && j < h->childCount[i]; j++) { + uint32_t child = h->childIndex[i] + j; + if (cont[child].semantic_x != IMUI_SizeKind_Expand) { + continue; + } + if (cont[i].vertical) { + len[child].x = len[i].x; +// printf ("xc:%d p:%d l:%d\n", child, len[i].x, len[child].x); + } else { + uint32_t space = len[i].x - tlen.x; + int *p = Ent_GetComponent (ent[child], c_percent_x, reg); + len[child].x += *p * space / elen.x; +// printf ("xc:%d p:%d s:%d e:%d: l:%d\n", child, *p, space, +// elen.x, len[child].x); + } + } + for (uint32_t j = 0; ecount.y && j < h->childCount[i]; j++) { + uint32_t child = h->childIndex[i] + j; + if (cont[child].semantic_y != IMUI_SizeKind_Expand) { + continue; + } + if (cont[i].vertical) { + uint32_t space = len[i].y - tlen.y; + int *p = Ent_GetComponent (ent[child], c_percent_y, reg); + len[child].y += *p * space / elen.y; +// printf ("yc:%d p:%d s:%d e:%d: l:%d\n", child, *p, space, +// elen.y, len[child].y); + } else { + len[child].y = len[i].y; +// printf ("yc:%d p:%d l:%d\n", child, len[i].y, len[child].y); + } + } + } +} + +//FIXME currently works properly only for grav_northwest +static void +layout_objects (imui_ctx_t *ctx) +{ + auto ref = View_GetRef (ctx->root_view); + auto h = ref->hierarchy; + + view_pos_t *pos = h->components[view_pos]; + view_pos_t *len = h->components[view_len]; + viewcont_t *cont = h->components[view_control]; + uint32_t *parent = h->parentIndex; + boolpair_t down_depend[h->num_objects]; + + // the root view size is always explicit + down_depend[0] = (boolpair_t) { false, false }; + calc_upwards_dependent (ctx, h, down_depend); + calc_downwards_dependent (h); + calc_expansions (ctx, h); + //dump_tree (h, 0, 0, ctx); + // resolve conflicts + //fflush (stdout); view_pos_t cpos = {}; uint32_t cur_parent = 0; @@ -337,13 +511,10 @@ layout_objects (imui_ctx_t *ctx) } if (cont[i].semantic_x != IMUI_SizeKind_Null && cont[i].semantic_y != IMUI_SizeKind_Null) { - modified[i] |= (pos[i].x != cpos.x) | (pos[i].y != cpos.y); pos[i] = cpos; } else if (cont[i].semantic_x != IMUI_SizeKind_Null) { - modified[i] |= pos[i].x != cpos.x; pos[i].x = cpos.x; } else if (cont[i].semantic_y != IMUI_SizeKind_Null) { - modified[i] |= pos[i].y != cpos.y; pos[i].y = cpos.y; } if (cont[parent[i]].vertical) { @@ -397,14 +568,25 @@ void IMUI_PushLayout (imui_ctx_t *ctx, bool vertical) { DARRAY_APPEND (&ctx->parent_stack, ctx->current_parent); - ctx->current_parent = View_New (ctx->vsys, ctx->current_parent); - *View_Control (ctx->current_parent) = (viewcont_t) { + auto view = View_New (ctx->vsys, ctx->current_parent); + auto pcont = View_Control (ctx->current_parent); + ctx->current_parent = view; + auto x_size = pcont->vertical ? IMUI_SizeKind_Expand + : IMUI_SizeKind_ChildrenSum; + auto y_size = pcont->vertical ? IMUI_SizeKind_ChildrenSum + : IMUI_SizeKind_Expand; + *View_Control (view) = (viewcont_t) { .gravity = grav_northwest, .visible = 1, - .semantic_x = IMUI_SizeKind_ChildrenSum, - .semantic_y = IMUI_SizeKind_ChildrenSum, + .semantic_x = x_size, + .semantic_y = y_size, .vertical = vertical, }; + View_SetLen (view, 0, 0); + uint32_t c_percent_x = ctx->csys.imui_base + imui_percent_x; + uint32_t c_percent_y = ctx->csys.imui_base + imui_percent_y; + *(int*) Ent_AddComponent (view.id, c_percent_x, ctx->csys.reg) = 100; + *(int*) Ent_AddComponent (view.id, c_percent_y, ctx->csys.reg) = 100; } void @@ -542,6 +724,8 @@ IMUI_Checkbox (imui_ctx_t *ctx, bool *flag, const char *label) auto text = View_New (ctx->vsys, view); set_control (ctx, text, false); add_text (text, state, ctx); + auto c = View_GetChild (text, 0); + *(uint32_t *)Ent_AddComponent (c.id, ctx->tsys.text_base + text_color, c.reg) = 0xfb; if (check_button_state (ctx, state->entity)) { *flag = !*flag; @@ -591,3 +775,20 @@ IMUI_Slider (imui_ctx_t *ctx, float *value, float minval, float maxval, const char *label) { } + +void +IMUI_FlexibleSpace (imui_ctx_t *ctx) +{ + auto view = View_New (ctx->vsys, ctx->current_parent); + View_SetLen (ctx->current_parent, 0, 0); + + set_control (ctx, view, false); + View_Control (view)->semantic_x = IMUI_SizeKind_Expand; + View_Control (view)->semantic_y = IMUI_SizeKind_Expand; + uint32_t c_percent_x = ctx->csys.imui_base + imui_percent_x; + uint32_t c_percent_y = ctx->csys.imui_base + imui_percent_y; + *(int*) Ent_AddComponent (view.id, c_percent_x, ctx->csys.reg) = 100; + *(int*) Ent_AddComponent (view.id, c_percent_y, ctx->csys.reg) = 100; + + set_fill (ctx, view, 0xfb); +}