From a3e6730e0b7db2d11b5a714a73c5f3ebae36b833 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Fri, 17 Jan 2025 15:23:00 +0100 Subject: [PATCH] id_stack, modifiable ui for struct based on type info! --- src/core/core_type_info.c | 17 ++ src/wasm_app/ui.c | 338 +++++++++++++++++++++++++++++++++----- src/wasm_app/ui.h | 21 ++- 3 files changed, 328 insertions(+), 48 deletions(-) diff --git a/src/core/core_type_info.c b/src/core/core_type_info.c index 08475cd..a9ad22b 100644 --- a/src/core/core_type_info.c +++ b/src/core/core_type_info.c @@ -52,6 +52,23 @@ fn void ti_enum_write_value(void *p, i64 value, type_t *type) { } } +fn i64 ti_enum_next_value(void *p, i64 value, type_t *type) { + assert(type->kind == type_kind_enum); + i32 next_i = 0; + for (i32 i = 0; i < type->count; i += 1) { + type_member_t *it = type->members + i; + if (value == it->value) { + if (i + 1 < type->count) { + next_i = i + 1; + break; + } + } + } + + type_member_t *tm = type->members + next_i; + return tm->value; +} + fn type_member_t *ti_get_member(s8_t name, type_t *type) { for (i32 i = 0; i < type->count; i += 1) { type_member_t *it = type->members + i; diff --git a/src/wasm_app/ui.c b/src/wasm_app/ui.c index a89681b..f43629b 100644 --- a/src/wasm_app/ui.c +++ b/src/wasm_app/ui.c @@ -39,6 +39,33 @@ fn ui_id_t ui_id_from_code_loc(ui_code_loc_t loc) { return result; } +fn u64 ui_hash_from_stack(void) { + u64 result = 34294923; + for (i32 i = 0; i < ui->id_stack.len; i += 1) { + result = hash_mix(result, ui->id_stack.data[i].value); + } + return result; +} + +fn ui_id_t ui_id(s8_t string) { + string = ui_get_hash_string(string); + u64 string_hash = hash_data(string); + u64 stack_hash = ui_hash_from_stack(); + u64 result = hash_mix(string_hash, stack_hash); + return (ui_id_t){result}; +} + +fn ui_id_t ui_idf(char *str, ...) { + S8_FMT(tcx.temp, str, string); + return ui_id(string); +} + +fn void ui_push_id(ui_id_t id) { STACK_PUSH(ui->id_stack, id); } +fn void ui_pop_id(void) { STACK_POP(ui->id_stack); } +fn void ui_push_id_string(s8_t string) { ui_push_id(ui_id(string)); } +fn void ui_push_top(ui_box_t *new_top) { assert(new_top->parent == ui->top); ui->top = new_top; ui_push_id(new_top->id); } +fn void ui_pop_top(void) { ui->top = ui->top->parent; ui_pop_id(); } + fn ui_box_t *ui_alloc_box(void) { ui_box_t *box = NULL; if (ui->free_first) { @@ -94,12 +121,13 @@ fn ui_box_t *ui_build_box_from_id(ui_code_loc_t loc, ui_box_flags_t flags, ui_id box->last_touched_event_id = ui->event->id; box->id = id; box->flags = flags; + box->text_align = ui->pref_text_align; ui_push_box(ui->top, box); return box; } fn ui_box_t *ui_build_box_from_string(ui_code_loc_t loc, ui_box_flags_t flags, s8_t string) { - ui_id_t id = ui_id_from_string(ui_get_hash_string(string)); + ui_id_t id = ui_id(string); ui_box_t *box = ui_build_box_from_id(loc, flags, id); box->string = ui_get_display_string(string); v2f32_t string_size = rn_measure_string(&rn_state.main_font, box->string); @@ -148,7 +176,15 @@ fn ui_signal_t ui_signal_from_box(ui_box_t *box) { #define ui_button(...) ui__button(UILOC, __VA_ARGS__) fn ui_signal_t ui__button(ui_code_loc_t loc, char *str, ...) { S8_FMT(tcx.temp, str, string); - ui_box_t *box = ui_build_box_from_string(loc, (ui_box_flags_t){ .draw_rect = true, .draw_text = true, .fully_center_text = true }, string); + ui_box_t *box = ui_build_box_from_string(loc, (ui_box_flags_t){ .draw_border = true, .draw_rect = true, .draw_text = true }, string); + ui_signal_t signal = ui_signal_from_box(box); + return signal; +} + +#define ui_list_button(...) ui__list_button(UILOC, __VA_ARGS__) +fn ui_signal_t ui__list_button(ui_code_loc_t loc, char *str, ...) { + S8_FMT(tcx.temp, str, string); + ui_box_t *box = ui_build_box_from_string(loc, (ui_box_flags_t){ .draw_text = true }, string); ui_signal_t signal = ui_signal_from_box(box); return signal; } @@ -170,12 +206,16 @@ fn ui_signal_t ui__begin_expander(ui_code_loc_t loc, char *str, ...) { signal.clicked = box->expanded; if ( box->expanded) box->string = s8_printf(tcx.temp, "* %S", box->string); if (!box->expanded) box->string = s8_printf(tcx.temp, "> %S", box->string); - if (box->expanded) r2f32_cut_left(&ui->top->rect, ui_em(0.5f)); + if (box->expanded) { + r2f32_cut_left(&ui->top->rect, ui_em(0.5f)); + ui_push_id(box->id); + } return signal; } fn void ui_end_expander(void) { r2f32_add_left(&ui->top->rect, ui_em(0.5f)); + ui_pop_id(); } fn void ui_begin_build(ui_code_loc_t loc, app_event_t *ev, r2f32_t window_rect) { @@ -215,42 +255,58 @@ fn r2f32_t window_rect_from_frame(app_frame_t *frame) { return window_rect; } -fn void ui_draw(app_frame_t *frame) { - 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; - box->final_rect = box->full_rect; - r2f32_t rect = box->final_rect; +fn void ui__draw_box(app_frame_t *frame, ui_box_t *box) { + box->final_rect = box->full_rect; + r2f32_t rect = box->final_rect; - v4f32_t rect_color = primary_color_global; - v4f32_t text_color = black_color_global; - if (ui_is_hot_box(box)) { - rect_color = secondary_color_global; - text_color = accent2_color_global; - } - if (ui_is_active_box(box)) { - rect_color = accent1_color_global; - text_color = accent1_color_global; - } - - if (box->flags.draw_rect) { - rn_draw_rect(rect, rect_color); - } - if (box->flags.draw_border) { - rn_draw_rect_border(rect, accent2_color_global); - } - if (box->flags.draw_text) { - v2f32_t string_size = rn_measure_string(&rn_state.main_font, box->string); - v2f32_t rect_size = r2f32_get_size(rect); - v2f32_t rect_string_diff = v2f32_sub(rect_size, string_size); - v2f32_t center_pos = v2f32_divs(rect_string_diff, 2); - v2f32_t pos_in_rect = v2f32(0, center_pos.y); - if (box->flags.fully_center_text) { - pos_in_rect = center_pos; - } - v2f32_t pos = v2f32_add(pos_in_rect, rect.min); - rn_draw_string(&rn_state.main_font, pos, text_color, box->string); - } + v4f32_t rect_color = primary_color_global; + v4f32_t text_color = black_color_global; + if (ui_is_hot_box(box)) { + rect_color = secondary_color_global; + text_color = accent2_color_global; } + if (ui_is_active_box(box)) { + rect_color = accent1_color_global; + text_color = accent1_color_global; + } + + if (box->flags.draw_rect) { + rn_draw_rect(rect, rect_color); + } + if (box->flags.draw_border) { + rn_draw_rect_border(rect, accent2_color_global); + } + if (box->flags.draw_text) { + v2f32_t string_size = rn_measure_string(&rn_state.main_font, box->string); + v2f32_t rect_size = r2f32_get_size(rect); + v2f32_t rect_string_diff = v2f32_sub(rect_size, string_size); + v2f32_t center_pos = v2f32_divs(rect_string_diff, 2); + v2f32_t pos_in_rect = v2f32(0, center_pos.y); + if (box->text_align == ui_text_align_center) { + pos_in_rect = center_pos; + } + v2f32_t pos = v2f32_add(pos_in_rect, rect.min); + rn_draw_string(&rn_state.main_font, pos, text_color, box->string); + } + + r2f32_t prev_clip_rect = ui->clip_rect; + if (box->flags.clip_rect) { + ui->clip_rect = box->full_rect; + rn_set_clip(ui->clip_rect); + } + for (ui_box_t *it = box->first; it; it = it->next) { + ui__draw_box(frame, it); + } + if (box->flags.clip_rect) { + ui->clip_rect = prev_clip_rect; + rn_set_clip(ui->clip_rect); + } +} + +fn void ui_draw(app_frame_t *frame) { + ui->clip_rect = window_rect_from_frame(frame); + rn_set_clip(ui->clip_rect); + ui__draw_box(frame, &ui->root); } fn void ui_demo_init(ma_arena_t *arena) { @@ -258,28 +314,224 @@ fn void ui_demo_init(ma_arena_t *arena) { ui->box_arena = ma_push_arena(arena, mib(1)); } +fn void ui_serial_subtype(void *p, type_t *type, s8_t name) { + assert(type->kind != type_kind_invalid); + + switch(type->kind) { + case type_kind_i8: { + i8 *n = (i8 *)p; + if (ui_list_button("%S: %d##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (i8)delta; + } + return; + } break; + case type_kind_i16: { + i16 *n = (i16 *)p; + if (ui_list_button("%S: %d##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (i16)delta; + } + return; + } break; + case type_kind_i32: { + i32 *n = (i32 *)p; + if (ui_list_button("%S: %d##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (i32)delta; + } + return; + } break; + case type_kind_i64: { + i64 *n = (i64 *)p; + if (ui_list_button("%S: %lld##slider%S", name, (long long)*n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (i64)delta; + } + return; + } break; + case type_kind_u8: { + u8 *n = (u8 *)p; + if (ui_list_button("%S: %u##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (u8)delta; + } + return; + } break; + case type_kind_u16: { + u16 *n = (u16 *)p; + if (ui_list_button("%S: %u##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (u16)delta; + } + return; + } break; + case type_kind_u32: { + u32 *n = (u32 *)p; + if (ui_list_button("%S: %u##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (u32)delta; + } + return; + } break; + case type_kind_u64: { + u64 *n = (u64 *)p; + if (ui_list_button("%S: %llu##slider%S", name, (unsigned long long)*n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (u64)delta; + } + return; + } break; + case type_kind_b8: { + b8 *n = (b8 *)p; + if (ui_list_button("%S: %s", name, *n ? "true" : "false").clicked) { + *n = !*n; + } + return; + } break; + case type_kind_b16: { + b16 *n = (b16 *)p; + if (ui_list_button("%S: %s", name, *n ? "true" : "false").clicked) { + *n = !*n; + } + return; + } break; + case type_kind_b32: { + b32 *n = (b32 *)p; + if (ui_list_button("%S: %s", name, *n ? "true" : "false").clicked) { + *n = !*n; + } + return; + } break; + case type_kind_b64: { + b64 *n = (b64 *)p; + if (ui_list_button("%S: %s", name, *n ? "true" : "false").clicked) { + *n = !*n; + } + return; + } break; + case type_kind_f32: { + f32 *n = (f32 *)p; + if (ui_list_button("%S: %f##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += delta; + } + return; + } break; + case type_kind_f64: { + f64 *n = (f64 *)p; + if (ui_list_button("%S: %f##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += delta; + } + return; + } break; + case type_kind_isize: { + isize *n = (isize *)p; + if (ui_list_button("%S: %lld##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (isize)delta; + } + return;; + } break; + case type_kind_usize: { + usize *n = (usize *)p; + if (ui_list_button("%S: %llu##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (usize)delta; + } + return;; + } break; + case type_kind_int: { + int *n = (int *)p; + if (ui_list_button("%S: %d##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (int)delta; + } + return;; + } break; + case type_kind_char: { + char *n = (char *)p; + if (ui_list_button("%S: %c##slider%S", name, *n, name).dragging) { + f32 delta = ui->event->mouse_delta.x; + n[0] += (char)delta; + } + return;; + } break; + } + + if (type->kind == type_kind_pointer) { + ui_label("%S: %S = %llx", name, type->name, *(usize *)p); + return; + } + + if (type->kind == type_kind_enum) { + i64 value = ti_enum_read_value(p, type); + s8_t string_value = ti_enum_value_to_name(value, type); + if (ui_list_button("%S: %S = %S", name, type->name, string_value).clicked) { + i64 new_value = ti_enum_next_value(p, value, type); + ti_enum_write_value(p, new_value, type); + } + return; + } + + if (type->kind == type_kind_array) { + defer_if (ui_begin_expander("%S:", name).clicked, ui_end_expander()) { + for (i32 i = 0; i < type->count; i += 1) { + ma_temp_t scratch = ma_begin_scratch(); + s8_t index_name = s8_printf(scratch.arena, "[%d]", i); + void *tmp = ti_extract_array_idx(p, type, i); + ui_serial_subtype(tmp, type->base, index_name); + ma_end_scratch(scratch); + } + } + return; + } + + if (type->kind == type_kind_struct) { + defer_if (ui_begin_expander("%S:", name).clicked, ui_end_expander()) { + for (i32 i = 0; i < type->count; i += 1) { + type_member_t *tm = type->members + i; + void *tmp = ti_extract_member(p, tm); + ui_serial_subtype(tmp, tm->type, tm->name); + } + } + return; + } else { + ui_label("%S: %S", name, type->name); + } +} + +fn void ui_serial_type(void *p, type_t *type) { + ui_serial_subtype(p, type, type->name); +} + +app_event_t ui_test_event; fn void ui_demo_update(app_frame_t *frame) { for (app_event_t *ev = frame->first_event; ev; ev = ev->next) { ui_begin_build(UILOC, ev, window_rect_from_frame(frame)); ui->top->op = ui_op_idle; - ui_box_t *top_box = ui_boxf((ui_box_flags_t){.draw_rect = true, .draw_border = true}, "top_box"); + ui_box_t *top_box = ui_boxf((ui_box_flags_t){.draw_rect = true, .draw_border = true, .clip_rect = true}, "top_box"); ui_set_rect(top_box, r2f32_cut_top(&ui->top->rect, ui_em(1))); defer_block(ui_push_top(top_box), ui_pop_top()) { top_box->op = ui_op_cut_left; + ui->pref_text_align = ui_text_align_center; ui_button("file"); ui_button("edit"); + ui->pref_text_align = ui_text_align_left; } - ui_box_t *scroller_box = ui_boxf((ui_box_flags_t){.draw_rect = true}, "scroller"); + ui_box_t *scroller_box = ui_boxf((ui_box_flags_t){.draw_rect = true, .clip_rect = true}, "scroller"); ui_set_rect(scroller_box, r2f32_cut_right(&ui->top->rect, ui_em(1))); - ui_box_t *item_box = ui_boxf((ui_box_flags_t){.draw_rect = true}, "item_box"); + ui_box_t *item_box = ui_boxf((ui_box_flags_t){.draw_rect = true, .clip_rect = true}, "item_box"); ui_set_rect(item_box, r2f32_cut_left(&ui->top->rect, ui_max)); // @todo: now actually fill this out with struct data using type info static f32 scroller_value; defer_block(ui_push_top(item_box), ui_pop_top()) { + ui_serial_type(&ui_test_event, type(app_event_t)); defer_if (ui_begin_expander("app_event_t").clicked, ui_end_expander()) { for (int i = 0; i < 10; i += 1) { ui_label("kind: app_event_kind_t##a%d", i); @@ -298,7 +550,7 @@ fn void ui_demo_update(app_frame_t *frame) { } } - + // @todo: fix scroller to match mouse defer_block(ui_push_top(scroller_box), ui_pop_top()) { f32 item_count = (f32)item_box->node_count; f32 all_items_size = item_count * rn_state.main_font.size; @@ -312,7 +564,7 @@ fn void ui_demo_update(app_frame_t *frame) { scroller_box->op = ui_op_idle; r2f32_cut_top(&ui->top->rect, scroller_percent * scroller_box_size); - ui_box_t *box = ui_build_box_from_id(UILOC, (ui_box_flags_t){.draw_border = true, .draw_rect = true}, ui_id_from_string(s8_lit("slider"))); + ui_box_t *box = ui_build_box_from_id(UILOC, (ui_box_flags_t){.draw_border = true, .draw_rect = true}, ui_idf("slider")); ui_set_rect(box, r2f32_cut_top(&ui->top->rect, scroller_size * scroller_box_size)); ui_signal_t signal = ui_signal_from_box(box); if (signal.dragging) { @@ -337,8 +589,8 @@ fn void ui_demo_update(app_frame_t *frame) { } rn_begin(); - rn_draw_stringf(&rn_state.main_font, v2f32(0, frame->window_size.y - rn_state.main_font.size), black_color_global, "boxes: %d", ui->allocated_boxes); ui_draw(frame); + rn_draw_stringf(&rn_state.main_font, v2f32(0, frame->window_size.y - rn_state.main_font.size), black_color_global, "boxes: %d", ui->allocated_boxes); rn_end(frame->window_size, white_color_global); diff --git a/src/wasm_app/ui.h b/src/wasm_app/ui.h index 032026e..ced7ed0 100644 --- a/src/wasm_app/ui.h +++ b/src/wasm_app/ui.h @@ -16,9 +16,14 @@ struct ui_box_flags_t { b8 draw_border: 1; b8 draw_text: 1; - b8 fully_center_text : 1; + b8 clip_rect : 1; }; +typedef enum { + ui_text_align_left, + ui_text_align_center, +} ui_text_align_t; + typedef enum { ui_op_cut_top, ui_op_cut_bottom, @@ -41,6 +46,7 @@ struct ui_box_t { ui_box_flags_t flags; b8 created_new; + ui_text_align_t text_align; ui_op_t op; r2f32_t full_rect; @@ -65,19 +71,26 @@ struct ui_signal_t { typedef struct ui_t ui_t; struct ui_t { - ma_arena_t *box_arena; + // building ui_box_t root; ui_box_t *top; - app_event_t *event; + stack_t(ui_id_t, 32) id_stack; + ui_text_align_t pref_text_align; + // allocation + ma_arena_t *box_arena; i32 allocated_boxes; ui_box_t *free_first; ui_box_t *hash_first; ui_box_t *hash_last; + // interaction ui_id_t hot; ui_id_t active; + + // drawing + r2f32_t clip_rect; }; ui_t *ui; @@ -95,8 +108,6 @@ fn b32 ui_is_active_box(ui_box_t *box) { return !ui_is_null_box(box) && box->id. #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 void ui_push_top(ui_box_t *new_top) { assert(new_top->parent == ui->top); ui->top = new_top; } -fn void ui_pop_top(void) { ui->top = ui->top->parent; } fn void ui_set_rect(ui_box_t *box, r2f32_t rect) { box->rect = box->full_rect = rect; } #define UILOC (ui_code_loc_t){.file = __FILE__, .line = __LINE__, .counter = __COUNTER__}