Compare commits

...

5 Commits

Author SHA1 Message Date
Martin Fouilleul fe69cedd45 Remove OC_UI_FLAG_OVERFLOW_FIT.
Now OC_UI_FLAG_OVERFLOW_ALLOW controls both shrinking children to the parent's size and fitting parent to shrunk children size.
We can still emulate all combination of shrink * fit with this single flag (and sometimes a parent box).
This also makes fitting the default, which is more practical since fitting a box contents often requires fitting on all children.
2023-09-15 19:47:35 +02:00
Martin Fouilleul 623f5d4b84 [ui] layout is still subtly broken but less so 2023-09-15 17:44:16 +02:00
Martin Fouilleul 2f1212c0ac quick workaround for background showing on resize: clear to current theme's bg0 color 2023-09-15 12:46:27 +02:00
Martin Fouilleul 6a50a6bbdc [ui layout] implement ui box min size, + small fixes in layout code of ui sample 2023-09-15 12:41:23 +02:00
Ilia Demianenko ae392a1fc2 UI demo with styling 2023-09-15 07:41:27 +00:00
10 changed files with 1631 additions and 710 deletions

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -10,4 +10,5 @@ int strcmp(const char* s1, const char* s2);
int strncmp(const char* s1, const char* s2, size_t n);
char* strcpy(char* __restrict s1, const char* __restrict s2);
#define snprintf stbsp_snprintf
#define vsnprintf stbsp_vsnprintf

View File

