working example of the new layout system

This commit is contained in:
Alessandro Mauri 2025-09-05 13:10:16 +02:00
parent 2619873ca7
commit 0f7d5a6506
13 changed files with 639 additions and 247 deletions

View File

@ -218,3 +218,22 @@ at frame end:
Styling
=======
+-----------------------------------------+
| MARGIN |
| |
| +-----------------------------------+ |
| |xxxxxxxxxxx BORDER xxxxxxxxxxxx| |
| |x+-------------------------------+x| |
| |x| PADDING |x| |
| |x| +-----------------------+ |x| |
| |x| | | |x| |
| |x| | CONTENT | |x| |
| |x| +-----------------------+ |x| |
| |x| |x| |
| |x+-------------------------------+x| |
| |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| |
| +-----------------------------------+ |
| |
| |
+-----------------------------------------+

View File

@ -19,13 +19,14 @@ fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
Sprite* sprite = icon != "" ? ctx.sprite_atlas.get(icon)! : &&(Sprite){};
Rect min_size = {0, 0, style.size, style.size};
Rect text_size = ctx.get_text_bounds(label)!;
// TODO: get min size by style
TextSize text_size = ctx.measure_string(label)!;
Rect icon_size = sprite.rect();
ushort h_lh = (ushort)(ctx.font.line_height() / 2);
ushort left_pad = label != "" ? h_lh : 0;
ushort inner_pad = label != "" && icon != "" ? h_lh : 0;
ushort min_size = style.size;
ushort half_lh = (ushort)(ctx.font.line_height() / 2);
ushort left_pad = label != "" ? half_lh : 0;
ushort inner_pad = label != "" && icon != "" ? half_lh : 0;
ushort right_pad = left_pad;
/* |left_pad
@ -39,13 +40,25 @@ fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
* |inner_pad |right_pad
*/
Rect tot_size = {
.w = (short)max(min_size.w, icon_size.w + text_size.w + left_pad + inner_pad + right_pad),
.h = (short)max(min_size.h, max(icon_size.h, text_size.h)),
elem.layout.w = {
.min = (short)max(min_size, left_pad + icon_size.w + inner_pad + text_size.width.min + right_pad),
.max = (short)max(min_size, left_pad + icon_size.w + inner_pad + text_size.width.max + right_pad),
};
elem.bounds = ctx.layout_element(parent, tot_size, style);
if (elem.bounds.is_null()) return {};
elem.layout.h = {
.min = (short)max(min_size, left_pad + icon_size.h + text_size.height.min + right_pad),
.max = (short)max(min_size, left_pad + icon_size.w + text_size.height.max + right_pad),
};
// add style border and padding
elem.layout.margin = style.margin;
elem.layout.border = style.border;
elem.layout.padding = style.padding;
elem.layout.children.w = elem.layout.w;
elem.layout.children.h = elem.layout.h;
update_parent_grow(elem, parent);
update_parent_size(elem, parent);
elem.events = ctx.get_elem_events(elem);
Rect content_bounds = elem.content_bounds(style);
@ -55,7 +68,7 @@ fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
.w = content_bounds.w - icon_size.w - left_pad - inner_pad - right_pad,
.h = content_bounds.h
};
text_bounds = text_size.center_to(text_bounds);
//text_bounds = text_size.center_to(text_bounds);
Rect icon_bounds = {
.x = content_bounds.x,
@ -82,6 +95,7 @@ fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
return elem.events;
}
/*
// FIXME: this should be inside the style
macro Ctx.checkbox(&ctx, String desc, Point off, bool* active, String tick_sprite = {}, ...)
@ -152,3 +166,4 @@ fn void? Ctx.toggle_id(&ctx, Id id, String description, Point off, bool* active)
s.margin = s.border = s.padding = {};
ctx.push_rect(t, parent.div.z_index, &s)!;
}
*/

View File

@ -97,9 +97,9 @@ macro Ctx.push_cmd(&ctx, Cmd *cmd, int z_index)
default: return ctx.cmd_queue.enqueue(cmd);
}
if (cull_rect(rect, ctx.div_scissor)) {
io::print("NOPE: ");
io::print(cmd.rect.rect);
io::printn(cmd.z_index);
// io::print("NOPE: ");
// io::print(cmd.rect.rect);
// io::printn(cmd.z_index);
// unreachable();
return;
}

View File

