From 3f9be0ffae3425f812e873ed39533a4768fa43eb Mon Sep 17 00:00:00 2001
From: David Gonzalez Martin <davidgm94.work@protonmail.com>
Date: Thu, 19 Dec 2024 08:26:40 -0600
Subject: [PATCH] Improve ui library

---
 bootstrap/bloat-buster/bb_core.c |  36 ++++---
 bootstrap/include/std/base.h     |   2 +
 bootstrap/include/std/ui_core.h  |  31 +++++-
 bootstrap/std/ui_core.c          | 158 +++++++++++++++----------------
 4 files changed, 129 insertions(+), 98 deletions(-)

diff --git a/bootstrap/bloat-buster/bb_core.c b/bootstrap/bloat-buster/bb_core.c
index 192831d..b124852 100644
--- a/bootstrap/bloat-buster/bb_core.c
+++ b/bootstrap/bloat-buster/bb_core.c
@@ -65,6 +65,26 @@ STRUCT(BBGUIState)
 };
 global_variable BBGUIState state;
 
+fn void ui_top_bar()
+{
+    ui_push(pref_height, ui_em(1, 1));
+    {
+        ui_push(child_layout_axis, AXIS2_X);
+        auto* top_bar = ui_widget_make((UI_WidgetFlags) {
+                .draw_background = 1,
+                }, strlit("top_bar"));
+        ui_push(parent, top_bar);
+        {
+            ui_button(strlit("Button 1"));
+            ui_button(strlit("Button 2"));
+            ui_button(strlit("Button 3"));
+        }
+        ui_pop(parent);
+    }
+    ui_pop(pref_height);
+    ui_button(strlit("Hello!"));
+}
+
 fn void app_update()
 {
     auto frame_end = os_timestamp();
@@ -87,24 +107,16 @@ fn void app_update()
 
         if (likely(ui_build_begin(window->os, frame_ms, &state.event_queue)))
         {
-            ui_font_size(default_font_height);
-            ui_pref_width(ui_em(10, 1));
-            ui_pref_height(ui_em(2, 1));
+            ui_push(font_size, default_font_height);
 
-            if (unlikely(ui_button(strlit("Hello world\n")).clicked_left))
-            {
-                print("Clicked on hello world\n");
-            }
-
-            if (unlikely(ui_button(strlit("Bye world\n")).clicked_left))
-            {
-                print("Clicked on bye world\n");
-            }
+            ui_top_bar();
 
             ui_build_end();
 
             ui_draw();
 
+            ui_pop(font_size);
+
             renderer_window_frame_end(renderer, render_window);
         }
         else
diff --git a/bootstrap/include/std/base.h b/bootstrap/include/std/base.h
index 81e17fd..5cf184b 100644
--- a/bootstrap/include/std/base.h
+++ b/bootstrap/include/std/base.h
@@ -338,6 +338,8 @@ fn u64 safe_flag(u64 value, u64 flag)
     return result;
 }
 
+#define member_from_offset(pointer, type, memory_offset) (*(type*)((u8*)pointer + memory_offset))
+#define offset_of(T, member) __builtin_offsetof(T, member)
 
 #define my_panic(...) do \
 {\
diff --git a/bootstrap/include/std/ui_core.h b/bootstrap/include/std/ui_core.h
index a9a7a62..b95dee0 100644
--- a/bootstrap/include/std/ui_core.h
+++ b/bootstrap/include/std/ui_core.h
@@ -77,6 +77,7 @@ STRUCT(UI_Widget)
     UI_Widget* next;
     UI_Widget* previous;
     UI_Widget* parent;
+    u64 child_count;
 
     UI_Key key;
 
@@ -189,6 +190,33 @@ STRUCT(UI_Signal)
     };
 };
 
