From 6f3ffc76ffae366bd7cb5f92aaa40a85674cd0cf Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Tue, 28 Jan 2025 11:08:19 +0100 Subject: [PATCH] horiscroller --- src/ui/ui.c | 211 +++++++++++++++++++++++++++++++++------------------- src/ui/ui.h | 3 +- todo.txt | 8 ++ 3 files changed, 145 insertions(+), 77 deletions(-) diff --git a/src/ui/ui.c b/src/ui/ui.c index 679b4a6..9ebd7dd 100644 --- a/src/ui/ui.c +++ b/src/ui/ui.c @@ -194,6 +194,7 @@ fn ui_box_t *ui_build_box_from_id(ui_code_loc_t loc, ui_box_flags_t flags, ui_id 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(); @@ -201,18 +202,8 @@ fn void ui_box_auto_rect(ui_box_t *box) { if (ui->padding_stack && (axis == ui_axis2_x || axis == ui_axis2_y)) { string_size.e[axis] += ui_top_padding(); } - if (box->flags.dont_compute_rect == false) { - r2f32_t rect = ui_next_rect(ui_top_lop(), &ui->top->rect, string_size); - ui_set_rect(box, rect); - } -} - -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(string); - ui_box_t *box = ui_build_box_from_id(loc, flags, id); - box->string = ui_get_display_string(string); - ui_box_auto_rect(box); - return box; + r2f32_t rect = ui_next_rect(ui_top_lop(), &ui->top->rect, string_size); + ui_set_rect(box, rect); } typedef struct ui_box_params_t ui_box_params_t; @@ -308,8 +299,7 @@ fn void ui_set_to_be_determined(ui_box_t *box, ui_axis2_t axis) { } else_is_invalid; } -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 = rn_measure_string(rn->main_font, string); +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_get_size(rect); v2f32_t rect_string_diff = v2f32_sub(rect_size, string_size); v2f32_t center_pos = v2f32_divs(rect_string_diff, 2); @@ -375,7 +365,7 @@ fn void ui_text_input_draw(ui_box_t *box) { ui_text_input_t *ti = box->text_input; s8_t string = s8(ti->str, ti->len); - v2f32_t pos = ui_aligned_text_pos(box->string_pos_offset, box->text_align, co.rect, string); + 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); @@ -406,7 +396,7 @@ fn void ui_default_draw_box(ui_box_t *box) { 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); + 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); } } @@ -526,7 +516,6 @@ fn ui_signal_t ui__radio_button(ui_code_loc_t loc, i32 *value, i32 value_clicked 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; - // @todo? if (*value == value_clicked) box->background_color = ui_color_table[ui_color_rect_turned_on]; return signal; } @@ -569,26 +558,6 @@ fn void ui_end_expander(void) { ui_pop_id(); } -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->root.full_rect = ui->root.rect = window_rect; - ui->top = &ui->root; - ui->root.loc = loc; -} - fn ui_box_t *ui_get_prev_box(ui_box_t *box, b32 (*match)(ui_box_t *)) { for (ui_box_t *it = box; it;) { if (it->last) { @@ -627,24 +596,30 @@ fn ui_box_t *ui_get_next_box(ui_box_t *box, b32 (*match)(ui_box_t *)) { return box; } -fn b32 ui_match_axis(ui_box_t *box, ui_axis2_t axis) { - ui_axis2_t box_axis = ui_axis_from_lop(box->lop); - b32 result = box_axis == axis; - return result; -} - fn b32 ui_match_keynav(ui_box_t *box) { b32 result = box->flags.keyboard_nav; return result; } -fn b32 ui_match_keynav_xaxis(ui_box_t *box) { - b32 result = box->flags.keyboard_nav && ui_match_axis(box, ui_axis2_x); - return result; -} +fn void ui_begin_build(ui_code_loc_t loc, app_event_t *ev, r2f32_t window_rect) { + ui->event = ev; -fn ui_box_t *ui_get_right_box(ui_box_t *box) { return ui_get_next_box(box, ui_match_keynav_xaxis); } -fn ui_box_t *ui_get_left_box(ui_box_t *box) { return ui_get_prev_box(box, ui_match_keynav_xaxis); } + 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->root.full_rect = ui->root.rect = window_rect; + ui->top = &ui->root; + ui->root.loc = loc; +} fn void ui_end_build(void) { { @@ -980,7 +955,7 @@ fn void ui_end_reversal(void) { gb app_event_t ui_test_event; -gb i32 ui_g_panel = 3; +gb i32 ui_g_panel = 2; 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); @@ -1008,12 +983,14 @@ fn void ui_demo_update(app_frame_t *frame, mt_tweak_t *tweak_table, i32 tweak_co ui->top->rect = r2f32_shrinks(ui->top->rect, ui_em(1)); /////////////////////////////// - // First list panel + // Data panel if (ui_g_panel == 1) { - ui_box_t *scroller_box = ui_box(r2f32_cut_right(&ui->top->rect, 10 * frame->dpr), {.draw_rect = true, .clip_rect = true}); + ui_box_t *scroller_box = ui_box(r2f32_cut_right(&ui->top->rect, 10 * frame->dpr), {.draw_rect = true}); + ui_box_t *bottom_scroller = ui_box(r2f32_cut_bottom(&ui->top->rect, 10 * frame->dpr), {.draw_rect = true}); ui_box_t *item_box = ui_box(r2f32_cut_left(&ui->top->rect, ui_max), {.draw_rect = true, .clip_rect = true}); item_box->rect = r2f32_shrinks(item_box->rect, ui_em(1)); + ui_set_text_align(ui_text_align_left) ui_set_top(item_box) { locl char buff[128]; @@ -1040,8 +1017,7 @@ fn void ui_demo_update(app_frame_t *frame, mt_tweak_t *tweak_table, i32 tweak_co 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; - v2f32_t string_size = rn_measure_string(rn->main_font, signal.box->string); - f32 delta = (signal.drag.x / string_size.x) * size; + f32 delta = (signal.drag.x / signal.box->string_size.x) * size; *n = CLAMP(*n + delta, tweak->min, tweak->max); } } else_is_invalid; @@ -1054,7 +1030,7 @@ fn void ui_demo_update(app_frame_t *frame, mt_tweak_t *tweak_table, i32 tweak_co } locl f32 scroller_value; - defer_block(ui_push_top(scroller_box), ui_pop_top()) { + ui_set_top(scroller_box) { f32 item_count = (f32)item_box->node_count; f32 one_item_y_size = ui_em(1); f32 all_items_size = item_count * one_item_y_size; @@ -1088,10 +1064,50 @@ fn void ui_demo_update(app_frame_t *frame, mt_tweak_t *tweak_table, i32 tweak_co scroller_value -= ev->mouse_wheel_delta.y; } scroller_value = CLAMP(scroller_value, 0, all_items_size); + } + + locl f32 bottom_scroller_value; + ui_set_top(bottom_scroller) { + f32 max_size = 0; for (ui_box_t *it = item_box->first; it; it = it->next) { - ui_offset_box(it, v2f32(0, scroller_value)); + max_size = MAX(max_size, it->string_size.x); } + + f32 scroller_rect_pixels = r2f32_get_size(bottom_scroller->full_rect).x; + f32 scroller_button_size_norm = f32_clamp01(scroller_rect_pixels / max_size); + f32 scrollable_space = (1.f - scroller_button_size_norm); + + f32 scroller_value_norm = bottom_scroller_value / max_size; + f32 scroller_value_n = scroller_value_norm * scrollable_space; + ui_box_t *left_box = ui_box(r2f32_cut_left(&ui->top->rect, scroller_value_n * scroller_rect_pixels), {.draw_rect = true}); + ui_box_t *slider_box = ui_box(r2f32_cut_left(&ui->top->rect, scroller_button_size_norm * scroller_rect_pixels), {.draw_rect = true}); + ui_box_t *down_box = ui_box(r2f32_cut_left(&ui->top->rect, 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); + + f32 coef = (max_size / scroller_rect_pixels) / scrollable_space; + + if (signal.dragging) { + bottom_scroller_value += ev->mouse_delta.x * coef; + } + if (left_box_sig.dragging || right_box_sig.dragging) { + bottom_scroller_value = (ev->mouse_pos.x - left_box->full_rect.min.x) * coef; + bottom_scroller_value -= (r2f32_get_size(slider_box->full_rect).x / 2) * coef; + } + if (ev->kind == app_event_kind_mouse_wheel) { + bottom_scroller_value -= ev->mouse_wheel_delta.x; + } + + bottom_scroller_value = CLAMP(bottom_scroller_value, 0, max_size); + } + + for (ui_box_t *it = item_box->first; it; it = it->next) { + ui_offset_box(it, v2f32(bottom_scroller_value, scroller_value)); } } @@ -1231,16 +1247,17 @@ fn void ui_demo_update(app_frame_t *frame, mt_tweak_t *tweak_table, i32 tweak_co } } - ui_box_t *right_scroller = ui_box(.rect = r2f32_cut_right(&ui->top->rect, 10 * frame->dpr), .flags = {.draw_rect = true, .clip_rect = true}); + ui_box_t *right_scroller = ui_box(.rect = r2f32_cut_right(&ui->top->rect, 10 * frame->dpr), .flags = {.draw_rect = true}); + ui_box_t *bottom_scroller = ui_box(.rect = r2f32_cut_bottom(&ui->top->rect, 10 * frame->dpr), .flags = {.draw_rect = true}); ui_box_t *item_box = ui_box(.rect = r2f32_cut_left(&ui->top->rect, ui_max), .flags = {.draw_rect = true, .clip_rect = true}); - locl f32 scroller_value; + locl f32 right_scroller_value; f32 one_item_y_size = ui_em(1); f32 all_items_size = item_count * one_item_y_size; - f32 item_box_size = r2f32_get_size(item_box->full_rect).y; - f32 visible_item_count = f32_ceil(item_box_size / one_item_y_size); - f32 render_start_count = f32_floor(scroller_value / one_item_y_size); + f32 item_box_pixels = r2f32_get_size(item_box->full_rect).y; + f32 visible_item_count = f32_ceil(item_box_pixels / one_item_y_size); + f32 render_start_count = f32_floor(right_scroller_value / one_item_y_size); i32 istart = (i32)render_start_count; i32 iend = (i32)render_start_count + (i32)visible_item_count; @@ -1255,15 +1272,15 @@ fn void ui_demo_update(app_frame_t *frame, mt_tweak_t *tweak_table, i32 tweak_co } } - defer_block(ui_push_top(right_scroller), ui_pop_top()) { - f32 right_scroller_size = r2f32_get_size(right_scroller->full_rect).y; - f32 scroller_size = CLAMP(item_box_size / (all_items_size + frame->window_size.y), 0, 1.0f); + ui_set_top(right_scroller) { + f32 scroller_rect_pixels = r2f32_get_size(right_scroller->full_rect).y; + f32 scroller_size = f32_clamp01(item_box_pixels / (all_items_size + frame->window_size.y)); f32 scrollable_space = (1 - scroller_size); - f32 scroller_norm = scroller_value / (all_items_size); - f32 scroller_percent = scroller_norm * scrollable_space; + f32 scroller_value_norm = right_scroller_value / (all_items_size); + f32 scroller_value_n = scroller_value_norm * scrollable_space; - ui_box_t *upper_box = ui_box(r2f32_cut_top(&ui->top->rect, scroller_percent * right_scroller_size), {.draw_rect = true}); - ui_box_t *slider_box = ui_box(r2f32_cut_top(&ui->top->rect, scroller_size * right_scroller_size), {.draw_rect = true}); + ui_box_t *upper_box = ui_box(r2f32_cut_top(&ui->top->rect, scroller_value_n * scroller_rect_pixels), {.draw_rect = true}); + ui_box_t *slider_box = ui_box(r2f32_cut_top(&ui->top->rect, scroller_size * scroller_rect_pixels), {.draw_rect = true}); ui_box_t *down_box = ui_box(r2f32_cut_top(&ui->top->rect, 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]; @@ -1272,25 +1289,67 @@ fn void ui_demo_update(app_frame_t *frame, mt_tweak_t *tweak_table, i32 tweak_co ui_signal_t upper_box_signal = ui_signal_from_box(upper_box); ui_signal_t down_box_signal = ui_signal_from_box(down_box); f32 drag = ev->mouse_delta.y; - f32 coef = (all_items_size / item_box_size) / scrollable_space; + f32 coef = (all_items_size / item_box_pixels) / scrollable_space; if (signal.dragging) { - scroller_value += drag * coef; + right_scroller_value += drag * coef; } if (upper_box_signal.dragging || down_box_signal.dragging) { - scroller_value = (ev->mouse_pos.y - upper_box->full_rect.min.y) * coef; - scroller_value -= (r2f32_get_size(slider_box->full_rect).y / 2) * coef; + right_scroller_value = (ev->mouse_pos.y - upper_box->full_rect.min.y) * coef; + right_scroller_value -= (r2f32_get_size(slider_box->full_rect).y / 2) * coef; } if (ev->kind == app_event_kind_mouse_wheel) { - scroller_value -= ev->mouse_wheel_delta.y; + right_scroller_value -= ev->mouse_wheel_delta.y; } - scroller_value = CLAMP(scroller_value, 0, all_items_size); + right_scroller_value = CLAMP(right_scroller_value, 0, all_items_size); + } + locl f32 bottom_scroller_value; + ui_set_top(bottom_scroller) { + f32 max_size = 0; for (ui_box_t *it = item_box->first; it; it = it->next) { - ui_offset_box(it, v2f32(0, scroller_value)); + max_size = MAX(max_size, it->string_size.x); } + + f32 scroller_rect_pixels = r2f32_get_size(bottom_scroller->full_rect).x; + f32 scroller_button_size_norm = f32_clamp01(scroller_rect_pixels / max_size); + f32 scrollable_space = (1.f - scroller_button_size_norm); + + f32 scroller_value_norm = bottom_scroller_value / max_size; + f32 scroller_value_n = scroller_value_norm * scrollable_space; + ui_box_t *left_box = ui_box(r2f32_cut_left(&ui->top->rect, scroller_value_n * scroller_rect_pixels), {.draw_rect = true}); + ui_box_t *slider_box = ui_box(r2f32_cut_left(&ui->top->rect, scroller_button_size_norm * scroller_rect_pixels), {.draw_rect = true}); + ui_box_t *down_box = ui_box(r2f32_cut_left(&ui->top->rect, 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); + + f32 coef = (max_size / scroller_rect_pixels) / scrollable_space; + + if (signal.dragging) { + bottom_scroller_value += ev->mouse_delta.x * coef; + } + if (left_box_sig.dragging || right_box_sig.dragging) { + bottom_scroller_value = (ev->mouse_pos.x - left_box->full_rect.min.x) * coef; + bottom_scroller_value -= (r2f32_get_size(slider_box->full_rect).x / 2) * coef; + } + if (ev->kind == app_event_kind_mouse_wheel) { + bottom_scroller_value -= ev->mouse_wheel_delta.x; + } + + bottom_scroller_value = CLAMP(bottom_scroller_value, 0, max_size); + + } + + for (ui_box_t *it = item_box->first; it; it = it->next) { + ui_offset_box(it, v2f32(bottom_scroller_value, right_scroller_value)); } } + /////////////////////////////// + // Menus panel if (ui_g_panel == 3) { ui_box_t *item_box = ui_box(.rect = r2f32_cut_left(&ui->top->rect, ui_max), .flags = {.draw_rect = true, .clip_rect = true}); ui_set_top(item_box) { @@ -1334,6 +1393,8 @@ fn void ui_demo_update(app_frame_t *frame, mt_tweak_t *tweak_table, i32 tweak_co } + /////////////////////////////// + // Everything lister ui_id_t lister_id = ui_id_from_loc(UILOC); locl b32 lister_open; b32 lister_just_opened = false; diff --git a/src/ui/ui.h b/src/ui/ui.h index 15e2f59..ac50e5a 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -17,7 +17,6 @@ struct ui_box_flags_t { b8 draw_text: 1; b8 clip_rect: 1; - b8 dont_compute_rect: 1; b8 keyboard_nav: 1; }; @@ -76,6 +75,7 @@ struct ui_box_t { ui_code_loc_t loc; s8_t string; + v2f32_t string_size; ui_box_flags_t flags; b8 created_new; @@ -170,4 +170,3 @@ fn void ui_set_rect(ui_box_t *box, r2f32_t rect) { box->rect = box->full_rect = fn ui_id_t ui_id(s8_t string); fn ui_id_t ui_idf(char *str, ...); -fn v2f32_t ui_aligned_text_pos(f32 string_pos_offset, ui_text_align_t text_align, r2f32_t rect, s8_t string); \ No newline at end of file diff --git a/todo.txt b/todo.txt index 54253ae..6f0c6da 100644 --- a/todo.txt +++ b/todo.txt @@ -24,8 +24,16 @@ [ ] ui [ ] table [ ] vertical scroll + [x] working + [ ] we depend on ui_boxes existing to calculate max_size which doesn't play well with efficient horiscroll, canm this be fixed? + [x] horizontal scroll + [x] efficient, on fixed size + [x] non-efficient [ ] context menu + [x] working + [ ] maybe a separate root for context menu? [ ] hover tooltips + [ ] separate root which gets drawn on top of all ui [ ] upper left menu dynamics [x] debug console, lines [x] fix elements getting offset when font large and they are on the edge