#if 0 typedef struct ui_id_t ui_id_t; struct ui_id_t { u64 value; }; ui_id_t ui_string_to_id(const char *string) { // FNV HASH (1a?) u8 *data8 = (u8 *)string; u64 hash = (u64)14695981039346656037ULL; for (u64 i = 0; data8[i]; i++) { hash = hash ^ (u64)(data8[i]); hash = hash * (u64)1099511628211ULL; } return (ui_id_t){ .value = hash }; } #define ui_location_id() ui_string_to_id(FILE_AND_LINE) ui_id_t ui_element_pressed; typedef enum { cut_left, cut_right, cut_bottom, cut_top, } cut_t; typedef struct rcut_t rcut_t; struct rcut_t { r2f64_t *rect; cut_t cut; }; rcut_t rcut(r2f64_t *rect, cut_t cut) { return (rcut_t){rect, cut}; } r2f64_t ui_cut(r2f64_t *rect, f64 value, cut_t cut) { if (cut == cut_left) return r2f64_cut_left(rect, value); else if (cut == cut_right) return r2f64_cut_right(rect, value); else if (cut == cut_bottom) return r2f64_cut_bottom(rect, value); else if (cut == cut_top) return r2f64_cut_top(rect, value); else assert(!"invalid codepath"); return (r2f64_t){0}; } r2f64_t ui_cut2(rcut_t rc, f64 x, f64 y) { f64 cut_value = 0; if (rc.cut == cut_left || rc.cut == cut_right) cut_value = x; else cut_value = y; r2f64_t result = ui_cut(rc.rect, cut_value, rc.cut); return result; } typedef struct ui_flags_t ui_flags_t; struct ui_flags_t { b32 draw_text : 1; b32 text_centered : 1; b32 text_y_centered; b32 draw_rect : 1; b32 checkable : 1; b32 draw_checkbox : 1; b32 draw_checked; b32 clickable : 1; b32 slider : 1; }; typedef struct ui_signal_t ui_signal_t; struct ui_signal_t { b32 pressed : 1; b32 overlapping : 1; b32 interacting : 1; }; // TODO(Karol): Don't use inputs as flags! Use only flags. typedef struct ui_input_t ui_input_t; struct ui_input_t { char *title; b32 *value_b32; f64 *value_f64; ui_id_t id; }; v2f64_t style_padding = {50, 10}; ui_signal_t ui_widget(rcut_t rc, ui_input_t input, ui_flags_t flags) { // Calculate rectangles r2f64_t total_rect = {0}; r2f64_t rect = {0}; r2f64_t checkbox_rect = {0}; { v2f64_t size = style_padding; f64 font_height = get_font_height(); size.y += font_height; if (flags.draw_text) { size.x += measure_text(input.title); } total_rect = ui_cut2(rc, size.x, size.y); rect = total_rect; if (flags.draw_checkbox) { checkbox_rect = r2f64_cut_left(&rect, size.y); } } // Solve interactions ui_signal_t result = {0}; { if (r2f64_contains(total_rect, mouse_pos_g)) { result.overlapping = true; if (mouse_button_press_g[app_mouse_button_left]) { ui_element_pressed = input.id; result.pressed = true; } } if (ui_element_pressed.value == input.id.value && mouse_button_down_g[app_mouse_button_left]) { result.interacting = true; } if (flags.checkable && result.pressed) { *input.value_b32 = !*input.value_b32; } if (flags.slider && result.interacting) { f64 mouse_pos = mouse_pos_g.x; v2f64_t rect_size = r2f64_get_size(rect); *input.value_f64 = (mouse_pos - rect.min.x) / rect_size.x; *input.value_f64 = CLAMP(*input.value_f64, 0, 1.0); } } clip(total_rect); // Draw if (flags.draw_rect) { v4f32_t color = primary_color_global; if (flags.clickable) { if (result.overlapping) color = secondary_color_global; if (flags.draw_checked && input.value_b32[0]) color = accent1_color_global; } draw_rect(rect, color); } if (flags.slider) { v2f64_t rect_size = r2f64_get_size(rect); r2f64_t rect_split = r2f64_get_left(&rect, rect_size.x * input.value_f64[0]); draw_rect(rect_split, accent1_color_global); } if (flags.draw_text) { v2f64_t text_pos = rect.min; f64 text_width = measure_text(input.title); f64 font_height = get_font_height(); v2f64_t rect_size = r2f64_get_size(rect); v2f64_t offset_to_center = {(rect_size.x - text_width) / 2, (rect_size.y - font_height) / 2}; if (flags.text_y_centered) { text_pos.y += offset_to_center.y; } if (flags.text_centered) { text_pos = v2f64_add(text_pos, offset_to_center); } draw_text(text_pos, input.title); } if (flags.draw_checkbox) { v4f32_t color = accent1_color_global; if (flags.checkable) { if (input.value_b32[0]) { color = accent2_color_global; } } if ((flags.checkable || flags.clickable) && result.overlapping) { color = v4f32_lerp(color, v4f32(1,1,1,1), 0.25); } draw_rect(checkbox_rect, color); } clip(r2f64(-1000, -1000, 1000000, 1000000)); return result; } b32 ui_button(rcut_t rc, char *title) { ui_signal_t sig = ui_widget(rc, (ui_input_t){.title = title}, (ui_flags_t){.draw_text = true, .text_centered = true, .draw_rect = true, .clickable = true}); return sig.pressed; } b32 ui_checked_button(b32 *value, rcut_t rc, char *title) { ui_widget(rc, (ui_input_t){.title = title, .value_b32 = value}, (ui_flags_t){.draw_rect = true, .draw_text = true, .text_centered = true, .clickable = true, .checkable = true, .draw_checked = true}); return *value; } b32 ui_checkbox(rcut_t rc, b32 *value, char *title) { ui_widget(rc, (ui_input_t){.value_b32 = value, .title = title}, (ui_flags_t){.draw_checkbox = true, .draw_text = true, .text_y_centered = true, .draw_rect = true, .checkable = true}); return *value; } void ui_label(rcut_t rc, char *title) { ui_widget(rc, (ui_input_t){.title = title}, (ui_flags_t){.draw_text = true, .text_centered = true, .draw_rect = true}); } void ui_slider(rcut_t rc, ui_id_t id, f64 *value, char *title) { ui_widget(rc, (ui_input_t){.title = title, .value_f64 = value, .id = id}, (ui_flags_t){.draw_text = true, .text_centered = true, .slider = true, .draw_rect = true, .clickable = true}); } void ui_begin_frame(void) { if (mouse_button_release_g[app_mouse_button_left]) { ui_element_pressed = (ui_id_t){0}; } } void ui_demo(void) { ui_begin_frame(); f64 font_height = get_font_height(); r2f64_t window_rect = r2f64(0, 0, window_size_g.x, window_size_g.y); { draw_rect(window_rect, while_color_global); } static b32 panel_open = true; static b32 cool_rect = false; { r2f64_t top_rect = r2f64_cut_top(&window_rect, font_height*2); draw_rect(top_rect, primary_color_global); if (ui_checked_button(&cool_rect, rcut(&top_rect, cut_left), "file")) { draw_rect(window_rect, v4f32(0.5, 0.1, 0.1, 1.0)); } if (ui_button(rcut(&top_rect, cut_left), "edit")) {} if (ui_button(rcut(&top_rect, cut_left), "view")) {} if (ui_checked_button(&panel_open, rcut(&top_rect, cut_left), "open panel")) { } } if (panel_open) { r2f64_t panel_rect = r2f64_cut_left(&window_rect, measure_text("1234567890")*2); { v4f32_t color = primary_color_global; color.a = 0.3f; draw_rect(panel_rect, color); } static f64 value = 0.2f; ui_slider(rcut(&panel_rect, cut_top), ui_location_id(), &value, "value"); ui_widget(rcut(&panel_rect, cut_top), (ui_input_t){.title = "non centered label"}, (ui_flags_t){.draw_rect = true, .draw_text = true}); static b32 checkbox_memes; ui_checkbox(rcut(&panel_rect, cut_top), &checkbox_memes, "checkbox"); // TODO(Krzosa): Draw arrows / triangles { f64 font_height = get_font_height(); f64 cut_size = font_height + style_padding.y; r2f64_t rect = r2f64_cut_top(&panel_rect, cut_size); static int counter; if (ui_widget(rcut(&rect, cut_left), (ui_input_t){0}, (ui_flags_t){.draw_checkbox = true, .clickable = true}).pressed) { counter -= 1; } char buff[256]; stbsp_snprintf(buff, sizeof(buff), "counter: %d", counter); ui_label(rcut(&rect, cut_left), buff); if (ui_widget(rcut(&rect, cut_left), (ui_input_t){0}, (ui_flags_t){.draw_checkbox = true, .clickable = true}).pressed) { counter += 1; } } } } #endif