diff --git a/TODO b/TODO index b995b2a..454a826 100644 --- a/TODO +++ b/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 diff --git a/lib/ugui.c3l/src/ugui_button.c3 b/lib/ugui.c3l/src/ugui_button.c3 index b24a3d4..321434f 100644 --- a/lib/ugui.c3l/src/ugui_button.c3 +++ b/lib/ugui.c3l/src/ugui_button.c3 @@ -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); diff --git a/lib/ugui.c3l/src/ugui_cmd.c3 b/lib/ugui.c3l/src/ugui_cmd.c3 index e282a18..b64692f 100644 --- a/lib/ugui.c3l/src/ugui_cmd.c3 +++ b/lib/ugui.c3l/src/ugui_cmd.c3 @@ -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 = { diff --git a/lib/ugui.c3l/src/ugui_layout.c3 b/lib/ugui.c3l/src/ugui_layout.c3 index ab7db01..e59cb66 100644 --- a/lib/ugui.c3l/src/ugui_layout.c3 +++ b/lib/ugui.c3l/src/ugui_layout.c3 @@ -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) { diff --git a/lib/ugui.c3l/src/ugui_shapes.c3 b/lib/ugui.c3l/src/ugui_shapes.c3 index d268229..6c8e592 100644 --- a/lib/ugui.c3l/src/ugui_shapes.c3 +++ b/lib/ugui.c3l/src/ugui_shapes.c3 @@ -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, + }; } diff --git a/resources/style.css b/resources/style.css index 902ee2e..d062d5a 100644 --- a/resources/style.css +++ b/resources/style.css @@ -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 { diff --git a/src/main.c3 b/src/main.c3 index ea4dc0d..cef087f 100644 --- a/src/main.c3 +++ b/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()!!;