/* ** [ ] Choosing a keying strategy from user code ** [ ] Using parents ** [ ] Using file and line ** ** ** */ typedef struct ui_id_t ui_id_t; struct ui_id_t { u64 value; }; typedef struct ui_code_loc_t ui_code_loc_t; struct ui_code_loc_t { char *file; int line; int counter; }; #define UI_CODE_LOC (ui_code_loc_t){.file = __FILE__, .line = __LINE__, .counter = __COUNTER__} typedef enum { ui_axis2_x, ui_axis2_y, ui_axis2_count, } ui_axis2_t; typedef enum { ui_size_kind_null, ui_size_kind_pixels, ui_size_kind_text_content, ui_size_kind_percent_of_parent, ui_size_kind_children_sum, } ui_size_kind_t; typedef struct ui_size_t ui_size_t; struct ui_size_t { ui_size_kind_t kind; f32 value; f32 strictness; }; typedef enum { ui_box_flag_none = 0, } ui_box_flag_t; typedef struct ui_box_t ui_box_t; struct ui_box_t { // recreated every frame in building code ui_box_t *next; ui_box_t *prev; ui_box_t *parent; ui_box_t *first; ui_box_t *last; s8_t string; ui_size_t semantic_size[ui_axis2_count]; b32 grow_axis[ui_axis2_count]; ui_code_loc_t loc; // preserving state ui_id_t id; // important position!: offset(id) used for partial zeroing u64 last_touched_event_id; ui_box_t *hash_next; ui_box_t *hash_prev; // computed by layout system every frame f32 iter_pos[ui_axis2_count]; f32 computed_rel_pos[ui_axis2_count]; f32 computed_size[ui_axis2_count]; r2f32_t rect; // state b32 expanded; }; typedef struct ui_signal_t ui_signal_t; struct ui_signal_t { b8 clicked; b8 double_clicked; b8 right_clicked; b8 pressed; b8 released; b8 dragging; b8 hovering; }; typedef enum { ui_id_strategy_hierarchy, ui_id_strategy_code_loc, } ui_id_strategy_t; typedef struct ui_t ui_t; struct ui_t { ma_arena_t *box_arena; // required to be only used for boxes app_event_t *event; app_frame_t *frame; i32 allocated_boxes; ui_box_t *box_array; // first item on arena ui_box_t *root; ui_box_t *top; ui_box_t *free_first; ui_box_t *hash_first; ui_box_t *hash_last; ui_id_t hot; ui_id_t active; STACK(ui_id_t, 256) id_stack; int indent_stack; ui_id_strategy_t id_strategy; }; gb ui_t *ui = NULL; gb_read_only ui_id_t ui_id_null; gb_read_only ui_box_t ui_box_null; fn b32 ui_null_id(ui_id_t id) { return id.value == 0; } fn b32 ui_null(ui_box_t *box) { return box->id.value == 0; } fn b32 ui_hot(ui_box_t *box) { return !ui_null(box) && box->id.value == ui->hot.value; } fn b32 ui_active(ui_box_t *box) { return !ui_null(box) && box->id.value == ui->active.value; } #define ev_left(ev) ((ev)->mouse_button == app_mouse_button_left) #define ev_left_up(ev) ((ev)->kind == app_event_kind_mouse_up && ev_left(ev)) #define ev_left_down(ev) ((ev)->kind == app_event_kind_mouse_down && ev_left(ev)) fn u64 ui_hash_mix(u64 x, u64 y) { x ^= y; x *= 0xff51afd7ed558ccd; x ^= x >> 32; return x; } s8_t ui_get_display_string(s8_t string) { s8_t result = string; if (s8_seek(result, s8_lit("##"), s8_seek_none, &result.len)) { int a = 10; } return result; } s8_t ui_get_hash_string(s8_t string) { i64 len = 0; if (s8_seek(string, s8_lit("##"), s8_seek_none, &len)) { string = s8_skip(string, len + 2); } return string; } // @todo: we are not building a hierarchy, it's flat ! so this doesn't work!! fn ui_id_t ui_id_from_string(s8_t string) { u64 value = ht_hash_data(ui_get_hash_string(string)); ui_id_t id = {value}; return id; } fn ui_id_t ui_id_from_id_stack(void) { u64 value = 134; for (i32 i = 0; i < ui->id_stack.len; i += 1) { value = ui_hash_mix(value, ui->id_stack.data[i].value); } ui_id_t id = {value}; return id; } fn ui_box_t *ui_find_box(ui_id_t id) { if (id.value == 0) return NULL; for (ui_box_t *it = ui->hash_first; it; it = it->hash_next) { if (it->id.value == id.value) { return it; } } return NULL; } fn ui_box_t *ui_alloc_box(void) { ui_box_t *box = NULL; if (ui->free_first) { SLLS_POP_AND_STORE(ui->free_first, box); } else { box = ma_push_type(ui->box_arena, ui_box_t); ui->allocated_boxes += 1; } return box; } fn void ui_push_box(ui_box_t *box) { box->parent = ui->top; DLLQ_APPEND(ui->top->first, ui->top->last, box); } fn ui_box_t *ui_build_box_from_id(ui_code_loc_t loc, ui_id_t id) { ui_box_t *box = ui_find_box(id); if (box) { memory_zero(box, offsetof(ui_box_t, id)); } else { box = ui_alloc_box(); // @todo: we can't really make sure ids don't repeat can't we? DLLQ_APPEND_EX(ui->hash_first, ui->hash_last, box, hash_next, hash_prev); } box->loc = loc; box->last_touched_event_id = ui->event->id; box->id = id; ui_push_box(box); return box; } fn ui_box_t *ui_build_box_from_string(ui_code_loc_t loc, s8_t string) { ui_id_t id = {32}; ui_id_t string_id = ui_id_from_string(string); if (ui->id_strategy == ui_id_strategy_code_loc) { u64 file_hash = ht_hash_data(s8_from_char(loc.file)); u64 line_hash = ht_hash_data(s8((char *)&loc.line, sizeof(loc.line))); u64 cont_hash = ht_hash_data(s8((char *)&loc.counter, sizeof(loc.counter))); id.value = ui_hash_mix(file_hash, line_hash); id.value = ui_hash_mix(id.value, cont_hash); } else if (ui->id_strategy == ui_id_strategy_hierarchy) { ui_id_t id_from_stack = ui_id_from_id_stack(); id.value = ui_hash_mix(string_id.value, id_from_stack.value); } else_is_invalid; ui_box_t *box = ui_build_box_from_id(loc, id); box->string = ui_get_display_string(string); return box; } fn ui_signal_t ui_signal_from_box(ui_box_t *box) { ui_signal_t result = {0}; app_event_t *ev = ui->event; b32 move = ev->kind == app_event_kind_mouse_move; b32 inside = r2f32_contains(box->rect, ev->mouse_pos); if (ui_active(box)) { if (ev_left_up(ev)) { if (ui_hot(box)) { result.clicked = true; } else { ui->active.value = 0; } } } else if (ui_hot(box) && ev_left_down(ev)) { ui->active = box->id; } if (inside) { ui->hot.value = box->id.value; } else if (!inside && ui_hot(box)) { ui->hot = ui_id_null; } return result; } fn void ui_init(ma_arena_t *arena) { assert(arena != tcx.temp); ui = ma_push_type(arena, ui_t); ui->box_arena = arena; ui->root = ma_push_type(arena, ui_box_t); ui->box_array = ma_push_type(arena, ui_box_t); SLLS_PUSH(ui->free_first, ui->box_array); } fn void ui_begin_build(ui_code_loc_t loc, app_event_t *event) { ui->event = event; for (ui_box_t *box = ui->hash_first, *next = NULL; box; box = next) { next = box->hash_next; if (box->id.value == 0) { DLLQ_REMOVE_EX(ui->hash_first, ui->hash_last, box, hash_next, hash_prev); zero_struct(box); SLLS_PUSH(ui->free_first, box); } } zero_struct(ui->root); ui->top = ui->root; ui->root->loc = UI_CODE_LOC; } fn void ui_end_build(void) { assert(ui->top == ui->root); for (ui_box_t *box = ui->hash_first, *next = NULL; box; box = next) { next = box->hash_next; b32 touched_box_during_update = ui->event->id <= box->last_touched_event_id; if (!touched_box_during_update) { DLLQ_REMOVE_EX(ui->hash_first, ui->hash_last, box, hash_next, hash_prev); zero_struct(box); SLLS_PUSH(ui->free_first, box); } } } fn void ui_begin_frame(app_frame_t *frame) { ui->frame = frame; } fn void ui_push_top(ui_box_t *box) { ui->top = box; } fn ui_box_t *ui_pop_parent(void) { ui_box_t *top = ui->top; ui->top = ui->top->parent; return top; } fn ui_box_t *ui_spacer(ui_code_loc_t loc, char *spacing_char) { ui_box_t *box = ui_build_box_from_id(loc, ui_id_null); v2f32_t spacing = rn_measure_string(&rn_state.main_font, s8_from_char(spacing_char)); box->semantic_size[0] = (ui_size_t){ui_size_kind_pixels, spacing.x, 0.5}; box->semantic_size[1] = (ui_size_t){ui_size_kind_pixels, 10, 0.5}; return box; } fn ui_box_t *ui_push_list_container(ui_code_loc_t loc) { ui_box_t *box = ui_build_box_from_id(loc, ui_id_null); ui_push_top(box); box->semantic_size[0] = (ui_size_t){ui_size_kind_children_sum, 0, 0.5}; box->semantic_size[1] = (ui_size_t){ui_size_kind_children_sum, 0, 0.5}; box->grow_axis[1] = true; return box; } fn void ui_push_indent(void) { ui->indent_stack += 1; } fn void ui_pop_indent(void) { ui->indent_stack -= 1; } fn void ui_set_indented_string(ui_box_t *box, s8_t string) { box->string = s8_printf(tcx.temp, "%.*s%S", ui->indent_stack, " ", string); } fn ui_signal_t ui_push_exp(ui_code_loc_t loc, char *str, ...) { S8_FMT(tcx.temp, str, string); ui_box_t *box = ui_build_box_from_string(loc, string); box->semantic_size[0] = (ui_size_t){ui_size_kind_text_content, 0, 0.5}; box->semantic_size[1] = (ui_size_t){ui_size_kind_text_content, 0, 0.5}; ui_signal_t signal = ui_signal_from_box(box); if (signal.clicked) { box->expanded = !box->expanded; } signal.clicked = box->expanded; if (signal.clicked) { box->string = s8_printf(tcx.temp, "* %S", box->string); // ▼ } else { box->string = s8_printf(tcx.temp, "> %S", box->string); // ► } ui_set_indented_string(box, box->string); if (signal.clicked) { STACK_PUSH(ui->id_stack, box->id); ui_push_indent(); } return signal; } fn void ui_pop_exp(void) { ui_pop_indent(); STACK_POP(ui->id_stack); } fn ui_box_t *ui_label(ui_code_loc_t loc, char *fmt, ...) { S8_FMT(tcx.temp, fmt, string); ui_box_t *box = ui_build_box_from_id(loc, ui_id_null); ui_set_indented_string(box, string); box->semantic_size[0] = (ui_size_t){ui_size_kind_text_content, 0, 0.5}; box->semantic_size[1] = (ui_size_t){ui_size_kind_text_content, 0, 0.5}; return box; } typedef struct ui_preorder_iter_t ui_preorder_iter_t; struct ui_preorder_iter_t { ui_box_t *box; }; fn ui_preorder_iter_t ui_iterate_preorder(ui_box_t *box) { ui_preorder_iter_t iter = {box}; return iter; } fn b32 ui_preorder_iter_is_valid(ui_preorder_iter_t iter) { b32 result = iter.box != NULL; return result; } fn void ui_iter_advance_preorder(ui_preorder_iter_t *iter) { if (iter->box->first) { iter->box = iter->box->first; return; } while (iter->box) { if (iter->box->next) { iter->box = iter->box->next; break; } else { iter->box = iter->box->parent; } } } typedef struct ui_postorder_iter_t ui_postorder_iter_t; struct ui_postorder_iter_t { ui_box_t *box; }; fn ui_postorder_iter_t ui_iterate_postorder(ui_box_t *box) { while (box->first) { box = box->first; } ui_postorder_iter_t iter = {box}; return iter; } fn b32 ui_postorder_iter_is_valid(ui_postorder_iter_t iter) { b32 result = iter.box != NULL; return result; } fn void ui_iter_advance_postorder(ui_postorder_iter_t *iter) { while (iter->box) { if (iter->box->next) { iter->box = iter->box->next; while (iter->box->first) { iter->box = iter->box->first; } break; } else { iter->box = iter->box->parent; break; } } } fn void ui_test_stringify_preorder(sb8_t *sb, ui_box_t *box) { sb8_printf(sb, "%S\n", box->string); for (ui_box_t *it = box->first; it; it = it->next) { ui_test_stringify_preorder(sb, it); } } fn void ui_test_stringify_postorder(sb8_t *sb, ui_box_t *box) { for (ui_box_t *it = box->first; it; it = it->next) { ui_test_stringify_postorder(sb, it); } sb8_printf(sb, "%S\n", box->string); } fn void ui_end_frame(void) { for (app_event_t *ev = ui->frame->first_event; ev; ev = ev->next) { if (ev_left_up(ev)) { ui->active = ui_id_null; } } } fn void ui_draw(void) { rn_font_t *font = &rn_state.main_font; { ma_temp_t scratch = ma_begin_scratch(); sb8_t *sb = sb8_serial_begin(scratch.arena); ui_test_stringify_preorder(sb, ui->root); s8_t recursive_string = sb8_serial_end(scratch.arena, sb); sb = sb8_serial_begin(scratch.arena); for (ui_preorder_iter_t it = ui_iterate_preorder(ui->root); ui_preorder_iter_is_valid(it); ui_iter_advance_preorder(&it)) { sb8_printf(sb, "%S\n", it.box->string); } s8_t iter_string = sb8_serial_end(scratch.arena, sb); assert(s8_equal(recursive_string, iter_string)); ma_end_scratch(scratch); } // compute standalone sizes: (pixels, text_content) for (ui_preorder_iter_t it = ui_iterate_preorder(ui->root); ui_preorder_iter_is_valid(it); ui_iter_advance_preorder(&it)) { ui_box_t *box = it.box; for (i32 i = 0; i < ui_axis2_count; i += 1) { ui_size_t sem = box->semantic_size[i]; if (sem.kind == ui_size_kind_pixels) { box->computed_size[i] = sem.value; } else if (sem.kind == ui_size_kind_text_content) { box->computed_size[i] = rn_measure_string(font, box->string).e[i]; } } } // compute: (percent_of_parent) for (ui_preorder_iter_t it = ui_iterate_preorder(ui->root); ui_preorder_iter_is_valid(it); ui_iter_advance_preorder(&it)) { ui_box_t *box = it.box; ui_box_t *parent = box->parent; for (i32 i = 0; i < ui_axis2_count; i += 1) { ui_size_t sem = box->semantic_size[i]; if (sem.kind == ui_size_kind_percent_of_parent) { assert(sem.value >= 0 && sem.value <= 1.0); assert(parent->semantic_size[i].kind == ui_size_kind_pixels || parent->semantic_size[i].kind == ui_size_kind_text_content); box->computed_size[i] = sem.value * parent->computed_size[i]; } } } // compute: (children_sum) for (ui_postorder_iter_t it = ui_iterate_postorder(ui->root); ui_postorder_iter_is_valid(it); ui_iter_advance_postorder(&it)) { ui_box_t *box = it.box; for (i32 i = 0; i < ui_axis2_count; i += 1) { ui_size_t sem = box->semantic_size[i]; if (sem.kind != ui_size_kind_children_sum) continue; b32 grow_axis = box->grow_axis[i]; for (ui_box_t *child = box->first; child; child = child->next) { assert(child->computed_size[i] != 0.f); if (grow_axis) { box->computed_size[i] += child->computed_size[i]; } else { box->computed_size[i] = MAX(box->computed_size[i], child->computed_size[i]); } } } } // solve violations // compute relative positions for (ui_preorder_iter_t it = ui_iterate_preorder(ui->root); ui_preorder_iter_is_valid(it); ui_iter_advance_preorder(&it)) { ui_box_t *box = it.box; ui_box_t *parent = box->parent; if (ui->root == box) continue; // @todo: how to remove this? for (i32 i = 0; i < ui_axis2_count; i += 1) { f32 *pos = &box->computed_rel_pos[i]; f32 size = box->computed_size[i]; f32 parent_pos = parent->computed_rel_pos[i]; f32 *iter_pos = &parent->iter_pos[i]; *pos = parent_pos + *iter_pos; if (parent->grow_axis[i]) *iter_pos += size; } v2f32_t pos = v2f32(box->computed_rel_pos[0], box->computed_rel_pos[1]); v2f32_t size = v2f32(box->computed_size[0], box->computed_size[1]); box->rect = r2f32_mindim(pos, size); } // actually draw for (ui_preorder_iter_t it = ui_iterate_preorder(ui->root); ui_preorder_iter_is_valid(it); ui_iter_advance_preorder(&it)) { ui_box_t *box = it.box; v4f32_t rect_color = primary_color_global; if (ui_hot(box)) { rect_color = secondary_color_global; } if (ui_active(box)) { rect_color = accent1_color_global; } rn_draw_rect(box->rect, rect_color); rn_draw_string(font, box->rect.min, black_color_global, box->string); } }