+extern UI_State* ui_state;
+
+#define ui_stack_autopop_set(field_name, value) ui_state->stack_autopops.field_name = (value)
+#define ui_stack_push_impl(field_name, value, auto_pop_value) do \
+{\
+    *vb_add(&ui_state->stacks.field_name, 1) = (value);\
+    ui_stack_autopop_set(field_name, auto_pop_value);\
+} while (0)
+
+fn u8* ui_pop_generic(VirtualBuffer(u8)* stack, u32 element_size)
+{
+    auto length = stack->length;
+
+    assert(length > 0);
+    auto next_length = length - 1;
+    auto index = next_length;
+    auto* result = &stack->pointer[index * element_size];
+    stack->length = next_length;
+
+    return result;
+}
+
+#define ui_push(field_name, value) ui_stack_push_impl(field_name, value, 0)
+#define ui_push_next_only(field_name, value) ui_stack_push_impl(field_name, value, 1)
+#define ui_pop(field_name) (typeof(ui_state->stacks.field_name.pointer)) ui_pop_generic((VirtualBuffer(u8)*)&ui_state->stacks.field_name, sizeof(*ui_state->stacks.field_name.pointer))
+#define ui_top(field_name) (ui_state->stacks.field_name.length ? ui_state->stacks.field_name.pointer[ui_state->stacks.field_name.length - 1] : ui_state->stack_nulls.field_name)
+
 EXPORT UI_State* ui_state_allocate(Renderer* renderer, RenderWindow* window);
 EXPORT void ui_state_select(UI_State* state);
 EXPORT u8 ui_build_begin(OSWindow window, f64 frame_time, OSEventQueue* event_queue);
@@ -201,6 +229,3 @@ EXPORT UI_Widget* ui_widget_make(UI_WidgetFlags flags, String string);
 EXPORT UI_Size ui_pixels(u32 width, f32 strictness);
 EXPORT UI_Size ui_percentage(f32 percentage, f32 strictness);
 EXPORT UI_Size ui_em(f32 value, f32 strictness);
-EXPORT void ui_pref_width(UI_Size size);
-EXPORT void ui_pref_height(UI_Size size);
-EXPORT void ui_font_size(f32 size);
diff --git a/bootstrap/std/ui_core.c b/bootstrap/std/ui_core.c
index b23a3a7..70c01cc 100644
--- a/bootstrap/std/ui_core.c
+++ b/bootstrap/std/ui_core.c
@@ -1,42 +1,23 @@
+// This UI is heavily inspired by the ideas of Casey Muratori and Ryan Fleury ideas on GUI programming, to whom I am deeply grateful.
+// Here are some links which helped me achieve this build
+// https://www.youtube.com/watch?v=Z1qyvQsjK5Y
+// https://www.rfleury.com/p/ui-part-1-the-interaction-medium
+// https://www.rfleury.com/p/ui-part-2-build-it-every-frame-immediate
+// https://www.rfleury.com/p/ui-part-3-the-widget-building-language
+// https://www.rfleury.com/p/ui-part-4-the-widget-is-a-lie-node
+// https://www.rfleury.com/p/ui-part-5-visual-content
+// https://www.rfleury.com/p/ui-part-6-rendering
+// https://www.rfleury.com/p/ui-part-7-where-imgui-ends
+// https://www.rfleury.com/p/ui-part-8-state-mutation-jank-and
+// https://www.rfleury.com/p/ui-part-9-keyboard-and-gamepad-navigation
+// https://www.rfleury.com/p/ui-bonus-1-simple-single-line-text
+// https://www.rfleury.com/p/codebase-walkthrough-multi-window
+
 #include <std/ui_core.h>
 #include <std/format.h>
 #include <std/string.h>
 
-global_variable UI_State* ui_state = 0;
-#define ui_stack_autopop_set(field_name, value) ui_state->stack_autopops.field_name = (value)
-#define ui_stack_push_impl(field_name, value, auto_pop_value) do \
-{\
-    *vb_add(&ui_state->stacks.field_name, 1) = (value);\
-    ui_stack_autopop_set(field_name, auto_pop_value);\
-} while (0)
-
-fn u8* ui_pop_generic(VirtualBuffer(u8)* stack, u32 element_size)
-{
-    auto length = stack->length;
-
-    assert(length > 0);
-    auto next_length = length - 1;
-    auto index = next_length;
-    auto* result = &stack->pointer[index * element_size];
-    stack->length = next_length;
-
-    return result;
-}
-
-#define ui_stack_push(field_name, value) ui_stack_push_impl(field_name, value, 0)
-#define ui_stack_push_next_only(field_name, value) ui_stack_push_impl(field_name, value, 1)
-#define ui_stack_pop(field_name) (typeof(ui_state->stacks.field_name.pointer)) ui_pop_generic(&ui_state->stacks.field_name, sizeof(*ui_state->stacks.field_name.pointer))
-#define ui_stack_top(field_name) (ui_state->stacks.field_name.length ? ui_state->stacks.field_name.pointer[ui_state->stacks.field_name.length - 1] : ui_state->stack_nulls.field_name)
-
-void ui_pref_width(UI_Size size)
-{
-    ui_stack_push(pref_width, size);
-}
-
-void ui_pref_height(UI_Size size)
-{
-    ui_stack_push(pref_height, size);
-}
+UI_State* ui_state = 0;
 
 fn void ui_autopop(UI_State* state)
 {
@@ -234,13 +215,13 @@ UI_Widget* ui_widget_make_from_key(UI_WidgetFlags flags, UI_Key key)
         }
         else
         {
-            table_widget_slot->last->next = widget;
-            widget->previous = table_widget_slot->last;
+            table_widget_slot->last->hash_next = widget;
+            widget->hash_previous = table_widget_slot->last;
             table_widget_slot->last = widget;
         }
     }
 
