From 623f5d4b843ef43268213c1105311c2070b32ef1 Mon Sep 17 00:00:00 2001 From: Martin Fouilleul Date: Fri, 15 Sep 2023 17:44:16 +0200 Subject: [PATCH] [ui] layout is still subtly broken but less so --- samples/ui/src/main.c | 28 ++++----- src/ui/ui.c | 138 +++++++++++++++++++++++++++++++++++++----- src/ui/ui.h | 22 ++++--- 3 files changed, 148 insertions(+), 40 deletions(-) diff --git a/samples/ui/src/main.c b/samples/ui/src/main.c index 94f1589..968fd6f 100644 --- a/samples/ui/src/main.c +++ b/samples/ui/src/main.c @@ -106,7 +106,7 @@ void column_begin(const char* header, f32 widthFraction) | OC_UI_STYLE_BORDER_COLOR | OC_UI_STYLE_BORDER_SIZE | OC_UI_STYLE_ROUNDNESS); - oc_ui_box_begin(header, OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER); + oc_ui_box_begin(header, OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER | OC_UI_FLAG_OVERFLOW_FIT_X | OC_UI_FLAG_OVERFLOW_FIT_Y); oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, .layout.align.x = OC_UI_ALIGN_CENTER }, @@ -128,7 +128,7 @@ void column_begin(const char* header, f32 widthFraction) | OC_UI_STYLE_LAYOUT_ALIGN_X | OC_UI_STYLE_LAYOUT_MARGIN_X | OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_box_begin("contents", OC_UI_FLAG_NONE); + oc_ui_box_begin("contents", OC_UI_FLAG_NONE | OC_UI_FLAG_OVERFLOW_FIT_X | OC_UI_FLAG_OVERFLOW_FIT_Y); } void column_end() @@ -185,11 +185,11 @@ ORCA_EXPORT void oc_on_frame_refresh(void) oc_ui_style_mask defaultMask = OC_UI_STYLE_FONT; oc_ui_frame(frameSize, &defaultStyle, defaultMask) { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1, .minSize = 1200 }, - .size.height = { OC_UI_SIZE_PARENT, 1, .minSize = 838 } }, + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1, .minSize = 600 }, + .size.height = { OC_UI_SIZE_PARENT, 1, .minSize = 400 } }, OC_UI_STYLE_SIZE); - oc_ui_container("top level", OC_UI_FLAG_NONE) + oc_ui_container("top level", OC_UI_FLAG_NONE | OC_UI_FLAG_OVERFLOW_FIT_Y) { //-------------------------------------------------------------------------------------------- // Menu bar @@ -228,7 +228,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) | OC_UI_STYLE_LAYOUT_MARGINS | OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("background", OC_UI_FLAG_DRAW_BACKGROUND) + oc_ui_container("background", OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_OVERFLOW_FIT_Y) { column("Widgets", 1.0 / 3) { @@ -239,7 +239,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) OC_UI_STYLE_SIZE_WIDTH | OC_UI_STYLE_LAYOUT_AXIS | OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("top", OC_UI_FLAG_NONE) + oc_ui_container("top", OC_UI_FLAG_NONE | OC_UI_FLAG_OVERFLOW_FIT_X) { oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, .layout.spacing = 24 }, @@ -393,7 +393,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) // Scrollable panel //------------------------------------------------------------------------------------- oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 1, 1 }, + .size.height = { OC_UI_SIZE_PARENT, 1, 1, .minSize = 200 }, .layout.margin.x = 16, .layout.margin.y = 16, .layout.spacing = 8, @@ -555,13 +555,13 @@ ORCA_EXPORT void oc_on_frame_refresh(void) .layout.spacing = 32 }, OC_UI_STYLE_LAYOUT_AXIS | OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("controls", OC_UI_FLAG_NONE) + oc_ui_container("controls", OC_UI_FLAG_NONE | OC_UI_FLAG_OVERFLOW_FIT_X) { oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, .layout.spacing = 16 }, OC_UI_STYLE_LAYOUT_AXIS | OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("unselected", OC_UI_FLAG_NONE) + oc_ui_container("unselected", OC_UI_FLAG_NONE | OC_UI_FLAG_OVERFLOW_FIT_X) { oc_ui_style_next(&(oc_ui_style){ .fontSize = 16 }, OC_UI_STYLE_FONT_SIZE); @@ -569,7 +569,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("size", OC_UI_FLAG_NONE) + oc_ui_container("size", OC_UI_FLAG_NONE | OC_UI_FLAG_OVERFLOW_FIT_X) { f32 widthSlider = (unselectedWidth - 8) / 16; labeled_slider("Width", &widthSlider); @@ -644,7 +644,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) .layout.spacing = 16 }, OC_UI_STYLE_LAYOUT_AXIS | OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("selected", OC_UI_FLAG_NONE) + oc_ui_container("selected", OC_UI_FLAG_NONE | OC_UI_FLAG_OVERFLOW_FIT_X) { oc_ui_style_next(&(oc_ui_style){ .fontSize = 16 }, OC_UI_STYLE_FONT_SIZE); @@ -652,7 +652,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("size", OC_UI_FLAG_NONE) + oc_ui_container("size", OC_UI_FLAG_NONE | OC_UI_FLAG_OVERFLOW_FIT_X) { f32 widthSlider = (selectedWidth - 8) / 16; labeled_slider("Width", &widthSlider); @@ -727,7 +727,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) .layout.spacing = 16 }, OC_UI_STYLE_LAYOUT_AXIS | OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("label", OC_UI_FLAG_NONE) + oc_ui_container("label", OC_UI_FLAG_NONE | OC_UI_FLAG_OVERFLOW_FIT_X | OC_UI_FLAG_OVERFLOW_FIT_Y) { oc_ui_style_next(&(oc_ui_style){ .fontSize = 16 }, OC_UI_STYLE_FONT_SIZE); diff --git a/src/ui/ui.c b/src/ui/ui.c index 7aa4f36..458aef1 100644 --- a/src/ui/ui.c +++ b/src/ui/ui.c @@ -1092,13 +1092,13 @@ void oc_ui_layout_upward_dependent_size(oc_ui_context* ui, oc_ui_box* box, int a } //NOTE: solve downard conflicts - int overflowFlag = (OC_UI_FLAG_ALLOW_OVERFLOW_X << axis); - f32 sum = 0; + int overflowAllowFlag = (OC_UI_FLAG_OVERFLOW_ALLOW_X << axis); if(box->style.layout.axis == axis) { //NOTE: if we're solving in the layout axis, first compute total sum of children and // total slack available + int sum = 0; f32 slack = 0; oc_list_for(box->children, child, oc_ui_box, listElt) @@ -1111,14 +1111,13 @@ void oc_ui_layout_upward_dependent_size(oc_ui_context* ui, oc_ui_box* box, int a } } - if(!(box->flags & overflowFlag)) + if(!(box->flags & overflowAllowFlag)) { - //NOTE: then remove excess proportionally to each box slack, and recompute children sum. + //NOTE: then remove excess proportionally to each box slack f32 totalContents = sum + box->spacing[axis] + 2 * box->style.layout.margin.c[axis]; f32 excess = oc_clamp_low(totalContents - box->rect.c[2 + axis], 0); f32 alpha = oc_clamp(excess / slack, 0, 1); - sum = 0; oc_list_for(box->children, child, oc_ui_box, listElt) { f32 relax = child->style.size.c[axis].relax; @@ -1126,38 +1125,134 @@ void oc_ui_layout_upward_dependent_size(oc_ui_context* ui, oc_ui_box* box, int a f32 remove = alpha * child->rect.c[2 + axis] * relax; child->rect.c[2 + axis] = oc_max(minSize, child->rect.c[2 + axis] - remove); - - sum += child->rect.c[2 + axis]; } } } else { //NOTE: if we're solving on the secondary axis, we remove excess to each box individually - // according to its own slack. Children sum is the maximum child size. + // according to its own slack. oc_list_for(box->children, child, oc_ui_box, listElt) { if(!oc_ui_box_hidden(child) && !child->style.floating.c[axis]) { - if(!(box->flags & overflowFlag)) + if(!(box->flags & overflowAllowFlag)) { f32 totalContents = child->rect.c[2 + axis] + 2 * box->style.layout.margin.c[axis]; f32 excess = oc_clamp_low(totalContents - box->rect.c[2 + axis], 0); f32 relax = child->style.size.c[axis].relax; - child->rect.c[2 + axis] -= oc_min(excess, child->rect.c[2 + axis] * relax); + f32 minSize = child->style.size.c[axis].minSize; + f32 remove = oc_min(excess, child->rect.c[2 + axis] * relax); + + child->rect.c[2 + axis] = oc_max(minSize, child->rect.c[2 + axis] - remove); } - sum = oc_max(sum, child->rect.c[2 + axis]); } } } - box->childrenSum[axis] = sum; + f32 sum = 0; - //NOTE: recurse in children + //NOTE: recurse in children and recompute children sum oc_list_for(box->children, child, oc_ui_box, listElt) { oc_ui_layout_upward_dependent_size(ui, child, axis); + + if(box->style.layout.axis == axis) + { + sum += child->rect.c[2 + axis]; + } + else + { + sum = oc_max(sum, child->rect.c[2 + axis]); + } + } + box->childrenSum[axis] = sum; + + int overflowFitFlag = (OC_UI_FLAG_OVERFLOW_FIT_X << axis); + + if(box->flags & overflowFitFlag) + { + f32 minSize = sum + 2 * box->style.layout.margin.c[axis] + box->spacing[axis]; + box->rect.c[2 + axis] = oc_max(minSize, box->rect.c[2 + axis]); + } +} + +void oc_ui_layout_upward_dependent_fixup(oc_ui_context* ui, oc_ui_box* box, int axis) +{ + f32 margin = box->style.layout.margin.c[axis]; + f32 availableSize = oc_max(0, box->rect.c[2 + axis] - box->spacing[axis] - 2 * margin); + + if(axis == box->style.layout.axis) + { + f32 availableToParentSized = availableSize; + f32 relaxSum = 0; + + oc_list_for(box->children, child, oc_ui_box, listElt) + { + oc_ui_size* size = &child->style.size.c[axis]; + if(size->kind == OC_UI_SIZE_PARENT || size->kind == OC_UI_SIZE_PARENT_MINUS_PIXELS) + { + f32 wantedSize = 0; + if(size->kind == OC_UI_SIZE_PARENT) + { + wantedSize = availableSize * size->value; + } + else + { + wantedSize = availableSize - size->value; + } + if(wantedSize > child->rect.c[2 + axis]) + { + relaxSum += size->relax; + } + } + availableToParentSized -= child->rect.c[2 + axis]; + } + + oc_list_for(box->children, child, oc_ui_box, listElt) + { + oc_ui_size* size = &child->style.size.c[axis]; + if(size->kind == OC_UI_SIZE_PARENT || size->kind == OC_UI_SIZE_PARENT_MINUS_PIXELS) + { + f32 wantedSize = 0; + if(size->kind == OC_UI_SIZE_PARENT) + { + wantedSize = availableSize * size->value; + } + else + { + wantedSize = availableSize - size->value; + } + + if(wantedSize > child->rect.c[2 + axis]) + { + child->rect.c[2 + axis] += availableToParentSized * (size->relax / relaxSum); + } + } + } + } + + oc_list_for(box->children, child, oc_ui_box, listElt) + { + //TODO also give back to parent dependent in layout direction if parent has grown + + if(axis == box->style.layout.axis) + { + } + else + { + oc_ui_size* size = &child->style.size.c[axis]; + if(size->kind == OC_UI_SIZE_PARENT) + { + child->rect.c[2 + axis] = oc_max(child->rect.c[2 + axis], availableSize * size->value); + } + else if(size->kind == OC_UI_SIZE_PARENT_MINUS_PIXELS) + { + child->rect.c[2 + axis] = oc_max(child->rect.c[2 + axis], oc_max(0, availableSize - size->value)); + } + } + oc_ui_layout_upward_dependent_fixup(ui, child, axis); } } @@ -1290,6 +1385,8 @@ void oc_ui_solve_layout(oc_ui_context* ui) { oc_ui_layout_downward_dependent_size(ui, ui->root, axis); oc_ui_layout_upward_dependent_size(ui, ui->root, axis); + + oc_ui_layout_upward_dependent_fixup(ui, ui->root, axis); } oc_ui_layout_compute_rect(ui, ui->root, (oc_vec2){ 0, 0 }); @@ -1433,6 +1530,15 @@ void oc_ui_draw_box(oc_ui_box* box) oc_set_color(style->borderColor); oc_ui_rectangle_stroke(box->rect, style->roundness); } + +#if 0 + if(box->rect.w && box->rect.h) + { + oc_set_width(1); + oc_set_color_rgba(1, 0, 0, 1); + oc_ui_rectangle_stroke(box->rect, 0); + } +#endif } void oc_ui_draw() @@ -2106,8 +2212,8 @@ void oc_ui_panel_begin(const char* str, oc_ui_flags flags) flags = flags | OC_UI_FLAG_CLIP | OC_UI_FLAG_BLOCK_MOUSE - | OC_UI_FLAG_ALLOW_OVERFLOW_X - | OC_UI_FLAG_ALLOW_OVERFLOW_Y + | OC_UI_FLAG_OVERFLOW_ALLOW_X + | OC_UI_FLAG_OVERFLOW_ALLOW_Y | OC_UI_FLAG_SCROLL_WHEEL_X | OC_UI_FLAG_SCROLL_WHEEL_Y; @@ -2516,7 +2622,7 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_ OC_UI_FLAG_CLICKABLE | OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER - | OC_UI_FLAG_ALLOW_OVERFLOW_X + | OC_UI_FLAG_OVERFLOW_ALLOW_X | OC_UI_FLAG_CLIP); f32 maxOptionWidth = 0; diff --git a/src/ui/ui.h b/src/ui/ui.h index dac8e0a..8ec3d77 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -477,18 +477,20 @@ typedef enum OC_UI_FLAG_BLOCK_MOUSE = (1 << 3), OC_UI_FLAG_HOT_ANIMATION = (1 << 4), OC_UI_FLAG_ACTIVE_ANIMATION = (1 << 5), - //WARN: these two following flags need to be kept as consecutive bits to + //WARN: these four following flags need to be kept as consecutive bits to // play well with axis-agnostic functions - OC_UI_FLAG_ALLOW_OVERFLOW_X = (1 << 6), - OC_UI_FLAG_ALLOW_OVERFLOW_Y = (1 << 7), - OC_UI_FLAG_CLIP = (1 << 8), - OC_UI_FLAG_DRAW_BACKGROUND = (1 << 9), - OC_UI_FLAG_DRAW_FOREGROUND = (1 << 10), - OC_UI_FLAG_DRAW_BORDER = (1 << 11), - OC_UI_FLAG_DRAW_TEXT = (1 << 12), - OC_UI_FLAG_DRAW_PROC = (1 << 13), + OC_UI_FLAG_OVERFLOW_ALLOW_X = (1 << 6), + OC_UI_FLAG_OVERFLOW_ALLOW_Y = (1 << 7), + OC_UI_FLAG_OVERFLOW_FIT_X = (1 << 8), + OC_UI_FLAG_OVERFLOW_FIT_Y = (1 << 9), + OC_UI_FLAG_CLIP = (1 << 10), + OC_UI_FLAG_DRAW_BACKGROUND = (1 << 11), + OC_UI_FLAG_DRAW_FOREGROUND = (1 << 12), + OC_UI_FLAG_DRAW_BORDER = (1 << 13), + OC_UI_FLAG_DRAW_TEXT = (1 << 14), + OC_UI_FLAG_DRAW_PROC = (1 << 15), - OC_UI_FLAG_OVERLAY = (1 << 14), + OC_UI_FLAG_OVERLAY = (1 << 16), } oc_ui_flags; struct oc_ui_box