module ugui; import std::io; // button element struct ElemButton { int filler; } 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 = ctx.styles.get_style(@str_hash("button")); Sprite* sprite = icon != "" ? ctx.sprite_atlas.get(icon)! : &&(Sprite){}; Rect icon_size = sprite.rect(); ushort min_size = style.size; ushort half_lh = (ushort)(ctx.font.line_height() / 2); ushort inner_pad = label != "" && icon != "" ? half_lh : 0; /* * +--------------------------------------+ * | +--------+ | * | | | +-----------------+ | * | | icon | | label | | * | | | +-----------------+ | * | +--------+<->| | * +-------------^------------------------+ * |inner_pad */ Point content_size = { .x = icon_size.w + inner_pad, // text sizing is handled differently .y = icon_size.h + inner_pad, }; elem.layout.w = @fit(min_size); elem.layout.h = @fit(min_size); elem.layout.children.w = @exact(content_size.x); elem.layout.children.h = @exact(content_size.y); elem.layout.text = ctx.measure_string(label)!; elem.layout.content_offset = style.margin + style.border + style.padding; update_parent_grow(elem, parent); update_parent_size(elem, parent); elem.events = ctx.get_elem_events(elem); Rect content_bounds = elem.content_bounds(); Rect icon_bounds = { .x = content_bounds.x, .y = content_bounds.y, .w = icon_size.w, .h = icon_size.h }; icon_bounds = icon_size.center_to(icon_bounds); Rect text_bounds = { .x = content_bounds.x + icon_bounds.w + inner_pad, .y = content_bounds.y, .w = content_bounds.w - icon_bounds.w - inner_pad, .h = content_bounds.h, }; //text_bounds = text_size.center_to(text_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; } ctx.push_rect(elem.bounds.pad(style.margin), parent.div.z_index, &s)!; if (icon != "") { ctx.push_sprite(icon_bounds, sprite.uv(), ctx.sprite_atlas.id, parent.div.z_index, type: sprite.type)!; } if (label != "") { ctx.layout_string(label, text_bounds, CENTER, parent.div.z_index, style.fg)!; } return elem.events; } macro Ctx.checkbox(&ctx, String desc, bool* active, String tick_sprite = "", ...) => ctx.checkbox_id(@compute_id($vasplat), desc, active, tick_sprite); fn void? Ctx.checkbox_id(&ctx, Id id, String description, bool* active, String tick_sprite) { id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!; Style* style = ctx.styles.get_style(@str_hash("checkbox")); short inner_pad = description != "" ? style.size/2 : 0; /* * |< >| style.size/2 * +---------------------|---|-----------+ * | | .-----. ---|-- * | +-----------------+ ' ### ' | ^ * | | description | | ##### | | style.size * | +-----------------+ . ### . | v * | '-----' ---|-- * +-------------------------|-------|---+ * |<----->| style.size */ elem.layout.w = @fit(style.size); elem.layout.h = @fit(style.size); elem.layout.children.w = @exact(style.size + inner_pad); elem.layout.children.h = @exact(style.size); elem.layout.text = ctx.measure_string(description)!; elem.layout.content_offset = style.margin + style.border + style.padding; update_parent_grow(elem, parent); update_parent_size(elem, parent); elem.events = ctx.get_elem_events(elem); if (elem.events.mouse_hover && elem.events.mouse_release) *active = !(*active); Rect content_bounds = elem.bounds.pad(elem.layout.content_offset); Rect text_bounds = { .x = content_bounds.x, .y = content_bounds.y, .w = content_bounds.w - inner_pad - style.size, .h = content_bounds.h }; Rect check_bounds = { .x = content_bounds.x + text_bounds.w + inner_pad, .y = content_bounds.y + (content_bounds.h - style.size)/2, .w = style.size, .h = style.size, }; Style s; s.bg = style.bg; s.secondary = style.secondary; s.border = style.border; s.radius = style.radius; ctx.layout_string(description, text_bounds, CENTER, parent.div.z_index, style.fg)!; if (tick_sprite != "") { ctx.push_rect(check_bounds, parent.div.z_index, &s)!; if (*active) { Sprite* sprite = ctx.sprite_atlas.get(tick_sprite)!; Id tex_id = ctx.sprite_atlas.id; ctx.push_sprite(sprite.rect().center_to(check_bounds), sprite.uv(), tex_id, parent.div.z_index, type: sprite.type)!; } } else { if (*active) { s.bg = style.primary; ctx.push_rect(check_bounds, parent.div.z_index, &s)!; } else { ctx.push_rect(check_bounds, parent.div.z_index, &s)!; } } } /* // FIXME: this should be inside the style 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 = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!; Style* style = ctx.styles.get_style(@str_hash("toggle")); Rect size = {off.x, off.y, style.size*2, style.size}; elem.bounds = ctx.layout_element(parent, size, style); // 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; elem.events = ctx.get_elem_events(elem); if (elem.events.mouse_hover && elem.events.mouse_release) *active = !(*active); // Draw the button // FIXME: THIS IS SHIT ctx.push_rect(elem.bounds, parent.div.z_index, style)!; Rect t = elem.bounds.add({*active ? (style.size+3) : +3, +3, -style.size-6, -6}); Style s = *style; s.bg = s.primary; s.margin = s.border = s.padding = {}; ctx.push_rect(t, parent.div.z_index, &s)!; }