[ui layout]
- implement ui box min size, + small fixes in layout code of ui sample - quick workaround for background showing on resize: clear to current theme's bg0 color - precompute minsize based on children to avoid upward fixup step - fix scrollable panel - Simplify color overrides, override more things to counteract the light theme - Change dragging to active for after #106 is merged - when clamping box to minsize during shrinking, redistribute excess to siblings that still have some slack - wrap demo in a scrollable panel Co-authored-by: Ilia Demianenko <ilia.demianenko@gmail.com> Co-authored-by: Martin Fouilleul <martinfouilleul@gmail.com>
This commit is contained in:
parent
33078fd9dc
commit
7d3f29e43b
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
1191
sketches/ui/main.c
1191
sketches/ui/main.c
File diff suppressed because it is too large
Load Diff
289
src/ui/ui.c
289
src/ui/ui.c
|
@ -1021,23 +1021,54 @@ bool oc_ui_layout_downward_dependency(oc_ui_box* child, int axis)
|
|||
|
||||
void oc_ui_layout_downward_dependent_size(oc_ui_context* ui, oc_ui_box* box, int axis)
|
||||
{
|
||||
//NOTE: layout children and compute spacing
|
||||
f32 count = 0;
|
||||
//NOTE: layout children and compute spacing and minimum size
|
||||
i32 count = 0;
|
||||
f32 minSum = 0;
|
||||
|
||||
oc_list_for(box->children, child, oc_ui_box, listElt)
|
||||
{
|
||||
if(!oc_ui_box_hidden(child))
|
||||
{
|
||||
oc_ui_layout_downward_dependent_size(ui, child, axis);
|
||||
|
||||
if(box->style.layout.axis == axis
|
||||
&& !child->style.floating.c[axis])
|
||||
if(!child->style.floating.c[axis])
|
||||
{
|
||||
count++;
|
||||
if(box->style.layout.axis == axis)
|
||||
{
|
||||
count++;
|
||||
minSum += child->minSize[axis];
|
||||
}
|
||||
else
|
||||
{
|
||||
minSum = oc_max(minSum, child->minSize[axis]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
box->spacing[axis] = oc_max(0, count - 1) * box->style.layout.spacing;
|
||||
|
||||
switch(box->style.size.c[axis].kind)
|
||||
{
|
||||
case OC_UI_SIZE_TEXT:
|
||||
case OC_UI_SIZE_PIXELS:
|
||||
box->minSize[axis] = box->rect.c[2 + axis];
|
||||
break;
|
||||
|
||||
case OC_UI_SIZE_CHILDREN:
|
||||
case OC_UI_SIZE_PARENT:
|
||||
case OC_UI_SIZE_PARENT_MINUS_PIXELS:
|
||||
{
|
||||
int overflowFlag = (OC_UI_FLAG_OVERFLOW_ALLOW_X << axis);
|
||||
|
||||
if(!(box->flags & overflowFlag))
|
||||
{
|
||||
box->minSize[axis] = minSum + box->spacing[axis] + 2 * box->style.layout.margin.c[axis];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
box->minSize[axis] = oc_max(box->minSize[axis], box->style.size.c[axis].minSize);
|
||||
|
||||
oc_ui_size* size = &box->style.size.c[axis];
|
||||
if(size->kind == OC_UI_SIZE_CHILDREN)
|
||||
{
|
||||
|
@ -1082,77 +1113,209 @@ void oc_ui_layout_upward_dependent_size(oc_ui_context* ui, oc_ui_box* box, int a
|
|||
oc_ui_size* size = &child->style.size.c[axis];
|
||||
if(size->kind == OC_UI_SIZE_PARENT)
|
||||
{
|
||||
child->rect.c[2 + axis] = availableSize * size->value;
|
||||
child->rect.c[2 + axis] = oc_max(child->minSize[axis], availableSize * size->value);
|
||||
}
|
||||
else if(size->kind == OC_UI_SIZE_PARENT_MINUS_PIXELS)
|
||||
{
|
||||
child->rect.c[2 + axis] = oc_max(0, availableSize - size->value);
|
||||
child->rect.c[2 + axis] = oc_max(child->minSize[axis], oc_max(0, availableSize - size->value));
|
||||
}
|
||||
}
|
||||
|
||||
//NOTE: solve downard conflicts
|
||||
int overflowFlag = (OC_UI_FLAG_ALLOW_OVERFLOW_X << axis);
|
||||
f32 sum = 0;
|
||||
//NOTE: solve downward conflicts
|
||||
int overflowFlag = (OC_UI_FLAG_OVERFLOW_ALLOW_X << axis);
|
||||
|
||||
if(box->style.layout.axis == axis)
|
||||
if(!(box->flags & overflowFlag))
|
||||
{
|
||||
//NOTE: if we're solving in the layout axis, first compute total sum of children and
|
||||
// total slack available
|
||||
f32 slack = 0;
|
||||
|
||||
oc_list_for(box->children, child, oc_ui_box, listElt)
|
||||
if(box->style.layout.axis == axis)
|
||||
{
|
||||
if(!oc_ui_box_hidden(child)
|
||||
&& !child->style.floating.c[axis])
|
||||
{
|
||||
sum += child->rect.c[2 + axis];
|
||||
slack += child->rect.c[2 + axis] * child->style.size.c[axis].relax;
|
||||
}
|
||||
}
|
||||
f32 prevSum = FLT_MAX;
|
||||
int count = 0;
|
||||
|
||||
if(!(box->flags & overflowFlag))
|
||||
{
|
||||
//NOTE: then remove excess proportionally to each box slack, and recompute children sum.
|
||||
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;
|
||||
//NOTE: take into account the _original size_ minus the _original slack_ in minimum size. This way the widget
|
||||
// never gives up more than wantedSize * relax.
|
||||
oc_list_for(box->children, child, oc_ui_box, listElt)
|
||||
{
|
||||
f32 relax = child->style.size.c[axis].relax;
|
||||
child->rect.c[2 + axis] -= alpha * child->rect.c[2 + axis] * relax;
|
||||
sum += child->rect.c[2 + axis];
|
||||
if(!oc_ui_box_hidden(child)
|
||||
&& !child->style.floating.c[axis])
|
||||
{
|
||||
child->minSize[axis] = oc_max(child->minSize[axis], child->rect.c[2 + axis] * (1 - child->style.size.c[axis].relax));
|
||||
}
|
||||
}
|
||||
|
||||
//NOTE: Loop while we can remove excess. Each iterations reaffects excess to boxes that still have some slack.
|
||||
while(1)
|
||||
{
|
||||
//NOTE: if we're solving in the layout axis, first compute total sum of children and
|
||||
// total slack available
|
||||
f32 sum = 0;
|
||||
f32 slack = 0;
|
||||
|
||||
oc_list_for(box->children, child, oc_ui_box, listElt)
|
||||
{
|
||||
if(!oc_ui_box_hidden(child)
|
||||
&& !child->style.floating.c[axis])
|
||||
{
|
||||
sum += child->rect.c[2 + axis];
|
||||
slack += oc_min(child->rect.c[2 + axis] * child->style.size.c[axis].relax,
|
||||
child->rect.c[2 + axis] - child->minSize[axis]);
|
||||
}
|
||||
}
|
||||
if(prevSum - sum < 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
prevSum = 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);
|
||||
|
||||
oc_list_for(box->children, child, oc_ui_box, listElt)
|
||||
{
|
||||
if(!oc_ui_box_hidden(child) && !child->style.floating.c[axis])
|
||||
{
|
||||
f32 relax = child->style.size.c[axis].relax;
|
||||
f32 minSize = child->minSize[axis];
|
||||
f32 remove = alpha * oc_clamp(child->rect.c[2 + axis] * relax, 0, child->rect.c[2 + axis] - child->minSize[axis]);
|
||||
|
||||
child->rect.c[2 + axis] = oc_max(minSize, child->rect.c[2 + axis] - remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
|
||||
oc_list_for(box->children, child, oc_ui_box, listElt)
|
||||
else
|
||||
{
|
||||
if(!oc_ui_box_hidden(child) && !child->style.floating.c[axis])
|
||||
//NOTE: if we're solving on the secondary axis, we remove excess to each box individually
|
||||
// according to its own slack.
|
||||
oc_list_for(box->children, child, oc_ui_box, listElt)
|
||||
{
|
||||
if(!(box->flags & overflowFlag))
|
||||
if(!oc_ui_box_hidden(child) && !child->style.floating.c[axis])
|
||||
{
|
||||
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->minSize[axis];
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
f32 sum = 0;
|
||||
|
||||
//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(!oc_ui_box_hidden(child)
|
||||
&& !child->style.floating.c[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;
|
||||
|
||||
//NOTE: recurse in children
|
||||
OC_ASSERT(box->rect.c[2 + axis] >= box->minSize[axis], "parent->string = %.*s, box->string = %.*s, axis = %i, box->size[axis].kind = %i, box->rect.c[2+axis] = %f, box->minSize[axis] = %f",
|
||||
oc_str8_ip(box->parent->string),
|
||||
oc_str8_ip(box->string),
|
||||
axis,
|
||||
box->style.size.c[axis].kind,
|
||||
box->rect.c[2 + axis],
|
||||
box->minSize[axis]);
|
||||
/*
|
||||
if(!(box->flags & overflowFlag) && !oc_list_empty(box->children))
|
||||
{
|
||||
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];
|
||||
}
|
||||
availableToParentSized = oc_max(0, availableToParentSized);
|
||||
|
||||
if(availableToParentSized && relaxSum)
|
||||
{
|
||||
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)
|
||||
{
|
||||
oc_ui_layout_upward_dependent_size(ui, child, axis);
|
||||
if(axis != box->style.layout.axis)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1233,6 +1396,11 @@ void oc_ui_layout_compute_rect(oc_ui_context* ui, oc_ui_box* box, oc_vec2 pos)
|
|||
currentPos.c[layoutAxis] += child->rect.c[2 + layoutAxis] + spacing;
|
||||
}
|
||||
}
|
||||
if(isnan(box->rect.w) || isnan(box->rect.h))
|
||||
{
|
||||
oc_log_error("error in box %.*s\n", oc_str8_ip(box->string));
|
||||
OC_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
void oc_ui_layout_find_next_hovered_recursive(oc_ui_context* ui, oc_ui_box* box, oc_vec2 p)
|
||||
|
@ -1285,6 +1453,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 });
|
||||
|
||||
|
@ -1428,6 +1598,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()
|
||||
|
@ -2121,13 +2300,23 @@ 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;
|
||||
|
||||
oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1, 1 },
|
||||
.size.height = { OC_UI_SIZE_PARENT, 1, 1 },
|
||||
.layout.margin.x = 0,
|
||||
.layout.margin.y = 0 },
|
||||
OC_UI_STYLE_SIZE
|
||||
| OC_UI_STYLE_LAYOUT_MARGINS);
|
||||
oc_ui_box_begin(str, flags);
|
||||
|
||||
oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 },
|
||||
.size.height = { OC_UI_SIZE_PARENT, 1 } },
|
||||
OC_UI_STYLE_SIZE);
|
||||
|
||||
oc_ui_box_begin("contents", 0);
|
||||
}
|
||||
|
||||
|
@ -2532,7 +2721,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;
|
||||
|
|
17
src/ui/ui.h
17
src/ui/ui.h
|
@ -69,7 +69,7 @@ typedef struct oc_ui_layout
|
|||
|
||||
typedef enum oc_ui_size_kind
|
||||
{
|
||||
OC_UI_SIZE_TEXT,
|
||||
OC_UI_SIZE_TEXT = 0,
|
||||
OC_UI_SIZE_PIXELS,
|
||||
OC_UI_SIZE_CHILDREN,
|
||||
OC_UI_SIZE_PARENT,
|
||||
|
@ -82,6 +82,7 @@ typedef struct oc_ui_size
|
|||
oc_ui_size_kind kind;
|
||||
f32 value;
|
||||
f32 relax;
|
||||
f32 minSize;
|
||||
} oc_ui_size;
|
||||
|
||||
typedef union oc_ui_box_size
|
||||
|
@ -479,8 +480,8 @@ typedef enum
|
|||
OC_UI_FLAG_ACTIVE_ANIMATION = (1 << 5),
|
||||
//WARN: these two 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_OVERFLOW_ALLOW_X = (1 << 6),
|
||||
OC_UI_FLAG_OVERFLOW_ALLOW_Y = (1 << 7),
|
||||
OC_UI_FLAG_CLIP = (1 << 8),
|
||||
OC_UI_FLAG_DRAW_BACKGROUND = (1 << 9),
|
||||
OC_UI_FLAG_DRAW_FOREGROUND = (1 << 10),
|
||||
|
@ -488,7 +489,7 @@ typedef enum
|
|||
OC_UI_FLAG_DRAW_TEXT = (1 << 12),
|
||||
OC_UI_FLAG_DRAW_PROC = (1 << 13),
|
||||
|
||||
OC_UI_FLAG_OVERLAY = (1 << 14),
|
||||
OC_UI_FLAG_OVERLAY = (1 << 16),
|
||||
} oc_ui_flags;
|
||||
|
||||
struct oc_ui_box
|
||||
|
@ -525,6 +526,7 @@ struct oc_ui_box
|
|||
oc_vec2 floatPos;
|
||||
f32 childrenSum[2];
|
||||
f32 spacing[2];
|
||||
f32 minSize[2];
|
||||
oc_rect rect;
|
||||
|
||||
// signals
|
||||
|
@ -656,12 +658,8 @@ ORCA_API void oc_ui_set_theme(oc_ui_theme* theme);
|
|||
ORCA_API oc_ui_key oc_ui_key_make_str8(oc_str8 string);
|
||||
ORCA_API oc_ui_key oc_ui_key_make_path(oc_str8_list path);
|
||||
|
||||
ORCA_API oc_ui_box* oc_ui_box_lookup_key(oc_ui_key key);
|
||||
ORCA_API oc_ui_box* oc_ui_box_lookup_str8(oc_str8 string);
|
||||
|
||||
// C-string helper
|
||||
#define oc_ui_key_make(s) oc_ui_key_make_str8(OC_STR8(s))
|
||||
#define oc_ui_box_lookup(s) oc_ui_box_lookup_str8(OC_STR8(s))
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
// Box hierarchy building
|
||||
|
@ -677,6 +675,9 @@ ORCA_API void oc_ui_box_push(oc_ui_box* box);
|
|||
ORCA_API void oc_ui_box_pop(void);
|
||||
ORCA_API oc_ui_box* oc_ui_box_top(void);
|
||||
|
||||
ORCA_API oc_ui_box* oc_ui_box_lookup_key(oc_ui_key key);
|
||||
ORCA_API oc_ui_box* oc_ui_box_lookup_str8(oc_str8 string);
|
||||
|
||||
ORCA_API void oc_ui_box_set_draw_proc(oc_ui_box* box, oc_ui_box_draw_proc proc, void* data);
|
||||
|
||||
// C-string helpers
|
||||
|
|
Loading…
Reference in New Issue