@ -9,6 +9,15 @@ import std::core::string;
import std::core::mem::allocator;
macro println(...)
{
$for var $i = 0; $i < $vacount; $i++:
io::print($vaexpr[$i]);
$endfor
io::printn();
}
// element ids are just long ints
alias Id = uint;
@ -45,7 +54,9 @@ struct Elem {
ElemFlags flags;
ElemEvents events;
Rect bounds;
Rect children_bounds;
ElemType type;
Layout layout;
union {
ElemDiv div;
ElemButton button;
@ -131,6 +142,7 @@ const uint GOLDEN_RATIO = 0x9E3779B9;
// with the Cantor pairing function
fn Id? Ctx.gen_id(&ctx, Id id2)
{
// FIXME: this is SHIT
Id id1 = ctx.tree.get(ctx.active_div)!;
// Mix the two IDs non-linearly
Id mixed = id1 ^ id2.rotate_left(13);
@ -160,6 +172,7 @@ fn Elem*? Ctx.get_elem(&ctx, Id id, ElemType type)
elem.flags = (ElemFlags)0;
elem.flags.is_new = is_new;
elem.id = id;
elem.layout = {};
if (is_new == false && elem.type != type) {
return WRONG_ELEMENT_TYPE?;
} else {
@ -232,16 +245,15 @@ fn void? Ctx.frame_begin(&ctx)
//elem.flags.has_focus = ctx.has_focus;
elem.bounds = {0, 0, ctx.width, ctx.height};
elem.div.layout = LAYOUT_ROW;
elem.div.z_index = 0;
elem.div.children_bounds = elem.bounds;
elem.div.scroll_x.enabled = false;
elem.div.scroll_y.enabled = false;
elem.div.pcb = {};
elem.div.origin_c = {};
elem.div.origin_r = {};
elem.layout.dir = ROW;
elem.layout.anchor = TOP_LEFT;
elem.layout.w = @exact(ctx.width);
elem.layout.h = @exact(ctx.height);
ctx.div_scissor = {0, 0, ctx.width, ctx.height};
ctx.div_scissor = elem.bounds;
// The root element does not push anything to the stack
// TODO: add a background color taken from a theme or config
@ -253,7 +265,13 @@ fn void? Ctx.frame_end(&ctx)
{
// FIXME: this is not guaranteed to be root. the user might forget to close a div or some other element
Elem* root = ctx.get_active_div()!;
if (root.id != ROOT_ID) return WRONG_ID?;
if (root.id != ROOT_ID) {
io::printn(root.id);
return WRONG_ID?;
}
// DO THE LAYOUT
ctx.layout_element_tree();
// 1. clear the tree
ctx.tree.nuke();

View File

@ -5,7 +5,6 @@ import std::math;
// div element
struct ElemDiv {
Layout layout;
struct scroll_x {
bool enabled;
bool on;
@ -18,28 +17,42 @@ struct ElemDiv {
}
ushort scroll_size;
int z_index;
Rect children_bounds; // current frame children bounds
Rect pcb; // previous frame children bounds
Point origin_r, origin_c;
}
// useful macro to start and end a div, capturing the trailing block
macro Ctx.@div(&ctx, Rect size, bool scroll_x = false, bool scroll_y = false, ...; @body())
macro Ctx.@div(&ctx,
Size width = @grow, Size height = @grow,
LayoutDirection dir = ROW,
Anchor anchor = TOP_LEFT,
bool scroll_x = false, bool scroll_y = false,
...;
@body()
)
{
ctx.div_begin(size, scroll_x, scroll_y, $vasplat)!;
ctx.div_begin(width, height, dir, anchor, scroll_x, scroll_y, $vasplat)!;
@body();
ctx.div_end()!;
}
// begin a widget container, or div, the size determines the offset (x,y) width and height.
// if the width or height are zero the width or height are set to the maximum available.
// if the width or height are negative the width or height will be calculated based on the children size
// sort similar to a flexbox, and the minimum size is set by the negative of the width or height
// FIXME: there is a bug if the size.w or size.h == -0
macro Ctx.div_begin(&ctx, Rect size, bool scroll_x = false, bool scroll_y = false, ...)
=> ctx.div_begin_id(@compute_id($vasplat), size, scroll_x, scroll_y);
fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y)
macro Ctx.div_begin(&ctx,
Size width = @grow(), Size height = @grow(),
LayoutDirection dir = ROW,
Anchor anchor = TOP_LEFT,
bool scroll_x = false, bool scroll_y = false,
...
)
{
return ctx.div_begin_id(@compute_id($vasplat), width, height, dir, anchor, scroll_x, scroll_y);
}
fn void? Ctx.div_begin_id(&ctx,
Id id,
Size width, Size height,
LayoutDirection dir,
Anchor anchor,
bool scroll_x, bool scroll_y
)
{
id = ctx.gen_id(id)!;
@ -55,30 +68,24 @@ fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y)
elem.div.scroll_size = slider_style.size ? slider_style.size : (style.size ? style.size : DEFAULT_STYLE.size);
elem.div.z_index = parent.div.z_index + 1;
// 2. layout the element
Rect wanted_size = {
.x = size.x,
.y = size.y,
.w = size.w < 0 ? max(elem.div.pcb.w, (short)-size.w) : size.w,
.h = size.h < 0 ? max(elem.div.pcb.h, (short)-size.h) : size.h,
};
elem.bounds = ctx.layout_element(parent, wanted_size, style);
elem.div.children_bounds = {.x = elem.bounds.x, .y = elem.bounds.y};
// update the ctx scissor
ctx.div_scissor = elem.bounds;
ctx.push_scissor(elem.bounds, elem.div.z_index)!;
// 4. Fill the div fields
elem.div.origin_c = {
.x = elem.bounds.x,
.y = elem.bounds.y
// update layout with correct info
elem.layout = {
.w = width,
.h = height,
.dir = dir,
.anchor = anchor,
.margin = style.margin,
.border = style.border,
.padding = style.padding,
};
elem.div.origin_r = elem.div.origin_c;
elem.div.layout = parent.div.layout;
// update parent grow children
update_parent_grow(elem, parent);
// Add the background to the draw stack
bool do_border = parent.div.layout == LAYOUT_FLOATING;
ctx.push_rect(elem.bounds, elem.div.z_index, style)!;
elem.events = ctx.get_elem_events(elem);
@ -89,14 +96,9 @@ fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y)
fn void? Ctx.div_end(&ctx)
{
// swap the children bounds
Elem* elem = ctx.get_active_div()!;
elem.div.pcb = elem.div.children_bounds;
// FIXME: this causes all elements inside the div to loose focus since the mouse press happens
// both inside the element and inside the div bounds
//elem.events = ctx.get_elem_events(elem);
/* FIXME: Redo slider functionality
Rect cb = elem.div.pcb;
// children bounds bottom-right corner
Point cbc = {
@ -155,10 +157,13 @@ fn void? Ctx.div_end(&ctx)
ctx.slider_hor_id(hsid_raw, hslider, &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15))!;
elem.div.layout = prev_l;
}
*/
// the active_div returns to the parent of the current one
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
Elem* parent = ctx.get_parent()!;
// TODO: reset the scissor back to the parent div
ctx.div_scissor = parent.bounds;
ctx.push_scissor(parent.bounds, elem.div.z_index)!;
update_parent_size(elem, parent);
}

View File

@ -9,11 +9,34 @@ import std::io;
import std::ascii;
// ---------------------------------------------------------------------------------- //
// CODEPOINT //
// ---------------------------------------------------------------------------------- //
// unicode code point, different type for a different hash
alias Codepoint = uint;
<*
@require off != null
@require str.ptr != null
*>
fn Codepoint str_to_codepoint(char[] str, usz* off)
{
Codepoint cp;
isz b = grapheme::decode_utf8(str, str.len, (uint*)&cp);
if (b == 0 || b > str.len) {
return 0;
}
*off = b;
return cp;
}
//macro uint Codepoint.hash(self) => ((uint)self).hash();
// ---------------------------------------------------------------------------------- //
// FONT ATLAS //
// ---------------------------------------------------------------------------------- //
/* width and height of a glyph contain the kering advance
* (u,v)
* +-------------*---+ -
@ -60,7 +83,7 @@ struct Font {
fn void? Font.load(&font, String name, ZString path, uint height, float scale)
{
font.table.init(allocator::heap(), capacity: FONT_CACHED);
font.table.init(allocator::mem, capacity: FONT_CACHED);
font.id = name.hash();
font.size = height*scale;
@ -160,26 +183,39 @@ fn void Font.free(&font)
schrift::freefont(font.sft.font);
}
// ---------------------------------------------------------------------------------- //
// FONT LOAD AND QUERY //
// ---------------------------------------------------------------------------------- //
fn void? Ctx.load_font(&ctx, String name, ZString path, uint height, float scale = 1.0)
{
return ctx.font.load(name, path, height, scale);
}
<*
@require off != null
@require str.ptr != null
*>
fn Codepoint str_to_codepoint(char[] str, usz* off)
// TODO: check if the font is present in the context
fn Id Ctx.get_font_id(&ctx, String label)
{
Codepoint cp;
isz b = grapheme::decode_utf8(str, str.len, (uint*)&cp);
if (b == 0 || b > str.len) {
return 0;
}
*off = b;
return cp;
return (Id)label.hash();
}
fn Atlas*? Ctx.get_font_atlas(&ctx, String name)
{
// TODO: use the font name, for now there is only one font
if (name.hash() != ctx.font.id) {
return WRONG_ID?;
}
return &ctx.font.atlas;
}
fn int Font.line_height(&font) => (int)(font.ascender - font.descender + (float)0.5);
// ---------------------------------------------------------------------------------- //
// TEXT MEASUREMENT //
// ---------------------------------------------------------------------------------- //
const uint TAB_SIZE = 4;
// TODO: change the name
@ -189,6 +225,7 @@ struct TextInfo {
Rect bounds;
String text;
usz off;
Size width, height;
// current glyph info
Point origin;
@ -295,20 +332,81 @@ fn Rect? Ctx.get_text_bounds(&ctx, String text)
return ti.text_bounds;
}
// TODO: check if the font is present in the context
fn Id Ctx.get_font_id(&ctx, String label)
{
return (Id)label.hash();
struct TextSize {
Size width, height;
}
fn Atlas*? Ctx.get_font_atlas(&ctx, String name)
// Measeure the size of a string.
// width.min: as if each word is broken up by a new line
// width.max: the width of the string left as-is
// height.min: the height of the string left as-is
// height.max: the height of the string with each word broken up by a new line
fn TextSize? Ctx.measure_string(&ctx, String text)
{
// TODO: use the font name, for now there is only one font
if (name.hash() != ctx.font.id) {
return WRONG_ID?;
Font* font = &ctx.font;
short baseline = (short)font.ascender;
short line_height = (short)font.line_height();
short line_gap = (short)font.linegap;
short space_width = font.get_glyph(' ').adv!;
short tab_width = space_width * TAB_SIZE;
isz off;
usz x;
TextSize ts;
short word_width;
short words = 1;
Rect bounds; // unaltered text bounds;
Point origin;
Codepoint cp = str_to_codepoint(text[off..], &x);
for (; cp != 0; cp = str_to_codepoint(text[off..], &x)) {
off += x;
Glyph* gp = font.get_glyph(cp)!;
// update the text bounds
switch {
case cp == '\n':
origin.x = 0;
origin.y += line_height + line_gap;
case cp == '\t':
origin.x += tab_width;
case ascii::is_cntrl((char)cp):
break;
default:
Rect b = {
.x = origin.x + gp.ox,
.y = origin.y + gp.oy + baseline,
.w = gp.w,
.h = gp.h,
};
bounds = containing_rect(bounds, b);
origin.x += gp.adv;
}
// update the word width
switch {
case ascii::is_space((char)cp):
if (word_width > ts.width.min) ts.width.min = word_width;
word_width = 0;
words++;
default:
word_width += gp.w + gp.ox;
if (off < text.len) word_width += gp.adv;
}
}
// end of string is also end of word
if (word_width > ts.width.min) ts.width.min = word_width;
return &ctx.font.atlas;
ts.width.max = bounds.w;
ts.height.min = bounds.h;
ts.height.max = words * line_height + line_gap * (words-1);
//io::print("'");
//io::print(text);
//io::print("': ");
//io::printn(ts);
return ts;
}
fn int Font.line_height(&font) => (int)(font.ascender - font.descender + (float)0.5);

View File

@ -1,54 +1,102 @@
module ugui;
enum Layout {
LAYOUT_ROW,
LAYOUT_COLUMN,
LAYOUT_FLOATING,
LAYOUT_ABSOLUTE,
import std::math;
import std::io;
enum LayoutDirection {
ROW,
COLUMN,
}
fn void? Ctx.layout_set_row(&ctx)
{
Elem *parent = ctx.get_parent()!;
parent.div.layout = LAYOUT_ROW;
enum Anchor {
TOP_LEFT,
LEFT,
BOTTOM_LEFT,
BOTTOM,
BOTTOM_RIGHT,
RIGHT,
TOP_RIGHT,
TOP,
CENTER
}
fn void? Ctx.layout_set_column(&ctx)
{
Elem *parent = ctx.get_parent()!;
parent.div.layout = LAYOUT_COLUMN;
struct Layout {
Size w, h;
struct children {
Size w, h;
}
ushort grow_children;
short occupied;
struct origin {
short x, y;
}
LayoutDirection dir;
Anchor anchor;
// FIXME: all this cruft with margin border and padding could be resolved by simply providing
// a "Rect content_offset" that represents the combined effect of all three above
Rect margin, border, padding;
}
fn void? Ctx.layout_set_floating(&ctx)
// Total width of a layout element, complete with margin, border and padding
macro Size Layout.total_width(&el)
{
Elem *parent = ctx.get_parent()!;
parent.div.layout = LAYOUT_FLOATING;
Rect min = (Rect){.w = el.w.min, .h = el.h.min}.expand(el.margin).expand(el.border).expand(el.padding);
Rect max = (Rect){.w = el.w.max, .h = el.h.max}.expand(el.margin).expand(el.border).expand(el.padding);
return {.min = min.w, .max = max.w};
}
fn void? Ctx.layout_next_row(&ctx)
// Total height of a layout element, complete with margin, border and padding
macro Size Layout.total_height(&el)
{
Elem *parent = ctx.get_parent()!;
Rect min = (Rect){.w = el.w.min, .h = el.h.min}.expand(el.margin).expand(el.border).expand(el.padding);
Rect max = (Rect){.w = el.w.max, .h = el.h.max}.expand(el.margin).expand(el.border).expand(el.padding);
return {.min = min.h, .max = max.h};
}
parent.div.origin_r = {
.x = parent.bounds.x,
.y = parent.div.children_bounds.bottom_right().y,
// The content space of the element
macro Point Elem.content_space(&e)
{
return {
.x = e.bounds.w - e.layout.border.x - e.layout.border.w - e.layout.padding.x - e.layout.padding.w,
.y = e.bounds.h - e.layout.border.y - e.layout.border.h - e.layout.padding.y - e.layout.padding.h,
};
parent.div.origin_c = parent.div.origin_r;
}
// Update the parent element's grow children counter
fn void update_parent_grow(Elem* child, Elem* parent)
{
switch (parent.layout.dir) {
case ROW:
if (child.layout.w.@is_grow()) parent.layout.grow_children++;
case COLUMN:
if (child.layout.h.@is_grow()) parent.layout.grow_children++;
}
}
fn void? Ctx.layout_next_column(&ctx)
// Update the parent element's children size
fn void update_parent_size(Elem* child, Elem* parent)
{
Elem *parent = ctx.get_parent()!;
Layout* cl = &child.layout;
Layout* pl = &parent.layout;
parent.div.origin_c = {
.x = parent.div.children_bounds.bottom_right().x,
.y = parent.bounds.y,
};
parent.div.origin_r = parent.div.origin_c;
switch (pl.dir) {
case ROW: // on rows grow the ch width by the child width and only grow ch height if it exceeds
pl.children.w += cl.total_width();
pl.children.h = pl.children.h.comb_max(cl.total_height());
case COLUMN: // do the opposite on column
pl.children.w = pl.children.w.comb_max(cl.total_width());
pl.children.h += cl.total_height();
}
}
fn void update_children_bounds(Elem* child, Elem* parent)
{
parent.children_bounds = containing_rect(child.bounds, parent.bounds);
}
macro Rect Elem.content_bounds(&elem, style) => elem.bounds.pad(style.border).pad(style.padding);
/*
macro Rect Elem.get_view(&elem)
{
Rect off;
@ -67,91 +115,266 @@ macro Point Elem.get_view_off(&elem)
{
return elem.get_view().sub(elem.bounds).position();
}
*/
// position the rectangle inside the parent according to the layout
// parent: parent div
// rect: the requested size
// style: apply style
<*
@require ctx != null
@require parent.type == ETYPE_DIV
*>
fn Rect Ctx.layout_element(&ctx, Elem *parent, Rect rect, Style* style)
// Assign the width and height of an element in the directions that it doesn't need to grow
fn void resolve_dimensions(Elem* e, Elem* p)
{
ElemDiv* div = &parent.div;
// The element's border and paddign total width and height
Point m_tot = {.x = e.layout.margin.x + e.layout.margin.w, .y = e.layout.margin.y + e.layout.margin.h};
Point b_tot = {.x = e.layout.border.x + e.layout.border.w, .y = e.layout.border.y + e.layout.border.h};
Point p_tot = {.x = e.layout.padding.x + e.layout.padding.w, .y = e.layout.padding.y + e.layout.padding.h};
Rect parent_bounds, parent_view;
Rect child_placement, child_occupied;
// ASSIGN WIDTH
switch {
case e.layout.w.@is_exact():
// if width is exact then assign it to the bounds and add border and paddign
e.bounds.w = e.layout.w.min + b_tot.x + p_tot.x;
case e.layout.w.@is_grow():
// done in another pass
break;
case e.layout.w.@is_fit(): // fit the element's children
Size elem_width = e.layout.w; // the element's content width
Size children_width = e.layout.children.w; // element children (content) width
Size combined = elem_width.combine(children_width);
// 1. Select the right origin
Point origin;
switch (div.layout) {
case LAYOUT_ROW:
origin = div.origin_r;
case LAYOUT_COLUMN:
origin = div.origin_c;
case LAYOUT_FLOATING: // none, relative to zero zero
case LAYOUT_ABSOLUTE: // absolute position, this is a no-op, return the rect
return rect;
default: // error
return {};
if (combined.min <= combined.max) { // OK!
// the final element width is the content width plus padding and border
e.bounds.w = min(elem_width.max, children_width.max) + b_tot.x + p_tot.x;
} else {
unreachable("Cannot fit children width: min:%d max:%d", combined.min, combined.max);
}
/*
Size w = e.needed_width().combine(e.layout.children.w);
if (w.max >= w.min) { // OK!
e.bounds.w = w.min;
} else {
println(e.needed_width(), " ", e.needed_height());
println(e.layout.children.w, " ", e.layout.children.h);
unreachable("Cannot fit children width: min:%d max:%d", w.min, w.max);
}
*/
default: unreachable("width is not exact, grow or fit");
}
// 2. Compute the parent's view
parent_bounds = parent.bounds;
parent_view = parent.get_view();
// ASSIGN HEIGHT
switch {
case e.layout.h.@is_exact():
// if width is exact then assign it to the bounds and add border and paddign
e.bounds.h = e.layout.h.min + b_tot.y + p_tot.y;
case e.layout.h.@is_grow():
break;
// done in another pass
case e.layout.h.@is_fit(): // fit the element's children
Size elem_height = e.layout.h; // the element's content width
Size children_height = e.layout.children.h; // element children (content) width
Size combined = elem_height.combine(children_height);
// 3. Compute the placement and occupied area
if (combined.min <= combined.max) { // OK!
// the final element width is the content width plus padding and border
e.bounds.h = min(elem_height.max, children_height.max) + b_tot.y + p_tot.y;
} else {
unreachable("Cannot fit children height: min:%d max:%d", combined.min, combined.max);
}
/*
Size h = e.needed_height().combine(e.layout.children.h);
if (h.max >= h.min) { // OK!
e.bounds.h = h.max;
} else {
unreachable("Cannot fit children height: min:%d max:%d", h.min, h.max);
}
*/
default: unreachable("width is not exact, grow or fit");
}
// grow rect (wanted size) when widht or height are less than zero
bool adapt_x = rect.w <= 0;
bool adapt_y = rect.h <= 0;
if (adapt_x) rect.w = parent_bounds.w - parent_bounds.x - origin.x;
if (adapt_y) rect.h = parent_bounds.h - parent_bounds.y - origin.y;
// offset placement and area
child_placement = child_placement.off(origin.add(rect.position()));
child_occupied = child_occupied.off(origin.add(rect.position()));
Rect margin = style.margin;
Rect border = style.border;
Rect padding = style.padding;
// padding, grows both the placement and occupied area
child_placement = child_placement.grow(padding.position().add(padding.size()));
child_occupied = child_occupied.grow(padding.position().add(padding.size()));
// border, grows both the placement and occupied area
child_placement = child_placement.grow(border.position().add(border.size()));
child_occupied = child_occupied.grow(border.position().add(border.size()));
// margin, offsets the placement and grows the occupied area
child_placement = child_placement.off(margin.position());
child_occupied = child_occupied.grow(margin.position().add(margin.size()));
// oh yeah also adjust the rect if i was to grow
if (adapt_x) rect.w -= padding.x+padding.w + border.x+border.w + margin.x+margin.w;
if (adapt_y) rect.h -= padding.y+padding.h + border.y+border.h + margin.y+margin.h;
// set the size
child_placement = child_placement.grow(rect.size());
child_occupied = child_occupied.grow(rect.size());
// 4. Update the parent's origin
div.origin_r = {
.x = child_occupied.bottom_right().x,
.y = origin.y,
};
div.origin_c = {
.x = origin.x,
.y = child_occupied.bottom_right().y,
};
// 5. Update the parent's children bounds
div.children_bounds = containing_rect(div.children_bounds, child_occupied);
// 99. return the placement
if (child_placement.collides(parent_view)) {
return child_placement.off(parent.get_view_off().neg());
} else {
return {};
// the occupied space must account for the element's margin as well
switch (p.layout.dir) {
case ROW:
if (!e.layout.w.@is_grow()) p.layout.occupied += e.bounds.w + m_tot.x;
case COLUMN:
if (!e.layout.h.@is_grow()) p.layout.occupied += e.bounds.h + m_tot.y;
}
}
fn void resolve_grow_elements(Elem* e, Elem* p)
{
Point m_tot = {.x = e.layout.margin.x + e.layout.margin.w, .y = e.layout.margin.y + e.layout.margin.h};
Point b_tot = {.x = e.layout.border.x + e.layout.border.w, .y = e.layout.border.y + e.layout.border.h};
Point p_tot = {.x = e.layout.padding.x + e.layout.padding.w, .y = e.layout.padding.y + e.layout.padding.h};
// WIDTH
if (e.layout.w.@is_grow()) {
if (p.layout.dir == ROW) { // grow along the axis, divide the parent size
short slot = (short)((p.content_space().x - p.layout.occupied) / p.layout.grow_children);
// the space slot accounts for the total size, while the element bounds does not account
// for the margin
e.bounds.w = slot - m_tot.x;
p.layout.grow_children--;
p.layout.occupied += slot;
} else if (p.layout.dir == COLUMN) { // grow across the layout axis, inherit width of the parent
e.bounds.w = p.content_space().x - m_tot.x;
}
}
// HEIGHT
if (e.layout.h.@is_grow()) {
if (p.layout.dir == COLUMN) { // grow along the axis, divide the parent size
short slot = (short)((p.content_space().y - p.layout.occupied) / p.layout.grow_children);
e.bounds.h = slot - m_tot.y;
p.layout.grow_children--;
p.layout.occupied += slot;
} else if (p.layout.dir == ROW) { // grow across the layout axis, inherit width of the parent
e.bounds.h = p.content_space().y - m_tot.y;
}
}
}
fn void resolve_placement(Elem* c, Elem* p)
{
Layout* pl = &p.layout;
Layout* cl = &c.layout;
Point off = {
.x = p.bounds.x + pl.origin.x + cl.margin.x,
.y = p.bounds.y + pl.origin.y + cl.margin.x,
};
switch (pl.anchor) {
case TOP_LEFT:
c.bounds.x = off.x;
c.bounds.y = off.y;
case LEFT:
c.bounds.x = off.x;
c.bounds.y = off.y + p.bounds.h/2;
if (pl.dir == COLUMN) {
c.bounds.y -= pl.occupied/2;
} else if (pl.dir == ROW) {
c.bounds.y -= c.bounds.h/2;
}
case BOTTOM_LEFT:
c.bounds.x = off.x;
c.bounds.y = off.y + p.bounds.h ;
if (pl.dir == COLUMN) {
c.bounds.y -= pl.occupied;
} else if (pl.dir == ROW) {
c.bounds.y -= c.bounds.h;
}
case BOTTOM:
c.bounds.x = off.x + p.bounds.w/2;
c.bounds.y = off.y + p.bounds.h;
if (pl.dir == COLUMN) {
c.bounds.y -= pl.occupied;
c.bounds.x -= c.bounds.w/2;
} else if (pl.dir == ROW) {
c.bounds.y -= c.bounds.h;
c.bounds.x -= pl.occupied/2;
}
case BOTTOM_RIGHT:
c.bounds.x = off.x + p.bounds.w;
c.bounds.y = off.y + p.bounds.h;
if (pl.dir == COLUMN) {
c.bounds.y -= pl.occupied;
c.bounds.x -= c.bounds.w;
} else if (pl.dir == ROW) {
c.bounds.y -= c.bounds.h;
c.bounds.x -= pl.occupied;
}
case RIGHT:
c.bounds.x = off.x + p.bounds.w;
c.bounds.y = off.y + p.bounds.h/2;
if (pl.dir == COLUMN) {
c.bounds.y -= pl.occupied/2;
c.bounds.x -= c.bounds.w;
} else if (pl.dir == ROW) {
c.bounds.y -= c.bounds.h/2;
c.bounds.x -= pl.occupied;
}
case TOP_RIGHT:
c.bounds.x = off.x + p.bounds.w;
c.bounds.y = off.y;
if (pl.dir == COLUMN) {
c.bounds.x -= c.bounds.w;
} else if (pl.dir == ROW) {
c.bounds.x -= pl.occupied;
}
case TOP:
c.bounds.x = off.x + p.bounds.w/2;
c.bounds.y = off.y;
if (pl.dir == COLUMN) {
c.bounds.x -= c.bounds.w/2;
} else if (pl.dir == ROW) {
c.bounds.x -= pl.occupied/2;
}
case CENTER:
c.bounds.x = off.x + p.bounds.w/2;
c.bounds.y = off.y + p.bounds.h/2;
if (pl.dir == COLUMN) {
c.bounds.x -= c.bounds.w/2;
c.bounds.y -= pl.occupied/2;
} else if (pl.dir == ROW) {
c.bounds.x -= pl.occupied/2;
c.bounds.y -= c.bounds.h/2;
}
break;
}
switch (pl.dir) {
case ROW:
pl.origin.x += c.bounds.w + cl.margin.x + cl.margin.w;
case COLUMN:
pl.origin.y += c.bounds.h + cl.margin.x + cl.margin.w;
default: unreachable("unknown layout direction");
}
//sprintln("origin: ", p.layout.origin, " bounds: ", c.bounds);
}
fn void Ctx.layout_element_tree(&ctx)
{
isz cursor = -1;
isz current = ctx.tree.level_order_it(0, &cursor)!!;
for (; current >= 0; current = ctx.tree.level_order_it(0, &cursor)!!) {
Elem* p = ctx.find_elem(ctx.tree.get(current))!!;
// offset the origin by the margin
p.layout.origin.x = p.layout.border.x + p.layout.padding.x;
p.layout.origin.y = p.layout.border.y + p.layout.padding.y;
// RESOLVE KNOWN DIMENSIONS
isz ch_cur = 0;
isz ch = ctx.tree.children_it(current, &ch_cur)!!;
for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) {
Elem* c = ctx.find_elem(ctx.tree.get(ch))!!;
if (ctx.tree.is_root(ch)!!) {
resolve_dimensions(p, &&{});
} else {
resolve_dimensions(c, p);
}
}
// RESOLVE GROW CHILDREN
ch_cur = 0;
ch = ctx.tree.children_it(current, &ch_cur)!!;
for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) {
Elem* c = ctx.find_elem(ctx.tree.get(ch))!!;
if (ctx.tree.is_root(ch)!!) {
resolve_grow_elements(p, &&{});
} else {
resolve_grow_elements(c, p);
}
}
// RESOLVE CHILDREN PLACEMENT
ch_cur = 0;
ch = ctx.tree.children_it(current, &ch_cur)!!;
for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) {
Elem* c = ctx.find_elem(ctx.tree.get(ch))!!;
if (ctx.tree.is_root(ch)!!) {
resolve_placement(p, &&{});
update_children_bounds(c, p);
} else {
resolve_placement(c, p);
update_children_bounds(c, p);
}
}
}
}

View File

@ -174,6 +174,16 @@ macro Rect Rect.pad(Rect a, Rect b)
};
}
macro Rect Rect.expand(Rect a, Rect b)
{
return {
.x = a.x - b.x,
.y = a.y - b.y,
.w = a.w + b.x + b.w,
.h = a.h + b.y + b.h,
};
}
// ---------------------------------------------------------------------------------- //
// POINT //
@ -189,39 +199,12 @@ macro bool Point.in_rect(Point p, Rect r)
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h);
}
macro Point Point.add(Point a, Point b)
{
return {
.x = a.x + b.x,
.y = a.y + b.y,
};
}
macro Point Point.add(Point a, Point b) @operator_s(+) => {.x = a.x+b.x, .y = a.y+b.y};
macro Point Point.sub(Point a, Point b) @operator_s(-) => {.x = a.x-b.x, .y = a.y-b.y};
macro Point Point.neg(Point p) @operator_s(-) => {-p.x, -p.y};
macro Point Point.sub(Point a, Point b)
{
return {
.x = a.x - b.x,
.y = a.y - b.y,
};
}
macro Point Point.neg(Point p) => {-p.x, -p.y};
macro Point Point.max(Point a, Point b)
{
return {
.x = max(a.x, b.x),
.y = max(a.y, b.y),
};
}
macro Point Point.min(Point a, Point b)
{
return {
.x = min(a.x, b.x),
.y = min(a.y, b.y),
};
}
macro Point Point.max(Point a, Point b) => {.x = max(a.x, b.x), .y = max(a.y, b.y)};
macro Point Point.min(Point a, Point b) => {.x = min(a.x, b.x), .y = min(a.y, b.y)};
// ---------------------------------------------------------------------------------- //
// COLOR //
@ -252,8 +235,26 @@ macro Color uint.@to_rgba($u)
};
}
macro uint Color.to_uint(c)
{
uint u = c.r | (c.g << 8) | (c.b << 16) | (c.a << 24);
return u;
macro uint Color.to_uint(c) => c.r | (c.g << 8) | (c.b << 16) | (c.a << 24);
// ---------------------------------------------------------------------------------- //
// SIZE //
// ---------------------------------------------------------------------------------- //
struct Size {
short min, max;
}
macro Size @grow() => {.min = 0, .max = 0};
macro Size @exact(short s) => {.min = s, .max = s};
macro Size @fit(short min = 0, short max = short.max/2) => {.min = min, .max = max};
macro bool Size.@is_grow(s) => (s.min == 0 && s.max == 0);
macro bool Size.@is_exact(s) => (s.min == s.max && s.min != 0);
macro bool Size.@is_fit(s) => (s.min != s.max);
macro Size Size.add(a, Size b) @operator_s(+) => {.min = a.min+b.min, .max = a.max+b.max};
macro Size Size.sub(a, Size b) @operator_s(-) => {.min = a.min-b.min, .max = a.max-b.max};
macro Size Size.combine(a, Size b) => {.min = max(a.min, b.min), .max = min(a.max, b.max)};
macro Size Size.comb_max(a, Size b) => {.min = max(a.min, b.min), .max = max(a.max, b.max)};
macro Size Size.comb_min(a, Size b) => {.min = min(a.min, b.min), .max = min(a.max, b.max)};

View File

@ -8,12 +8,12 @@ struct ElemSlider {
Rect handle;
}
/* handle
* +----+-----+---------------------+
* | |#####| |
* +----+-----+---------------------+
*/
/*
macro Ctx.slider_hor(&ctx, Rect size, float* value, float hpercent = 0.25, ...)
=> ctx.slider_hor_id(@compute_id($vasplat), size, value, hpercent);
<*
@ -63,6 +63,7 @@ fn ElemEvents? Ctx.slider_hor_id(&ctx, Id id, Rect size, float* value, float hpe
return elem.events;
}
*/
/*
@ -77,6 +78,7 @@ fn ElemEvents? Ctx.slider_hor_id(&ctx, Id id, Rect size, float* value, float hpe
* | |
* +--+
*/
/*
macro Ctx.slider_ver(&ctx, Rect size, float* value, float hpercent = 0.25, ...)
=> ctx.slider_ver_id(@compute_id($vasplat), size, value, hpercent);
fn ElemEvents? Ctx.slider_ver_id(&ctx, Id id, Rect size, float* value, float hpercent = 0.25)
@ -134,3 +136,4 @@ fn ElemEvents? Ctx.slider_ver_id(&ctx, Id id, Rect size, float* value, float hpe
macro short calc_slider(short off, short dim, float value) => (short)off + (short)(dim * value);
macro float calc_value(short off, short mouse, short dim, short slider)
=> math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f);
*/

View File

@ -44,7 +44,7 @@ fn void? SpriteAtlas.init(&this, String name, AtlasType type, ushort width, usho
this.id = name.hash();
this.atlas.new(this.id, AtlasType.ATLAS_R8G8B8A8, width, height)!;
this.sprites.init(allocator::heap(), capacity: SRITES_PER_ATLAS);
this.sprites.init(allocator::mem, capacity: SRITES_PER_ATLAS);
this.should_update = false;
}
@ -104,13 +104,14 @@ fn void? Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t
{
QOIDesc desc;
char[] pixels = qoi::read(allocator::heap(), path, &desc, QOIChannels.RGBA)!;
char[] pixels = qoi::read(allocator::mem, path, &desc, QOIChannels.RGBA)!;
defer mem::free(pixels);
ctx.sprite_atlas.insert(name, type, pixels, (ushort)desc.width, (ushort)desc.height, (ushort)desc.width)!;
}
/*
macro Ctx.sprite(&ctx, String name, Point off = {0,0}, ...)
=> ctx.sprite_id(@compute_id($vasplat), name, off);
fn void? Ctx.sprite_id(&ctx, Id id, String name, Point off)
@ -151,3 +152,4 @@ fn void? Ctx.draw_sprite_raw(&ctx, String name, Rect bounds, bool center = false
return ctx.push_sprite(bounds, sprite.uv(), tex_id, parent.div.z_index, type: sprite.type)!;
}
*/

View File

@ -9,6 +9,7 @@ struct ElemText {
Rect bounds;
}
/*
macro Ctx.text_unbounded(&ctx, String text, ...)
=> ctx.text_unbounded_id(@compute_id($vasplat), text);
fn void? Ctx.text_unbounded_id(&ctx, Id id, String text)
@ -77,3 +78,4 @@ fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Rect size, char[] text, usz* text_le
return elem.events;
}
*/

View File

@ -4,7 +4,9 @@ default {
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
border: 1;
border: 0;
padding: 0;
margin: 0;
}
button {

View File

@ -216,6 +216,7 @@ $endswitch
TimeStats dts = draw_times.get_stats();
TimeStats uts = ui_times.get_stats();
/*
ui.layout_set_floating()!!;
// FIXME: I cannot anchor shit to the bottom of the screen
ui.@div({0, ui.height-150, -300, 150}) {
@ -224,13 +225,13 @@ $endswitch
ui.text_unbounded(string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;
ui.text_unbounded(string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[:ui.input.keyboard.text_len]))!!;
}!!;
*/
ui.frame_end()!!;
/* End UI Handling */
ui_times.push(clock.mark());
//ui_times.print_stats();
/* Start UI Drawing */
ren.begin_render(true);
@ -253,6 +254,7 @@ $endswitch
return 0;
}
/*
fn void debug_app(ugui::Ctx* ui)
{
static bool toggle;
@ -324,6 +326,7 @@ fn void debug_app(ugui::Ctx* ui)
};
ui.div_end()!!;
}
*/
import std::os::process;
@ -361,34 +364,36 @@ fn void calculator(ugui::Ctx* ui)
}
// ui input/output
ui.@div(ugui::DIV_FILL) {
ui.layout_set_column()!!;
ui.@div(ugui::@grow(), ugui::@grow(), ROW, CENTER) {
/*
ui.@div({0,0,250,50}) {
ui.text_unbounded((String)buffer[:len])!!;
}!!;
*/
ui.@div({0,0,250,-100}) {
ui.layout_set_row()!!;
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
ui.button("7")!!.mouse_press ? buffer[len++] = '7' : 0;
ui.button("8")!!.mouse_press ? buffer[len++] = '8' : 0;
ui.button("9")!!.mouse_press ? buffer[len++] = '9' : 0;
ui.layout_next_row()!!;
}!!;
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
ui.button("4")!!.mouse_press ? buffer[len++] = '4' : 0;
ui.button("5")!!.mouse_press ? buffer[len++] = '5' : 0;
ui.button("6")!!.mouse_press ? buffer[len++] = '6' : 0;
ui.layout_next_row()!!;
}!!;
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
ui.button("3")!!.mouse_press ? buffer[len++] = '3' : 0;
ui.button("2")!!.mouse_press ? buffer[len++] = '2' : 0;
ui.button("1")!!.mouse_press ? buffer[len++] = '1' : 0;
ui.layout_next_row()!!;
}!!;
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
ui.button("0")!!.mouse_press ? buffer[len++] = '0' : 0;
ui.button(".")!!.mouse_press ? buffer[len++] = '.' : 0;
ui.button("(")!!.mouse_press ? buffer[len++] = '(' : 0;
ui.layout_next_column()!!;
}!!;
/*
ui.@div({0,0,0,-1}) {
ui.button("C")!!.mouse_press ? len = 0 : 0;
ui.button("D")!!.mouse_press ? len-- : 0;
@ -409,7 +414,6 @@ fn void calculator(ugui::Ctx* ui)
buffer[:x.len] = x[..];
len = x.len;
}
}!!;
}!!;
*/
}!!;
}