#include "gui.h" #include "../render/2d.h" #include "../render/render.h" #include "../lib/math.h" #include "../lib/color.h" #include "text_draw.h" #include "../debug/logger.h" #include "stdio.h" #include "string.h" #include "../platform.h" #include "../lib/hashing.h" Gui_State global_gui_state; void gui_button_draw_inner_text(Gui_Context *ctx, Rect r, const char *text, v4 color, Rect *actual_drawn_rect = NULL); bool gui_init() { gui_context_init(&global_gui_state.default_context); global_gui_state.selected_context = &global_gui_state.default_context; bool success = gui_text_draw_init(); return success; } void gui_deinit() { gui_text_draw_deinit(); } void gui_context_init(Gui_Context *ctx) { ctx->width = 100; ctx->height = 100; ctx-> last_frame_time = 0; ctx->current_frame_time = 0; ctx-> active = NULL; ctx-> hot = NULL; ctx->possibly_hot = NULL; ctx->active_start_time = 0; ctx-> hot_start_time = 0; ctx->active_status = 0; ctx->text_cursor_position = 0; ctx->text_length = 0; ctx->windows = NULL; ctx->window_count = 0; ctx->window_capacity = 0; ctx->current_window = NULL; ctx->id_count = 0; ctx->id_capacity = 8; ctx->id_stack = (Gui_Id*)p_alloc(sizeof(Gui_Id) * ctx->id_capacity); ctx->input.pointer_position = {0, 0}; ctx->input.absolute_pointer_position = {0, 0}; ctx->input.mouse_pressed = false; ctx->input.mouse_pressed_this_frame = false; ctx->input.mouse_released_this_frame = false; ctx->input.text_cursor_move = 0; ctx->input.text[0] = '\0'; ctx->input.absolute_pointer_position_last_frame = {0, 0}; ctx->style.font_size = 12; ctx->style.animation_base_time = 0.100; ctx->style.text_color = v4{1.0, 1.0, 1.0, 1.0}; ctx->style.text_align = GUI_ALIGN_CENTER; ctx->style.button_color = v4{0.4f, 0.4f, 0.4f, 1.0f}*0.8f; ctx->style.button_color_hovered = v4{0.3f, 0.3f, 0.3f, 1.0f}*0.9f; ctx->style.button_color_pressed = v4{0.1f, 0.1f, 0.1f, 1.0f}; ctx->style.button_text_color = v4{1.0f, 1.0f, 1.0f, 1.0f}; ctx->style.button_text_color_hovered = v4{1.0f, 1.0f, 1.0f, 1.0f}; ctx->style.button_text_color_pressed = v4{1.0f, 1.0f, 1.0f, 1.0f}; ctx->style.button_radius = 3; ctx->style.slider_fill_color = {0.0f, 0.3f, 0.9f, 1.0f}; ctx->style.window_background_color = {0.01,0.01,0.01, 0.98}; ctx->style.window_border_color = {1.0,0.06,0.0,1.0}; ctx->style.window_background_color_inactive = {0.05,0.05,0.05, 0.95}; ctx->style.window_border_color_inactive = {0.3,0.3,0.3, 1.0}; ctx->style.window_corner_radius = 5; ctx->style.window_titlebar_color = ctx->style.window_border_color; ctx->style.window_titlebar_color_inactive = {0.1,0.1,0.1,0.1}; } void gui_context_select(Gui_Context *ctx) { global_gui_state.selected_context = ctx; } void gui_frame_begin(Gui_Context *ctx, f64 curr_time) { ctx-> last_frame_time = ctx->current_frame_time; ctx->current_frame_time = curr_time; } void gui_frame_begin(f64 curr_time) { gui_frame_begin(&global_gui_state.default_context, curr_time); } void gui_frame_end(Gui_Context *ctx) { // Render windows for(u32 i = 0; i < ctx->window_count; i++) { Gui_Window *w = &ctx->windows[i]; if(w->still_open) { Rect r_uv = Rect{0,0,1,-1}; // y is flipped when rendering framebuffer's textures r_2d_immediate_rectangle(w->r, v4{1,1,1,1}, r_uv, &w->framebuffer.color_texture); w->still_open = false; // Will be set to true if still open } } // @Performance: cleanup unused windows // Fix state if(ctx->hot != ctx->possibly_hot) { ctx->hot = ctx->possibly_hot; ctx->hot_start_time = ctx->current_frame_time; } ctx->input.mouse_pressed_this_frame = false; ctx->input.mouse_released_this_frame = false; ctx->input.absolute_pointer_position_last_frame = ctx->input.absolute_pointer_position; ctx->input.text[0] = '\0'; ctx->input.text_cursor_move = 0; ctx->possibly_hot = NULL; } void gui_frame_end() { gui_frame_end(&global_gui_state.default_context); } void gui_handle_event(Gui_Context *ctx, Event *e) { switch(e->type) { case EVENT_MOUSE_MOVE: { if(!e->mouse_move.relative) { ctx->input.pointer_position = e->mouse_move.position; ctx->input.absolute_pointer_position = e->mouse_move.position; } } break; case EVENT_KEY: { switch(e->key.key_code) { case KEY_MOUSE_LEFT: { ctx->input.mouse_pressed = e->key.pressed; if(e->key.pressed) ctx->input.mouse_pressed_this_frame = true; else ctx->input.mouse_released_this_frame = true; } break; case KEY_ARROW_LEFT: { if(e->key.pressed) ctx->input.text_cursor_move--; } break; case KEY_ARROW_RIGHT: { if(e->key.pressed) ctx->input.text_cursor_move++; } break; default: { } break; } } break; case EVENT_RESIZE: { } break; case EVENT_TEXT: { strcat(ctx->input.text, e->text.data); } break; default: { } break; } } void gui_handle_event(Event *e) { gui_handle_event(&global_gui_state.default_context, e); } // ### Widgets ### // Text void gui_text(Gui_Context *ctx, Rect r, const char *text) { // @Feature: Clip text to Rect r gui_text_draw(r, text, ctx->style.font_size, ctx->style.text_color); } void gui_text(Rect r, const char *text) { gui_text(&global_gui_state.default_context, r, text); } void gui_text_aligned(Gui_Context *ctx, Rect r, const char *text, Gui_Text_Align alignment) { // @Cleanup: this should not depend on setting state. We should have a function that gets alignment as an argument Gui_Text_Align old_alignment = ctx->style.text_align; ctx->style.text_align = alignment; gui_button_draw_inner_text(ctx, r, text, ctx->style.text_color); ctx->style.text_align = old_alignment; } void gui_text_aligned(Rect r, const char *text, Gui_Text_Align alignment) { gui_text_aligned(&global_gui_state.default_context, r, text, alignment); } v2 gui_text_compute_size(Gui_Context *ctx, const char *text) { return gui_text_draw_size(text, ctx->style.font_size); } v2 gui_text_compute_size(const char *text) { return gui_text_compute_size(&global_gui_state.default_context, text); } // Button bool gui_button(Gui_Context *ctx, Rect r, const char *text) { Gui_Id widget_id = gui_id_from_pointer(ctx, text); bool behaviuor = gui_button_behaviuor(ctx, widget_id, r); // Compute colors v4 button_color = ctx->style.button_color; v4 text_color = ctx->style.button_text_color; { if(ctx->hot == widget_id) { f64 delta_t = (ctx->current_frame_time - ctx->hot_start_time); f32 interpolation = clamp(0, 1, delta_t / ctx->style.animation_base_time); button_color = lerp(ctx->style.button_color, ctx->style.button_color_hovered, interpolation); text_color = lerp(ctx->style.button_text_color, ctx->style.button_text_color_hovered, interpolation); } if(ctx->active == widget_id) { f64 delta_t = (ctx->current_frame_time - ctx->active_start_time); f32 interpolation = clamp(0, 1, delta_t / ctx->style.animation_base_time); button_color = lerp(ctx->style.button_color_hovered, ctx->style.button_color_pressed, interpolation * 0.4 + 0.6); text_color = lerp(ctx->style.button_text_color_hovered, ctx->style.button_text_color_pressed, interpolation * 0.4 + 0.6); } } // Draw button and text r_2d_immediate_rounded_rectangle(r, ctx->style.button_radius, button_color); gui_button_draw_inner_text(ctx, r, text, text_color); return behaviuor; } bool gui_button(Rect r, const char *text) { return gui_button(&global_gui_state.default_context, r, text); } void gui_button_draw_inner_text(Gui_Context *ctx, Rect r, const char *text, v4 color, Rect *actual_drawn_rect) { v2 text_size = gui_text_draw_size(text, ctx->style.font_size); // Alignment (center, left, right) v2 text_position = r.position + (r.size - text_size) * v2{0.5, 0.5}; if(ctx->style.text_align == GUI_ALIGN_LEFT) text_position = r.position + (r.size - text_size) * v2{0, 0.5}; if(ctx->style.text_align == GUI_ALIGN_RIGHT) text_position = r.position + (r.size - text_size) * v2{1, 0.5}; // Draw Rect text_rect = { .position = text_position, .size = text_size }; // @Feature: Clip text to Rect r gui_text_draw(text_rect, text, ctx->style.font_size, color); if(actual_drawn_rect) *actual_drawn_rect = text_rect; } // Slider bool gui_slider(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value) { Gui_Id widget_id = gui_id_from_pointer(ctx, value); bool behaviour = gui_button_behaviuor(ctx, widget_id, r); if(ctx->active == widget_id) { f32 pointer_ratio = (ctx->input.pointer_position.x - r.position.x) / r.size.x; *value = clamp(0.0f, 1.0f, pointer_ratio) * (max - min) + min; } // Colors v4 button_color = ctx->style.button_color; v4 text_color = ctx->style.button_text_color; { f64 delta_t = (ctx->current_frame_time - ctx->hot_start_time); f32 interpolation = sin(10 * delta_t) * 0.5 + 0.5; if(ctx->hot == widget_id) { button_color = lerp(ctx->style.button_color, ctx->style.button_color_hovered, interpolation); text_color = lerp(ctx->style.button_text_color, ctx->style.button_text_color_hovered, interpolation); } if(ctx->active == widget_id) { button_color = lerp(ctx->style.button_color_hovered, ctx->style.button_color_pressed, interpolation * 0.4 + 0.6); text_color = lerp(ctx->style.button_text_color_hovered, ctx->style.button_text_color_pressed, interpolation * 0.4 + 0.6); } } // Draw f32 border = 2; f32 radius = ctx->style.button_radius; // Draw background v4 background_color = ctx->style.button_color; r_2d_immediate_rounded_rectangle(r, radius, background_color); // Background // Draw fill f32 ratio = (*value - min) / (max - min); Rect fill_r = r; fill_r.position += v2{border, border}; fill_r.size = v2{maximum(0, fill_r.size.x - 2*border), maximum(0, fill_r.size.y - 2*border)}; f32 fill_radius = maximum(0, radius - border); fill_r.size.x = fill_r.size.x * ratio; r_2d_immediate_rounded_rectangle(fill_r, fill_radius, ctx->style.slider_fill_color); // Draw border v4 border_color = ctx->style.button_color_pressed; Rect border_r = r; border_r.position += v2{border, border} * 0.5; border_r.size = v2{maximum(0, border_r.size.x - border), maximum(0, border_r.size.y - border)}; f32 border_radius = maximum(0, radius - border*0.5); r_2d_immediate_rounded_rectangle_outline(border_r, border_radius, border_color, border); // Draw value text char text[64]; sprintf(text, "%f", *value); gui_button_draw_inner_text(ctx, r, text, text_color); return behaviour || ctx->active == widget_id; } bool gui_slider(Rect r, f32 min, f32 max, f32 *value) { return gui_slider(&global_gui_state.default_context, r, min, max, value); } bool gui_slider_text(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value, const char *text) { Gui_Id widget_id = gui_id_from_pointer(ctx, value); bool behaviour = gui_button_behaviuor(ctx, widget_id, r); if(ctx->active == widget_id) { f32 pointer_ratio = (ctx->input.pointer_position.x - r.position.x) / r.size.x; *value = clamp(0.0f, 1.0f, pointer_ratio) * (max - min) + min; } // Colors v4 button_color = ctx->style.button_color; v4 text_color = ctx->style.button_text_color; { f64 delta_t = (ctx->current_frame_time - ctx->hot_start_time); f32 interpolation = sin(10 * delta_t) * 0.5 + 0.5; if(ctx->hot == widget_id) { button_color = lerp(ctx->style.button_color, ctx->style.button_color_hovered, interpolation); text_color = lerp(ctx->style.button_text_color, ctx->style.button_text_color_hovered, interpolation); } if(ctx->active == widget_id) { button_color = lerp(ctx->style.button_color_hovered, ctx->style.button_color_pressed, interpolation * 0.4 + 0.6); text_color = lerp(ctx->style.button_text_color_hovered, ctx->style.button_text_color_pressed, interpolation * 0.4 + 0.6); } } // Draw f32 border = 2; f32 radius = ctx->style.button_radius; // Draw background v4 background_color = ctx->style.button_color; r_2d_immediate_rounded_rectangle(r, radius, background_color); // Background // Draw fill f32 ratio = (*value - min) / (max - min); Rect fill_r = r; fill_r.position += v2{border, border}; fill_r.size = v2{maximum(0, fill_r.size.x - 2*border), maximum(0, fill_r.size.y - 2*border)}; f32 fill_radius = maximum(0, radius - border); fill_r.size.x = fill_r.size.x * ratio; r_2d_immediate_rounded_rectangle(fill_r, fill_radius, ctx->style.slider_fill_color); // Draw border v4 border_color = ctx->style.button_color_pressed; Rect border_r = r; border_r.position += v2{border, border} * 0.5; border_r.size = v2{maximum(0, border_r.size.x - border), maximum(0, border_r.size.y - border)}; f32 border_radius = maximum(0, radius - border*0.5); r_2d_immediate_rounded_rectangle_outline(border_r, border_radius, border_color, border); // Draw value text gui_button_draw_inner_text(ctx, r, text, text_color); return behaviour || ctx->active == widget_id; } bool gui_slider_text(Rect r, f32 min, f32 max, f32 *value, const char *text) { return gui_slider_text(&global_gui_state.default_context, r, min, max, value, text); } // Images bool gui_image(Gui_Context *ctx, Rect r, r_texture *texture) { Gui_Id widget_id = gui_id_from_pointer(ctx, texture); v4 color = {1,1,1,1}; r_2d_immediate_rectangle(r, color, {0,0,1,1}, texture); return gui_button_behaviuor(ctx, widget_id, r);; } bool gui_image(Rect r, r_texture *texture) { return gui_image(&global_gui_state.default_context, r, texture); } bool gui_image(Gui_Context *ctx, Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags) { r_texture texture = r_texture_create((u8*)bmp, {width, height}, flags | R_TEXTURE_DONT_OWN); bool result = gui_image(ctx, r, &texture); r_texture_destroy(&texture); return result; } bool gui_image(Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags) { return gui_image(&global_gui_state.default_context, r, bmp, width, height, channels, flags); } // Text input bool gui_text_input(Gui_Context *ctx, Rect r, char *text, u64 max_size) { Gui_Id widget_id = gui_id_from_pointer(ctx, text); bool behaviour = gui_text_input_behaviuor(ctx, widget_id, r); bool edited = false; // Cursor, mouse click, input from keyboard/os if(ctx->active == widget_id && ctx->input.mouse_pressed_this_frame) { ctx->text_length = strlen(text); ctx->text_cursor_position = ctx->text_length; } // Move cursors between UTF8 codepoints (not bytes) if(ctx->input.text_cursor_move != 0) { while(ctx->input.text_cursor_move > 0) { if(text[ctx->text_cursor_position] == '\0') { ctx->input.text_cursor_move = 0; break; } ctx->text_cursor_position += utf8_bytes_to_next_valid_codepoint(text, ctx->text_cursor_position); ctx->input.text_cursor_move--; } while(ctx->input.text_cursor_move < 0) { if(ctx->text_cursor_position == 0) { ctx->input.text_cursor_move = 0; break; } ctx->text_cursor_position -= utf8_bytes_to_prev_valid_codepoint(text, ctx->text_cursor_position); ctx->input.text_cursor_move++; } } if(ctx->active == widget_id && ctx->input.text[0] != 0) { // @Bug: Should iterate on utf8 codepoints. If we don't, there's the possibility // of inserting half of a multi-byte codepoint. for(char *c = ctx->input.text; *c != 0; c++) { if(*c == 0x08) // Backspace { if(ctx->text_cursor_position > 0) { u32 codepoint_bytes = utf8_bytes_to_prev_valid_codepoint(text, ctx->text_cursor_position); u64 from_index = ctx->text_cursor_position; u64 to_index = ctx->text_cursor_position - codepoint_bytes; memmove(text + to_index, text + from_index, ctx->text_length + 1 - from_index); ctx->text_length -= codepoint_bytes; ctx->text_cursor_position -= codepoint_bytes; } continue; } if(*c == 0x7F) // Delete { if(ctx->text_cursor_position < ctx->text_length) { u32 codepoint_bytes = utf8_bytes_to_next_valid_codepoint(text, ctx->text_cursor_position); u64 from_index = ctx->text_cursor_position + codepoint_bytes; u64 to_index = ctx->text_cursor_position; memmove(text + to_index, text + from_index, ctx->text_length + 1 - from_index); ctx->text_length -= codepoint_bytes; } continue; } if(ctx->text_length < max_size - 1) // Leave space for 0 terminator { memmove(text + ctx->text_cursor_position + 1, text + ctx->text_cursor_position, ctx->text_length + 1 - ctx->text_cursor_position); text[ctx->text_cursor_position] = *c; ctx->text_length += 1; ctx->text_cursor_position += 1; } } edited = true; } r_2d_immediate_rounded_rectangle(r, ctx->style.button_radius, ctx->style.button_color); Rect text_rect; gui_button_draw_inner_text(ctx, r, text, ctx->style.button_text_color, &text_rect); if(ctx->active == widget_id) { // Draw cursor f64 delta_t = ctx->current_frame_time - ctx->active_start_time; f32 u = clamp(0, 1, sin(delta_t * 5) * 0.7 + 0.6); v4 cursor_color = ctx->style.button_text_color; cursor_color *= lerp(0, cursor_color.a, u); char replaced = text[ctx->text_cursor_position]; text[ctx->text_cursor_position] = 0; v2 cursor_position; v2 text_size = gui_text_draw_size(text, ctx->style.font_size, &cursor_position); text[ctx->text_cursor_position] = replaced; Rect cursor_rect = { .position = text_rect.position + cursor_position - v2{0, ctx->style.font_size}, .size = ctx->style.font_size * v2{0.1, 0.9} }; r_2d_immediate_rectangle(cursor_rect, cursor_color); } return edited; } bool gui_text_input(Rect r, char *text, u64 max_size) { return gui_text_input(&global_gui_state.default_context, r, text, max_size); } // Panels void gui_panel(Gui_Context *ctx, Rect r) { Gui_Id widget_id = 0; bool behaviuor = gui_button_behaviuor(ctx, widget_id, r); bool is_inactive = true; v4 background_color = is_inactive ? ctx->style.window_background_color_inactive : ctx->style.window_background_color; v4 border_color = is_inactive ? ctx->style.window_border_color_inactive : ctx->style.window_border_color; Rect background_rect = {r.x + 0.5, r.y + 0.5, floor(r.w)-1.0, floor(r.h)-1.0}; r_2d_immediate_rounded_rectangle(background_rect, ctx->style.window_corner_radius, background_color); r_2d_immediate_rounded_rectangle_outline(background_rect, ctx->style.window_corner_radius, border_color, 1.0); } void gui_panel(Rect r) { gui_panel(&global_gui_state.default_context, r); } // Windows bool gui_window_start(Gui_Context *ctx, Rect r, Gui_Id id) { gui_id_stack_push(ctx, id); Gui_Window *window = gui_window_by_id(ctx, r, id); window->still_open = true; gui_window_update_rect(ctx, window, r); u32 window_index = window - ctx->windows; bool hovered = gui_is_hovered(ctx, id, r); if(hovered && ctx->input.mouse_pressed_this_frame) { // Bring window on top u32 move_count = ctx->window_count - 1 - window_index; if(move_count > 0) { Gui_Window tmp = *window; memmove(ctx->windows + window_index, ctx->windows + window_index + 1, sizeof(Gui_Window) * move_count); ctx->windows[ctx->window_count - 1] = tmp; window_index = ctx->window_count - 1; window = &ctx->windows[window_index]; } } ctx->current_window = window; ctx->input.pointer_position = ctx->input.absolute_pointer_position - window->r.position; ctx->old_framebuffer = r_render_state.current_framebuffer; r_framebuffer_select(&window->framebuffer); bool is_inactive = window_index != ctx->window_count-1; v4 background_color = is_inactive ? ctx->style.window_background_color_inactive : ctx->style.window_background_color; v4 border_color = is_inactive ? ctx->style.window_border_color_inactive : ctx->style.window_border_color; r_clear({0,0,0,0}); Rect background_rect = {0.5, 0.5, floor(r.w)-1.0, floor(r.h)-1.0}; r_2d_immediate_rounded_rectangle(background_rect, ctx->style.window_corner_radius, background_color); r_2d_immediate_rounded_rectangle_outline(background_rect, ctx->style.window_corner_radius, border_color, 1.0); return true; } bool gui_window_start(Rect r, Gui_Id id) { return gui_window_start(&global_gui_state.default_context, r, id); } void gui_window_end(Gui_Context *ctx) { gui_id_stack_pop(ctx); ctx->current_window = NULL; ctx->input.pointer_position = ctx->input.absolute_pointer_position; r_framebuffer_select(ctx->old_framebuffer); } void gui_window_end() { return gui_window_end(&global_gui_state.default_context); } bool gui_window_titlebar(Gui_Context *ctx, Rect r, const char *title, bool *close, v2 *move) { Gui_Id widget_id = gui_id_from_pointer(ctx, title); bool behaviour = gui_button_behaviuor(ctx, widget_id, r); if(close) *close = false; if(move) *move = {0,0}; // Background v4 titlebar_color = ctx->style.window_titlebar_color_inactive; if(ctx->current_window && ctx->current_window - ctx->windows == ctx->window_count - 1) { titlebar_color = ctx->style.window_titlebar_color; } r_2d_immediate_rounded_rectangle(r, ctx->style.window_corner_radius, titlebar_color); // Title v2 title_size = gui_text_compute_size(title); Rect title_r = r; title_r.size = title_size; title_r.position = r.position + (r.size - title_r.size) / 2; gui_text(title_r, title); // Exit button f32 smallest_side = minimum(r.w, r.h); f32 exit_size = smallest_side; Gui_Style exit_style = ctx->style; exit_style.button_color = v4{0.8f, 0.8f, 0.8f, 1.0f}*0.0f; exit_style.button_color_hovered = v4{1.0f, 0.0f, 0.0f, 1.0f}*1.0f; exit_style.button_color_pressed = v4{0.8f, 0.0f, 0.0f, 1.0f}; exit_style.button_text_color = v4{1.0f, 1.0f, 1.0f, 1.0f}; exit_style.button_text_color_hovered = v4{1.0f, 1.0f, 1.0f, 1.0f}; exit_style.button_text_color_pressed = v4{1.0f, 1.0f, 1.0f, 1.0f}; exit_style.button_radius = ctx->style.window_corner_radius; Gui_Style old_style = ctx->style; ctx->style = exit_style; Rect exit_button_r = {r.x + r.w - exit_size, r.y, exit_size, exit_size}; if(gui_button(ctx, exit_button_r, "тип")) { if(close) *close = true; behaviour = true; } ctx->style = old_style; // Move if(ctx->active == widget_id && !ctx->input.mouse_pressed_this_frame) { if(move) { *move = ctx->input.absolute_pointer_position - ctx->input.absolute_pointer_position_last_frame; } } return behaviour || ctx->active == widget_id; } bool gui_window_titlebar(Rect r, const char *title, bool *close, v2 *move) { return gui_window_titlebar(&global_gui_state.default_context, r, title, close, move); } Gui_Window *gui_window_by_id(Gui_Context *ctx, Rect r, Gui_Id id) { Gui_Window *window = NULL; for(u32 i = 0; i < ctx->window_count; i++) { if(ctx->windows[i].id == id) { window = &ctx->windows[i]; break; } } if(!window) { if(ctx->window_count >= ctx->window_capacity) { if(ctx->window_capacity == 0) ctx->window_capacity = 1; ctx->window_capacity *= 2; ctx->windows = (Gui_Window*) p_realloc(ctx->windows, sizeof(Gui_Window) * ctx->window_capacity); } window = &ctx->windows[ctx->window_count]; ctx->window_count++; window->id = id; window->r = r; window->framebuffer = r_framebuffer_create(V2S(r.size), 0); } return window; } void gui_window_update_rect(Gui_Context *ctx, Gui_Window *window, Rect r) { if(window->r.size != r.size) { r_framebuffer_update_size(&window->framebuffer, V2S(r.size)); } window->r = r; } // Helpers bool gui_is_hovered(Gui_Context *ctx, Gui_Id widget_id, Rect r) { if(is_inside(r, ctx->input.pointer_position)) { s32 current_window_index = -1; // We use -1 to indicate we are not in a window. When we iterate over windows we do a +1 and start from 0, aka the first window. If we used 0, we would start from 1 and skip over window index 0. // The ctx->windows array is sorted from back to front. If we are inside a window, only the following windows in the array can overlap up. The ones before are covered by the current window. if(ctx->current_window) current_window_index = ctx->current_window - ctx->windows; // Am I a window? If so, we start checking from us. If ctx->current_window is set and widget_id is a window, it means we are a subwindow. // Subwindow are not supported yet though (20 September 2023), so this should be a bug in the user code. Yeah we don't check to prevent this, but anyways. for(s32 i = current_window_index + 1; i < ctx->window_count; i++) { Gui_Id window_id = ctx->windows[i].id; if(widget_id == window_id) { current_window_index = i; break; } } // Iterate over windows that cover the current one for(u32 i = current_window_index + 1; i < ctx->window_count; i++) { Gui_Id window_id = ctx->windows[i].id; Rect window_rect = ctx->windows[i].r; if(is_inside(window_rect, ctx->input.absolute_pointer_position)) { return false; } } return true; } return false; } bool gui_button_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r) { bool behaviour = false; if(gui_is_hovered(ctx, widget_id, r)) { if(!ctx->active || ctx->active == widget_id || !(ctx->active_status & GUI_WIDGET_STATUS_PREVENT_HOT)) ctx->possibly_hot = widget_id; if(ctx->hot == widget_id && ctx->input.mouse_pressed_this_frame) { ctx->active = widget_id; ctx->active_start_time = ctx->current_frame_time; ctx->active_status = GUI_WIDGET_STATUS_PREVENT_HOT; } if(ctx->active == widget_id && ctx->input.mouse_released_this_frame) { behaviour = true; } } if(ctx->active == widget_id && ctx->input.mouse_released_this_frame) { ctx->active = NULL; ctx->active_status = 0; } return behaviour; } bool gui_text_input_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r) { bool behaviour = false; if(gui_is_hovered(ctx, widget_id, r)) { if(!ctx->active || ctx->active == widget_id || !(ctx->active_status & GUI_WIDGET_STATUS_PREVENT_HOT)) ctx->possibly_hot = widget_id; if(ctx->hot == widget_id && ctx->input.mouse_pressed_this_frame) { ctx->active = widget_id; ctx->active_start_time = ctx->current_frame_time; ctx->active_status = 0; } if(ctx->active == widget_id && ctx->input.mouse_released_this_frame) { behaviour = true; } } if(ctx->active == widget_id && ctx->input.mouse_released_this_frame) { // ctx->active = NULL; // ctx->active_status = 0; } return behaviour; } Gui_Id gui_id_from_pointer(Gui_Context *ctx, const void* ptr) { u32 seed = 0xFFFFFFFF; if(ctx->id_count) seed = ctx->id_stack[ctx->id_count - 1]; return hash_crc32(&ptr, sizeof(void*), seed); } void gui_id_stack_push(Gui_Context *ctx, Gui_Id id) { if(ctx->id_capacity <= ctx->id_count) { u32 new_capacity = maximum(ctx->id_count + 1, ctx->id_capacity * 2); ctx->id_stack = (Gui_Id*)p_realloc(ctx->id_stack, sizeof(Gui_Id) * new_capacity); ctx->id_capacity = new_capacity; } ctx->id_stack[ctx->id_count] = id; ctx->id_count++; } void gui_id_stack_pop(Gui_Context *ctx) { if(ctx->id_count > 0) ctx->id_count--; }