From f48151b38e06837301575de9fcdf22885386c83f Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 31 Oct 2024 00:04:49 +0100 Subject: [PATCH] initial refactor --- src/main.c3 | 82 +++++-------------------- src/ugui_button.c3 | 48 +++++++++++++++ src/ugui_data.c3 | 1 + src/ugui_impl.c3 | 149 ++++++++++++--------------------------------- src/ugui_input.c3 | 61 ++++++++++++++++--- 5 files changed, 157 insertions(+), 184 deletions(-) create mode 100644 src/ugui_button.c3 diff --git a/src/main.c3 b/src/main.c3 index 989e1d7..350e05b 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -15,18 +15,15 @@ fn int main(String[] args) rl::init_window(width, height, "Ugui Test"); ctx.input_window_size(width, height)!!; - double[10][4] median_times; isz frame; - double median_input, median_layout, median_draw, median_tot; // Main loop while (!rl::window_should_close()) { const int PARTIAL_INPUT = 0; const int PARTIAL_LAYOUT = 1; const int PARTIAL_DRAW = 2; - //timer_start(); - /*** Start Input Handling ***/ + /* Start Input Handling */ if (rl::is_window_resized()) { width = (short)rl::get_screen_width(); height = (short)rl::get_screen_height(); @@ -48,47 +45,40 @@ fn int main(String[] args) buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT); buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE); ctx.input_mouse_button(buttons); + /* End Input Handling */ - //timer_partial(PARTIAL_INPUT); - /*** End Input Handling ***/ - - /*** Start UI Handling ***/ + /* Start UI Handling */ ctx.frame_begin()!!; // main div, fill the whole window ctx.div_begin("main", ugui::DIV_FILL)!!; {| ctx.layout_set_column()!!; - if (ctx.button("button0", ugui::Rect{.y = 100, .x = 100, .w = 30, .h = 30})!!.mouse_hold) { - io::printn("HOLDING button0"); + if (ctx.button("button0", ugui::Rect{100,100,30,30})!!.mouse_press) { + io::printn("press button0"); } ctx.layout_next_column()!!; - ctx.button("button1", ugui::Rect{.w = 30, .h = 30})!!; + if (ctx.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) { + io::printn("press button1"); + } ctx.layout_next_column()!!; - ctx.button("button2", ugui::Rect{.w = 30, .h = 30})!!; + if (ctx.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { + io::printn("release button2"); + } |}; ctx.div_end()!!; ctx.frame_end()!!; - //timer_partial(PARTIAL_LAYOUT); - /*** End UI Handling ***/ + /* End UI Handling */ - /*** Start UI Drawing ***/ + /* Start UI Drawing */ rl::begin_drawing(); // ClearBackground(BLACK); - io::printn("----- Draw Begin -----"); rl::Color c; for (Cmd* cmd; (cmd = ctx.cmd_queue.dequeue() ?? null) != null;) { switch (cmd.type) { case ugui::CmdType.CMD_RECT: - io::printfn( - "draw rect x=%d y=%d w=%d h=%d", - cmd.rect.rect.x, - cmd.rect.rect.y, - cmd.rect.rect.w, - cmd.rect.rect.h - ); c = rl::Color{ .r = cmd.rect.color.r, .g = cmd.rect.color.g, @@ -106,52 +96,10 @@ fn int main(String[] args) io::printfn("Unknown cmd type: %d", cmd.type); } } - io::printf("----- Draw End -----\n\n"); rl::end_drawing(); - //timer_partial(PARTIAL_DRAW); - //timer_stop(); - /*** End UI Drawing ***/ - /* - median_times[frame][PARTIAL_INPUT] = - 1e3 * timer_get_sec(PARTIAL_INPUT); - median_times[frame][PARTIAL_LAYOUT] = - 1e3 * timer_get_sec(PARTIAL_LAYOUT); - median_times[frame][PARTIAL_DRAW] = - 1e3 * timer_get_sec(PARTIAL_DRAW); - median_times[frame][3] = 1e3 * timer_get_sec(-1); - */ - frame += 1; - frame %= 10; - /* - if (frame == 0) { - median_input = 0; - median_layout = 0; - median_draw = 0; - median_tot = 0; - for (size_t i = 0; i < 10; i++) { - median_input += median_times[i][PARTIAL_INPUT]; - median_layout += median_times[i][PARTIAL_LAYOUT]; - median_draw += median_times[i][PARTIAL_DRAW]; - median_tot += median_times[i][3]; - } - median_input /= 10; - median_layout /= 10; - median_draw /= 10; - median_tot /= 10; - } - printf("input time: %lfms\n", median_input); - printf("layout time: %lfms\n", median_layout); - printf("draw time: %lfms\n", median_draw); - printf("total time: %lfms\n", median_tot); - - // Throttle Frames - // TODO: add an fps limit, time frame generation and log it - const float TARGET_FPS = 100; - float wait_time = MAX((1.0 / TARGET_FPS) - timer_get_sec(-1), 0); - WaitTime(wait_time); - */ + // TODO: throttle FPS } rl::close_window(); @@ -160,6 +108,7 @@ fn int main(String[] args) return 0; } +/* fn void! test_vtree() @test { vtree::VTree() vt; @@ -194,3 +143,4 @@ fn void! test_cache() @test r = cc.get_or_insert(&&"Ciao Mamma", 1)!; assert(*r!! == "Ciao Mamma", "incorrect string"); } +*/ diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 new file mode 100644 index 0000000..20d4d9e --- /dev/null +++ b/src/ugui_button.c3 @@ -0,0 +1,48 @@ +module ugui; + +// draw a button, return the events on that button +fn ElemEvents! Ctx.button(&ctx, String label, Rect size) +{ + Id id = hash(label); + + Elem *parent = ctx.get_parent()!; + Elem *c_elem = ctx.get_elem(id)!; + // 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); + + // if the element is new or the parent was updated then redo layout + if (c_elem.flags.is_new || parent.flags.updated) { + // 2. Layout + c_elem.rect = ctx.position_element(parent, size, true); + + // TODO: 3. Fill the button specific fields + } + + // TODO: Check for interactions + // FIXME: this is not how normal buttons work, usually focus follows both + // mouse hover and mouse down + c_elem.events = ctx.get_elem_events(c_elem); + if (parent.flags.has_focus) { + if (c_elem.events.mouse_hover) { + c_elem.flags.has_focus = true; + bg_color = uint_to_rgba(0x00ff00ff); + } + } + + // Draw the button + Cmd cmd = { + .type = CMD_RECT, + .rect = { + .rect = c_elem.rect, + .color = bg_color, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; + + return c_elem.events; +} diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 7883b62..178be3b 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -28,6 +28,7 @@ enum ElemType { bitstruct ElemFlags : uint { bool updated : 0; bool has_focus : 1; + bool is_new : 2; } bitstruct ElemEvents : uint { diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index f2d3dca..27b9efe 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -11,6 +11,19 @@ fn Elem*! Ctx.get_parent(&ctx) return ctx.cache.search(parent_id); } +// get or push an element from the cache, return a pointer to it +// resets all flags except is_new which is set accordingly +macro Ctx.get_elem(&ctx, Id id) +{ + Elem empty_elem; + bool is_new; + Elem* c_elem; + c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; + c_elem.flags = (ElemFlags)0; + c_elem.flags.is_new = is_new; + return c_elem; +} + fn void! Ctx.init(&ctx) { ctx.tree.init(MAX_ELEMENTS)!; @@ -39,56 +52,40 @@ fn void Ctx.free(&ctx) fn void! Ctx.frame_begin(&ctx) { - // 1. Create the root div element - // NOTE: in c3 everythong is zero initialized by default - Rect space = { - .w = ctx.width, - .h = ctx.height, - }; - - Elem root = { - .id = ROOT_ID, - .type = ETYPE_DIV, - .rect = space, - .div = { - .layout = LAYOUT_ROW, - }, - }; + // 2. Get the root element from the cache and update it + Elem* c_elem = ctx.get_elem(ROOT_ID)!; // The root should have the updated flag only if the size of the window // was changed between frames, this propagates an element size recalculation // down the element tree - if (ctx.input.events.resize) { - root.flags.updated = true; - } - + c_elem.flags.updated = ctx.input.events.resize; // if the window has focus then the root element also has focus, no other // computation needed, child elements need to check the mouse positon and // other stuff - if (ctx.has_focus) { - root.flags.has_focus = true; - } - - // FIXME: check errors - // 2. Get the root element from the cache and update it - bool is_new; - Elem empty_elem; - Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, root.id, &is_new)!; - // flags always need to be set to the new flags - c_elem.flags = root.flags; - if (is_new || root.flags.updated) { - *c_elem = root; + c_elem.flags.has_focus = ctx.has_focus; + + if (c_elem.flags.is_new || c_elem.flags.updated) { + Elem def_root = { + .id = ROOT_ID, + .type = ETYPE_DIV, + .rect = { + .w = ctx.width, + .h = ctx.height, + }, + .div = { + .layout = LAYOUT_ROW, + }, + .flags = c_elem.flags, + }; + + *c_elem = def_root; } // 3. Push the root element into the element tree - ctx.active_div = ctx.tree.add(root.id, 0)!; - - // print_tree(ctx); + ctx.active_div = ctx.tree.add(ROOT_ID, 0)!; // The root element does not push anything to the stack // TODO: add a background color taken from a theme or config - - io::printn("##### Frame Begin #####"); } fn void! Ctx.frame_end(&ctx) @@ -113,36 +110,22 @@ $if 1: }; ctx.cmd_queue.enqueue(&cmd)!; $endif - - io::printn("##### Frame End #####"); } fn void! Ctx.div_begin(&ctx, String label, Rect size) { Id id = hash(label); - bool is_new; - Elem empty_elem; - Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; - - // FIXME: why save the id in the tree and not something more direct like - // the element pointer or the index into the cache vector? - isz div_node = ctx.tree.add(id, ctx.active_div)!; - Elem *parent = ctx.get_parent()!; - - ctx.tree.print(); - - // Use the current div + Elem* c_elem = ctx.get_elem(id)!; + isz div_node = ctx.tree.add(id, ctx.active_div)!; ctx.active_div = div_node; // 1. Fill the element fields - // this resets the flags c_elem.type = ETYPE_DIV; - c_elem.flags = (ElemFlags)0; // do layout and update flags only if the element was updated - if (is_new || parent.flags.updated) { + if (c_elem.flags.is_new || parent.flags.updated) { // 2. layout the element c_elem.rect = ctx.position_element(parent, size); @@ -183,62 +166,10 @@ fn void! Ctx.div_end(&ctx) ctx.active_div = ctx.tree.parentof(ctx.active_div)!; } -// @ensure elem != null +/** + * @ensure elem != null +**/ fn bool Ctx.is_hovered(&ctx, Elem *elem) { return point_in_rect(ctx.input.mouse.pos, elem.rect); } - -fn ElemEvents! Ctx.button(&ctx, String label, Rect size) -{ - Id id = hash(label); - - // TODO: do layouting if the element is new or the parent has updated - bool is_new; - Elem empty_elem; - Elem *c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; - - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; - ctx.tree.print(); - - Elem *parent = ctx.get_parent()!; - - // 1. Fill the element fields - // this resets the flags - c_elem.type = ETYPE_BUTTON; - c_elem.flags = (ElemFlags)0; - Color bg_color = uint_to_rgba(0x0000ffff); - - // if the element is new or the parent was updated then redo layout - if (is_new || parent.flags.updated) { - // 2. Layout - c_elem.rect = ctx.position_element(parent, size, true); - - // TODO: 3. Fill the button specific fields - } - - // TODO: Check for interactions - if (parent.flags.has_focus) { - if (ctx.is_hovered(c_elem)) { - c_elem.flags.has_focus = true; - c_elem.events.mouse_hover = true; - bg_color = uint_to_rgba(0x00ff00ff); - c_elem.events.mouse_hold = ctx.input.mouse.down.btn_left; - } else { - c_elem.events.mouse_hover = false; - } - } - - // Draw the button - Cmd cmd = { - .type = CMD_RECT, - .rect = { - .rect = c_elem.rect, - .color = bg_color, - }, - }; - ctx.cmd_queue.enqueue(&cmd)!; - - return c_elem.events; -} diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index d8c63f3..886865f 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -40,21 +40,64 @@ bitstruct MouseButtons : uint { bool btn_5 : 4; } +macro Ctx.mouse_pressed(&ctx) +{ + return ctx.input.mouse.updated & ctx.input.mouse.down; +} + +macro Ctx.mouse_released(&ctx) +{ + return ctx.input.mouse.updated & ~ctx.input.mouse.down; +} + +macro Ctx.mouse_down(&ctx) +{ + return ctx.input.mouse.down; +} + +const MouseButtons BTN_NONE = (MouseButtons)0u; +const MouseButtons BTN_ANY = (MouseButtons)(uint.max); +const MouseButtons BTN_LEFT = {.btn_left = true}; +const MouseButtons BTN_MIDDLE = {.btn_middle = true}; +const MouseButtons BTN_RIGHT = {.btn_right = true}; +const MouseButtons BTN_4 = {.btn_4 = true}; +const MouseButtons BTN_5 = {.btn_5 = true}; + +// FIXME: hthis compairson could be done with a cast using MouseButtons.inner +// property but I could not figure out how +macro Ctx.is_mouse_pressed(&ctx, MouseButtons btn) +{ + return (ctx.mouse_pressed() & btn) != BTN_NONE; +} + +macro Ctx.is_mouse_released(&ctx, MouseButtons btn) +{ + return (ctx.mouse_released() & btn) != BTN_NONE; +} + +macro Ctx.is_mouse_down(&ctx, MouseButtons btn) +{ + return (ctx.mouse_down() & btn) != BTN_NONE; +} + +macro ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) +{ + // TODO: add the other events + ElemEvents ev = { + .mouse_hover = ctx.is_hovered(elem), + .mouse_press = ctx.is_mouse_pressed(BTN_ANY), + .mouse_release = ctx.is_mouse_released(BTN_ANY), + .mouse_hold = ctx.is_mouse_down(BTN_ANY), + }; + return ev; +} + // Mouse Button moved fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) { ctx.input.mouse.updated = ctx.input.mouse.down ^ buttons; ctx.input.mouse.down = buttons; ctx.input.events.mouse_btn = true; - - io::printfn( - "Mouse Down: %s%s%s%s%s", - buttons.btn_left ? "BTN_LEFT " : "", - buttons.btn_right ? "BTN_RIGHT " : "", - buttons.btn_middle ? "BTN_MIDDLE " : "", - buttons.btn_4 ? "BTN_4 " : "", - buttons.btn_5 ? "BTN_5 " : "" - ); } // Mouse was moved, report absolute position