-    auto* parent = ui_stack_top(parent);
+    auto* parent = ui_top(parent);
 
     if (parent)
     {
@@ -253,9 +234,11 @@ UI_Widget* ui_widget_make_from_key(UI_WidgetFlags flags, UI_Key key)
         {
             auto* previous_last = parent->last;
             previous_last->next = widget;
+            widget->previous = previous_last;
             parent->last = widget;
         }
 
+        parent->child_count += 1;
         widget->parent = parent;
     }
     else
@@ -271,9 +254,9 @@ UI_Widget* ui_widget_make_from_key(UI_WidgetFlags flags, UI_Key key)
     widget->first = 0;
     widget->last = 0;
     widget->last_build_touched = ui_state->build_count;
-    widget->pref_size[AXIS2_X] = ui_stack_top(pref_width);
-    widget->pref_size[AXIS2_Y] = ui_stack_top(pref_height);
-    widget->child_layout_axis = ui_stack_top(child_layout_axis);
+    widget->pref_size[AXIS2_X] = ui_top(pref_width);
+    widget->pref_size[AXIS2_Y] = ui_top(pref_height);
+    widget->child_layout_axis = ui_top(child_layout_axis);
 
     ui_autopop(ui_state);
 
@@ -314,10 +297,6 @@ UI_Signal ui_signal_from_widget(UI_Widget* widget)
 {
     auto rect = widget->rect;
     auto mouse_position = ui_state->mouse_position;
-    if (widget->flags.mouse_clickable & (ui_state->mouse_button_events[OS_EVENT_MOUSE_LEFT].action == OS_EVENT_MOUSE_RELEASE))
-    {
-        print("Clicked on {u32}x{u32}. Rect ({u32}, {u32}), ({u32}, {u32})\n", (u32)mouse_position.x, (u32)mouse_position.y, (u32)rect.p0.x, (u32)rect.p0.y, (u32)rect.p1.x, (u32)rect.p1.y);
-    }
     UI_Signal signal = {
         .clicked_left = 
             (widget->flags.mouse_clickable & (ui_state->mouse_button_events[OS_EVENT_MOUSE_LEFT].action == OS_EVENT_MOUSE_RELEASE)) &
@@ -358,7 +337,7 @@ UI_Size ui_percentage(f32 percentage, f32 strictness)
 
 UI_Size ui_em(f32 value, f32 strictness)
 {
-    auto font_size = ui_stack_top(font_size);
+    auto font_size = ui_top(font_size);
     assert(font_size);
     return (UI_Size) {
         .kind = UI_SIZE_PIXEL_COUNT,
@@ -367,11 +346,6 @@ UI_Size ui_em(f32 value, f32 strictness)
     };
 }
 
-void ui_font_size(f32 size)
-{
-    ui_stack_push(font_size, size);
-}
-
 u8 ui_build_begin(OSWindow os_window, f64 frame_time, OSEventQueue* event_queue)
 {
     ui_state->build_count += 1;
@@ -478,20 +452,21 @@ u8 ui_build_begin(OSWindow os_window, f64 frame_time, OSEventQueue* event_queue)
         // }
 
         auto framebuffer_size = os_window_framebuffer_size_get(os_window);
-        ui_stack_push_next_only(pref_width, ui_pixels(framebuffer_size.width, 1.0f));
-        ui_stack_push_next_only(pref_height, ui_pixels(framebuffer_size.height, 1.0f));
-        ui_stack_push_next_only(child_layout_axis, AXIS2_Y);
+        ui_push_next_only(pref_width, ui_pixels(framebuffer_size.width, 1.0f));
+        ui_push_next_only(pref_height, ui_pixels(framebuffer_size.height, 1.0f));
+        ui_push_next_only(child_layout_axis, AXIS2_Y);
 
         auto* root = ui_widget_make_format((UI_WidgetFlags) {}, "window_root_{u64}", os_window);
         assert(!ui_state->stack_autopops.child_layout_axis);
 
-        ui_stack_push(parent, root);
+        ui_push(parent, root);
 
-        ui_stack_push(font_size, 12);
-        ui_stack_push(text_color, Color4(1, 1, 1, 1));
-        ui_stack_push(background_color, Color4(0, 0, 0, 1));
-        ui_stack_push(pref_width, ui_percentage(1.0, 0.0));
-        ui_stack_push(pref_height, ui_em(1.8, 0.0));
+        ui_push(font_size, 12);
+        ui_push(text_color, Color4(1, 1, 1, 1));
+        ui_push(background_color, Color4(0, 0, 0, 1));
+        ui_push(pref_width, ui_percentage(1.0, 0.0));
+        ui_push(pref_height, ui_percentage(1.0, 0.0));
+        // ui_push(pref_height, ui_em(1.8, 0.0));
     }
 
     return open;
@@ -691,7 +666,7 @@ void ui_build_end()
         }
     }
 
