2025-01-05 21:54:19 -06:00

811 lines
27 KiB
C

// 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
#pragma once
global_variable UI_State* ui_state = 0;
fn void ui_autopop(UI_State* state)
{
let(stack_end, (u32*)((u8*)&state->stacks + sizeof(state->stacks)));
let(bitset_pointer, (u64*)&state->stack_autopops);
u64 bitset_index = 0;
for (let(stack_pointer, (u32*)&state->stacks); stack_pointer != stack_end; stack_pointer += sizeof(VirtualBuffer(u8)) / sizeof(u32))
{
let(bitset, *bitset_pointer);
let(shift_value, 1 << bitset_index);
let(autopop, (bitset & shift_value) != 0);
let(mask, ~shift_value);
*bitset_pointer = bitset & mask;
let(length_pointer, stack_pointer + (offsetof(VirtualBuffer(u8), length) / sizeof(u32)));
let(current_length, *length_pointer);
assert(!autopop | current_length);
*length_pointer -= autopop;
u64 increment_bitset_element = (bitset_index > 0) & (bitset_index % 64 == 0);
bitset_pointer += increment_bitset_element;
bitset_index = increment_bitset_element ? 0 : bitset_index + 1;
}
}
fn void ui_state_select(UI_State* state)
{
ui_state = state;
}
fn UI_State* ui_state_get()
{
return ui_state;
}
fn Arena* ui_build_arena()
{
let(arena, ui_state->build_arenas[ui_state->build_count % array_length(ui_state->build_arenas)]);
return arena;
}
fn UI_Key ui_key_null()
{
UI_Key key = {};
return key;
}
fn UI_State* ui_state_allocate(Renderer* renderer, RenderWindow* window)
{
Arena* arena = arena_initialize(GB(8), MB(2), MB(2));
UI_State* state = arena_allocate(arena, UI_State, 1);
state->renderer = renderer;
state->render_window = window;
state->arena = arena;
state->widget_table.length = 4096;
state->widget_table.pointer = arena_allocate(arena, UI_WidgetSlot, state->widget_table.length);
for (u64 i = 0; i < array_length(state->build_arenas); i += 1)
{
state->build_arenas[i] = arena_initialize(GB(8), MB(2), MB(2));
}
state->stack_nulls = (UI_StateStackNulls){
.parent = 0,
.child_layout_axis = AXIS2_COUNT,
.pref_width = {},
.pref_height = {},
};
let(stack_end, (u32*)((u8*)&state->stacks + sizeof(state->stacks)));
for (let(stack_pointer, (u32*)&state->stacks); stack_pointer != stack_end; stack_pointer += sizeof(VirtualBuffer(u8)) / sizeof(u32))
{
let(length_pointer, stack_pointer + (offsetof(VirtualBuffer(u8), length) / sizeof(u32)));
assert(*length_pointer == 0);
}
return state;
}
fn u64 ui_widget_index_from_key(UI_Key key)
{
let(length, ui_state->widget_table.length);
assert(is_power_of_two(length));
return key.value & (length - 1);
}
fn String ui_text_from_key_string(String string)
{
String result = string;
String text_end_delimiter = strlit("##");
let(index, string_first_occurrence(string, text_end_delimiter));
if (index < string.length)
{
result.length = index;
}
return result;
}
fn String ui_hash_from_key_string(String string)
{
String result = string;
String hash_start_delimiter = strlit("###");
let(index, string_first_occurrence(string, hash_start_delimiter));
if (index < string.length)
{
result = s_get_slice(u8, string, index, string.length);
}
return result;
}
fn UI_Key ui_key_from_string(UI_Key seed, String string)
{
UI_Key key = ui_key_null();
if (string.length)
{
key = seed;
for (u64 i = 0; i < string.length; i += 1)
{
key.value = ((key.value << 5) + key.value) + string.pointer[i];
}
}
return key;
}
fn UI_Key ui_key_from_string_format(UI_Key seed, char* format, ...)
{
u8 buffer[256];
va_list args;
va_start(args, format);
let(string, format_string_va((String)array_to_slice(buffer), format, args));
va_end(args);
let(result, ui_key_from_string(seed, string));
return result;
}
fn u8 ui_key_equal(UI_Key a, UI_Key b)
{
return a.value == b.value;
}
fn UI_Widget* ui_widget_from_key(UI_Key key)
{
UI_Widget* result = 0;
if (!ui_key_equal(key, ui_key_null()))
{
let(index, ui_widget_index_from_key(key));
for (UI_Widget* widget = ui_state->widget_table.pointer[index].first; widget; widget = widget->hash_next)
{
if (ui_key_equal(widget->key, key))
{
result = widget;
break;
}
}
}
return result;
}
fn UI_Widget* ui_widget_make_from_key(UI_WidgetFlags flags, UI_Key key)
{
let(widget, ui_widget_from_key(key));
static let(count, 0);
count += 1;
if (widget)
{
if (widget->last_build_touched == ui_state->build_count)
{
key = ui_key_null();
widget = 0;
}
}
u8 first_frame = 0;
if (!widget)
{
let(index, ui_widget_index_from_key(key));
first_frame = 1;
widget = arena_allocate(ui_state->arena, UI_Widget, 1);
let(table_widget_slot, &ui_state->widget_table.pointer[index]);
if (!table_widget_slot->last)
{
table_widget_slot->first = widget;
table_widget_slot->last = widget;
}
else
{
table_widget_slot->last->hash_next = widget;
widget->hash_previous = table_widget_slot->last;
table_widget_slot->last = widget;
}
}
let(parent, ui_top(parent));
if (parent)
{
if (!parent->last)
{
parent->last = widget;
parent->first = widget;
}
else
{
let(previous_last, parent->last);
previous_last->next = widget;
widget->previous = previous_last;
parent->last = widget;
}
parent->child_count += 1;
widget->parent = parent;
}
else
{
ui_state->root = widget;
}
widget->key = key;
for (u64 i = 0; i < array_length(widget->background_colors); i += 1)
{
widget->background_colors[i] = ui_top(background_color);
}
widget->text_color = ui_top(text_color);
widget->flags = flags;
widget->first = 0;
widget->last = 0;
widget->last_build_touched = ui_state->build_count;
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);
return widget;
}
fn UI_Widget* ui_widget_make(UI_WidgetFlags flags, String string)
{
// TODO:
let(seed, ui_key_null());
let(hash_string, ui_hash_from_key_string(string));
let(key, ui_key_from_string(seed, hash_string));
let(widget, ui_widget_make_from_key(flags, key));
if (flags.draw_text)
{
widget->text = ui_text_from_key_string(string);
}
return widget;
}
fn UI_Widget* ui_widget_make_format(UI_WidgetFlags flags, const char* format, ...)
{
va_list args;
u8 buffer[4096];
va_start(args, format);
let(string, format_string_va((String)array_to_slice(buffer), format, args));
va_end(args);
let(result, ui_widget_make(flags, string));
return result;
}
fn UI_Signal ui_signal_from_widget(UI_Widget* widget)
{
let(rect, widget->rect);
let(mouse_position, ui_state->mouse_position);
UI_Signal signal = {
.clicked_left =
(widget->flags.mouse_clickable & (ui_state->mouse_button_events[WINDOWING_EVENT_MOUSE_LEFT].action == WINDOWING_EVENT_MOUSE_RELEASE)) &
((mouse_position.x >= rect.x0) & (mouse_position.x <= rect.x1)) &
((mouse_position.y >= rect.y0) & (mouse_position.y <= rect.y1)),
};
return signal;
}
fn void ui_stack_reset(UI_State* state)
{
let(stack_end, (u32*)((u8*)&state->stacks + sizeof(state->stacks)));
for (let(stack_pointer, (u32*)&state->stacks); stack_pointer != stack_end; stack_pointer += sizeof(VirtualBuffer(u8)) / sizeof(u32))
{
let(length_pointer, stack_pointer + (offsetof(VirtualBuffer(u8), length) / sizeof(u32)));
*length_pointer = 0;
}
}
fn UI_Size ui_pixels(u32 width, f32 strictness)
{
return (UI_Size) {
.kind = UI_SIZE_PIXEL_COUNT,
.strictness = strictness,
.value = (f32)width,
};
}
fn UI_Size ui_percentage(f32 percentage, f32 strictness)
{
return (UI_Size) {
.kind = UI_SIZE_PERCENTAGE,
.strictness = strictness,
.value = percentage,
};
}
fn UI_Size ui_em(f32 value, f32 strictness)
{
let(font_size, ui_top(font_size));
assert(font_size);
return (UI_Size) {
.kind = UI_SIZE_PIXEL_COUNT,
.strictness = strictness,
.value = value * font_size,
};
}
fn u8 ui_build_begin(WindowingInstance* window, f64 frame_time, WindowingEventQueue* event_queue)
{
ui_state->build_count += 1;
let(build_arena, ui_build_arena());
arena_reset(build_arena);
ui_state->frame_time = frame_time;
ui_state->window = window;
ui_stack_reset(ui_state);
u8 open = 1;
let(mouse_button_count, 0);
for (u32 generic_event_index = 0; open & (generic_event_index < event_queue->descriptors.length); generic_event_index += 1)
{
let(event_descriptor, event_queue->descriptors.pointer[generic_event_index]);
u32 event_index = event_descriptor.index;
switch (event_descriptor.type)
{
case WINDOWING_EVENT_TYPE_MOUSE_BUTTON:
{
let(button, event_queue->mouse_buttons.pointer[event_index]);
let(previous_button_event, ui_state->mouse_button_events[button.button]);
switch (button.event.action)
{
case WINDOWING_EVENT_MOUSE_RELAX:
unreachable();
case WINDOWING_EVENT_MOUSE_RELEASE:
{
assert(previous_button_event.action == WINDOWING_EVENT_MOUSE_PRESS);
} break;
case WINDOWING_EVENT_MOUSE_PRESS:
{
// TODO: handle properly
assert(previous_button_event.action == WINDOWING_EVENT_MOUSE_RELAX || mouse_button_count);
} break;
case WINDOWING_EVENT_MOUSE_REPEAT:
{
unreachable();
} break;
}
ui_state->mouse_button_events[button.button] = button.event;
mouse_button_count += 1;
} break;
case WINDOWING_EVENT_TYPE_WINDOW_FOCUS:
{
} break;
case WINDOWING_EVENT_TYPE_CURSOR_POSITION:
{
let(mouse_position, event_queue->cursor_positions.pointer[event_index]);
ui_state->mouse_position = (UI_MousePosition) {
.x = mouse_position.x,
.y = mouse_position.y,
};
} break;
case WINDOWING_EVENT_TYPE_CURSOR_ENTER:
{
todo();
} break;
case WINDOWING_EVENT_TYPE_WINDOW_POSITION:
{
// event_queue->window_positions.pointer[event_index];
// todo();
} break;
case WINDOWING_EVENT_TYPE_WINDOW_CLOSE:
{
open = 0;
} break;
}
}
if (open)
{
for (u64 i = 0; i < ui_state->widget_table.length; i += 1)
{
let(widget_table_element, &ui_state->widget_table.pointer[i]);
for (UI_Widget* widget = widget_table_element->first, *next = 0; widget; widget = next)
{
next = widget->hash_next;
if (ui_key_equal(widget->key, ui_key_null()) || widget->last_build_touched + 1 < ui_state->build_count)
{
// Remove from the list
if (widget->hash_previous)
{
widget->hash_previous->hash_next = widget->hash_next;
}
if (widget->hash_next)
{
widget->hash_next->hash_previous = widget->hash_previous;
}
if (widget_table_element->first == widget)
{
widget_table_element->first = widget->hash_next;
}
if (widget_table_element->last == widget)
{
widget_table_element->last = widget->hash_previous;
}
}
}
}
let(framebuffer_size, windowing_get_instance_framebuffer_size(window));
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);
let(root, ui_widget_make_format((UI_WidgetFlags) {}, "window_root_{u64}", window));
assert(!ui_state->stack_autopops.child_layout_axis);
ui_push(parent, root);
ui_push(font_size, 12);
ui_push(text_color, VEC4(0.9, 0.9, 0.02, 1));
ui_push(background_color, VEC4(0.1, 0.1, 0.1, 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;
}
fn void ui_compute_independent_sizes(UI_Widget* widget)
{
for (Axis2 axis = 0; axis < AXIS2_COUNT; axis += 1)
{
let(pref_size, widget->pref_size[axis]);
switch (pref_size.kind)
{
default: break; case UI_SIZE_COUNT: unreachable();
case UI_SIZE_PIXEL_COUNT:
{
#if BB_HAS_NATIVE_FLOAT2
widget->computed_size[axis] = floorf(widget->pref_size[axis].value);
#else
widget->computed_size.v[axis] = floorf(widget->pref_size[axis].value);
#endif
} break;
}
}
for (UI_Widget* child_widget = widget->first; child_widget; child_widget = child_widget->next)
{
ui_compute_independent_sizes(child_widget);
}
}
fn void ui_compute_upward_dependent_sizes(UI_Widget* widget)
{
// TODO: optimize loop out if possible
for (Axis2 axis = 0; axis < AXIS2_COUNT; axis += 1)
{
let(pref_size, widget->pref_size[axis]);
switch (pref_size.kind)
{
default: break; case UI_SIZE_COUNT: unreachable();
case UI_SIZE_PERCENTAGE:
{
for (UI_Widget* ancestor = widget->parent; ancestor; ancestor = ancestor->parent)
{
if (ancestor->pref_size[axis].kind != UI_SIZE_BY_CHILDREN)
{
#if BB_HAS_NATIVE_FLOAT2
widget->computed_size[axis] = floorf(ancestor->computed_size[axis] * widget->pref_size[axis].value);
#else
widget->computed_size.v[axis] = floorf(ancestor->computed_size.v[axis] * widget->pref_size[axis].value);
#endif
break;
}
}
} break;
}
}
for (UI_Widget* child_widget = widget->first; child_widget; child_widget = child_widget->next)
{
ui_compute_upward_dependent_sizes(child_widget);
}
}
fn void ui_compute_downward_dependent_sizes(UI_Widget* widget)
{
for (UI_Widget* child_widget = widget->first; child_widget; child_widget = child_widget->next)
{
ui_compute_downward_dependent_sizes(child_widget);
}
for (Axis2 axis = 0; axis < AXIS2_COUNT; axis += 1)
{
let(pref_size, widget->pref_size[axis]);
switch (pref_size.kind)
{
default: break; case UI_SIZE_COUNT: unreachable();
case UI_SIZE_BY_CHILDREN:
{
todo();
} break;
}
}
}
fn void ui_resolve_conflicts(UI_Widget* widget)
{
for (Axis2 axis = 0; axis < AXIS2_COUNT; axis += 1)
{
#if BB_HAS_NATIVE_FLOAT2
let(available_space, widget->computed_size[axis]);
#else
let(available_space, widget->computed_size.v[axis]);
#endif
f32 taken_space = 0;
f32 total_fixup_budget = 0;
if (!(widget->flags.v & (UI_WIDGET_FLAG_OVERFLOW_X << axis)))
{
for (UI_Widget* child_widget = widget->first; child_widget; child_widget = child_widget->next)
{
if (!(child_widget->flags.v & (UI_WIDGET_FLAG_FLOATING_X << axis)))
{
if (axis == widget->child_layout_axis)
{
#if BB_HAS_NATIVE_FLOAT2
taken_space += child_widget->computed_size[axis];
#else
taken_space += child_widget->computed_size.v[axis];
#endif
}
else
{
#if BB_HAS_NATIVE_FLOAT2
taken_space = MAX(taken_space, child_widget->computed_size[axis]);
#else
taken_space = MAX(taken_space, child_widget->computed_size.v[axis]);
#endif
}
#if BB_HAS_NATIVE_FLOAT2
let(fixup_budget_this_child, child_widget->computed_size[axis] * (1 - child_widget->pref_size[axis].strictness));
#else
let(fixup_budget_this_child, child_widget->computed_size.v[axis] * (1 - child_widget->pref_size[axis].strictness));
#endif
total_fixup_budget += fixup_budget_this_child;
}
}
let(conflict, taken_space - available_space);
if (conflict > 0 && total_fixup_budget > 0)
{
for (UI_Widget* child_widget = widget->first; child_widget; child_widget = child_widget->next)
{
if (!(child_widget->flags.v & (UI_WIDGET_FLAG_FLOATING_X << axis)))
{
#if BB_HAS_NATIVE_FLOAT2
let(fixup_budget_this_child, child_widget->computed_size[axis] * (1 - child_widget->pref_size[axis].strictness));
#else
let(fixup_budget_this_child, child_widget->computed_size.v[axis] * (1 - child_widget->pref_size[axis].strictness));
#endif
f32 fixup_size_this_child = 0;
if (axis == widget->child_layout_axis)
{
fixup_size_this_child = fixup_budget_this_child * (conflict / total_fixup_budget);
}
else
{
#if BB_HAS_NATIVE_FLOAT2
fixup_size_this_child = child_widget->computed_size[axis] - available_space;
#else
fixup_size_this_child = child_widget->computed_size.v[axis] - available_space;
#endif
}
fixup_size_this_child = CLAMP(0, fixup_size_this_child, fixup_budget_this_child);
#if BB_HAS_NATIVE_FLOAT2
child_widget->computed_size[axis] = floorf(child_widget->computed_size[axis] - fixup_size_this_child);
#else
child_widget->computed_size.v[axis] = floorf(child_widget->computed_size.v[axis] - fixup_size_this_child);
#endif
}
}
}
}
if (axis == widget->child_layout_axis)
{
f32 p = 0;
for (UI_Widget* child_widget = widget->first; child_widget; child_widget = child_widget->next)
{
if (!(child_widget->flags.v & (UI_WIDGET_FLAG_FLOATING_X << axis)))
{
#if BB_HAS_NATIVE_FLOAT2
child_widget->computed_relative_position[axis] = p;
p += child_widget->computed_size[axis];
#else
child_widget->computed_relative_position.v[axis] = p;
p += child_widget->computed_size.v[axis];
#endif
}
}
}
else
{
for (UI_Widget* child_widget = widget->first; child_widget; child_widget = child_widget->next)
{
if (!(child_widget->flags.v & (UI_WIDGET_FLAG_FLOATING_X << axis)))
{
#if BB_HAS_NATIVE_FLOAT2
child_widget->computed_relative_position[axis] = 0;
#else
child_widget->computed_relative_position.v[axis] = 0;
#endif
}
}
}
for (UI_Widget* child_widget = widget->first; child_widget; child_widget = child_widget->next)
{
let(last_relative_rect, child_widget->relative_rect);
#if BB_HAS_NATIVE_FLOAT2
child_widget->relative_rect.p0[axis] = child_widget->computed_relative_position[axis];
child_widget->relative_rect.p1[axis] = child_widget->relative_rect.p0[axis] + child_widget->computed_size[axis];
#else
child_widget->relative_rect.p0.v[axis] = child_widget->computed_relative_position.v[axis];
child_widget->relative_rect.p1.v[axis] = child_widget->relative_rect.p0.v[axis] + child_widget->computed_size.v[axis];
#endif
float2 last_corner_01 = { last_relative_rect.x0, last_relative_rect.y1 };
float2 last_corner_10 = { last_relative_rect.x1, last_relative_rect.y0 };
float2 this_corner_01 = { child_widget->relative_rect.x0, child_widget->relative_rect.y1 };
float2 this_corner_10 = { child_widget->relative_rect.x1, child_widget->relative_rect.y0 };
#if BB_HAS_NATIVE_FLOAT2
child_widget->relative_corner_delta[CORNER_00][axis] = child_widget->relative_rect.p0[axis] - last_relative_rect.p0[axis];
child_widget->relative_corner_delta[CORNER_01][axis] = this_corner_01[axis] - last_corner_01[axis];
child_widget->relative_corner_delta[CORNER_10][axis] = this_corner_10[axis] - last_corner_10[axis];
child_widget->relative_corner_delta[CORNER_11][axis] = child_widget->relative_rect.p1[axis] - last_relative_rect.p1[axis];
child_widget->rect.p0[axis] = widget->rect.p0[axis] + child_widget->relative_rect.p0[axis] - widget->view_offset[axis];
child_widget->rect.p1[axis] = child_widget->rect.p0[axis] + child_widget->computed_size[axis];
if (!(child_widget->flags.v & (UI_WIDGET_FLAG_FLOATING_X << axis)))
{
child_widget->rect.p0[axis] = floorf(child_widget->rect.p0[axis]);
child_widget->rect.p1[axis] = floorf(child_widget->rect.p1[axis]);
}
#else
child_widget->relative_corner_delta[CORNER_00].v[axis] = child_widget->relative_rect.p0.v[axis] - last_relative_rect.p0.v[axis];
child_widget->relative_corner_delta[CORNER_01].v[axis] = this_corner_01.v[axis] - last_corner_01.v[axis];
child_widget->relative_corner_delta[CORNER_10].v[axis] = this_corner_10.v[axis] - last_corner_10.v[axis];
child_widget->relative_corner_delta[CORNER_11].v[axis] = child_widget->relative_rect.p1.v[axis] - last_relative_rect.p1.v[axis];
child_widget->rect.p0.v[axis] = widget->rect.p0.v[axis] + child_widget->relative_rect.p0.v[axis] - widget->view_offset.v[axis];
child_widget->rect.p1.v[axis] = child_widget->rect.p0.v[axis] + child_widget->computed_size.v[axis];
if (!(child_widget->flags.v & (UI_WIDGET_FLAG_FLOATING_X << axis)))
{
child_widget->rect.p0.v[axis] = floorf(child_widget->rect.p0.v[axis]);
child_widget->rect.p1.v[axis] = floorf(child_widget->rect.p1.v[axis]);
}
#endif
}
for (UI_Widget* child_widget = widget->first; child_widget; child_widget = child_widget->next)
{
ui_resolve_conflicts(child_widget);
}
}
}
fn void ui_build_end()
{
// Clear release button presses
for (u32 i = 0; i < array_length(ui_state->mouse_button_events); i += 1)
{
let(event, &ui_state->mouse_button_events[i]);
if (event->action == WINDOWING_EVENT_MOUSE_RELEASE)
{
event->action = WINDOWING_EVENT_MOUSE_RELAX;
}
}
ui_pop(parent);
ui_compute_independent_sizes(ui_state->root);
ui_compute_upward_dependent_sizes(ui_state->root);
ui_compute_downward_dependent_sizes(ui_state->root);
ui_resolve_conflicts(ui_state->root);
}
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))
fn WidgetIterator ui_widget_recurse_depth_first(UI_Widget* widget, u64 sibling_offset, u64 child_offset)
{
WidgetIterator it = {};
let(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)
{
let(sibling, member_from_offset(w, UI_Widget*, sibling_offset));
if (sibling)
{
it.next = sibling;
break;
}
it.pop_count += 1;
}
}
return it;
}
fn void ui_draw()
{
UI_Widget* root = ui_state->root;
UI_Widget* widget = root;
RenderWindow* window = ui_state->render_window;
Renderer* renderer = ui_state->renderer;
while (widget)
{
if (widget->flags.draw_background)
{
window_render_rect(window, (RectDraw) {
.colors = { widget->background_colors[0], widget->background_colors[1], widget->background_colors[2], widget->background_colors[3] },
.vertex = widget->rect,
});
}
if (widget->flags.draw_text)
{
window_render_text(renderer, window, widget->text, widget->text_color, RENDER_FONT_TYPE_PROPORTIONAL, widget->rect.x0, widget->rect.y0);
}
widget = ui_widget_recurse_depth_first_postorder(widget).next;
}
}