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; } }