#include "ui.gen.c" #include "buffer16.c" fn ui_caret_t ui_caret_clamp(ui_caret_t c, i32 min, i32 max) { return (ui_caret_t){CLAMP(c.e[0],min,max), CLAMP(c.e[1],min,max), c.ifront}; } fn i32 ui_caret_front(ui_caret_t c) { return c.e[c.ifront]; } fn i32 ui_caret_back(ui_caret_t c) { return c.e[(c.ifront + 1) % 2]; } ui_caret_t ui_caret(i32 front, i32 back) { ui_caret_t result = {0}; if (front >= back) { result.range.min = back; result.range.max = front; result.ifront = 1; } else { result.range.min = front; result.range.max = back; result.ifront = 0; } return result; } ui_caret_t ui_caret_set_back(ui_caret_t caret, i32 back) { i32 front = ui_caret_front(caret); ui_caret_t result = ui_caret(front, back); return result; } ui_caret_t ui_caret_set_front(ui_caret_t caret, i32 front) { i32 back = ui_caret_back(caret); ui_caret_t result = ui_caret(front, back); return result; } fn ui_caret_t ui_carets(i32 x) { return ui_caret(x, x); } fn s8_t ui_tprint_loc(ui_code_loc_t loc) { return s8_printf(tcx->temp, "%s(%d)", loc.file, loc.line); } fn s8_t ui_get_display_string(s8_t string) { s8_t result = string; s8_seek(result, s8("##"), s8_seek_none, &result.len); return result; } fn s8_t ui_get_hash_string(s8_t string) { i64 len = 0; if (s8_seek(string, s8("##"), s8_seek_none, &len)) { string = s8_skip(string, len + 2); } return string; } fn ui_id_t ui_id_from_string(s8_t string) { u64 value = hash_data(string); ui_id_t result = {value}; return result; } fn r2f32_t window_rect_from_frame(app_frame_t *frame) { r2f32_t window_rect = r2f32(0, 0, frame->window_size.x, frame->window_size.y); return window_rect; } fn u64 ui_hash_code_loc(ui_code_loc_t loc) { u64 result = 4242843; u64 file_hash = hash_data(s8_from_char(loc.file)); u64 line_hash = hash_data(s8_struct(loc.line)); u64 cont_hash = hash_data(s8_struct(loc.counter)); result = hash_mix(result, file_hash); result = hash_mix(result, line_hash); result = hash_mix(result, cont_hash); return result; } #define ui_id_here() ui_id_from_loc(UILOC) fn ui_id_t ui_id_from_loc(ui_code_loc_t loc) { u64 id = ui_hash_code_loc(loc); ui_id_t result = {id}; return result; } fn u64 ui_hash_from_stack(void) { u64 result = 34294923; for (ui_id_node_t *it = ui->id_stack; it; it = it->next) { result = hash_mix(result, it->value.value); } return result; } fn b32 ui_id_is_null(ui_id_t id) { return id.value == 0; } 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_string(s8_t string) { ui_push_id(ui_id(string)); } #define ui_set_top_ex(x) defer_block(ui_push_top_ex(x), ui_pop_top_ex()) fn void ui_push_top_ex(ui_box_t *box) { assert(ui->top == ui->top); ui->top = box; } fn ui_box_t *ui_pop_top_ex(void) { ui_box_t *result = ui->top; ui->top = ui->top->parent; return result; } #define ui_set_top(x) defer_block(ui_push_top(x), ui_pop_top()) 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); ui_push_rect(new_top->rect); } fn ui_box_t *ui_pop_top(void) { ui_box_t *result = ui->top; ui->top = ui->top->parent; ui_pop_id(); ui_pop_rect(); return result; } fn r2f32_t *ui_top_rectp(void) { return &ui->rect_stack->value; } fn void ui_set_children_sums(ui_box_t *box) { for (ui_box_t *it = box->first; it; it = it->next) { ui_set_children_sums(it); } if (box->flags.children_sum_x) { f32 min = box->rect.min.x; f32 max = box->rect.max.x; if (box->first) { f32 min_child = MIN(box->first->rect.min.x, box->last->rect.min.x); f32 max_child = MAX(box->first->rect.max.x, box->last->rect.max.x); min = MIN(box->rect.min.x, min_child); max = MAX(box->rect.max.x, max_child); } box->rect.min.x = min; box->rect.max.x = max; } if (box->flags.children_sum_y) { f32 min = box->rect.min.y; f32 max = box->rect.max.y; if (box->first) { f32 min_child = MIN(box->first->rect.min.y, box->last->rect.min.y); f32 max_child = MAX(box->first->rect.max.y, box->last->rect.max.y); min = MIN(box->rect.min.y, min_child); max = MAX(box->rect.max.y, max_child); } box->rect.min.y = min; box->rect.max.y = max; } } fn void ui_clear_appear_t(ui_box_t *box) { box->appear_t = 0; for (ui_box_t *it = box->first; it; it = it->next) { ui_clear_appear_t(it); } } 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 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 void ui_push_box(ui_box_t *parent, ui_box_t *box) { box->parent = parent; DLLQ_APPEND(parent->first, parent->last, box); parent->node_count += 1; } fn ui_axis2_t ui_axis_from_lop(ui_lop_t op) { switch (op) { case ui_lop_cut_top: return ui_axis2_y; break; case ui_lop_cut_bottom: return ui_axis2_y; break; case ui_lop_cut_left: return ui_axis2_x; break; case ui_lop_cut_right: return ui_axis2_x; break; case ui_lop_idle: return ui_axis2_invalid; break; default_is_invalid; } return ui_axis2_invalid; } fn r2f32_t ui_next_rect(ui_lop_t op, r2f32_t *rect, v2f32_t required_size) { switch (op) { case ui_lop_cut_top: return r2f32_cut_top_no_squash(rect, required_size.y); break; case ui_lop_cut_bottom: return r2f32_cut_bottom_no_squash(rect, required_size.y); break; case ui_lop_cut_left: return r2f32_cut_left_no_squash(rect, required_size.x); break; case ui_lop_cut_right: return r2f32_cut_right_no_squash(rect, required_size.x); break; case ui_lop_idle: break; default_is_invalid; } return (r2f32_t){0}; } fn void ui_offset_one_box(ui_box_t *box, v2f32_t offset) { box->rect = r2f32_sub_v2f32(box->rect, offset); } fn void ui_offset_children(ui_box_t *box, v2f32_t offset) { for (ui_box_t *it = box->first; it; it = it->next) { ui_offset_one_box(it, offset); ui_offset_children(it, offset); } } fn void ui_offset_box(ui_box_t *box, v2f32_t offset) { ui_offset_one_box(box, offset); ui_offset_children(box, offset); } fn ui_box_t *ui_build_box_from_id(ui_code_loc_t loc, ui_box_flags_t flags, ui_id_t id) { ui_box_t *box = ui_find_box(id); if (box) { expect (box->last_touched_event_id != ui->event->id) { fatalf("likely ui id collision between: %S and %S", ui_tprint_loc(loc), ui_tprint_loc(box->loc)); } memory_zero(box, offsetof(ui_box_t, id)); } else { box = ui_alloc_box(); box->created_new = true; 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; box->flags = flags; box->text_align = ui_top_text_align(); box->border_thickness = ui_top_border_thickness(); box->string_pos_offset = ui_top_string_pos_offset(); ui_box_fill_with_colors(box); ui_push_box(ui->top, box); return box; } fn void ui_box_auto_rect(ui_box_t *box) { v2f32_t string_size = rn_measure_string(rn->main_font, box->string); box->string_size = string_size; ui_axis2_t axis = ui_axis_from_lop(ui_top_lop()); if (ui->required_size_stack && (axis == ui_axis2_x || axis == ui_axis2_y)) { string_size.e[axis] = ui_top_required_size(); } if (ui->padding_stack && (axis == ui_axis2_x || axis == ui_axis2_y)) { string_size.e[axis] += ui_top_padding(); } box->rect = ui_next_rect(ui_top_lop(), ui_top_rectp(), string_size); } typedef struct ui_box_params_t ui_box_params_t; struct ui_box_params_t { ui_code_loc_t loc; r2f32_t rect; ui_box_flags_t flags; s8_t string; ui_id_t id; b32 null_id; // normally null id is interpreted as no value }; #define ui_box(...) ui__make_box((ui_box_params_t){.loc = UILOC, __VA_ARGS__}) fn ui_box_t *ui__make_box(ui_box_params_t params) { ui_id_t id = params.id; if (ui_id_is_null(id) && params.null_id == false) { if (params.string.len != 0) { id = ui_id(ui_get_hash_string(params.string)); id.value = hash_mix(id.value, ui_hash_from_stack()); } if (ui_id_is_null(id)) { id = ui_id_from_loc(params.loc); id.value = hash_mix(id.value, ui_hash_from_stack()); } } ui_box_t *box = ui_build_box_from_id(params.loc, params.flags, id); box->string = ui_get_display_string(params.string); if (r2f32_is_null(params.rect)) { ui_box_auto_rect(box); } else { box->rect = params.rect; } return box; } fn ui_signal_t ui_signal_from_box(ui_box_t *box) { ui_signal_t result = {box}; app_event_t *ev = ui->event; b32 inside = r2f32_contains(box->final_rect, ev->mouse_pos); if (ui_is_active_box(box)) { result.dragging = true; result.drag = ui->event->mouse_delta; if (ev_left_up(ev)) { if (ui_is_hot_box(box)) { result.clicked = true; } else { ui->active.value = 0; } } } else if (ui_is_hot_box(box) && ev_left_down(ev)) { ui->active = box->id; if (box->flags.keyboard_nav) { ui->focus = box->id; } } if (ui_is_focused_box(box)) { if (ev->kind == app_event_kind_key_down && ev->key == app_key_left) { result.drag.x -= 1; result.dragging = true; result.clicked = true; } else if (ev->kind == app_event_kind_key_down && ev->key == app_key_right) { result.drag.x += 1; result.dragging = true; result.clicked = true; } if (ev->kind == app_event_kind_key_down && ev->key == app_key_enter) { result.clicked = true; } } if (inside) { ui->hot.value = box->id.value; } else if (!inside && ui_is_hot_box(box)) { ui->hot = ui_null_id; } return result; } fn v2f32_t ui_aligned_text_pos(f32 offset, ui_text_align_t text_align, r2f32_t rect, s8_t string, v2f32_t string_size) { v2f32_t rect_size = r2f32_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 (text_align == ui_text_align_center) { pos_in_rect = center_pos; } v2f32_t pos = v2f32_add(pos_in_rect, rect.min); pos.x += offset; return pos; } typedef struct ui_draw_compute_t ui_draw_compute_t; struct ui_draw_compute_t { r2f32_t rect; v4f32_t background_color; v4f32_t border_color; v4f32_t text_color; }; fn r2f32_t ui_get_appear_rect(ui_box_t *box) { r2f32_t result = box->rect; if (box->flags.animate_appear && !ui_id_is_null(box->id)) { v2f32_t size = v2f32_muls(r2f32_size(result), 0.15f); r2f32_t smaller_rect = r2f32_shrink(result, size); f32 appear_t = f32_ease_out_n(f32_clamp01(box->appear_t * 2), 10); result = r2f32_lerp(smaller_rect, result, appear_t); } return result; } fn ui_draw_compute_t ui_draw_compute(ui_box_t *box) { ui_draw_compute_t co = {0}; co.rect = ui_get_appear_rect(box); co.background_color = box->background_color; co.text_color = box->text_color; co.border_color = box->border_color; if (ui_is_active_box(box)) { f32 active_t = f32_ease_out_n(box->active_t, 50.f); active_t = f32_clamp01(active_t); co.background_color = v4f32_lerp(co.background_color, box->bg_active_color, 1.0); // co.text_color = v4f32_lerp(co.text_color, box->text_active_color, active_t); box->flags.draw_rect = true; } else if (ui_is_hot_box(box)) { f32 hot_t = f32_ease_out_n(box->hot_t, 50.f); hot_t = f32_clamp01(hot_t); co.background_color = v4f32_lerp(co.background_color, box->bg_hot_color, hot_t); // co.text_color = v4f32_lerp(co.background_color, box->text_hot_color, hot_t); box->flags.draw_rect = true; } if (ui_is_focused_box(box)) { co.background_color = v4f32_lerp(co.background_color, ui_color_table[ui_color_focused_rect], 0.7f); // co.text_color = v4f32_hsla_to_rgba((v4f32_t){0.1f, 0.6f, 0.7f, 1.0f}); box->flags.draw_rect = true; } return co; } fn void ui_text_input_draw(ui_box_t *box) { ui_draw_compute_t co = ui_draw_compute(box); rn_draw_rect(co.rect, co.background_color); ui_text_input_t *ti = box->text_input; s8_t string = s8_make(ti->str, ti->len); v2f32_t pos = ui_aligned_text_pos(box->string_pos_offset, box->text_align, co.rect, string, rn_measure_string(rn->main_font, string)); rn_draw_string(rn->main_font, pos, co.text_color, string); ti->caret = ui_caret_clamp(ti->caret, 0, (i32)ti->len); { s8_t string_min = s8_make(ti->str, ti->caret.range.min); s8_t string_max = s8_make(ti->str, ti->caret.range.max); v2f32_t size_min = rn_measure_string(rn->main_font, string_min); v2f32_t size_max = rn_measure_string(rn->main_font, string_max); r2f32_t selection_rect = r2f32(co.rect.min.x + size_min.x, co.rect.min.y, co.rect.min.x + size_max.x, co.rect.min.y + ui_em(1)); rn_draw_rect(selection_rect, v4f32(1,1,1,0.5f)); v2f32_t size_front = ti->caret.ifront == 0 ? size_min : size_max; r2f32_t caret_rect = r2f32(co.rect.min.x + size_front.x, co.rect.min.y, co.rect.min.x + size_front.x + 1, co.rect.min.y + ui_em(1)); rn_draw_rect(caret_rect, co.text_color); } // rn_draw_rect_border(co.rect, co.border_color, 1); r2f32_t bottom = r2f32_cut_bottom(&co.rect, box->border_thickness); rn_draw_rect(bottom, co.text_color); } fn void ui_default_draw_box(ui_box_t *box) { ui_draw_compute_t co = ui_draw_compute(box); if (box->flags.draw_rect) { rn_draw_rect(co.rect, co.background_color); } if (box->flags.draw_border) { rn_draw_rect_border(co.rect, co.border_color, box->border_thickness); } if (box->flags.draw_text) { v2f32_t text_pos = ui_aligned_text_pos(box->string_pos_offset, box->text_align, co.rect, box->string, box->string_size); rn_draw_string(rn->main_font, text_pos, co.text_color, box->string); } } fn i32 ui_text_replace(ui_text_input_t *ti, r1i32_t range, s8_t string) { range.min = CLAMP(range.min, 0, (i32)ti->len); range.max = CLAMP(range.max, range.min, (i32)ti->len); assert(range.min >= 0 && range.max <= ti->len); i32 size_to_remove = r1i32_size(range); i32 size_to_add = (i32)string.len; i32 change_size = size_to_add - size_to_remove; if (ti->len + change_size > ti->cap) { i32 rem = ((i32)ti->len + change_size) - ti->cap; size_to_add -= rem; change_size -= rem; assert(change_size >= 0); } u8 *begin_remove = (u8 *)ti->str + range.min; u8 *end_remove = begin_remove + size_to_remove; i32 remain_len = (i32)ti->len - (range.min + size_to_remove); u8 *begin_add = begin_remove; u8 *end_add = begin_add + size_to_add; memory_move(end_add, end_remove, remain_len); memory_copy(begin_add, string.str, size_to_add); ti->len = ti->len + change_size; return change_size; } fn void ui_text_clear(ui_text_input_t *ti) { ti->len = 0; ti->caret = ui_carets(0); } fn_test void ui_test_text_replace(void) { ui_text_input_t ti = {0}; char buff[4] = {0}; ti.str = buff; ti.cap = lengthof(buff); ui_text_replace(&ti, r1i32(0,0), s8("astfpo")); assert(s8_are_equal(ti.string, s8("astf"))); ui_text_replace(&ti, r1i32(0,4), s8("qwer")); assert(s8_are_equal(ti.string, s8("qwer"))); ui_text_replace(&ti, r1i32(1,2), s8("a")); assert(s8_are_equal(ti.string, s8("qaer"))); } #define ui_text_input(ti, ...) ui__text_input(UILOC, ti, (ui_box_flags_t){ .draw_border = true, .draw_rect = true, .draw_text = true, .keyboard_nav = true, __VA_ARGS__ }) fn ui_signal_t ui__text_input(ui_code_loc_t loc, ui_text_input_t *ti, ui_box_flags_t flags) { ui_box_t *box = ui_box(.loc = loc, .string = s8("text_input"), .flags = flags); box->text_input = ti; box->custom_draw = ui_text_input_draw; ui_signal_t signal = ui_signal_from_box(box); b32 sim = flags.sim_even_if_no_focus || ui_is_focused_box(box); if (sim) { app_event_t *ev = ui->event; i32 sel_size = r1i32_size(ti->caret.range); if (ev->kind == app_event_kind_text) { signal.text_changed = true; ui_text_replace(ti, ti->caret.range, ev->text); if (sel_size) { ti->caret = ui_carets(ti->caret.range.min); } else { ti->caret = ui_carets(ti->caret.range.min + 1); } } else if (ev->kind == app_event_kind_key_down) { if (ev->key == app_key_backspace) { signal.text_changed = true; if (ev->ctrl) { if (sel_size) { ui_text_replace(ti, ti->caret.range, s8("")); } else { ui_text_replace(ti, r1i32(0, ti->caret.range.max), s8("")); } } else { if (sel_size) { ui_text_replace(ti, ti->caret.range, s8("")); } else { ui_text_replace(ti, r1i32(ti->caret.e[0] - 1, ti->caret.e[0]), s8_null); ti->caret = ui_carets(ti->caret.range.min - 1); } } } else if (ev->key == app_key_delete) { signal.text_changed = true; if (sel_size) { ui_text_replace(ti, ti->caret.range, s8("")); } else { ui_text_replace(ti, r1i32(ti->caret.e[0], ti->caret.e[0] + 1), s8_null); } } else if (ev->key == app_key_left) { signal.text_changed = true; if (ev->shift) { ti->caret = ui_caret_set_front(ti->caret, ui_caret_front(ti->caret) - 1); } else { ti->caret = ui_carets(ui_caret_front(ti->caret) - 1); } } else if (ev->key == app_key_right) { signal.text_changed = true; if (ev->shift) { ti->caret = ui_caret_set_front(ti->caret, ui_caret_front(ti->caret) + 1); } else { ti->caret = ui_carets(ui_caret_front(ti->caret) + 1); } } else if (ev->key == app_key_enter) { signal.text_commit = true; } } v2f32_t size = rn_measure_string(rn->main_font, s8("_")); v2f32_t pos = v2f32_sub(ev->mouse_pos, box->final_rect.min); i32 p = (i32)f32_round(pos.x / size.x); if (ev->kind == app_event_kind_mouse_down && ev->mouse_button == app_mouse_button_left) { ti->caret = ui_carets(p); } if (signal.dragging) { ti->caret = ui_caret_set_front(ti->caret, p); } ti->caret = ui_caret_clamp(ti->caret, 0, (i32)ti->len); } return signal; } #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_box(.loc = loc, .string = string, .flags = { .draw_border = true, .draw_rect = true, .draw_text = true, .keyboard_nav = true }); ui_signal_t signal = ui_signal_from_box(box); return signal; } #define ui_radio_button(...) ui__radio_button(UILOC, __VA_ARGS__) fn ui_signal_t ui__radio_button(ui_code_loc_t loc, i32 *value, i32 value_clicked, char *str, ...) { S8_FMT(tcx->temp, str, string); ui_box_t *box = ui_box(.loc = loc, .string = string, .flags = { .draw_border = true, .draw_rect = true, .draw_text = true, .keyboard_nav = true }); ui_signal_t signal = ui_signal_from_box(box); if (signal.clicked) *value = value_clicked; if (*value == value_clicked) box->background_color = ui_color_table[ui_color_rect_turned_on]; return signal; } #define ui_label_button(...) ui__label_button(UILOC, __VA_ARGS__) fn ui_signal_t ui__label_button(ui_code_loc_t loc, char *str, ...) { S8_FMT(tcx->temp, str, string); ui_box_t *box = ui_box(.loc = loc, .string = string, .flags = {.draw_text = true, .keyboard_nav = true}); ui_signal_t signal = ui_signal_from_box(box); return signal; } #define ui_label(...) ui__label(UILOC, __VA_ARGS__) fn ui_box_t *ui__label(ui_code_loc_t loc, char *str, ...) { S8_FMT(tcx->temp, str, string); ui_box_t *box = ui_box(.loc = loc, .string = string, .flags = {.draw_text = true}); return box; } fn void ui_tree_table_begin(ui_code_loc_t loc) { ui_box_t *box = ui_box(.loc = loc, .rect = ui_next_rect(ui_top_lop(), ui_top_rectp(), v2f32_null), .flags = { .draw_border = true, .children_sum_y = true, .clip_rect = true, }); ui_push_top(box); } fn void ui_tree_table_end(void) { ui_box_t *box = ui_pop_top(); ui_set_children_sums(box); ui_next_rect(ui_top_lop(), ui_top_rectp(), r2f32_size(box->rect)); } #define ui_tree_table_expandable(...) defer_if (ui_tree_table_push_expandable(UILOC, __VA_ARGS__).clicked, ui_tree_table_pop_expandable()) fn void ui_tree_table_pop_expandable(void) { r2f32_add_left(ui_top_rectp(), ui_dm(1)); // r2f32_cut_right(ui_top_rectp(), ui_dm(0.1f)); // ui_top_rectp()[0] = r2f32_expand(ui_top_rect(), v2f32(ui_dm(1), 0)); ui_tree_table_end(); ui_pop_id(); } fn ui_signal_t ui_tree_table_push_expandable(ui_code_loc_t loc, char *str, ...) { S8_FMT(tcx->temp, str, string); ui_id_t id = ui_id(ui_get_hash_string(string)); ui_push_id(id); ui_tree_table_begin(loc); ui_box_t *box = ui_box(.loc = loc, .string = string, .flags = { .draw_rect = true, .draw_text = true, .keyboard_nav = true}); ui_signal_t button = ui_signal_from_box(box); if (button.box->created_new) button.box->expanded = true; if (button.clicked) button.box->expanded = !button.box->expanded; button.clicked = button.box->expanded; // ui_top_rectp()[0] = r2f32_shrink(ui_top_rect(), v2f32(ui_dm(1), 0)); r2f32_cut_left(ui_top_rectp(), ui_dm(1)); // r2f32_cut_right(ui_top_rectp(), ui_dm(0.1f)); if (button.clicked == false) { ui_tree_table_pop_expandable(); } if ( button.box->expanded) button.box->string = s8_printf(tcx->temp, "* %S", button.box->string); if (!button.box->expanded) button.box->string = s8_printf(tcx->temp, "> %S", button.box->string); return button; } fn ui_box_t *ui_get_next_box_ex(ui_box_t *box) { if (box->first) { return box->first; } if (box->next) { return box->next; } for (ui_box_t *it = box->parent; it; it = it->parent) { if (it->next != NULL) { return it->next; } } return NULL; } fn ui_box_t *ui_get_prev_box_ex(ui_box_t *box) { if (box->last) { return box->last; } if (box->prev) { return box->prev; } for (ui_box_t *it = box->parent; it; it = it->parent) { if (it->prev != NULL) { return it->prev; } } return NULL; } fn ui_box_t *ui_get_next_box(ui_box_t *box, b32 (*match)(ui_box_t *)) { for (ui_box_t *it = box; it;) { it = ui_get_next_box_ex(it); if (it && match(it)) return it; } return box; } fn ui_box_t *ui_get_prev_box(ui_box_t *box, b32 (*match)(ui_box_t *)) { for (ui_box_t *it = box; it;) { it = ui_get_prev_box_ex(it); if (it && match(it)) return it; } return box; } fn b32 ui_match_keynav(ui_box_t *box) { b32 result = box->flags.keyboard_nav; return result; } #define ui_reverse_node_order() defer_block(ui_begin_reversal(), ui_end_reversal()) fn void ui_begin_reversal(void) { ui->reverse_order_ref = ui->top->last; } fn void ui_end_reversal(void) { assert(ui->reverse_order_ref); assert(ui->reverse_order_ref->next); ui_box_t *first = ui->reverse_order_ref->next; ui_box_t *last = ui->top->last; ui->reverse_order_ref = NULL; b32 connected = false; for (ui_box_t *it = first; it; it = it->next) { if (it == last) { connected = true; break; } } assert(connected); ui_box_t *first2 = NULL; ui_box_t *last2 = NULL; for (ui_box_t *it = first, *next = NULL; it; it = next) { next = it->next; DLLQ_REMOVE(it->parent->first, it->parent->last, it); DLLQ_APPEND(first2, last2, it); } for (ui_box_t *it = last2, *prev = NULL; it; it = prev) { prev = it->prev; DLLQ_REMOVE(first2, last2, it); DLLQ_APPEND(it->parent->first, it->parent->last, it); } } 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; ui_signal_t sig = ui_label_button("%S: %d##slider%S", name, *n, name); if (sig.dragging) { n[0] += (i8)sig.drag.x; } return; } break; case type_kind_i16: { i16 *n = (i16 *)p; ui_signal_t sig = ui_label_button("%S: %d##slider%S", name, *n, name); if (sig.dragging) { n[0] += (i16)sig.drag.x; } return; } break; case type_kind_i32: { i32 *n = (i32 *)p; ui_signal_t sig = ui_label_button("%S: %d##slider%S", name, *n, name); if (sig.dragging) { n[0] += (i32)sig.drag.x; } return; } break; case type_kind_i64: { i64 *n = (i64 *)p; ui_signal_t sig = ui_label_button("%S: %lld##slider%S", name, (long long)*n, name); if (sig.dragging) { n[0] += (i64)sig.drag.x; } return; } break; case type_kind_u8: { u8 *n = (u8 *)p; ui_signal_t sig = ui_label_button("%S: %u##slider%S", name, *n, name); if (sig.dragging) { n[0] += (u8)sig.drag.x; } return; } break; case type_kind_u16: { u16 *n = (u16 *)p; ui_signal_t sig = ui_label_button("%S: %u##slider%S", name, *n, name); if (sig.dragging) { n[0] += (u16)sig.drag.x; } return; } break; case type_kind_u32: { u32 *n = (u32 *)p; ui_signal_t sig = ui_label_button("%S: %u##slider%S", name, *n, name); if (sig.dragging) { n[0] += (u32)sig.drag.x; } return; } break; case type_kind_u64: { u64 *n = (u64 *)p; ui_signal_t sig = ui_label_button("%S: %llu##slider%S", name, (unsigned long long)*n, name); if (sig.dragging) { n[0] += (u64)sig.drag.x; } return; } break; case type_kind_b8: { b8 *n = (b8 *)p; ui_signal_t sig = ui_label_button("%S: %s##button%S", name, *n ? "true" : "false", name); if (sig.clicked) { *n = !*n; } return; } break; case type_kind_b16: { b16 *n = (b16 *)p; ui_signal_t sig = ui_label_button("%S: %s##button%S", name, *n ? "true" : "false"); if (sig.clicked) { *n = !*n; } return; } break; case type_kind_b32: { b32 *n = (b32 *)p; ui_signal_t sig = ui_label_button("%S: %s##button%S", name, *n ? "true" : "false"); if (sig.clicked) { *n = !*n; } return; } break; case type_kind_b64: { b64 *n = (b64 *)p; ui_signal_t sig = ui_label_button("%S: %s##button%S", name, *n ? "true" : "false"); if (sig.clicked) { *n = !*n; } return; } break; case type_kind_f32: { f32 *n = (f32 *)p; ui_signal_t sig = ui_label_button("%S: %f##slider%S", name, *n, name); if (sig.dragging) { n[0] += sig.drag.x; } return; } break; case type_kind_f64: { f64 *n = (f64 *)p; ui_signal_t sig = ui_label_button("%S: %f##slider%S", name, *n, name); if (sig.dragging) { n[0] += sig.drag.x; } return; } break; case type_kind_isize: { isize *n = (isize *)p; ui_signal_t sig = ui_label_button("%S: %lld##slider%S", name, *n, name); if (sig.dragging) { n[0] += (isize)sig.drag.x; } return;; } break; case type_kind_usize: { usize *n = (usize *)p; ui_signal_t sig = ui_label_button("%S: %llu##slider%S", name, *n, name); if (sig.dragging) { n[0] += (usize)sig.drag.x; } return;; } break; case type_kind_int: { int *n = (int *)p; ui_signal_t sig = ui_label_button("%S: %d##slider%S", name, *n, name); if (sig.dragging) { n[0] += (int)sig.drag.x; } return;; } break; case type_kind_char: { char *n = (char *)p; ui_signal_t sig = ui_label_button("%S: %c##slider%S", name, *n, name); if (sig.dragging) { n[0] += (char)sig.drag.x; } 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); ui_signal_t sig = ui_label_button("%S: %S = %S##button%S", name, type->name, string_value, name); if (sig.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) { ui_tree_table_expandable("%S:", name) { 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) { ui_tree_table_expandable("%S:", name) { 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(ui_code_loc_t loc, void *p, type_t *type) { ui_set_id(ui_id_from_loc(loc)) ui_serial_subtype(p, type, type->name); } typedef struct ui_scroller_params_t ui_scroller_params_t; struct ui_scroller_params_t { ui_box_t *parent; struct { b8 enabled; f32 *value; f32 max_size; ui_code_loc_t loc; } hori; struct { b8 enabled; f32 *value; // @option1: efficient scroll f32 item_pixels; i32 item_count; // @option2: non efficient f32 max_size; ui_code_loc_t loc; } verti; }; typedef struct ui_scroller_t ui_scroller_t; struct ui_scroller_t { struct { ui_box_t *box; i32 istart; i32 iend; f32 item_box_pixels; } verti; struct { ui_box_t *box; } hori; ui_scroller_params_t p; }; fn void ui_scroller_calc_vertical(ui_scroller_t s) { ui_set_top(s.verti.box) { f32 scroller_rect_pixels = r2f32_size(s.verti.box->rect).y; f32 all_items_size = s.p.verti.max_size; f32 scroller_size = f32_clamp01(s.verti.item_box_pixels / (all_items_size + s.verti.item_box_pixels)); f32 scrollable_space = (1 - scroller_size); f32 scroller_value_norm = s.p.verti.value[0] / (all_items_size); f32 scroller_value_n = scroller_value_norm * scrollable_space; ui_box_t *upper_box = ui_box(r2f32_cut_top(ui_top_rectp(), scroller_value_n * scroller_rect_pixels), {.draw_rect = true}); ui_box_t *slider_box = ui_box(r2f32_cut_top(ui_top_rectp(), scroller_size * scroller_rect_pixels), {.draw_rect = true}); ui_box_t *down_box = ui_box(r2f32_cut_top(ui_top_rectp(), ui_max), {.draw_rect = true}); slider_box->background_color = ui_color_table[ui_color_scroller]; slider_box->bg_hot_color = ui_color_table[ui_color_scroller_hot]; slider_box->bg_active_color = ui_color_table[ui_color_scroller_active]; ui_signal_t signal = ui_signal_from_box(slider_box); ui_signal_t upper_box_signal = ui_signal_from_box(upper_box); ui_signal_t down_box_signal = ui_signal_from_box(down_box); app_event_t *ev = ui->event; f32 drag = ev->mouse_delta.y; f32 coef = f32_safe_ratio0((all_items_size / s.verti.item_box_pixels), scrollable_space); if (signal.dragging) { s.p.verti.value[0] += drag * coef; } if (upper_box_signal.dragging || down_box_signal.dragging) { s.p.verti.value[0] = (ev->mouse_pos.y - upper_box->rect.min.y) * coef; s.p.verti.value[0] -= (r2f32_size(slider_box->rect).y / 2) * coef; } if (ev->kind == app_event_kind_mouse_wheel) { s.p.verti.value[0] -= ev->mouse_wheel_delta.y; } s.p.verti.value[0] = CLAMP(s.p.verti.value[0], 0, all_items_size); } } fn ui_scroller_t ui_begin_scroller(ui_code_loc_t loc, ui_scroller_params_t p) { ui_scroller_t s = {.p = p}; if (p.verti.enabled) { s.verti.box = ui_box(.loc = loc, .rect = r2f32_cut_right(&p.parent->rect, ui_dm(0.5f)), .flags = {.draw_rect = true}); r2f32_cut_bottom(&s.verti.box->rect, ui_dm(0.5f)); s.verti.item_box_pixels = r2f32_size(p.parent->rect).y; if (p.verti.item_count) { s.p.verti.max_size = s.p.verti.item_pixels * s.p.verti.item_count; ui_scroller_calc_vertical(s); f32 visible_item_count = f32_ceil(s.verti.item_box_pixels / p.verti.item_pixels); f32 render_start_count = f32_floor(p.verti.value[0] / p.verti.item_pixels); i32 istart = (i32)render_start_count; i32 iend = (i32)render_start_count + (i32)visible_item_count + 1; s.verti.istart = CLAMP(istart, 0, (i32)p.verti.item_count); s.verti.iend = CLAMP(iend, 0, (i32)p.verti.item_count); } } if (p.hori.enabled) { loc.counter += 10000; s.hori.box = ui_box(.loc = loc, .rect = r2f32_cut_bottom(&p.parent->rect, ui_dm(0.5f)), .flags = {.draw_rect = true}); } return s; } fn void ui_cut_top_scroller_offset(ui_scroller_t s) { r2f32_cut_top_no_squash(ui_top_rectp(), (f32)s.verti.istart * s.p.verti.item_pixels); } fn v2f32_t ui_scroller_get_scroll_value(ui_scroller_t *s) { v2f32_t result = v2f32(s->p.hori.value ? s->p.hori.value[0] : 0, s->p.verti.value ? s->p.verti.value[0] : 0); return result; } fn void ui_end_scroller(ui_scroller_t s) { if (s.p.verti.enabled && s.p.verti.item_count == 0) { ui_scroller_calc_vertical(s); } if (s.p.hori.enabled) { ui_set_top(s.hori.box) { f32 scroller_rect_pixels = r2f32_size(s.hori.box->rect).x; f32 scroller_button_size_norm = f32_clamp01(scroller_rect_pixels / s.p.hori.max_size); f32 scrollable_space = (1.f - scroller_button_size_norm); f32 scroller_value_norm = s.p.hori.value[0] / s.p.hori.max_size; f32 scroller_value_n = scroller_value_norm * scrollable_space; ui_box_t *left_box = ui_box(r2f32_cut_left(ui_top_rectp(), scroller_value_n * scroller_rect_pixels), {.draw_rect = true}); ui_box_t *slider_box = ui_box(r2f32_cut_left(ui_top_rectp(), scroller_button_size_norm * scroller_rect_pixels), {.draw_rect = true}); ui_box_t *down_box = ui_box(r2f32_cut_left(ui_top_rectp(), ui_max), {.draw_rect = true}); slider_box->background_color = ui_color_table[ui_color_scroller]; slider_box->bg_hot_color = ui_color_table[ui_color_scroller_hot]; slider_box->bg_active_color = ui_color_table[ui_color_scroller_active]; ui_signal_t left_box_sig = ui_signal_from_box(left_box); ui_signal_t signal = ui_signal_from_box(slider_box); ui_signal_t right_box_sig = ui_signal_from_box(down_box); app_event_t *ev = ui->event; f32 coef = f32_safe_ratio0((s.p.hori.max_size / scroller_rect_pixels), scrollable_space); if (signal.dragging) { s.p.hori.value[0] += ev->mouse_delta.x * coef; } if (left_box_sig.dragging || right_box_sig.dragging) { s.p.hori.value[0] = (ev->mouse_pos.x - left_box->rect.min.x) * coef; s.p.hori.value[0] -= (r2f32_size(slider_box->rect).x / 2) * coef; } if (ev->kind == app_event_kind_mouse_wheel) { s.p.hori.value[0] -= ev->mouse_wheel_delta.x; } s.p.hori.value[0] = CLAMP(s.p.hori.value[0], 0, s.p.hori.max_size); } } ui_offset_children(s.p.parent, ui_scroller_get_scroll_value(&s)); } /////////////////////////////// // main ui control points fn void ui_begin_build(ui_code_loc_t loc, app_event_t *ev, r2f32_t window_rect) { ui->event = ev; 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); } } ui_push_init_values(); zero_struct(&ui->root); ui->top = &ui->root; ui->root.loc = loc; } fn void ui_end_build(void) { ui_set_children_sums(&ui->root); { app_event_t *ev = ui->event; ui_box_t *focus_box = ui_find_box(ui->focus); if (focus_box == NULL) { ui->focus = ui_get_next_box(&ui->root, ui_match_keynav)->id; } if (ev->kind == app_event_kind_key_down && ev->key == app_key_up) { ui->focus = ui_get_prev_box(focus_box, ui_match_keynav)->id; } else if (ev->kind == app_event_kind_key_down && ev->key == app_key_down) { ui->focus = ui_get_next_box(focus_box, ui_match_keynav)->id; } } assert(ui->top == &ui->root); ui_pop_init_values(); ui_assert_stacks_are_null(); 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__draw_box(app_frame_t *frame, ui_box_t *box) { box->final_rect = r2f32_intersect(box->rect, ui->clip_rect); if (box->custom_draw) { box->custom_draw(box); } else { ui_default_draw_box(box); } r2f32_t prev_clip_rect = ui->clip_rect; if (box->flags.clip_rect) { r2f32_t rect = ui_get_appear_rect(box); ui->clip_rect = r2f32_intersect(ui->clip_rect, 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(void) { ui->clip_rect = window_rect_from_frame(ui->frame); rn_set_clip(ui->clip_rect); ui__draw_box(ui->frame, &ui->root); } fn void ui_begin_frame(app_frame_t *frame) { ui = tcx->ui_ctx; ui->frame = frame; } fn void ui_end_frame(void) { for (ui_box_t *box = ui->hash_first, *next = NULL; box; box = next) { next = box->hash_next; if (box->flags.animate_appear) { box->appear_t += (f32)ui->frame->delta; } else { box->appear_t = 1; } if (ui_is_hot_box(box)) { box->hot_t += (f32)ui->frame->delta; } else { box->hot_t = 0; } if (ui_is_active_box(box)) { box->active_t += (f32)ui->frame->delta; } else { box->active_t = 0; } } for (app_event_t *ev = ui->frame->first_event; ev; ev = ev->next) { if (ev_left_up(ev)) { ui->active = ui_null_id; } } } fn void ui_reload(void) { ui_init_colors(); } fn void ui_init(ma_arena_t *arena) { tcx->ui_ctx = ma_push_type(arena, ui_t); ui = tcx->ui_ctx; ui->box_arena = ma_push_arena(arena, mib(1)); ui_reload(); } fn r2f32_t ui_get_centered_rect(v2f32_t parent_rect_size, v2f32_t size) { v2f32_t max_size = v2f32_sub(parent_rect_size, v2f32(ui_em(2), ui_em(2))); v2f32_t lister_size = v2f32_clamp(size, v2f32_null, max_size); v2f32_t pos = v2f32_divs(v2f32_sub(parent_rect_size, lister_size), 2.0); pos.y *= 0.05f; r2f32_t rect = r2f32_min_dim(pos, lister_size); return rect; } gb i32 ui_g_panel = 1; fn void ui_demo_everything_lister(void) { app_event_t *ev = ui->event; app_frame_t *frame = ui->frame; ui_id_t lister_id = ui_id_from_loc(UILOC); locl b32 lister_open; b32 lister_just_opened = false; if (ev->kind == app_event_kind_key_down && ev->key == app_key_p && ev->ctrl) { lister_open = !lister_open; if (lister_open) lister_just_opened = true; } s8_t cmds[] = { s8("show data tab"), s8("show log tab"), s8("show menus tab"), }; if (lister_open) { r2f32_t rect = ui_get_centered_rect(frame->window_size, v2f32(ui_dm(50), ui_em(25))); ui_box_t *lister = ui_box(.id = lister_id, .rect = rect, .flags = {.animate_appear = true, .draw_rect = true, .draw_border = true, .clip_rect = true}); locl f32 verti_scroller_value; ui_scroller_t scroller = ui_begin_scroller(UILOC, (ui_scroller_params_t){ .parent = lister, .verti = { .enabled = true, .value = &verti_scroller_value, .item_pixels = ui_em(1), .item_count = (i32)lengthof(cmds), }, }); ui_set_top(lister) ui_set_lop(ui_lop_cut_top) { locl char buff[128]; locl ui_text_input_t text_input; text_input.str = buff; text_input.cap = lengthof(buff); ui_signal_t ti_sig = ui_text_input(&text_input, .sim_even_if_no_focus = true); if (lister_just_opened) text_input.len = 0; ma_temp_t scratch = ma_begin_scratch(); s8_t needle = s8_make(text_input.str, text_input.len); fuzzy_pair_t *pairs = s8_fuzzy_rate_array(scratch.arena, needle, cmds, lengthof(cmds)); b32 set_focus = lister_just_opened || ti_sig.text_changed; ui_set_string_pos_offset(ui_dm(1)) { ui_cut_top_scroller_offset(scroller); for (i32 i = scroller.verti.istart; i < scroller.verti.iend; i += 1) { ui_signal_t sig = ui_button(cmds[pairs[i].index].str); if (set_focus) { ui->focus = sig.box->id; set_focus = false; } } } if (ti_sig.text_commit) { fuzzy_pair_t *pair = &pairs[0]; ui_g_panel = (i32)pair->index + 1; ui_text_clear(&text_input); lister_open = false; } ma_end_scratch(scratch); } ui_end_scroller(scroller); } } gb app_event_t ui_test_event; fn void ui_demo_update(app_frame_t *frame, mt_tweak_t *tweak_table, i32 tweak_count) { ui_begin_frame(frame); rn_begin_frame(frame); for (app_event_t *ev = frame->first_event; ev; ev = ev->next) { ui_begin_build(UILOC, ev, window_rect_from_frame(frame)); if (ev->kind == app_event_kind_key_down && ev->key == app_key_f1) { ui_g_panel = 1; } else if (ev->kind == app_event_kind_key_down && ev->key == app_key_f2) { ui_g_panel = 2; } else if (ev->kind == app_event_kind_key_down && ev->key == app_key_f3) { ui_g_panel = 3; } ui_box_t *top_box = ui_box(.null_id = true, .rect = r2f32_cut_top(ui_top_rectp(), ui_em(1.5f)), .flags = {.draw_rect = true, .clip_rect = true}); ui_set_padding(ui_em(3)) ui_set_text_align(ui_text_align_center) ui_set_lop(ui_lop_cut_left) ui_set_top(top_box) { ui_radio_button(&ui_g_panel, 1, "data"); ui_radio_button(&ui_g_panel, 2, "log"); ui_radio_button(&ui_g_panel, 3, "menus"); } ui_top_rectp()[0] = r2f32_shrinks(ui_top_rect(), ui_em(1)); /////////////////////////////// // Data panel if (ui_g_panel == 1) { ui_box_t *item_box = ui_box(r2f32_cut_left(ui_top_rectp(), ui_max), {.draw_rect = true, .clip_rect = true, .animate_appear = true}); static f32 verti_scroller_value; ui_scroller_t scroller = ui_begin_scroller(UILOC, (ui_scroller_params_t){ .parent = item_box, .verti = { .enabled = true, .value = &verti_scroller_value, }, }); ui_set_text_align(ui_text_align_left) ui_set_top(item_box) { ui_top_rectp()[0] = r2f32_shrinks(ui_top_rect(), ui_em(1)); for (i32 i = 0; i < tweak_count; i += 1) { mt_tweak_t *tweak = tweak_table + i; if (s8_starts_with(tweak->name, s8("_"))) { continue; } if (tweak->type->kind == type_kind_b32) { b32 *n = (b32 *)tweak->ptr; if (ui_label_button("%S: %s##slider%S", tweak->name, *n ? "true" : "false", tweak->name).clicked) { *n = !*n; } } else if (tweak->type->kind == type_kind_f32) { f32 *n = (f32 *)tweak->ptr; ui_signal_t signal = ui_label_button("%S: %f##slider%S", tweak->name, *n, tweak->name); if (signal.dragging) { f32 size = tweak->max - tweak->min; f32 delta = (signal.drag.x / signal.box->string_size.x) * size; *n = CLAMP(*n + delta, tweak->min, tweak->max); } } else_is_invalid; } ui_label("allocated boxes: %d", ui->allocated_boxes); ui_serial_type(UILOC, &ui_test_event, type(app_event_t)); ui_serial_type(UILOC, &ui_test_event, type(app_event_t)); ui_serial_type(UILOC, &ui_test_event, type(app_event_t)); ui_serial_type(UILOC, &ui_test_event, type(app_event_t)); ui_serial_type(UILOC, &ui_test_event, type(app_event_t)); ui_serial_type(UILOC, &ui_test_event, type(app_event_t)); ui_serial_type(UILOC, &ui_test_event, type(app_event_t)); r2f32_t scroll_rect = r2f32_fix(ui_top_rect()); scroller.p.verti.max_size = r2f32_size(scroll_rect).y; } ui_end_scroller(scroller); } /////////////////////////////// // Log panel if (ui_g_panel == 2) { char *log_lines[] = { "[27.01.2025 08:55:20] Module loaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll ", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll", "[27.01.2025 08:55:20] Module loaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll ", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll", "[27.01.2025 08:55:20] Module loaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll ", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll", "[27.01.2025 08:55:20] Module loaded: 00007ff9`22db0000 00007ff9`22dfe000 C:/Windows/System32/powrprof.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`22d90000 00007ff9`22da4000 C:/Windows/System32/umpdc.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`22900000 00007ff9`2295e000 C:/Windows/System32/winsta.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`23080000 00007ff9`2309a000 C:/Windows/System32/kernel.appcore.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`0d900000 00007ff9`0da46000 C:/Windows/System32/TextInputFramework.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1eec0000 00007ff9`1efe5000 C:/Windows/System32/CoreMessaging.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1c680000 00007ff9`1c963000 C:/Windows/System32/CoreUIComponents.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`201d0000 00007ff9`20338000 C:/Windows/System32/WinTypes.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`26860000 00007ff9`26908000 C:/Windows/System32/clbcatq.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`06a80000 00007ff9`06add000 C:/Windows/System32/ApplicationTargetedFeatureDatabase.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`16a50000 00007ff9`16c87000 C:/Windows/System32/twinapi.appcore.dll ", "[27.01.2025 08:55:20] Thread exited: handle 0x854 with exit code 0 (0x0)", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1c410000 00007ff9`1c51f000 C:/dev/wasm/build/app_temp_2106011489.dll (symbols loaded)", "opengl message: Buffer detailed info: Buffer object 1 (bound to NONE, usage hint is GL_DYNAMIC_DRAW) will use VIDEO memory as the source for buffer object operations.", "failed to load library: app_temp_1403378047.dll", "failed to load library: app_temp_1765879053.dll", "failed to load library: app_temp_1515670551.dll", "[27.01.2025 08:55:28] Module loaded: 00007ff9`07c70000 00007ff9`07d7d000 C:/dev/wasm/build/app_temp_2418804559.dll ", "[27.01.2025 08:55:28] Module loaded: 00007ff9`002c0000 00007ff9`003cf000 C:/dev/wasm/build/app_temp_2347134081.dll (symbols loaded)", "[27.01.2025 08:55:37] Thread exited: handle 0x8f0 with exit code 0 (0x0)", "[27.01.2025 08:55:37] Thread exited: handle 0x7f0 with exit code 0 (0x0)", "[27.01.2025 08:55:37] Thread exited: handle 0x784 with exit code 0 (0x0)", "[27.01.2025 08:55:37] Thread exited: handle 0x808 with exit code 0 (0x0)", "[27.01.2025 08:55:37] Thread exited: handle 0x8d4 with exit code 0 (0x0)", "[27.01.2025 08:55:37] Thread exited: handle 0x920 with exit code 0 (0x0)", "[27.01.2025 08:55:37] Thread exited: handle 0x8bc with exit code 0 (0x0)", "[27.01.2025 08:55:37] Thread exited: handle 0x86c with exit code 0 (0x0)", "[27.01.2025 08:50:16] Module loaded: 00007ff9`0d900000 00007ff9`0da46000 C:/Windows/System32/TextInputFramework.dll ", "[27.01.2025 08:50:16] Module loaded: 00007ff9`1eec0000 00007ff9`1efe5000 C:/Windows/System32/CoreMessaging.dll ", "[27.01.2025 08:50:16] Module loaded: 00007ff9`1c680000 00007ff9`1c963000 C:/Windows/System32/CoreUIComponents.dll ", "[27.01.2025 08:50:16] Module loaded: 00007ff9`201d0000 00007ff9`20338000 C:/Windows/System32/WinTypes.dll ", "[27.01.2025 08:50:16] Module loaded: 00007ff9`26860000 00007ff9`26908000 C:/Windows/System32/clbcatq.dll ", "[27.01.2025 08:50:16] Module loaded: 00007ff9`06a80000 00007ff9`06add000 C:/Windows/System32/ApplicationTargetedFeatureDatabase.dll ", "[27.01.2025 08:50:16] Module loaded: 00007ff9`16a50000 00007ff9`16c87000 C:/Windows/System32/twinapi.appcore.dll ", "[27.01.2025 08:50:16] Thread exited: handle 0x82c with exit code 0 (0x0)", "[27.01.2025 08:50:16] Module loaded: 00007ff9`1c410000 00007ff9`1c51f000 C:/dev/wasm/build/app_temp_2106011489.dll (symbols loaded)", "opengl message: Buffer detailed info: Buffer object 1 (bound to NONE, usage hint is GL_DYNAMIC_DRAW) will use VIDEO memory as the source for buffer object operations.", "[27.01.2025 08:50:46] Thread exited: handle 0x7d0 with exit code 0 (0x0)", "[27.01.2025 08:51:46] Thread exited: handle 0x8bc with exit code 0 (0x0)", "[27.01.2025 08:51:46] Thread exited: handle 0x900 with exit code 0 (0x0)", "[27.01.2025 08:51:46] Thread exited: handle 0x8cc with exit code 0 (0x0)", "failed to load library: app_temp_1403378047.dll", "failed to load library: app_temp_1765879053.dll", "failed to load library: app_temp_1515670551.dll", "[27.01.2025 08:53:21] Module loaded: 00007ff9`07c70000 00007ff9`07d7d000 C:/dev/wasm/build/app_temp_2418804559.dll ", "[27.01.2025 08:53:21] Module loaded: 00007ff9`002c0000 00007ff9`003cf000 C:/dev/wasm/build/app_temp_2347134081.dll (symbols loaded)", "[27.01.2025 08:53:39] Module loaded: 00007ff8`f7b80000 00007ff8`f7c8e000 C:/dev/wasm/build/app_temp_919248920.dll ", "[27.01.2025 08:53:39] Module loaded: 00007ff8`f6450000 00007ff8`f655f000 C:/dev/wasm/build/app_temp_3200004952.dll (symbols loaded)", "failed to load library: app_temp_1322949950.dll", "[27.01.2025 08:55:14] Module loaded: 00007ff8`f3900000 00007ff8`f3a0d000 C:/dev/wasm/build/app_temp_642754436.dll ", "[27.01.2025 08:55:14] Module loaded: 00007ff8`e0ed0000 00007ff8`e0fdf000 C:/dev/wasm/build/app_temp_4237056459.dll (symbols loaded)", "[27.01.2025 08:55:19] Thread exited: handle 0x904 with exit code 0 (0x0)", "[27.01.2025 08:55:19] Thread exited: handle 0x7d0 with exit code 0 (0x0)", "[27.01.2025 08:55:19] Thread exited: handle 0x878 with exit code 0 (0x0)", "[27.01.2025 08:55:19] Thread exited: handle 0x584 with exit code 0 (0x0)", "[27.01.2025 08:55:19] Thread exited: handle 0x8c0 with exit code 0 (0x0)", "[27.01.2025 08:55:19] Thread exited: handle 0x8c8 with exit code 0 (0x0)", "[27.01.2025 08:55:19] Thread exited: handle 0x8f0 with exit code 0 (0x0)", "[27.01.2025 08:55:19] Process exited: with exit code 0 (0x0)", "[27.01.2025 08:55:19] Debugging new process...[OK] Process ID: 17200", "[27.01.2025 08:55:19] Module loaded: 00007ff6`a9fb0000 00007ff6`aa056000 C:/dev/wasm/build/app_win32.exe (symbols loaded)", "[27.01.2025 08:55:19] Module loaded: 00007ff9`26d60000 00007ff9`26fc3000 C:/Windows/System32/ntdll.dll ", "[27.01.2025 08:55:19] Module loaded: 00007ff9`25c00000 00007ff9`25cc8000 C:/Windows/System32/kernel32.dll ", "[27.01.2025 08:55:19] Module loaded: 00007ff9`24800000 00007ff9`24bb2000 C:/Windows/System32/KernelBase.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`217a0000 00007ff9`217ce000 C:/Windows/System32/dwmapi.dll ", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll", "[27.01.2025 08:55:20] Module loaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll ", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll", "[27.01.2025 08:55:20] Module loaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll ", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll", "[27.01.2025 08:55:20] Module loaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll ", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`26ac0000 00007ff9`26ac8000 C:/Windows/System32/psapi.dll", "[27.01.2025 08:55:20] Module unloaded: 00007ff9`1a760000 00007ff9`1a9a1000 C:/Windows/System32/dbghelp.dll", "[27.01.2025 08:55:20] Module loaded: 00007ff9`22db0000 00007ff9`22dfe000 C:/Windows/System32/powrprof.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`22d90000 00007ff9`22da4000 C:/Windows/System32/umpdc.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`22900000 00007ff9`2295e000 C:/Windows/System32/winsta.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`23080000 00007ff9`2309a000 C:/Windows/System32/kernel.appcore.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`0d900000 00007ff9`0da46000 C:/Windows/System32/TextInputFramework.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1eec0000 00007ff9`1efe5000 C:/Windows/System32/CoreMessaging.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1c680000 00007ff9`1c963000 C:/Windows/System32/CoreUIComponents.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`201d0000 00007ff9`20338000 C:/Windows/System32/WinTypes.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`26860000 00007ff9`26908000 C:/Windows/System32/clbcatq.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`06a80000 00007ff9`06add000 C:/Windows/System32/ApplicationTargetedFeatureDatabase.dll ", "[27.01.2025 08:55:20] Module loaded: 00007ff9`16a50000 00007ff9`16c87000 C:/Windows/System32/twinapi.appcore.dll ", "[27.01.2025 08:55:20] Thread exited: handle 0x854 with exit code 0 (0x0)", "[27.01.2025 08:55:20] Module loaded: 00007ff9`1c410000 00007ff9`1c51f000 C:/dev/wasm/build/app_temp_2106011489.dll (symbols loaded)", "opengl message: Buffer detailed info: Buffer object 1 (bound to NONE, usage hint is GL_DYNAMIC_DRAW) will use VIDEO memory as the source for buffer object operations.", "failed to load library: app_temp_1403378047.dll", "failed to load library: app_temp_1765879053.dll", "failed to load library: app_temp_1515670551.dll", }; locl f32 item_count; if (item_count == 0) { item_count = lengthof(log_lines) * 2; } ui_set_padding(ui_em(3)) ui_set_text_align(ui_text_align_center) ui_set_lop(ui_lop_cut_right) ui_set_top(top_box) ui_reverse_node_order() { if (ui_button("C").clicked) { item_count = lengthof(log_lines); } if (ui_button("B").clicked) { item_count = lengthof(log_lines) * 4; } if (ui_button("A").clicked) { item_count = 4; } } ui_box_t *item_box = ui_box(.rect = r2f32_cut_left(ui_top_rectp(), ui_max), .flags = {.draw_rect = true, .clip_rect = true, .animate_appear = true}); locl f32 right_scroller_value; locl f32 bottom_scroller_value; ui_scroller_t scroller = ui_begin_scroller(UILOC, (ui_scroller_params_t){ .parent = item_box, .verti = { .enabled = true, .value = &right_scroller_value, .item_pixels = ui_em(1), .item_count = (i32)item_count }, .hori = { .enabled = true, .value = &bottom_scroller_value, .max_size = 2000 }, }); ui_set_top(item_box) { ui_cut_top_scroller_offset(scroller); for (i32 i = scroller.verti.istart; i < scroller.verti.iend; i += 1) { char *it = log_lines[i % lengthof(log_lines)]; ui_label("%s##log_line%d", it, i); } } ui_end_scroller(scroller); } /////////////////////////////// // Menus panel if (ui_g_panel == 3) { ui_box_t *item_box = ui_box(.rect = r2f32_cut_left(ui_top_rectp(), ui_max), .flags = {.draw_rect = true, .clip_rect = true, .animate_appear = true}); ui_set_top(item_box) { ui_label("right click to bring up the context menu!"); } /////////////////////////////// // context menu { locl b32 context_menu_open; locl v2f32_t menu_pos; if (ev_right_down(ev)) { context_menu_open = 1; menu_pos = ev->mouse_pos; } if (context_menu_open) { ui_box_t *menu = ui_box(.rect = r2f32_min_dim(menu_pos, v2f32(ui_em(10), ui_children_sum)), .flags = {.draw_border = true, .draw_rect = true, .children_sum_y = true, .animate_appear = true}); ui_set_top(menu) ui_set_text_align(ui_text_align_left) ui_set_string_pos_offset(ui_em(1)) ui_set_padding(ui_em(0.2f)) { ui_label_button("file"); ui_label_button("memes"); r2f32_cut_left(ui_top_rectp(), ui_em(1)); ui_box_t *a = ui_box(.flags = {.draw_border = true, .draw_rect = true, .children_sum_y = true, .animate_appear = true}); ui_set_top(a) { ui_label_button("a"); ui_label_button("memes"); } } if (ev_left_down(ev) && !r2f32_contains(menu->final_rect, ev->mouse_pos)) { context_menu_open = 0; } if (ev_right_down(ev)) { ui_clear_appear_t(menu); } } } } ui_demo_everything_lister(); ui_end_build(); } rn_begin(white_color); ui_draw(); rn_end(); ui_end_frame(); }