unified button element

This commit is contained in:
Alessandro Mauri 2025-07-13 20:08:18 +02:00
parent b48c413d2d
commit 80d17d7b33
7 changed files with 107 additions and 132 deletions

View file

@ -8,110 +8,80 @@ struct ElemButton {
}
// draw a button, return the events on that button
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)
macro Ctx.button(&ctx, String label = "", String icon = "", ...)
=> ctx.button_id(@compute_id($vasplat), label, icon);
fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
{
id = ctx.gen_id(id)!;
Elem *parent = ctx.get_parent()!;
Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!;
Style* style_norm = ctx.styles.get_style(@str_hash("button"));
elem.bounds = ctx.position_element(parent, size, style_norm);
// if the bounds are null the element is outside the div view,
// no interaction should occur so just return
if (elem.bounds.is_null()) { return {}; }
Style* style_active = ctx.styles.get_style(@str_hash("button-active"));
elem.events = ctx.get_elem_events(elem);
bool is_active = active || ctx.elem_focus(elem) || elem.events.mouse_hover;
// Draw the button
ctx.push_rect(elem.bounds, parent.div.z_index, is_active ? style_active : style_norm)!;
return elem.events;
}
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 = ctx.gen_id(id)!;
Elem *parent = ctx.get_parent()!;
Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!;
Style* style_norm = ctx.styles.get_style(@str_hash("button"));
Style* style_active = ctx.styles.get_style(@str_hash("button-active"));
// TODO: cache text_size just like text_unbounded() does
Rect text_size = ctx.get_text_bounds(label)!;
Rect btn_size = text_size.grow({10,10});
// 2. Layout
elem.bounds = ctx.position_element(parent, btn_size, style_norm);
if (elem.bounds.is_null()) { return {}; }
Style* style = ctx.styles.get_style(@str_hash("button"));
text_size.x = elem.bounds.x;
text_size.y = elem.bounds.y;
text_size = text_size.center_to(elem.bounds);
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)!;
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 right_pad = left_pad;
/* |left_pad
* +--v-----------------------------------+
* |<->+--------+ |
* | | | +-----------------+ |
* | | icon | | label | |
* | | | +-----------------+ |
* | +--------+<->| |<->|
* +-------------^--------------------^---+
* |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.bounds = ctx.position_element(parent, tot_size, style);
if (elem.bounds.is_null()) return {};
elem.events = ctx.get_elem_events(elem);
bool is_active = active || ctx.elem_focus(elem) || elem.events.mouse_hover;
Style* style = is_active ? style_active : style_norm;
// Draw the button
ctx.push_rect(elem.bounds, parent.div.z_index, style)!;
ctx.push_string(text_size, label, parent.div.z_index, style.fg)!;
return elem.events;
}
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 = ctx.gen_id(id)!;
Elem *parent = ctx.get_parent()!;
Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!;
Sprite* def_sprite = ctx.sprite_atlas.get(icon)!;
Sprite* on_sprite = ctx.sprite_atlas.get(on_icon) ?? &&(Sprite){};
Rect max_size = def_sprite.rect().max(on_sprite.rect());
Style* style_norm = ctx.styles.get_style(@str_hash("button"));
elem.bounds = ctx.position_element(parent, max_size, style_norm);
// if the bounds are null the element is outside the div view,
// no interaction should occur so just return
if (elem.bounds.is_null()) { return {}; }
Style* style_active = ctx.styles.get_style(@str_hash("button-active"));
elem.events = ctx.get_elem_events(elem);
bool is_active = active || ctx.elem_focus(elem) || elem.events.mouse_hover;
Style* style = is_active ? style_active : style_norm;
Id tex_id = ctx.sprite_atlas.id;
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)!;
Rect content_bounds = elem.content_bounds(style);
Rect text_bounds = {
.x = content_bounds.x + icon_size.w + left_pad + inner_pad,
.y = content_bounds.y,
.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);
Rect icon_bounds = {
.x = content_bounds.x,
.y = content_bounds.y,
.w = icon_size.w + right_pad + inner_pad,
.h = (short)max(icon_size.h, content_bounds.h)
};
icon_bounds = icon_size.center_to(icon_bounds);
bool is_active = ctx.elem_focus(elem) || elem.events.mouse_hover;
Style s = *style;
if (is_active) {
s.secondary = s.primary;
s.bg = s.accent;
}
// Draw the button
ctx.push_rect(elem.bounds, parent.div.z_index, style)!;
ctx.push_rect(elem.bounds, parent.div.z_index, &s)!;
ctx.push_sprite(icon_bounds, sprite.uv(), ctx.sprite_atlas.id, parent.div.z_index, type: sprite.type)!;
// Style ss = {.bg = 0x000000ffu.@to_rgba()};
// ctx.push_rect(content_bounds, parent.div.z_index, &ss)!;
ctx.push_string(text_bounds, label, parent.div.z_index, style.fg)!;
return elem.events;
}
// FIXME: this should be inside the style
macro Ctx.checkbox(&ctx, String desc, Point off, bool* active, String tick_sprite = {}, ...)
=> ctx.checkbox_id(@compute_id($vasplat), desc, off, active, tick_sprite);

View file

@ -93,7 +93,6 @@ fn void? Ctx.push_scissor(&ctx, Rect rect, int z_index)
fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
{
Rect border = style.border;
Rect padding = style.padding;
ushort radius = style.radius;
Color bg = style.bg;
Color border_color = style.secondary;
@ -111,10 +110,10 @@ fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
Cmd cmd = {
.type = CMD_RECT,
.rect.rect = {
.x = rect.x + border.x + padding.x,
.y = rect.y + border.y + padding.y,
.w = rect.w - (border.x+border.w) - (padding.x+padding.w),
.h = rect.h - (border.y+border.h) - (padding.y+padding.h),
.x = rect.x + border.x,
.y = rect.y + border.y,
.w = rect.w - (border.x+border.w),
.h = rect.h - (border.y+border.h),
},
.rect.color = bg,
.rect.radius = radius,
@ -123,6 +122,7 @@ fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
ctx.push_cmd(&cmd, z_index)!;
}
// TODO: accept a Sprite* instead of all this shit
fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_index, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL)
{
Cmd cmd = {

View file

@ -78,6 +78,7 @@ fn void? Ctx.layout_next_column(&ctx)
parent.div.origin_r = parent.div.origin_c;
}
macro Rect Elem.content_bounds(&elem, style) => elem.bounds.pad(style.border).pad(style.padding);
macro Rect Elem.get_view(&elem)
{

View file

@ -156,8 +156,22 @@ macro Point Rect.bottom_right(Rect r)
macro Rect Rect.center_to(Rect a, Rect b)
{
Point off = {.x = (b.w - a.w)/2, .y = (b.h - a.h)/2};
return a.off(off);
return {
.x = b.x + (b.w - a.w)/2,
.y = b.y + (b.h - a.h)/2,
.w = a.w,
.h = a.h,
};
}
macro Rect Rect.pad(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,
};
}