[ui, layout] layout constraint solving, first draft

This commit is contained in:
Martin Fouilleul 2023-03-09 12:36:38 +01:00
parent a569454df5
commit 56874c99d6
4 changed files with 161 additions and 177 deletions

View File

@ -78,6 +78,7 @@ void debug_print_size(ui_box* box, ui_axis axis, int indent)
{
debug_print_indent(indent);
printf("size %s: ", axis == UI_AXIS_X ? "x" : "y");
f32 value = box->targetStyle->size.c[axis].value;
switch(box->targetStyle->size.c[axis].kind)
{
case UI_SIZE_TEXT:
@ -89,11 +90,15 @@ void debug_print_size(ui_box* box, ui_axis axis, int indent)
break;
case UI_SIZE_PARENT:
printf("parent\n");
printf("parent: %f\n", value);
break;
case UI_SIZE_PARENT_MINUS_PIXELS:
printf("parent minus pixels: %f\n", value);
break;
case UI_SIZE_PIXELS:
printf("pixels\n");
printf("pixels: %f\n", value);
break;
}
@ -233,7 +238,10 @@ void panel_end(void)
ui_box* scrollBarX = 0;
ui_box* scrollBarY = 0;
if(contentsW > panel->rect.w)
bool needsScrollX = contentsW > panel->rect.w;
bool needsScrollY = contentsH > panel->rect.h;
if(needsScrollX)
{
f32 thumbRatioX = panel->rect.w / contentsW;
f32 sliderX = panel->scroll.x /(contentsW - panel->rect.w);
@ -242,8 +250,9 @@ void panel_end(void)
.size.height = {UI_SIZE_PIXELS, 10, 0},
.floating.x = true,
.floating.y = true,
.floatTarget = {0, panel->rect.h - 12}},
UI_STYLE_SIZE);
.floatTarget = {0, panel->rect.h - 10}},
UI_STYLE_SIZE
|UI_STYLE_FLOAT);
scrollBarX = ui_slider("scrollerX", thumbRatioX, &sliderX);
@ -255,16 +264,18 @@ void panel_end(void)
}
}
if(contentsH > panel->rect.h)
if(needsScrollY)
{
f32 thumbRatioY = panel->rect.h / contentsH;
f32 sliderY = panel->scroll.y /(contentsH - panel->rect.h);
f32 spacerSize = needsScrollX ? 10 : 0;
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PIXELS, 10, 0},
.size.height = {UI_SIZE_PARENT, 1., 0},
.size.height = {UI_SIZE_PARENT_MINUS_PIXELS, spacerSize, 0},
.floating.x = true,
.floating.y = true,
.floatTarget = {panel->rect.w - 12, 0}},
.floatTarget = {panel->rect.w - 10, 0}},
UI_STYLE_SIZE
|UI_STYLE_FLOAT);
@ -277,22 +288,9 @@ void panel_end(void)
ui_box_activate(scrollBarY);
}
}
panel->scroll.x = Clamp(panel->scroll.x, 0, contentsW - panel->rect.w);
panel->scroll.y = Clamp(panel->scroll.y, 0, contentsH - panel->rect.h);
if(scrollBarX)
{
// ui_box_set_floating(scrollBarX, UI_AXIS_X, panel->scroll.x);
// ui_box_set_floating(scrollBarX, UI_AXIS_Y, panel->scroll.y + panel->rect.h - 12);
}
if(scrollBarY)
{
// ui_box_set_floating(scrollBarY, UI_AXIS_X, panel->scroll.x + panel->rect.w - 12);
// ui_box_set_floating(scrollBarY, UI_AXIS_Y, panel->scroll.y);
}
ui_box_end();
}
@ -416,15 +414,17 @@ int main()
}
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PARENT, 1},
.size.height = {UI_SIZE_PIXELS, 500}},
.size.height = {UI_SIZE_PARENT, 1, 1}},
UI_STYLE_SIZE);
ui_style_next(&(ui_style){.layout.axis = UI_AXIS_X}, UI_STYLE_LAYOUT_AXIS);
ui_container("contents", debugFlags)
{
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PARENT, 0.5},
.size.height = {UI_SIZE_PARENT, 1}},
UI_STYLE_SIZE);
.size.height = {UI_SIZE_PARENT, 1},
.borderColor = {0, 0, 1, 1}},
UI_STYLE_SIZE
|UI_STYLE_BORDER_COLOR);
ui_container("left", debugFlags)
{
@ -519,13 +519,14 @@ int main()
}
}
}
/*
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PARENT, 0.5},
.size.height = {UI_SIZE_PARENT, 1}},
UI_STYLE_SIZE);
ui_container("right", debugFlags)
{
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PARENT, 1},
.size.height = {UI_SIZE_PARENT, 0.33}},
UI_STYLE_SIZE);
@ -555,136 +556,46 @@ int main()
widget_view("Color")
{
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PARENT, 1},
.size.height = {UI_SIZE_PARENT, 0.7}},
UI_STYLE_SIZE);
.size.height = {UI_SIZE_PARENT, 0.7},
.layout.axis = UI_AXIS_X},
UI_STYLE_SIZE
|UI_STYLE_LAYOUT_AXIS);
panel("Panel", UI_FLAG_DRAW_BORDER)
{
ui_style_next(&(ui_style){.layout.axis = UI_AXIS_X},
UI_STYLE_LAYOUT_AXIS);
ui_container("contents", 0)
{
ui_style_next(&(ui_style){.layout.spacing = 20},
UI_STYLE_LAYOUT_SPACING);
ui_container("buttons", 0)
{
ui_button("Button A");
ui_button("Button B");
ui_button("Button C");
ui_button("Button D");
}
ui_style_next(&(ui_style){.layout.spacing = 20},
UI_STYLE_LAYOUT_SPACING);
ui_container("buttons", 0)
{
ui_button("Button A");
ui_button("Button B");
ui_button("Button C");
ui_button("Button D");
ui_style_next(&(ui_style){.layout.axis = UI_AXIS_X,
.layout.spacing = 20},
UI_STYLE_LAYOUT_SPACING
|UI_STYLE_LAYOUT_AXIS);
ui_container("buttons2", 0)
{
ui_button("Button A");
ui_button("Button B");
ui_button("Button C");
ui_button("Button D");
}
}
}
}
}
}
}
/*
ui_pattern pattern = {0};
ui_pattern_push(mem_scratch(), &pattern, (ui_selector){.kind = UI_SEL_TEXT, .text = STR8("b")});
ui_pattern_push(mem_scratch(), &pattern, (ui_selector){.kind = UI_SEL_TAG, .tag = ui_tag_make("foo")});
ui_style_match_before(pattern, &(ui_style){.fontSize = 36}, UI_STYLE_FONT_SIZE);
ui_style_match_before(ui_pattern_all(),
&defaultStyle,
UI_STYLE_FONT
|UI_STYLE_FONT_SIZE
|UI_STYLE_COLOR
|UI_STYLE_BORDER_SIZE
|UI_STYLE_BORDER_COLOR
|UI_STYLE_SIZE_WIDTH
|UI_STYLE_SIZE_HEIGHT
|UI_STYLE_LAYOUT);
pattern = (ui_pattern){0};
ui_pattern_push(mem_scratch(), &pattern, (ui_selector){.kind = UI_SEL_TEXT, .text = STR8("c")});
ui_pattern_push(mem_scratch(), &pattern, (ui_selector){.kind = UI_SEL_TAG, .tag = ui_tag_make("button")});
ui_style_match_after(pattern,
&(ui_style){.bgColor = {1, 0.5, 0.5, 1}},
UI_STYLE_BG_COLOR);
pattern = (ui_pattern){0};
ui_pattern_push(mem_scratch(), &pattern, (ui_selector){.kind = UI_SEL_TEXT, .text = STR8("c")});
ui_pattern_push(mem_scratch(), &pattern, (ui_selector){.kind = UI_SEL_TAG, .tag = ui_tag_make("button")});
ui_pattern_push(mem_scratch(), &pattern, (ui_selector){.kind = UI_SEL_STATUS, .op = UI_SEL_AND, .status = UI_ACTIVE|UI_HOVER});
ui_style_match_after(pattern,
&(ui_style){.bgColor = {0.5, 1, 0.5, 1}},
UI_STYLE_BG_COLOR);
ui_style_next(&(ui_style){.bgColor = {0.7, 0.7, 0.7, 1}}, UI_STYLE_BG_COLOR);
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PARENT, 1}, .size.height = {UI_SIZE_PARENT, 1}}, UI_STYLE_SIZE);
ui_container("a", defaultFlags)
{
ui_pattern pattern = {0};
ui_pattern_push(mem_scratch(), &pattern, (ui_selector){.kind = UI_SEL_TEXT, .text = STR8("b")});
ui_style_match_before(pattern, &(ui_style){.fontSize = 22}, UI_STYLE_FONT_SIZE);
ui_container("b", defaultFlags)
{
ui_container("c", defaultFlags)
{
if(ui_button("button d").clicked)
{
printf("clicked button d\n");
}
}
ui_container("e", defaultFlags)
{
ui_tag_next("foo");
if(ui_button("button f").clicked)
{
printf("clicked button f\n");
}
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PARENT, 1},
.size.height = {UI_SIZE_PIXELS, 20, 0}},
UI_STYLE_SIZE);
static f32 slider1 = 0;
ui_slider("slider1", 0.3, &slider1);
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PARENT, 1},
.size.height = {UI_SIZE_PIXELS, 20, 0}},
UI_STYLE_SIZE);
static f32 slider2 = 0;
ui_slider("slider2", 0.3, &slider2);
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PARENT, 1},
.size.height = {UI_SIZE_PIXELS, 20}},
UI_STYLE_SIZE);
static f32 slider3 = 0;
ui_slider("slider3", 0.3, &slider3);
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PIXELS, 20},
.size.height = {UI_SIZE_PIXELS, 200}},
UI_STYLE_SIZE);
static f32 slider4 = 0;
ui_slider("slider4", 0.3, &slider4);
}
}
ui_style_next(&(ui_style){.layout.axis = UI_AXIS_X}, UI_STYLE_LAYOUT_AXIS);
ui_container("f", defaultFlags)
{
ui_tag_next("foo");
ui_label("label d");
ui_style_next(&(ui_style){.size.width = {UI_SIZE_PIXELS, 300},
.size.height = {UI_SIZE_TEXT}},
UI_STYLE_SIZE);
static str8 text = {};
ui_text_box_result res = ui_text_box("textbox", mem_scratch(), text);
if(res.changed)
{
mem_arena_clear(&textArena);
text = str8_push_copy(&textArena, res.text);
}
}*/
}
}
*/
}
if(printDebugStyle)
{
@ -693,32 +604,8 @@ int main()
mg_surface_prepare(surface);
// mg_set_color_rgba(1, 0, 0, 1);
// mg_rectangle_fill(100, 100, 400, 200);
ui_draw();
/*
mg_mat2x3 transform = {1, 0, 0,
0, -1, 800};
bool oldTextFlip = mg_get_text_flip();
mg_set_text_flip(true);
mg_matrix_push(transform);
mg_set_font(font);
mg_set_font_size(20);
mg_set_color_rgba(0, 0, 0, 1);
mg_move_to(0, 38);
mg_text_outlines(STR8("hello, world"));
mg_fill();
mg_matrix_pop();
mg_set_text_flip(oldTextFlip);
*/
mg_flush();
mg_surface_present(surface);

View File

@ -890,7 +890,7 @@ static void mp_process_mouse_button(NSEvent* nsEvent, mp_window_data* window, mp
double factor = [nsEvent hasPreciseScrollingDeltas] ? 0.1 : 1.0;
event.move.x = 0;
event.move.y = 0;
event.move.deltaX = [nsEvent scrollingDeltaX]*factor;
event.move.deltaX = -[nsEvent scrollingDeltaX]*factor;
event.move.deltaY = -[nsEvent scrollingDeltaY]*factor;
event.move.mods = mp_convert_osx_mods([nsEvent modifierFlags]);

108
src/ui.c
View File

@ -18,10 +18,10 @@ static ui_style UI_STYLE_DEFAULTS =
{
.size.width = {.kind = UI_SIZE_CHILDREN,
.value = 0,
.strictness = 0},
.relax = 0},
.size.height = {.kind = UI_SIZE_CHILDREN,
.value = 0,
.strictness = 0},
.relax = 0},
.layout = {.axis = UI_AXIS_Y,
.align = {UI_ALIGN_START,
@ -622,7 +622,7 @@ void ui_animate_ui_size(ui_context* ui, ui_size* size, ui_size target, f32 anima
{
size->kind = target.kind;
ui_animate_f32(ui, &size->value, target.value, animationTime);
ui_animate_f32(ui, &size->strictness, target.strictness, animationTime);
ui_animate_f32(ui, &size->relax, target.relax, animationTime);
}
void ui_box_compute_styling(ui_context* ui, ui_box* box)
@ -1037,6 +1037,7 @@ void ui_layout_upward_dependent_size(ui_context* ui, ui_box* box, int axis)
ui_size* size = &box->style.size.c[axis];
if(size->kind == UI_SIZE_PARENT)
{
ui_box* parent = box->parent;
@ -1045,7 +1046,15 @@ void ui_layout_upward_dependent_size(ui_context* ui, ui_box* box, int axis)
f32 margin = parent->style.layout.margin.c[axis];
box->rect.c[2+axis] = maximum(0, parent->rect.c[2+axis] - parent->spacing[axis] - 2*margin) * size->value;
}
//TODO else?
}
else if(size->kind == UI_SIZE_PARENT_MINUS_PIXELS)
{
ui_box* parent = box->parent;
if(parent)
{
f32 margin = parent->style.layout.margin.c[axis];
box->rect.c[2+axis] = maximum(0, parent->rect.c[2+axis] - parent->spacing[axis] - 2*margin - size->value);
}
}
for_list(&box->children, child, ui_box, listElt)
@ -1055,8 +1064,95 @@ void ui_layout_upward_dependent_size(ui_context* ui, ui_box* box, int axis)
}
void ui_layout_solve_conflicts(ui_context* ui, ui_box* box, int axis)
{
//TODO
{/*
f32 totalContents = box->childrenSum[axis]
+ box->spacing[axis]
+ 2*box->style.layout.margin.c[axis];
*/
f32 sum = 0;
if(box->style.layout.axis == axis)
{
//NOTE: if we're solving in the layout axis, first recompute the total size
// and total slack of children.
//NOTE: we also recompute children that depend on the parent. Maybe we could combine with upward dependent stuff...
f32 margin = box->style.layout.margin.c[axis];
f32 parentAvailableSize = maximum(0, box->rect.c[2+axis] - box->spacing[axis] - 2*margin);
f32 slack = 0;
for_list(&box->children, child, ui_box, listElt)
{
ui_size* size = &child->style.size.c[axis];
if(size->kind == UI_SIZE_PARENT)
{
child->rect.c[2+axis] = parentAvailableSize * size->value;
}
else if(size->kind == UI_SIZE_PARENT_MINUS_PIXELS)
{
child->rect.c[2+axis] = maximum(0, parentAvailableSize - size->value);
}
if(!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 totalContents = sum + box->spacing[axis] + 2*box->style.layout.margin.c[axis];
f32 excess = ClampLowBound(totalContents - box->rect.c[2+axis], 0);
f32 alpha = Clamp(excess / slack, 0, 1);
//NOTE: then remove excess proportionally to each box slack, and recompute children sum.
sum = 0;
for_list(&box->children, child, 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];
}
}
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.
f32 margin = box->style.layout.margin.c[axis];
f32 parentAvailableSize = maximum(0, box->rect.c[2+axis] - box->spacing[axis] - 2*margin);
for_list(&box->children, child, ui_box, listElt)
{
ui_size* size = &child->style.size.c[axis];
if(size->kind == UI_SIZE_PARENT)
{
child->rect.c[2+axis] = parentAvailableSize * size->value;
}
else if(size->kind == UI_SIZE_PARENT_MINUS_PIXELS)
{
child->rect.c[2+axis] = maximum(0, parentAvailableSize - size->value);
}
if(!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 = ClampLowBound(totalContents - box->rect.c[2+axis], 0);
f32 relax = child->style.size.c[axis].relax;
child->rect.c[2+axis] -= minimum(excess, child->rect.c[2+axis]*relax);
sum = maximum(sum, child->rect.c[2+axis]);
}
}
}
box->childrenSum[axis] = sum;
//NOTE: recurse in children
for_list(&box->children, child, ui_box, listElt)
{
ui_layout_solve_conflicts(ui, child, axis);
}
}
void ui_layout_compute_rect(ui_context* ui, ui_box* box, vec2 pos)

View File

@ -85,6 +85,7 @@ typedef enum ui_size_kind
UI_SIZE_PIXELS,
UI_SIZE_CHILDREN,
UI_SIZE_PARENT,
UI_SIZE_PARENT_MINUS_PIXELS,
} ui_size_kind;
@ -92,7 +93,7 @@ typedef struct ui_size
{
ui_size_kind kind;
f32 value;
f32 strictness;
f32 relax;
} ui_size;
typedef union ui_box_size