-    ui_stack_pop(parent);
+    ui_pop(parent);
 
     ui_compute_independent_sizes(ui_state->root);
     ui_compute_upward_dependent_sizes(ui_state->root);
@@ -709,6 +684,43 @@ fn RenderRect render_rect(F32Interval2 rect)
     };
 }
 
+STRUCT(WidgetIterator)
+{
+    UI_Widget* next;
+    u32 push_count;
+    u32 pop_count;
+};
+
+#define ui_widget_recurse_depth_first_preorder(widget) ui_widget_recurse_depth_first((widget), offset_of(UI_Widget, next), offset_of(UI_Widget, first))
+#define ui_widget_recurse_depth_first_postorder(widget) ui_widget_recurse_depth_first((widget), offset_of(UI_Widget, previous), offset_of(UI_Widget, last))
+
+WidgetIterator ui_widget_recurse_depth_first(UI_Widget* widget, u64 sibling_offset, u64 child_offset)
+{
+    WidgetIterator it = {};
+    auto* child = member_from_offset(widget, UI_Widget*, child_offset);
+    if (child)
+    {
+        it.next = child;
+        it.push_count += 1;
+    }
+    else
+    {
+        for (UI_Widget* w = widget; w; w = w->parent)
+        {
+            auto* sibling = member_from_offset(w, UI_Widget*, sibling_offset);
+            if (sibling)
+            {
+                it.next = sibling;
+                break;
+            }
+
+            it.pop_count += 1;
+        }
+    }
+
+    return it;
+}
+
 void ui_draw()
 {
     UI_Widget* root = ui_state->root;
@@ -717,9 +729,8 @@ void ui_draw()
     RenderWindow* window = ui_state->render_window;
     Renderer* renderer = ui_state->renderer;
 
-    while (1)
+    while (widget)
     {
-        // print("Widget 0x{u64:x}. {u32} {u32} {u32} {u32}\n", widget, (u32)widget->rect.p0.x, (u32)widget->rect.p0.y, (u32)widget->rect.p1.x, (u32)widget->rect.p1.y);
         if (widget->flags.draw_background)
         {
             window_render_rect(window, (RectDraw) {
@@ -733,26 +744,7 @@ void ui_draw()
             window_render_text(renderer, window, widget->text, widget->text_color, RENDER_FONT_TYPE_PROPORTIONAL, widget->rect.x0, widget->rect.y0);
         }
 
-        if (widget->first)
-        {
-            widget = widget->first;
-        }
-        else if (widget->next)
-        {
-            widget = widget->next;
-        }
-        else if (widget->parent == ui_state->root)
-        {
-            break;
-        }
-        else if (widget->parent)
-        {
-            widget = widget->parent;
-        }
-        else
-        {
-            break;
-        }
+        widget = ui_widget_recurse_depth_first_postorder(widget).next;
     }
 }