diff --git a/.gitignore b/.gitignore index 3df608d..0af3826 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.a build/* **/.ccls-cache +perf.data* diff --git a/TODO b/TODO index dd8d4d6..757c3af 100644 --- a/TODO +++ b/TODO @@ -9,6 +9,7 @@ [ ] Do not redraw if there was no update (no layout and no draw) [ ] Better handling of the active and focused widgets, try to maintain focus until mouse release (fix scroll bars) +[ ] Clip element bounds to parent div, specifically text ## Commands diff --git a/project.json b/project.json index 6c11f55..8922226 100644 --- a/project.json +++ b/project.json @@ -22,7 +22,7 @@ //"RAYGUI_CUSTOM_ICONS", ], // Authors, optionally with email. - "authors": [ "John Doe " ], + "authors": [ "Alessandro Mauri " ], // Version using semantic versioning. "version": "0.1.0", // Sources compiled for all targets. diff --git a/resources/hack-nerd.ttf b/resources/hack-nerd.ttf new file mode 100644 index 0000000..f397f20 Binary files /dev/null and b/resources/hack-nerd.ttf differ diff --git a/src/main.c3 b/src/main.c3 index a2ad397..ee322f4 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -70,7 +70,7 @@ fn int main(String[] args) { ugui::Ctx ui; ui.init()!!; - ui.load_font("font1", "/usr/share/fonts/TTF/HackNerdFontMono-Regular.ttf", 16)!!; + ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; short width = 800; short height = 450; @@ -123,7 +123,7 @@ fn int main(String[] args) {| ui.layout_set_row()!!; - if (ui.button("button0", ugui::Rect{0,0,30,30})!!.mouse_press) { + if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) { io::printn("press button0"); toggle = !toggle; ui.force_update()!!; @@ -147,9 +147,7 @@ fn int main(String[] args) |}; ui.div_end()!!; - ui.div_begin("second", ugui::DIV_FILL)!!; - ugui::Elem* de = ui.get_elem_by_label("second")!!; - de.div.scroll.can_y = true; + ui.div_begin("second", ugui::DIV_FILL, scroll_y: true)!!; {| ui.layout_set_column()!!; if (ui.slider_ver("slider_other", ugui::Rect{0,0,30,100})!!.update) { @@ -160,10 +158,12 @@ fn int main(String[] args) ui.button("button11", ugui::Rect{0,0,50,50})!!; ui.button("button12", ugui::Rect{0,0,50,50})!!; ui.button("button13", ugui::Rect{0,0,50,50})!!; - ui.button("button14", ugui::Rect{0,0,50,50})!!; - ui.button("button15", ugui::Rect{0,0,50,50})!!; - ui.button("button16", ugui::Rect{0,0,50,50})!!; - ui.button("button17", ugui::Rect{0,0,50,50})!!; + if (toggle) { + ui.button("button14", ugui::Rect{0,0,50,50})!!; + ui.button("button15", ugui::Rect{0,0,50,50})!!; + ui.button("button16", ugui::Rect{0,0,50,50})!!; + ui.button("button17", ugui::Rect{0,0,50,50})!!; + } |}; ui.div_end()!!; @@ -172,13 +172,13 @@ fn int main(String[] args) TimeStats uts = ui_times.get_stats(); ui.layout_set_floating()!!; - ui.div_begin("fps", ugui::Rect{0, ui.height-50, 200, 50})!!; + ui.div_begin("fps", ugui::Rect{0, ui.height-60, 200, 60})!!; {| ui.layout_set_row()!!; - ui.text_unbounded("ui avg", string::tformat("ui avg: %s", uts.avg))!!; + ui.text_unbounded("ui avg", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; - ui.layout_next_row()!!; - ui.text_unbounded("draw avg", string::tformat("draw avg: %s", dts.avg))!!; + //ui.layout_next_row()!!; + //ui.text_unbounded("draw avg", string::tformat("draw avg: %s", dts.avg))!!; //ui.force_update()!!; |}; @@ -187,7 +187,7 @@ fn int main(String[] args) ui.frame_end()!!; /* End UI Handling */ ui_times.push(clock.mark()); - ui_times.print_stats(); + //ui_times.print_stats(); /* Start UI Drawing */ rl::begin_drawing(); @@ -253,7 +253,7 @@ fn int main(String[] args) } } draw_times.push(clock.mark()); - draw_times.print_stats(); + //draw_times.print_stats(); rl::end_drawing(); /* End Drawing */ diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index d9abc64..362826a 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -2,8 +2,21 @@ module ugui; import std::io; +// button element +struct ElemButton { + Color color; + bool active; +} + +const Elem BUTTON_DEFAULTS = { + .type = ETYPE_BUTTON, + .button = { + .color = uint_to_rgba(0x0000ffff), + }, +}; + // draw a button, return the events on that button -fn ElemEvents! Ctx.button(&ctx, String label, Rect size) +fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) { Id id = label.hash(); @@ -12,29 +25,39 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size) // add it to the tree ctx.tree.add(id, ctx.active_div)!; - // 1. Fill the element fields - // this resets the flags - c_elem.type = ETYPE_BUTTON; - Color bg_color = uint_to_rgba(0x0000ffff); + bool needs_layout = c_elem.flags.is_new || parent.flags.updated; + + if (c_elem.flags.is_new) { + *c_elem = BUTTON_DEFAULTS; + } else if (c_elem.type != ETYPE_BUTTON) { + return UgError.WRONG_ELEMENT_TYPE?; + } // if the element is new or the parent was updated then redo layout - if (c_elem.flags.is_new || parent.flags.updated) { - // 2. Layout + if (needs_layout) { c_elem.bounds = ctx.position_element(parent, size, true); - - // TODO: 3. Fill the button specific fields } + // if the bounds are null the element is outside the div view, + // no interaction should occur so just return + if (c_elem.bounds.is_null()) { return ElemEvents{}; } + c_elem.events = ctx.get_elem_events(c_elem); - if (parent.flags.has_focus) { - if (c_elem.events.mouse_hover) { + if (c_elem.events.mouse_hover) { + if (parent.flags.has_focus) { c_elem.flags.has_focus = true; - bg_color = uint_to_rgba(0x00ff00ff); + c_elem.button.color = uint_to_rgba(0x00ff00ff); } + } else { + c_elem.button.color = uint_to_rgba(0x0000ffff); + } + + if (state) { + c_elem.button.color = uint_to_rgba(0xff00ffff); } // Draw the button - ctx.push_rect(c_elem.bounds, bg_color, do_border: true, do_radius: true)!; + ctx.push_rect(c_elem.bounds, c_elem.button.color, do_border: true, do_radius: true)!; return c_elem.events; } diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 259bb48..7599d35 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -135,7 +135,8 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text) .w = gp.w, .h = gp.h, }; - if (rect_collision(gb, bounds)) { + // push the sprite only if it collides with the bounds + if (gb.collides(bounds)) { ctx.push_sprite(gb, gt, texture_id)!; } line_len += gp.adv; @@ -173,4 +174,4 @@ fn void! Ctx.push_scissor(&ctx, Rect rect) .scissor.rect = rect, }; ctx.cmd_queue.enqueue(&sc)!; -} \ No newline at end of file +} diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 7cd39da..c65269a 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -47,28 +47,6 @@ bitstruct ElemEvents : uint { bool update : 7; } -enum DivLayout { - LAYOUT_ROW, - LAYOUT_COLUMN, - LAYOUT_FLOATING, -} - -// div element -struct ElemDiv { - DivLayout layout; - struct scroll { - bool can_x; - bool can_y; - bool on_x; - bool on_y; - float value_x; - float value_y; - } - Rect children_bounds; - Point origin_r, origin_c; - Color color_bg; -} - // slider element struct ElemSlider { float value; @@ -88,6 +66,7 @@ struct Elem { ElemType type; union { ElemDiv div; + ElemButton button; ElemSlider slider; ElemText text; } @@ -107,14 +86,15 @@ fault UgError { INVALID_SIZE, EVENT_UNSUPPORTED, UNEXPECTED_ELEMENT, + WRONG_ELEMENT_TYPE, } -macro uint_to_rgba(uint u) { +macro Color uint_to_rgba(uint $u) { return Color{ - .r = (char)((u >> 24) & 0xff), - .g = (char)((u >> 16) & 0xff), - .b = (char)((u >> 8) & 0xff), - .a = (char)((u >> 0) & 0xff) + .r = (char)(($u >> 24) & 0xff), + .g = (char)(($u >> 16) & 0xff), + .b = (char)(($u >> 8) & 0xff), + .a = (char)(($u >> 0) & 0xff) }; } @@ -179,12 +159,12 @@ macro point_in_rect(Point p, Rect r) } // return true if rect a contains b -macro rect_contains(Rect a, Rect b) +macro bool Rect.contains(Rect a, Rect b) { return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h); } -macro rect_intersection(Rect a, Rect b) +macro Rect Rect.intersection(Rect a, Rect b) { return Rect{ .x = (short)max(a.x, b.x), @@ -195,7 +175,9 @@ macro rect_intersection(Rect a, Rect b) } // rect intersection not null -macro rect_collision(Rect a, Rect b) +macro bool Rect.collides(Rect a, Rect b) { return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y); } + +macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0; diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 3706c34..970b722 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -2,7 +2,39 @@ module ugui; import std::io; -fn void! Ctx.div_begin(&ctx, String label, Rect size) +enum DivLayout { + LAYOUT_ROW, + LAYOUT_COLUMN, + LAYOUT_FLOATING, +} + +// div element +struct ElemDiv { + DivLayout layout; + struct scroll { + bool can_x; + bool can_y; + bool on_x; + bool on_y; + float value_x; + float value_y; + } + Rect children_bounds; + Point origin_r, origin_c; + Color bgcolor; +} + +const Elem DIV_DEFAULTS = { + .type = ETYPE_DIV, + .div = { + .scroll.can_x = false, + .scroll.can_y = false, + .scroll.value_x = 0, + .scroll.value_y = 0, + }, +}; + +fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) { Id id = label.hash(); @@ -11,21 +43,22 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) isz div_node = ctx.tree.add(id, ctx.active_div)!; ctx.active_div = div_node; - // 1. Fill the element fields - c_elem.type = ETYPE_DIV; + bool needs_layout = c_elem.flags.is_new || parent.flags.updated; + + if (c_elem.flags.is_new) { + *c_elem = DIV_DEFAULTS; + } else if (c_elem.type != ETYPE_DIV) { + return UgError.WRONG_ELEMENT_TYPE?; + } + c_elem.div.scroll.can_x = scroll_x; + c_elem.div.scroll.can_y = scroll_y; // do layout and update flags only if the element was updated - if (c_elem.flags.is_new || parent.flags.updated) { + if (needs_layout) { // 2. layout the element c_elem.bounds = ctx.position_element(parent, size); - if (c_elem.flags.is_new) { - c_elem.div.children_bounds = c_elem.bounds; - c_elem.div.color_bg = ctx.style.bgcolor; - c_elem.div.scroll.can_x = false; - c_elem.div.scroll.can_y = false; - c_elem.div.scroll.value_x = 0; - c_elem.div.scroll.value_y = 0; - } + c_elem.div.children_bounds = c_elem.bounds; + c_elem.div.bgcolor = ctx.style.bgcolor; // 3. Mark the element as updated c_elem.flags.updated = true; @@ -39,14 +72,14 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) } // Add the background to the draw stack - bool do_border = parent.div.layout == LAYOUT_FLOATING; - ctx.push_rect(c_elem.bounds, c_elem.div.color_bg, do_border: do_border)!; + bool do_border = parent.div.layout == LAYOUT_FLOATING; + ctx.push_rect(c_elem.bounds, c_elem.div.bgcolor, do_border: do_border)!; // TODO: check active // TODO: check resizeable // check and draw scroll bars - if (!rect_contains(c_elem.bounds, c_elem.div.children_bounds)) { + if (!c_elem.bounds.contains(c_elem.div.children_bounds)) { Point cbc = { .x = c_elem.div.children_bounds.x + c_elem.div.children_bounds.w, .y = c_elem.div.children_bounds.y + c_elem.div.children_bounds.h, diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index e2da44a..9b7d824 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -124,7 +124,7 @@ fn void! Ctx.frame_end(&ctx) root.div.layout = LAYOUT_ROW; // 1. clear the tree - ctx.tree.prune(0)!; + ctx.tree.nuke(); // 2. clear input fields bool f = ctx.input.events.force_update; diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index dcf63f4..423f127 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -163,7 +163,7 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) } // 6. check if the placement is inside the view - if (rect_collision(placement, view)) { + if (placement.collides(view)) { return Rect{ .x = placement.x - off.x, .y = placement.y - off.y, diff --git a/src/vtree.c3 b/src/vtree.c3 index 42b5d19..8cdcff7 100644 --- a/src/vtree.c3 +++ b/src/vtree.c3 @@ -27,7 +27,7 @@ macro @zero() $if $assignable(0, ElemType): return 0; $else - return ElemType{0}; + return ElemType{}; $endif } @@ -186,6 +186,15 @@ fn usz! VTree.prune(&tree, isz ref) return count; } +fn usz VTree.nuke(&tree) +{ + tree.vector[0..] = @zero(); + tree.refs[0..] = -1; + usz x = tree.elements; + tree.elements = 0; + return x; +} + // find the size of the subtree starting from ref fn usz! VTree.subtree_size(&tree, isz ref) {