improved the api to not require explicit labels everywhere

This commit is contained in:
Alessandro Mauri 2025-06-30 18:24:50 +02:00
parent 972c9b581d
commit 586e81935c
7 changed files with 123 additions and 80 deletions

View File

@ -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)!;
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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)!;

View File

@ -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)!;

View File

@ -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)!;

View File

@ -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()!!;