diff --git a/lib/ugui.c3l/LAYOUT b/lib/ugui.c3l/LAYOUT index 33c804e..717ac05 100644 --- a/lib/ugui.c3l/LAYOUT +++ b/lib/ugui.c3l/LAYOUT @@ -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| | +| +-----------------------------------+ | +| | +| | ++-----------------------------------------+ + diff --git a/lib/ugui.c3l/src/ugui_button.c3 b/lib/ugui.c3l/src/ugui_button.c3 index f2132be..c3603fe 100644 --- a/lib/ugui.c3l/src/ugui_button.c3 +++ b/lib/ugui.c3l/src/ugui_button.c3 @@ -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)!; } +*/ \ No newline at end of file diff --git a/lib/ugui.c3l/src/ugui_cmd.c3 b/lib/ugui.c3l/src/ugui_cmd.c3 index 463605e..804f1c4 100644 --- a/lib/ugui.c3l/src/ugui_cmd.c3 +++ b/lib/ugui.c3l/src/ugui_cmd.c3 @@ -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; } diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index e3f1a01..19f0a83 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -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(); diff --git a/lib/ugui.c3l/src/ugui_div.c3 b/lib/ugui.c3l/src/ugui_div.c3 index 10d6c40..4e738db 100644 --- a/lib/ugui.c3l/src/ugui_div.c3 +++ b/lib/ugui.c3l/src/ugui_div.c3 @@ -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); } diff --git a/lib/ugui.c3l/src/ugui_font.c3 b/lib/ugui.c3l/src/ugui_font.c3 index 17a36ac..51ff0b9 100644 --- a/lib/ugui.c3l/src/ugui_font.c3 +++ b/lib/ugui.c3l/src/ugui_font.c3 @@ -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); diff --git a/lib/ugui.c3l/src/ugui_layout.c3 b/lib/ugui.c3l/src/ugui_layout.c3 index 4315e61..5d5eae9 100644 --- a/lib/ugui.c3l/src/ugui_layout.c3 +++ b/lib/ugui.c3l/src/ugui_layout.c3 @@ -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); + } + } } } diff --git a/lib/ugui.c3l/src/ugui_shapes.c3 b/lib/ugui.c3l/src/ugui_shapes.c3 index 6c8e592..b134203 100644 --- a/lib/ugui.c3l/src/ugui_shapes.c3 +++ b/lib/ugui.c3l/src/ugui_shapes.c3 @@ -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)}; diff --git a/lib/ugui.c3l/src/ugui_slider.c3 b/lib/ugui.c3l/src/ugui_slider.c3 index bacd4b6..709f087 100644 --- a/lib/ugui.c3l/src/ugui_slider.c3 +++ b/lib/ugui.c3l/src/ugui_slider.c3 @@ -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); +*/ \ No newline at end of file diff --git a/lib/ugui.c3l/src/ugui_sprite.c3 b/lib/ugui.c3l/src/ugui_sprite.c3 index 152f109..edd3b8f 100644 --- a/lib/ugui.c3l/src/ugui_sprite.c3 +++ b/lib/ugui.c3l/src/ugui_sprite.c3 @@ -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)!; } +*/ \ No newline at end of file diff --git a/lib/ugui.c3l/src/ugui_text.c3 b/lib/ugui.c3l/src/ugui_text.c3 index f1513a9..dd19776 100644 --- a/lib/ugui.c3l/src/ugui_text.c3 +++ b/lib/ugui.c3l/src/ugui_text.c3 @@ -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; } +*/ \ No newline at end of file diff --git a/resources/style.css b/resources/style.css index 33362ca..4681c69 100644 --- a/resources/style.css +++ b/resources/style.css @@ -4,7 +4,9 @@ default { primary: #cc241dff; secondary: #458588ff; accent: #fabd2fff; - border: 1; + border: 0; + padding: 0; + margin: 0; } button { diff --git a/src/main.c3 b/src/main.c3 index 7e6fed6..271f696 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -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; } - }!!; - }!!; + */ }!!; }