unified button element
This commit is contained in:
parent
b48c413d2d
commit
80d17d7b33
14
TODO
14
TODO
@ -15,8 +15,10 @@ to maintain focus until mouse release (fix scroll bars)
|
||||
[x] Implement a z index and sort command buffer based on that
|
||||
[ ] Ctx.set_z_index()
|
||||
[x] Sort command buffer on insertion
|
||||
[x] Standardize element handling, for example all buttons do almost the same thing, so write a lot of boiler plate and reuse it
|
||||
[x] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division
|
||||
[x] Standardize element handling, for example all buttons do almost the same thing, so write a lot
|
||||
of boiler plate and reuse it
|
||||
[x] The id combination in gen_id() uses an intger division, which is costly, use another combination
|
||||
function that is non-linear and doesn't use division
|
||||
[ ] Animations, somehow
|
||||
[ ] Maybe cache codepoint converted strings
|
||||
[x] Fix scroll wheel when div is scrolled
|
||||
@ -39,10 +41,10 @@ to maintain focus until mouse release (fix scroll bars)
|
||||
each struct Elem, struct Cmd, etc. is as big as the largest element. It would
|
||||
be better to use a different allcation strategy.
|
||||
[ ] Add a way to handle time events like double clicks
|
||||
[ ] Border and padding do not go well together if the library issues two rect commands, the visible
|
||||
border is effectively the border size plus the padding since there is a gap between the border
|
||||
rect and the internal rect. A better solution is to leave it up to the renderer to draw the rect
|
||||
correctly
|
||||
[x] Fix how padding is applied in push_rect. In CSS padding is applied between the border and the
|
||||
content, the background color is applied starting from the border. Right now push_rect() offsets
|
||||
the background rect by both border and padding
|
||||
|
||||
|
||||
## Layout
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 = {
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
@ -9,27 +9,15 @@ default {
|
||||
button {
|
||||
margin: 2 2 2 2;
|
||||
border: 2 2 2 2;
|
||||
padding: 1 1 1 1;
|
||||
padding: 2 2 2 2;
|
||||
radius: 10;
|
||||
size: 32;
|
||||
|
||||
bg: #3c3836ff;
|
||||
fg: #fbf1c7ff;
|
||||
primary: #cc241dff;
|
||||
secondary: #458588ff;
|
||||
accent: #fabd2fff;
|
||||
}
|
||||
|
||||
button-active {
|
||||
margin: 2 2 2 2;
|
||||
border: 2 2 2 2;
|
||||
padding: 1 1 1 1;
|
||||
radius: 10;
|
||||
|
||||
bg: #504945ff;
|
||||
fg: #fbf1c7ff;
|
||||
primary: #cc241dff;
|
||||
secondary: #cc241dff;
|
||||
accent: #fabd2fff;
|
||||
accent: #504945ff;
|
||||
}
|
||||
|
||||
checkbox {
|
||||
|
24
src/main.c3
24
src/main.c3
@ -202,16 +202,16 @@ fn int main(String[] args)
|
||||
ui.div_begin({.w=-100})!!;
|
||||
{
|
||||
ui.layout_set_column()!!;
|
||||
if (ui.button({0,0,30,30}, toggle)!!.mouse_press) {
|
||||
if (ui.button(icon: "tux")!!.mouse_press) {
|
||||
io::printn("press button0");
|
||||
toggle = !toggle;
|
||||
}
|
||||
//ui.layout_next_column()!!;
|
||||
if (ui.button({0,0,30,30})!!.mouse_press) {
|
||||
if (ui.button(label: "ciao", icon: "tick")!!.mouse_press) {
|
||||
io::printn("press button1");
|
||||
}
|
||||
//ui.layout_next_column()!!;
|
||||
if (ui.button({0,0,30,30})!!.mouse_release) {
|
||||
if (ui.button()!!.mouse_release) {
|
||||
io::printn("release button2");
|
||||
}
|
||||
|
||||
@ -227,7 +227,7 @@ fn int main(String[] args)
|
||||
ui.text_unbounded("Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!;
|
||||
|
||||
ui.layout_next_column()!!;
|
||||
ui.button_label("Continua!")!!;
|
||||
ui.button("Continua!")!!;
|
||||
|
||||
ui.layout_next_row()!!;
|
||||
static bool check;
|
||||
@ -250,15 +250,15 @@ fn int main(String[] args)
|
||||
if (ui.slider_ver({0,0,30,100}, &slider2)!!.update) {
|
||||
io::printfn("other slider: %f", slider2);
|
||||
}
|
||||
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.button()!!;
|
||||
ui.button()!!;
|
||||
ui.button()!!;
|
||||
ui.button()!!;
|
||||
if (toggle) {
|
||||
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.button()!!;
|
||||
ui.button()!!;
|
||||
ui.button()!!;
|
||||
ui.button()!!;
|
||||
}
|
||||
ui.layout_next_column()!!;
|
||||
ui.layout_set_row()!!;
|
||||
|
Loading…
Reference in New Issue
Block a user