From 586e81935c0b7684d48de720f7523420f54ed9f4 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 30 Jun 2025 18:24:50 +0200 Subject: [PATCH] improved the api to not require explicit labels everywhere --- lib/ugui.c3l/src/ugui_button.c3 | 51 +++++++++++++++++------------ lib/ugui.c3l/src/ugui_core.c3 | 13 ++++++-- lib/ugui.c3l/src/ugui_div.c3 | 14 ++++---- lib/ugui.c3l/src/ugui_slider.c3 | 48 ++++++++++++++++++--------- lib/ugui.c3l/src/ugui_sprite.c3 | 7 ++-- lib/ugui.c3l/src/ugui_text.c3 | 12 ++++--- src/main.c3 | 58 ++++++++++++++++----------------- 7 files changed, 123 insertions(+), 80 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_button.c3 b/lib/ugui.c3l/src/ugui_button.c3 index 81b817f..5661fbb 100644 --- a/lib/ugui.c3l/src/ugui_button.c3 +++ b/lib/ugui.c3l/src/ugui_button.c3 @@ -7,12 +7,13 @@ struct ElemButton { int filler; } + // draw a button, return the events on that button -// FIXME: "state" should be renamed "active" to toggle between an usable button and -// an inactive (greyed-out) button -fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false) +macro Ctx.button(&ctx, Rect size, bool state = false, ...) + => ctx.button_id(@compute_id($vasplat), size, state); +fn ElemEvents? Ctx.button_id(&ctx, Id id, Rect size, bool active) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -33,7 +34,7 @@ fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false) Color col = 0x0000ffffu.to_rgba(); elem.events = ctx.get_elem_events(elem); - if (state) { + if (active) { col = 0xff0000ffu.to_rgba(); } else if (ctx.elem_focus(elem) || elem.events.mouse_hover) { col = 0xff00ffffu.to_rgba(); @@ -45,9 +46,11 @@ fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false) return elem.events; } -fn ElemEvents? Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,short.max}, bool state = false) +macro Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,short.max}, bool active = false, ...) + => ctx.button_label_id(@compute_id($vasplat), label, size, active); +fn ElemEvents? Ctx.button_label_id(&ctx, Id id, String label, Rect size, bool active) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -68,7 +71,7 @@ fn ElemEvents? Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,s Color col = 0x0000ffffu.to_rgba(); elem.events = ctx.get_elem_events(elem); - if (state) { + if (active) { col = 0xff0000ffu.to_rgba(); } else if (ctx.elem_focus(elem) || elem.events.mouse_hover) { col = 0xff00ffffu.to_rgba(); @@ -86,9 +89,11 @@ fn ElemEvents? Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,s return elem.events; } -fn ElemEvents? Ctx.button_icon(&ctx, String label, String icon, String on_icon = "", bool state = false) +macro Ctx.button_icon(&ctx, String icon, String on_icon = "", bool active = false, ...) + => ctx.button_icon_id(@compute_id($vasplat), icon, on_icon, active); +fn ElemEvents? Ctx.button_icon_id(&ctx, Id id, String icon, String on_icon, bool active) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -115,7 +120,7 @@ fn ElemEvents? Ctx.button_icon(&ctx, String label, String icon, String on_icon = elem.events = ctx.get_elem_events(elem); Id tex_id = ctx.sprite_atlas.id; - if (state && on_icon != "") { + if (active && on_icon != "") { ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, parent.div.z_index, type: on_sprite.type)!; } else { ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, parent.div.z_index, type: def_sprite.type)!; @@ -129,9 +134,11 @@ fn ElemEvents? Ctx.button_icon(&ctx, String label, String icon, String on_icon = // FIXME: this should be inside the style const ushort DEFAULT_CHECKBOX_SIZE = 16; -fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* state, String tick_sprite = {}) +macro Ctx.checkbox(&ctx, String desc, Point off, bool* active, String tick_sprite = {}, ...) + => ctx.checkbox_id(@compute_id($vasplat), desc, off, active, tick_sprite); +fn void? Ctx.checkbox_id(&ctx, Id id, String description, Point off, bool* active, String tick_sprite) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -154,17 +161,17 @@ fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* s if (elem.bounds.is_null()) return; elem.events = ctx.get_elem_events(elem); - if (elem.events.mouse_hover && elem.events.mouse_release) *state = !(*state); + if (elem.events.mouse_hover && elem.events.mouse_release) *active = !(*active); Color col; if (tick_sprite != {}) { col = ctx.style.bgcolor; ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; - if (*state) { + if (*active) { ctx.draw_sprite_raw(tick_sprite, elem.bounds)!; } } else { - if (*state) { + if (*active) { col = 0xff0000ffu.to_rgba(); } else { col = 0xff00ffffu.to_rgba(); @@ -176,9 +183,11 @@ fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* s // FIXME: this should be inside the style const short DEFAULT_SWITCH_SIZE = 16; -fn void? Ctx.toggle(&ctx, String label, String description, Point off, bool* state) +macro Ctx.toggle(&ctx, String desc, Point off, bool* active) + => ctx.toggle_id(@compute_id($vasplat), desc, off, active); +fn void? Ctx.toggle_id(&ctx, Id id, String description, Point off, bool* active) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -201,10 +210,10 @@ fn void? Ctx.toggle(&ctx, String label, String description, Point off, bool* sta if (elem.bounds.is_null()) return; elem.events = ctx.get_elem_events(elem); - if (elem.events.mouse_hover && elem.events.mouse_release) *state = !(*state); + if (elem.events.mouse_hover && elem.events.mouse_release) *active = !(*active); Color col; - if (*state) { + if (*active) { col = 0xff0000ffu.to_rgba(); } else { col = 0xff00ffffu.to_rgba(); @@ -212,6 +221,6 @@ fn void? Ctx.toggle(&ctx, String label, String description, Point off, bool* sta // Draw the button // FIXME: THIS IS SHIT ctx.push_rect(elem.bounds, ctx.style.bgcolor, parent.div.z_index, do_border: true, do_radius: true)!; - Rect t = elem.bounds.add({*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6}); + Rect t = elem.bounds.add({*active ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6}); ctx.push_rect(t, col, parent.div.z_index, do_border: false, do_radius: true)!; } diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index 40696d5..24c90d2 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -136,10 +136,9 @@ const uint GOLDEN_RATIO = 0x9E3779B9; // generate an id combining the hashes of the parent id and the label // with the Cantor pairing function -macro Id? Ctx.gen_id(&ctx, String label) +fn Id? Ctx.gen_id(&ctx, Id id2) { Id id1 = ctx.tree.get(ctx.active_div)!; - Id id2 = label.hash(); // Mix the two IDs non-linearly Id mixed = id1 ^ id2.rotate_left(13); mixed ^= id1.rotate_left(7); @@ -147,6 +146,16 @@ macro Id? Ctx.gen_id(&ctx, String label) return mixed; } +// compute the id from arguments and the line of the call +macro Id @compute_id(...) +{ + Id id = (Id)$$LINE.hash() ^ (Id)@str_hash($$FILE); + $for var $i = 0; $i < $vacount; $i++: + id ^= (Id)$vaconst[$i].hash(); + $endfor + return id; +} + // get or push an element from the cache, return a pointer to it // resets all flags except is_new which is set accordingly fn Elem*? Ctx.get_elem(&ctx, Id id) diff --git a/lib/ugui.c3l/src/ugui_div.c3 b/lib/ugui.c3l/src/ugui_div.c3 index 4bfd45e..caa9e25 100644 --- a/lib/ugui.c3l/src/ugui_div.c3 +++ b/lib/ugui.c3l/src/ugui_div.c3 @@ -29,9 +29,11 @@ struct ElemDiv { // 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 -fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) +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) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem* parent = ctx.get_parent()!; Elem* elem = ctx.get_elem(id)!; @@ -110,8 +112,8 @@ fn void? Ctx.div_end(&ctx) // vertical overflow elem.div.scroll_y.on = cbc.y > bc.y && elem.div.scroll_y.enabled; - Id hsid = ctx.gen_id("div_scrollbar_horizontal")!; - Id vsid = ctx.gen_id("div_scrollbar_vertical")!; + Id hsid = ctx.gen_id("div_scrollbar_horizontal".hash())!; + Id vsid = ctx.gen_id("div_scrollbar_vertical".hash())!; short wdim = elem.div.scroll_y.on ? (ctx.focus_id == vsid || ctx.is_hovered(ctx.find_elem(vsid)) ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; short hdim = elem.div.scroll_x.on ? (ctx.focus_id == hsid || ctx.is_hovered(ctx.find_elem(hsid)) ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; @@ -128,7 +130,7 @@ fn void? Ctx.div_end(&ctx) }; Layout prev_l = elem.div.layout; elem.div.layout = LAYOUT_ABSOLUTE; - ctx.slider_ver("div_scrollbar_vertical", vslider, &elem.div.scroll_y.value, max((float)bc.y / cbc.y, (float)0.15))!; + ctx.slider_ver_id(vsid, vslider, &elem.div.scroll_y.value, max((float)bc.y / cbc.y, (float)0.15))!; elem.div.layout = prev_l; } @@ -145,7 +147,7 @@ fn void? Ctx.div_end(&ctx) }; Layout prev_l = elem.div.layout; elem.div.layout = LAYOUT_ABSOLUTE; - ctx.slider_hor("div_scrollbar_horizontal", hslider, &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15))!; + ctx.slider_hor_id(hsid, hslider, &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15))!; elem.div.layout = prev_l; } diff --git a/lib/ugui.c3l/src/ugui_slider.c3 b/lib/ugui.c3l/src/ugui_slider.c3 index a161389..7fb6d9a 100644 --- a/lib/ugui.c3l/src/ugui_slider.c3 +++ b/lib/ugui.c3l/src/ugui_slider.c3 @@ -8,23 +8,31 @@ struct ElemSlider { Rect handle; } + /* handle * +----+-----+---------------------+ * | |#####| | * +----+-----+---------------------+ */ +macro Ctx.slider_hor(&ctx, + Rect size, + float* value, + float hpercent = 0.25, + Color bgcolor = 0x0000ffffu.to_rgba(), + Color handlecolor = 0x0ff000ffu.to_rgba(), ...) + => ctx.slider_hor_id(@compute_id($vasplat), size, value, hpercent, bgcolor, handlecolor); <* @require value != null *> -fn ElemEvents? Ctx.slider_hor(&ctx, - String label, +fn ElemEvents? Ctx.slider_hor_id(&ctx, + Id id, Rect size, float* value, float hpercent = 0.25, Color bgcolor = 0x0000ffffu.to_rgba(), Color handlecolor = 0x0ff000ffu.to_rgba()) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -67,27 +75,35 @@ fn ElemEvents? Ctx.slider_hor(&ctx, return elem.events; } + /* - * +-+ - * | | - * | | - * +-+ - * |#| handle - * |#| - * +-+ - * | | - * | | - * +-+ + * +--+ + * | | + * | | + * +--+ + * |##| handle + * |##| + * +--+ + * | | + * | | + * +--+ */ -fn ElemEvents? Ctx.slider_ver(&ctx, - String label, +macro Ctx.slider_ver(&ctx, + Rect size, + float* value, + float hpercent = 0.25, + Color bgcolor = 0x0000ffffu.to_rgba(), + Color handlecolor = 0x0ff000ffu.to_rgba(), ...) + => ctx.slider_ver_id(@compute_id($vasplat), size, value, hpercent, bgcolor, handlecolor); +fn ElemEvents? Ctx.slider_ver_id(&ctx, + Id id, Rect size, float* value, float hpercent = 0.25, Color bgcolor = 0x0000ffffu.to_rgba(), Color handlecolor = 0x0ff000ffu.to_rgba()) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; diff --git a/lib/ugui.c3l/src/ugui_sprite.c3 b/lib/ugui.c3l/src/ugui_sprite.c3 index 7dca870..888a1b4 100644 --- a/lib/ugui.c3l/src/ugui_sprite.c3 +++ b/lib/ugui.c3l/src/ugui_sprite.c3 @@ -110,9 +110,12 @@ fn void? Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t ctx.sprite_atlas.insert(name, type, pixels, (ushort)desc.width, (ushort)desc.height, (ushort)desc.width)!; } -fn void? Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) + +macro Ctx.draw_sprite(&ctx, String name, Point off = {0,0}, ...) + => ctx.draw_sprite_id(@compute_id($vasplat), name, off); +fn void? Ctx.draw_sprite_id(&ctx, Id id, String name, Point off) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; diff --git a/lib/ugui.c3l/src/ugui_text.c3 b/lib/ugui.c3l/src/ugui_text.c3 index 2a6d436..dda6ce4 100644 --- a/lib/ugui.c3l/src/ugui_text.c3 +++ b/lib/ugui.c3l/src/ugui_text.c3 @@ -7,9 +7,11 @@ struct ElemText { usz cursor; // cursor offset } -fn void? Ctx.text_unbounded(&ctx, String label, String text) +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) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -30,9 +32,11 @@ fn void? Ctx.text_unbounded(&ctx, String label, String text) ctx.push_string(elem.bounds, text, parent.div.z_index)!; } -fn ElemEvents? Ctx.text_box(&ctx, String label, Rect size, char[] text, usz* text_len) +macro Ctx.text_box(&ctx, Rect size, char[] text, usz* text_len, ...) + => ctx.text_box_id(@compute_id($vasplat), size, text, text_len); +fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Rect size, char[] text, usz* text_len) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; diff --git a/src/main.c3 b/src/main.c3 index 71ead09..af2b584 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -195,71 +195,71 @@ fn int main(String[] args) if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true; - ui.div_begin("main", {.w=-100})!!; + ui.div_begin({.w=-100})!!; { ui.layout_set_column()!!; - if (ui.button("button0", {0,0,30,30}, toggle)!!.mouse_press) { + if (ui.button({0,0,30,30}, toggle)!!.mouse_press) { io::printn("press button0"); toggle = !toggle; } //ui.layout_next_column()!!; - if (ui.button("button1", {0,0,30,30})!!.mouse_press) { + if (ui.button({0,0,30,30})!!.mouse_press) { io::printn("press button1"); } //ui.layout_next_column()!!; - if (ui.button("button2", {0,0,30,30})!!.mouse_release) { + if (ui.button({0,0,30,30})!!.mouse_release) { io::printn("release button2"); } ui.layout_set_row()!!; ui.layout_next_row()!!; static float rf, gf, bf, af; - ui.slider_ver("slider_r", {0,0,30,100}, &rf)!!; - ui.slider_ver("slider_g", {0,0,30,100}, &gf)!!; - ui.slider_ver("slider_b", {0,0,30,100}, &bf)!!; - ui.slider_ver("slider_a", {0,0,30,100}, &af)!!; + ui.slider_ver({0,0,30,100}, &rf)!!; + ui.slider_ver({0,0,30,100}, &gf)!!; + ui.slider_ver({0,0,30,100}, &bf)!!; + ui.slider_ver({0,0,30,100}, &af)!!; ui.layout_next_column()!!; - ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!; + ui.text_unbounded("Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!; ui.layout_next_column()!!; ui.button_label("Continua!")!!; ui.layout_next_row()!!; static bool check; - ui.checkbox("check1", "", {}, &check, "tick")!!; - ui.toggle("toggle1", "", {}, &toggle)!!; + ui.checkbox("", {}, &check, "tick")!!; + ui.toggle("", {}, &toggle)!!; }; - ui.draw_sprite("sprite1", "tux")!!; + ui.draw_sprite("tux")!!; static char[128] text_box = "ciao mamma"; static usz text_len = "ciao mamma".len; - ui.text_box("text input", {0,0,200,200}, text_box[..], &text_len)!!; + ui.text_box({0,0,200,200}, text_box[..], &text_len)!!; ui.div_end()!!; - ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; + ui.div_begin(ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; { ui.layout_set_column()!!; static float slider2 = 0.5; - if (ui.slider_ver("slider", {0,0,30,100}, &slider2)!!.update) { + if (ui.slider_ver({0,0,30,100}, &slider2)!!.update) { io::printfn("other slider: %f", slider2); } - ui.button("button0", {0,0,50,50})!!; - ui.button("button1", {0,0,50,50})!!; - ui.button("button2", {0,0,50,50})!!; - ui.button("button3", {0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; if (toggle) { - ui.button("button4", {0,0,50,50})!!; - ui.button("button5", {0,0,50,50})!!; - ui.button("button6", {0,0,50,50})!!; - ui.button("button7", {0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; } ui.layout_next_column()!!; ui.layout_set_row()!!; static float f1, f2; - ui.slider_hor("hs1", {0,0,100,30}, &f1)!!; - ui.slider_hor("hs2", {0,0,100,30}, &f2)!!; + ui.slider_hor({0,0,100,30}, &f1)!!; + ui.slider_hor({0,0,100,30}, &f2)!!; }; ui.div_end()!!; @@ -268,12 +268,12 @@ fn int main(String[] args) TimeStats uts = ui_times.get_stats(); ui.layout_set_floating()!!; - ui.div_begin("fps", {0, ui.height-150, -300, 150})!!; + ui.div_begin({0, ui.height-150, -300, 150})!!; { ui.layout_set_column()!!; - ui.text_unbounded("frame number", string::tformat("frame %d, fps = %.2f", frame, fps))!!; - ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; - ui.text_unbounded("ui text input", string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[..]))!!; + ui.text_unbounded(string::tformat("frame %d, fps = %.2f", frame, fps))!!; + 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.div_end()!!;