@ -1083,22 +1083,22 @@ 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(size->minSize, 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(size->minSize, oc_max(0, availableSize - size->value));
}
}
//NOTE: solve downard conflicts
int overflowFlag = (OC_UI_FLAG_ALLOW_OVERFLOW_X << axis);
f32 sum = 0;
int overflowFlag = (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)
@ -1113,24 +1113,25 @@ void oc_ui_layout_upward_dependent_size(oc_ui_context* ui, oc_ui_box* box, int a
if(!(box->flags & overflowFlag))
{
//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;
child->rect.c[2 + axis] -= alpha * child->rect.c[2 + axis] * relax;
sum += child->rect.c[2 + axis];
f32 minSize = child->style.size.c[axis].minSize;
f32 remove = alpha * child->rect.c[2 + axis] * relax;
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.
// according to its own slack.
oc_list_for(box->children, child, oc_ui_box, listElt)
{
@ -1141,19 +1142,119 @@ void oc_ui_layout_upward_dependent_size(oc_ui_context* ui, oc_ui_box* box, int a
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(!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;
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)
{
//TODO also give back to parent dependent in layout direction if parent has grown
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);
}
}
@ -1234,6 +1335,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)
@ -1286,6 +1392,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 });
@ -1429,6 +1537,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()
@ -1620,7 +1737,7 @@ oc_ui_sig oc_ui_button_str8(oc_str8 label)
.layout.margin.y = 6,
.color = theme->primary,
.bgColor = theme->fill0,
.roundness = 3 };
.roundness = theme->roundnessSmall };
oc_ui_style_mask defaultMask = OC_UI_STYLE_SIZE_WIDTH
| OC_UI_STYLE_SIZE_HEIGHT
@ -1701,7 +1818,7 @@ oc_ui_sig oc_ui_checkbox(const char* name, bool* checked)
.size.height = { OC_UI_SIZE_PIXELS, 16 },
.bgColor = theme->primary,
.color = theme->white,
.roundness = 3 };
.roundness = theme->roundnessSmall };
oc_ui_style_mask defaultMask = OC_UI_STYLE_SIZE_WIDTH
| OC_UI_STYLE_SIZE_HEIGHT
@ -1740,7 +1857,7 @@ oc_ui_sig oc_ui_checkbox(const char* name, bool* checked)
.bgColor = { 0 },
.borderColor = theme->text3,
.borderSize = 1,
.roundness = 3 };
.roundness = theme->roundnessSmall };
oc_ui_style_mask defaultMask = OC_UI_STYLE_SIZE_WIDTH
| OC_UI_STYLE_SIZE_HEIGHT
@ -2102,8 +2219,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;
@ -2241,7 +2358,7 @@ void oc_ui_tooltip(const char* label)
.floatTarget = { 24, 0 },
.bgColor = theme->palette->grey7,
.color = theme->bg0,
.roundness = 6 };
.roundness = theme->roundnessMedium };
oc_ui_style_mask contentsMask = OC_UI_STYLE_SIZE
| OC_UI_STYLE_LAYOUT_MARGINS
| OC_UI_STYLE_FLOAT
@ -2512,7 +2629,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;
@ -2532,7 +2649,7 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_
.layout.margin.x = 12,
.layout.margin.y = 6,
.bgColor = theme->fill0,
.roundness = 3 },
.roundness = theme->roundnessSmall },
OC_UI_STYLE_SIZE
| OC_UI_STYLE_LAYOUT_MARGIN_X
| OC_UI_STYLE_LAYOUT_MARGIN_Y
@ -2543,7 +2660,16 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_
{
oc_ui_container("selected_option", 0)
{
oc_ui_label_str8(info->options[info->selectedIndex]);
if(info->selectedIndex == -1)
{
oc_ui_style_next(&(oc_ui_style){ .color = theme->text2 },
OC_UI_STYLE_COLOR);
oc_ui_label_str8(info->placeholder);
}
else
{
oc_ui_label_str8(info->options[info->selectedIndex]);
}
}
oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, button->rect.h },
@ -2589,7 +2715,7 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_
.bgColor = theme->bg3,
.borderColor = theme->elevatedBorder,
.borderSize = 1,
.roundness = 6 },
.roundness = theme->roundnessMedium },
OC_UI_STYLE_SIZE
| OC_UI_STYLE_FLOAT
| OC_UI_STYLE_LAYOUT
@ -2717,9 +2843,11 @@ oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_inf
for(int i = 0; i < info->optionCount; i++)
{
oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X,
.layout.spacing = 8 },
.layout.spacing = 8,
.layout.align.y = OC_UI_ALIGN_CENTER },
OC_UI_STYLE_LAYOUT_AXIS
| OC_UI_STYLE_LAYOUT_SPACING);
| OC_UI_STYLE_LAYOUT_SPACING
| OC_UI_STYLE_LAYOUT_ALIGN_Y);
oc_ui_box* row = oc_ui_box_begin_str8(info->options[i], OC_UI_FLAG_CLICKABLE);
oc_ui_flags flags = OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER | OC_UI_FLAG_DRAW_PROC;
oc_ui_box* radio = oc_ui_box_make("radio", flags);
@ -2798,6 +2926,7 @@ oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_inf
oc_ui_container("label", 0)
{
oc_ui_tag_next("label");
oc_ui_label_str8(info->options[i]);
}
@ -3599,7 +3728,7 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8
oc_ui_style frameStyle = { .layout.margin.x = 12,
.layout.margin.y = 6,
.bgColor = theme->fill0,
.roundness = 3 };
.roundness = theme->roundnessSmall };
oc_ui_style_mask frameMask = OC_UI_STYLE_LAYOUT_MARGIN_X
| OC_UI_STYLE_LAYOUT_MARGIN_Y
| OC_UI_STYLE_BG_COLOR
@ -4083,6 +4212,7 @@ oc_ui_theme OC_UI_DARK_THEME = {
.primary = { 0.33, 0.66, 1, 1 }, // blue5
.primaryHover = { 0.5, 0.757, 1, 1 }, // blue6
.primaryActive = { 0.66, 0.84, 1, 1 }, // blue7
.border = { 1, 1, 1, 0.08 },
.fill0 = { 1, 1, 1, 0.12 },
.fill1 = { 1, 1, 1, 0.16 },
.fill2 = { 1, 1, 1, 0.2 },
@ -4096,7 +4226,11 @@ oc_ui_theme OC_UI_DARK_THEME = {
.text2 = { 0.976, 0.976, 0.976, 0.6 }, // grey9
.text3 = { 0.976, 0.976, 0.976, 0.35 }, // grey9
.sliderThumbBorder = { 0, 0, 0, 0.075 },
.elevatedBorder = { 1, 1, 1, 0.1 }
.elevatedBorder = { 1, 1, 1, 0.1 },
.roundnessSmall = 3,
.roundnessMedium = 6,
.roundnessLarge = 9
};
oc_ui_palette OC_UI_LIGHT_PALETTE = {
@ -4269,6 +4403,7 @@ oc_ui_theme OC_UI_LIGHT_THEME = {
.primary = { 0.000, 0.392, 0.980, 1 }, // blue5
.primaryHover = { 0.000, 0.384, 0.839, 1 }, // blue6
.primaryActive = { 0.000, 0.310, 0.702, 1 }, // blue7
.border = { 0.110, 0.122, 0.137, 0.08 }, // grey9
.fill0 = { 0.180, 0.196, 0.220, 0.05 }, // grey8
.fill1 = { 0.180, 0.196, 0.220, 0.09 }, // grey8
.fill2 = { 0.180, 0.196, 0.220, 0.13 }, // grey8
@ -4281,6 +4416,10 @@ oc_ui_theme OC_UI_LIGHT_THEME = {
.text1 = { 0.110, 0.122, 0.137, 0.8 }, // grey9
.text2 = { 0.110, 0.122, 0.137, 0.62 }, // grey9
.text3 = { 0.110, 0.122, 0.137, 0.35 }, // grey9
.sliderThumbBorder = { 0, 0, 0, 0.075 },
.elevatedBorder = { 0, 0, 0, 0.075 }
.sliderThumbBorder = { 0, 0, 0, 0.125 },
.elevatedBorder = { 0, 0, 0, 0.075 },
.roundnessSmall = 3,
.roundnessMedium = 6,
.roundnessLarge = 9
};

View File

@ -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
@ -349,6 +350,7 @@ typedef struct oc_ui_theme
oc_color primary;
oc_color primaryHover;
oc_color primaryActive;
oc_color border;
oc_color fill0;
oc_color fill1;
oc_color fill2;
@ -364,6 +366,10 @@ typedef struct oc_ui_theme
oc_color sliderThumbBorder;
oc_color elevatedBorder;
f32 roundnessSmall;
f32 roundnessMedium;
f32 roundnessLarge;
oc_ui_palette* palette;
} oc_ui_theme;
@ -473,8 +479,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),
@ -482,7 +488,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
@ -623,7 +629,7 @@ typedef struct oc_ui_context
oc_ui_edit_move editSelectionMode;
i32 editWordSelectionInitialCursor;
i32 editWordSelectionInitialMark;
bool clipboardRegistered;
oc_ui_theme* theme;
@ -771,9 +777,10 @@ ORCA_API oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena,
typedef struct oc_ui_select_popup_info
{
bool changed;
int selectedIndex;
int selectedIndex; // -1 if nothing is selected
int optionCount;
oc_str8* options;
oc_str8 placeholder;
} oc_ui_select_popup_info;
ORCA_API oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_info* info);