From 65c4cab8aae2ee49d196995c4d0a9836fd85368b Mon Sep 17 00:00:00 2001 From: Luca Cuzzocrea Date: Fri, 27 Sep 2024 19:46:39 +0200 Subject: [PATCH] Updated GUI library --- code/gui/gui.cpp | 441 +++++++++++++++++++++++++++++++------------- code/gui/gui.h | 72 ++++++-- code/lib/array.h | 77 ++++++++ code/lib/geometry.h | 19 +- code/lib/math.h | 109 +++++++---- code/main.cpp | 8 +- 6 files changed, 535 insertions(+), 191 deletions(-) create mode 100644 code/lib/array.h diff --git a/code/gui/gui.cpp b/code/gui/gui.cpp index 27dd49c..01a21c0 100644 --- a/code/gui/gui.cpp +++ b/code/gui/gui.cpp @@ -35,9 +35,9 @@ void gui_context_init(Gui_Context *ctx) ctx-> last_frame_time = 0; ctx->current_frame_time = 0; - ctx-> active = NULL; - ctx-> hot = NULL; - ctx->possibly_hot = NULL; + ctx-> active = 0; + ctx-> hot = 0; + ctx->possibly_hot = 0; ctx->active_start_time = 0; ctx-> hot_start_time = 0; ctx->active_status = 0; @@ -45,14 +45,12 @@ void gui_context_init(Gui_Context *ctx) ctx->text_cursor_position = 0; ctx->text_length = 0; - ctx->windows = NULL; - ctx->window_count = 0; - ctx->window_capacity = 0; + ctx->windows.reserve(2); 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->clipping.reserve(); + + ctx->id_stack.reserve(); ctx->input.pointer_position = {0, 0}; ctx->input.absolute_pointer_position = {0, 0}; @@ -61,6 +59,7 @@ void gui_context_init(Gui_Context *ctx) ctx->input.mouse_released_this_frame = false; ctx->input.text_cursor_move = 0; ctx->input.text[0] = '\0'; + ctx->input.scroll_move = 0; ctx->input.absolute_pointer_position_last_frame = {0, 0}; @@ -89,6 +88,13 @@ void gui_context_init(Gui_Context *ctx) 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}; + + ctx->style.scrollbar_size = 10; + ctx->style.scrollbar_corner_radius = 4; + ctx->style.scrollbar_inner_bar_size = 10; + ctx->style.scrollbar_inner_bar_corner_radius = 4; + ctx->style.scrollbar_color = v4{.1f,.1f,.1f,1.0f}*0.2f; + ctx->style.scrollbar_inner_bar_color = v4{.5f,.5f,.5f,1.0f}; } void gui_context_select(Gui_Context *ctx) @@ -110,7 +116,7 @@ void gui_frame_begin(f64 curr_time) void gui_frame_end(Gui_Context *ctx) { // Render windows - for(u32 i = 0; i < ctx->window_count; i++) + for(u32 i = 0; i < ctx->windows.count; i++) { Gui_Window *w = &ctx->windows[i]; if(w->still_open) @@ -137,7 +143,9 @@ void gui_frame_end(Gui_Context *ctx) ctx->input.text[0] = '\0'; ctx->input.text_cursor_move = 0; - ctx->possibly_hot = NULL; + ctx->input.scroll_move = 0; + + ctx->possibly_hot = 0; } void gui_frame_end() @@ -166,6 +174,14 @@ void gui_handle_event(Gui_Context *ctx, Event *e) else ctx->input.mouse_released_this_frame = true; } break; + case KEY_MOUSE_WHEEL_UP: { + if(e->key.pressed) + ctx->input.scroll_move--; + } break; + case KEY_MOUSE_WHEEL_DOWN: { + if(e->key.pressed) + ctx->input.scroll_move++; + } break; case KEY_ARROW_LEFT: { if(e->key.pressed) ctx->input.text_cursor_move--; @@ -198,6 +214,7 @@ void gui_handle_event(Event *e) // Text void gui_text(Gui_Context *ctx, Rect r, const char *text) { + if(gui_is_clipped(ctx, r)) return; // @Feature: Clip text to Rect r gui_text_draw(r, text, ctx->style.font_size, ctx->style.text_color); } @@ -209,6 +226,7 @@ void gui_text(Rect r, const char *text) void gui_text_aligned(Gui_Context *ctx, Rect r, const char *text, Gui_Text_Align alignment) { + if(gui_is_clipped(ctx, r)) return; // @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; @@ -235,9 +253,10 @@ v2 gui_text_compute_size(const char *text) // Button bool gui_button(Gui_Context *ctx, Rect r, const char *text) { + if(gui_is_clipped(ctx, r)) return false; + 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; @@ -289,82 +308,46 @@ void gui_button_draw_inner_text(Gui_Context *ctx, Rect r, const char *text, v4 c } // Slider -bool gui_slider(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value) +bool gui_slider_range(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value) { + if(gui_is_clipped(ctx, r)) return false; + 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_id_stack_push(ctx, widget_id); + // Value text char text[64]; - sprintf(text, "%f", *value); - gui_button_draw_inner_text(ctx, r, text, text_color); + snprintf(text, 64, "%f", *value); - return behaviour || ctx->active == widget_id; + // Convert value from min-max to 0-1 range + f32 ratio = (*value - min) / (max - min); + + // Do slider + bool behaviour = gui_slider_text(ctx, r, &ratio, text); + + // Re-convert value from 0-1 to min-max range + *value = clamp(0.0f, 1.0f, ratio) * (max - min) + min; + + gui_id_stack_pop(ctx); + return behaviour; } -bool gui_slider(Rect r, f32 min, f32 max, f32 *value) +bool gui_slider_range(Rect r, f32 min, f32 max, f32 *value) { - return gui_slider(&global_gui_state.default_context, r, min, max, value); + return gui_slider_range(&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) +// ratio must be between 0 and 1. +bool gui_slider_text(Gui_Context *ctx, Rect r, f32 *ratio, const char *text) { - Gui_Id widget_id = gui_id_from_pointer(ctx, value); + if(gui_is_clipped(ctx, r)) return false; + + Gui_Id widget_id = gui_id_from_pointer(ctx, ratio); 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; + *ratio = clamp(0.0f, 1.0f, pointer_ratio); } // Colors @@ -394,12 +377,11 @@ bool gui_slider_text(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value, con 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; + 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 @@ -416,15 +398,17 @@ bool gui_slider_text(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value, con return behaviour || ctx->active == widget_id; } -bool gui_slider_text(Rect r, f32 min, f32 max, f32 *value, const char *text) +bool gui_slider_text(Rect r, f32 *ratio, const char *text) { - return gui_slider_text(&global_gui_state.default_context, r, min, max, value, text); + return gui_slider_text(&global_gui_state.default_context, r, ratio, text); } // Images bool gui_image(Gui_Context *ctx, Rect r, r_texture *texture) { + if(gui_is_clipped(ctx, r)) return false; + Gui_Id widget_id = gui_id_from_pointer(ctx, texture); v4 color = {1,1,1,1}; @@ -440,6 +424,8 @@ bool gui_image(Rect r, r_texture *texture) bool gui_image(Gui_Context *ctx, Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags) { + if(gui_is_clipped(ctx, r)) return false; + 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); @@ -455,6 +441,8 @@ bool gui_image(Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 f // Text input bool gui_text_input(Gui_Context *ctx, Rect r, char *text, u64 max_size) { + if(gui_is_clipped(ctx, r)) return false; + Gui_Id widget_id = gui_id_from_pointer(ctx, text); bool behaviour = gui_text_input_behaviuor(ctx, widget_id, r); bool edited = false; @@ -501,6 +489,11 @@ bool gui_text_input(Gui_Context *ctx, Rect r, char *text, u64 max_size) { if(ctx->text_cursor_position > 0) { + + // Panels + // void gui_panel(Gui_Context *ctx, Rect r); + // void gui_panel(Rect r); + // 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; @@ -536,6 +529,8 @@ bool gui_text_input(Gui_Context *ctx, Rect r, char *text, u64 max_size) edited = true; } + gui_clip_start(ctx, r); + 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); @@ -562,6 +557,8 @@ bool gui_text_input(Gui_Context *ctx, Rect r, char *text, u64 max_size) r_2d_immediate_rectangle(cursor_rect, cursor_color); } + gui_clip_end(ctx); + return edited; } @@ -593,6 +590,7 @@ void gui_panel(Rect r) } + // Windows bool gui_window_start(Gui_Context *ctx, Rect r, Gui_Id id) { @@ -600,20 +598,20 @@ bool gui_window_start(Gui_Context *ctx, Rect r, Gui_Id 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; + u32 window_index = window - ctx->windows.data; 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; + u32 move_count = ctx->windows.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); + memmove(ctx->windows.data + window_index, ctx->windows.data + window_index + 1, sizeof(Gui_Window) * move_count); - ctx->windows[ctx->window_count - 1] = tmp; - window_index = ctx->window_count - 1; + ctx->windows.last() = tmp; + window_index = ctx->windows.count - 1; window = &ctx->windows[window_index]; } } @@ -625,7 +623,7 @@ bool gui_window_start(Gui_Context *ctx, Rect r, Gui_Id id) r_framebuffer_select(&window->framebuffer); - bool is_inactive = window_index != ctx->window_count-1; + bool is_inactive = window_index != ctx->windows.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 : @@ -643,6 +641,29 @@ bool gui_window_start(Rect r, Gui_Id id) return gui_window_start(&global_gui_state.default_context, r, id); } +bool gui_window_with_titlebar_start(Gui_Context *ctx, Rect r, const char *title, Gui_Window_Titlebar_State *state) +{ + Gui_Id id = gui_id_from_pointer(ctx, title); + gui_window_start(ctx, r, id); + + Gui_Titlebar_State *titlebar_state = state ? &state->titlebar : NULL; + Rect titlebar_r = {0, 0, r.w, ctx->style.font_size}; + bool result = gui_window_titlebar(ctx, titlebar_r, title, titlebar_state); + + if(state) + { + state->inner_r.position = {0, titlebar_r.h}; + state->inner_r.size = r.size - v2{0, titlebar_r.h}; + } + + return result; +} + +bool gui_window_with_titlebar_start(Rect r, const char *title, Gui_Window_Titlebar_State *state) +{ + return gui_window_with_titlebar_start(&global_gui_state.default_context, r, title, state); +} + void gui_window_end(Gui_Context *ctx) { gui_id_stack_pop(ctx); @@ -658,18 +679,20 @@ void gui_window_end() } -bool gui_window_titlebar(Gui_Context *ctx, Rect r, const char *title, bool *close, v2 *move) +bool gui_window_titlebar(Gui_Context *ctx, Rect r, const char *title, Gui_Titlebar_State *state) { 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}; + + if(state) + { + state->close = false; + state->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) + if(ctx->current_window == &ctx->windows.last()) { titlebar_color = ctx->style.window_titlebar_color; } @@ -698,35 +721,155 @@ bool gui_window_titlebar(Gui_Context *ctx, Rect r, const char *title, bool *clos 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; + if(state) + state->close = true; behaviour = true; } ctx->style = old_style; // Move - if(ctx->active == widget_id && !ctx->input.mouse_pressed_this_frame) + if(state && ctx->active == widget_id) { - if(move) - { - *move = ctx->input.absolute_pointer_position - ctx->input.absolute_pointer_position_last_frame; - } + if(ctx->input.mouse_pressed_this_frame) + state->anchor_point = ctx->input.pointer_position; + + state->move = ctx->input.pointer_position - state->anchor_point; } return behaviour || ctx->active == widget_id; } -bool gui_window_titlebar(Rect r, const char *title, bool *close, v2 *move) +bool gui_window_titlebar(Rect r, const char *title, Gui_Titlebar_State *state) { - return gui_window_titlebar(&global_gui_state.default_context, r, title, close, move); + return gui_window_titlebar(&global_gui_state.default_context, r, title, state); } +bool gui_scrollable_area_start(Gui_Context *ctx, Rect r, v2 area_size, Rect *displayed_r) +{ + bool behaviour = false; + Gui_Id widget_id = gui_id_from_pointer(ctx, displayed_r); + gui_id_stack_push(ctx, widget_id); + + Rect displayed = {0,0,0,0}; + displayed.size = r.size; + if(displayed_r) + displayed.position = displayed_r->position; + + Rect vertical_scrollbar = {0,0,0,0}; + Rect horizontal_scrollbar = {0,0,0,0}; + if(area_size.y > r.h) + { + vertical_scrollbar.w = ctx->style.scrollbar_size; + vertical_scrollbar.h = r.h; + vertical_scrollbar.x = r.x + r.w - vertical_scrollbar.w; + vertical_scrollbar.y = r.y; + } + if(area_size.x > r.w) + { + horizontal_scrollbar.w = r.w; + horizontal_scrollbar.h = ctx->style.scrollbar_size; + horizontal_scrollbar.x = r.x; + horizontal_scrollbar.y = r.y + r.h - horizontal_scrollbar.h; + } + if(vertical_scrollbar.w && horizontal_scrollbar.h) + { + vertical_scrollbar.h -= horizontal_scrollbar.h; + horizontal_scrollbar.w -= vertical_scrollbar.w; + } + + displayed.size -= v2{vertical_scrollbar.w, horizontal_scrollbar.h}; + + if(vertical_scrollbar.w) + { + f32 relative_y = -(displayed.y - r.y) / area_size.y; + f32 relative_h = displayed.h / area_size.y; + Gui_Id vertical_id = gui_id_from_pointer(ctx, &vertical_scrollbar); + behaviour = behaviour || gui_button_behaviuor(ctx, vertical_id, vertical_scrollbar); + + if(gui_is_hovered(ctx, widget_id, r)) + { + behaviour = true; + f32 scroll_amount = relative_h / 3; + relative_y += ctx->input.scroll_move * scroll_amount; + } + + if(ctx->active == vertical_id) + { + behaviour = true; + f32 relative_pointer = ctx->input.pointer_position.y - vertical_scrollbar.y - 0.5*relative_h*vertical_scrollbar.h; + relative_pointer /= vertical_scrollbar.h; + relative_y = relative_pointer; + } + + relative_y = clamp(0, 1 - relative_h, relative_y); + displayed.y = r.y - relative_y * area_size.y; + + // Render + r_2d_immediate_rounded_rectangle(vertical_scrollbar, ctx->style.scrollbar_corner_radius, ctx->style.scrollbar_color); + Rect inner_bar_r = { + .x = vertical_scrollbar.x + (vertical_scrollbar.w - ctx->style.scrollbar_inner_bar_size) / 2, + .y = vertical_scrollbar.y + relative_y * vertical_scrollbar.h, + .w = ctx->style.scrollbar_inner_bar_size, + .h = relative_h * vertical_scrollbar.h + }; + r_2d_immediate_rounded_rectangle(inner_bar_r, ctx->style.scrollbar_inner_bar_corner_radius, ctx->style.scrollbar_inner_bar_color); + } + if(horizontal_scrollbar.h) + { + f32 relative_x = -(displayed.x - r.x) / area_size.x; + f32 relative_w = displayed.w / area_size.x; + Gui_Id horizontal_id = gui_id_from_pointer(ctx, &horizontal_scrollbar); + behaviour = behaviour || gui_button_behaviuor(ctx, horizontal_id, horizontal_scrollbar); + + if(ctx->active == horizontal_id) + { + behaviour = true; + f32 relative_pointer = ctx->input.pointer_position.x - horizontal_scrollbar.x - 0.5*relative_w*horizontal_scrollbar.w; + relative_pointer /= horizontal_scrollbar.w; + relative_x = clamp(0, 1 - relative_w, relative_pointer); + displayed.x = r.x - relative_x * area_size.x; + } + + // Render + r_2d_immediate_rounded_rectangle(horizontal_scrollbar, ctx->style.scrollbar_corner_radius, ctx->style.scrollbar_color); + Rect inner_bar_r = { + .x = horizontal_scrollbar.x + relative_x * horizontal_scrollbar.w, + .y = horizontal_scrollbar.y + (horizontal_scrollbar.h - ctx->style.scrollbar_inner_bar_size) / 2, + .w = relative_w * horizontal_scrollbar.w, + .h = ctx->style.scrollbar_inner_bar_size, + }; + r_2d_immediate_rounded_rectangle(inner_bar_r, ctx->style.scrollbar_inner_bar_corner_radius, ctx->style.scrollbar_inner_bar_color); + } + + if(displayed_r) + *displayed_r = displayed; + + gui_clip_start(ctx, Rect{r.x, r.y, displayed.w, displayed.h}); + return behaviour; +} + +bool gui_scrollable_area_start(Rect r, v2 area_size, Rect *displayed_r) +{ + return gui_scrollable_area_start(&global_gui_state.default_context, r, area_size, displayed_r); +} + +void gui_scrollable_area_end(Gui_Context *ctx) +{ + gui_clip_end(ctx); + gui_id_stack_pop(ctx); +} + +void gui_scrollable_area_end() +{ + gui_scrollable_area_end(&global_gui_state.default_context); +} + 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++) + for(u32 i = 0; i < ctx->windows.count; i++) { if(ctx->windows[i].id == id) { @@ -737,20 +880,13 @@ Gui_Window *gui_window_by_id(Gui_Context *ctx, Rect r, Gui_Id id) 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); + Gui_Window w = { + .id = id, + .r = r, + .framebuffer = r_framebuffer_create(V2S(r.size), 0) + }; + ctx->windows.push(w); + window = &ctx->windows.last(); } return window; @@ -771,15 +907,27 @@ bool gui_is_hovered(Gui_Context *ctx, Gui_Id widget_id, Rect r) { if(is_inside(r, ctx->input.pointer_position)) { + for(u64 i = ctx->clipping.count; i > 0; i--) // Start from the end. The last clipping is usually the smallest and the most likely to fail. + { + // @Correctness: I have a feeling this is wrong. What happens with a stack where the first clips where not in a window, while the last ones are in a window? We would have different relative pointer positions to consider. The clipping would also be relative to its parent window/framebuffer. + if(!is_inside(ctx->clipping[i-1], ctx->input.pointer_position)) + return false; + } + 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; + { + current_window_index = ctx->current_window - ctx->windows.data; + + if(!is_inside(ctx->current_window->r, ctx->input.absolute_pointer_position)) + return false; + } // 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++) + for(s32 i = current_window_index + 1; i < ctx->windows.count; i++) { Gui_Id window_id = ctx->windows[i].id; if(widget_id == window_id) @@ -790,7 +938,7 @@ bool gui_is_hovered(Gui_Context *ctx, Gui_Id widget_id, Rect r) } // Iterate over windows that cover the current one - for(u32 i = current_window_index + 1; i < ctx->window_count; i++) + for(u32 i = current_window_index + 1; i < ctx->windows.count; i++) { Gui_Id window_id = ctx->windows[i].id; Rect window_rect = ctx->windows[i].r; @@ -827,7 +975,7 @@ bool gui_button_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r) if(ctx->active == widget_id && ctx->input.mouse_released_this_frame) { - ctx->active = NULL; + ctx->active = 0; ctx->active_status = 0; } @@ -858,7 +1006,7 @@ bool gui_text_input_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r) if(ctx->active == widget_id && ctx->input.mouse_released_this_frame) { - // ctx->active = NULL; + // ctx->active = 0; // ctx->active_status = 0; } @@ -868,26 +1016,57 @@ bool gui_text_input_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r) 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]; + if(ctx->id_stack.count) + seed = ctx->id_stack.last(); 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++; + ctx->id_stack.push(id); } void gui_id_stack_pop(Gui_Context *ctx) { - if(ctx->id_count > 0) - ctx->id_count--; + ctx->id_stack.pop(); +} + +// Clipping +static void gui_clip_internal(Gui_Context *ctx, Rect r) +{ + f32 height = ctx->current_window ? ctx->current_window->r.h : ctx->height; + glScissor(floor(r.x), floor(height - r.y - r.h), ceil(r.w), ceil(r.h)); // Textures are rendered flipped vertically, so we need to start r.y far away from the bottom and end r.h farther. +} + +void gui_clip_start(Gui_Context *ctx, Rect r) +{ + ctx->clipping.push(r); + glEnable(GL_SCISSOR_TEST); + gui_clip_internal(ctx, r); +} + +void gui_clip_end(Gui_Context *ctx) +{ + ctx->clipping.pop(); + if(ctx->clipping.count) + gui_clip_internal(ctx, ctx->clipping.last()); + else + glDisable(GL_SCISSOR_TEST); +} + +bool gui_is_clipped(Gui_Context *ctx, Rect r) +{ + for(u64 i = 0; i < ctx->clipping.count; i++) + { + // @Correctness: I have a feeling this is wrong. What happens with a stack where the first clips where not in a window, while the last ones are in a window? We would have different relative pointer positions to consider. The clipping would also be relative to its parent window/framebuffer. + if(!is_inside(ctx->clipping[i], r.position) && !is_inside(ctx->clipping[i], r.position + r.size)) + return true; + } + if(ctx->current_window) + { + Rect window_r = {0, 0, ctx->current_window->r.w, ctx->current_window->r.h}; + if(!is_inside(window_r, r.position) && !is_inside(window_r, r.position + r.size)) + return true; + } + return false; } diff --git a/code/gui/gui.h b/code/gui/gui.h index 6511411..6c2769c 100644 --- a/code/gui/gui.h +++ b/code/gui/gui.h @@ -7,6 +7,7 @@ #include "../render/2d.h" #include "../lib/text.h" #include "../lib/event.h" +#include "../lib/array.h" @@ -22,6 +23,7 @@ struct Gui_Input bool mouse_released_this_frame; s64 text_cursor_move; char text[32]; + s32 scroll_move; v2 absolute_pointer_position_last_frame; }; @@ -58,6 +60,13 @@ struct Gui_Style f32 window_corner_radius; v4 window_titlebar_color; v4 window_titlebar_color_inactive; + + f32 scrollbar_size; + f32 scrollbar_corner_radius; + f32 scrollbar_inner_bar_size; + f32 scrollbar_inner_bar_corner_radius; + v4 scrollbar_color; + v4 scrollbar_inner_bar_color; }; struct Gui_Window @@ -94,16 +103,15 @@ struct Gui_Context u64 text_length; // Windows - Gui_Window *windows; - u32 window_count; - u32 window_capacity; + Array windows; Gui_Window *current_window; r_framebuffer *old_framebuffer; + // Clipping + Array clipping; + // ID - Gui_Id *id_stack; - u32 id_count; - u32 id_capacity; + Array id_stack; Gui_Input input; Gui_Style style; @@ -150,18 +158,20 @@ v2 gui_text_compute_size(const char *text); // Button bool gui_button(Gui_Context *ctx, Rect r, const char *text); bool gui_button(Rect r, const char *text); +// @Feature: Buttons with widgets inside // Slider -bool gui_slider(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value); -bool gui_slider(Rect r, f32 min, f32 max, f32 *value); +bool gui_slider_range(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value); +bool gui_slider_range(Rect r, f32 min, f32 max, f32 *value); -bool gui_slider_text(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value, const char *text); -bool gui_slider_text(Rect r, f32 min, f32 max, f32 *value, const char *text); -// Checkbox -// Option buttons -// Combo box -// Tooltips +bool gui_slider_text(Gui_Context *ctx, Rect r, f32 *ratio, const char *text); // ratio must be between 0 and 1. +bool gui_slider_text(Rect r, f32 *ratio, const char *text); + +// @Feature: Checkbox +// @Feature: Option buttons +// @Feature: Combo box +// @Feature: Tooltips // Images bool gui_image(Gui_Context *ctx, Rect r, r_texture *texture); @@ -178,13 +188,38 @@ void gui_panel(Gui_Context *ctx, Rect r); void gui_panel(Rect r); // Windows +struct Gui_Titlebar_State +{ + v2 move; + v2 anchor_point; + bool close; +}; + +struct Gui_Window_Titlebar_State +{ + Rect inner_r; + Gui_Titlebar_State titlebar; +}; + bool gui_window_start(Gui_Context *ctx, Rect r, Gui_Id id); // You have to provide some kind of unique id to identify windows bool gui_window_start(Rect r, Gui_Id id); +bool gui_window_with_titlebar_start(Gui_Context *ctx, Rect r, const char *title, Gui_Window_Titlebar_State *state); +bool gui_window_with_titlebar_start(Rect r, const char *title, Gui_Window_Titlebar_State *state); void gui_window_end(Gui_Context *ctx); void gui_window_end(); -bool gui_window_titlebar(Gui_Context *ctx, Rect r, const char *title, bool *close, v2 *move); -bool gui_window_titlebar(Rect r, const char *title, bool *close, v2 *move); +// Standalone titlebar for your custom windows +bool gui_window_titlebar(Gui_Context *ctx, Rect r, const char *title, Gui_Titlebar_State *state); +bool gui_window_titlebar(Rect r, const char *title, Gui_Titlebar_State *state); + + +// @Feature: Panel +// If you want to change which zone is displayed, change position in displayed_r. size will always be computed from r after removing the scrollbar size +bool gui_scrollable_area_start(Gui_Context *ctx, Rect r, v2 area_size, Rect *displayed_r); +bool gui_scrollable_area_start(Rect r, v2 area_size, Rect *displayed_r); +void gui_scrollable_area_end(Gui_Context *ctx); +void gui_scrollable_area_end(); + Gui_Window *gui_window_by_id(Gui_Context *ctx, Rect r, Gui_Id id); // Rect r might be needed for creation void gui_window_update_rect(Gui_Context *ctx, Gui_Window *window, Rect r); @@ -199,4 +234,9 @@ Gui_Id gui_id_from_pointer(Gui_Context *ctx, const void* ptr); void gui_id_stack_push(Gui_Context *ctx, Gui_Id id); void gui_id_stack_pop(Gui_Context *ctx); +// Clipping +void gui_clip_start(Gui_Context *ctx, Rect r); +void gui_clip_end(Gui_Context *ctx); +bool gui_is_clipped(Gui_Context *ctx, Rect r); // Returns true only if completely clipped + #endif diff --git a/code/lib/array.h b/code/lib/array.h new file mode 100644 index 0000000..9c76ae6 --- /dev/null +++ b/code/lib/array.h @@ -0,0 +1,77 @@ +#ifndef _PIUMA_LIB_ARRAY_H_ +#define _PIUMA_LIB_ARRAY_H_ + +#include "types.h" +#include "math.h" +#include + +template +struct Array +{ + T *data = NULL; + u64 count = 0; + u64 capacity = 0; + + inline void reserve(u64 new_capacity = 4) + { + new_capacity = maximum(new_capacity, count); + if(new_capacity > capacity) + { + data = (T*)realloc(data, sizeof(T) * new_capacity); + capacity = new_capacity; + } + } + + inline void clear() + { + count = 0; + } + + inline void reset() + { + free(data); + data = NULL; + count = 0; + capacity = 0; + } + + inline void push(T element) + { + if(count + 1 >= capacity) + reserve(maximum(count + 1, capacity * 2)); + data[count++] = element; + } + + inline void push_unchecked(T element) + { + data[count++] = element; + } + + inline bool pop(T *element = NULL) + { + if(count <= 0) + return false; + count--; + if(element) *element = data[count]; + return true; + } + + inline T pop_unchecked() + { + return data[--count]; + } + + inline T &operator[](u64 index) + { + return data[index]; + } + + inline T &last() + { + return data[count - 1]; + } +}; + + + +#endif diff --git a/code/lib/geometry.h b/code/lib/geometry.h index 4315ad2..3bc2e83 100644 --- a/code/lib/geometry.h +++ b/code/lib/geometry.h @@ -37,9 +37,9 @@ struct Box inline bool is_inside(Box b, v3 p) { return - (p.x < b.min.x || p.x > b.max.x) || - (p.y < b.min.y || p.y > b.max.y) || - (p.z < b.min.z || p.z > b.max.z); + (p.x > b.min.x && p.x < b.max.x) && + (p.y > b.min.y && p.y < b.max.y) && + (p.z > b.min.z && p.z < b.max.z); } inline bool overlaps(Box a, Box b) @@ -67,7 +67,7 @@ inline Box box_from_point_cloud(v3 *points, u32 count) Box box; box.min = points[0]; box.max = points[0]; - for(u32 i = 0; i < count; i++) + for(u32 i = 1; i < count; i++) { v3 p = points[i]; box.min.x = minimum(box.min.x, p.x); @@ -302,6 +302,17 @@ inline m4 scale(f32 factor) } +// Other geometric algebra +inline void compute_basis(v3 a, v3 *b, v3 *c) +{ + // from https://box2d.org/posts/2014/02/computing-a-basis/ + if(abs(a.x) >= 0.57735f) + *b = {a.y, -a.x, 0.0f}; + else + *b = {0.0f, a.z, -a.y}; + *b = normalize(*b); + *c = cross(a, *b); +} // Primitives diff --git a/code/lib/math.h b/code/lib/math.h index dffb0fe..38d3b99 100644 --- a/code/lib/math.h +++ b/code/lib/math.h @@ -414,6 +414,11 @@ inline v3 V3(f32 e[3]) return v3{e[0], e[1], e[2]}; } +inline v3 V3(v4 v) +{ + return v3{v.x / v.w, v.y / v.w, v.z / v.w}; +} + inline v3s V3S(v2s a, s32 z) { return {a.x, a.y, z}; @@ -475,6 +480,11 @@ inline m3 M3(m4 m) return m3{ m.row[0].xyz, m.row[1].xyz, m.row[2].xyz }; } +inline m4 M4(m3 m) +{ + return m4{ V4(m.row[0], 0), V4(m.row[1], 0), V4(m.row[2], 0), v4{0,0,0,1} }; +} + // Operators inline v2 operator+(v2 a) @@ -1079,38 +1089,6 @@ inline v4 & operator/=(v4 &a, f32 b) // Vector functions -inline f32 length(v2 a) -{ - return sqrt(square(a.x) + square(a.y)); -} - -inline f32 length(v3 a) -{ - return sqrt(square(a.x) + square(a.y) + square(a.z)); -} - -inline f32 length(v4 a) -{ - return sqrt(square(a.x) + square(a.y) + square(a.z) + square(a.w)); -} - - -inline f32 distance(v2 a, v2 b) -{ - return length(a - b); -} - -inline f32 distance(v3 a, v3 b) -{ - return length(a - b); -} - -inline f32 distance(v4 a, v4 b) -{ - return length(a - b); -} - - inline f32 dot(v2 a, v2 b) { return a.x*b.x + a.y*b.y; @@ -1138,6 +1116,69 @@ inline v3 cross(v3 a, v3 b) } +inline f32 length(v2 a) +{ + return sqrt(dot(a,a)); +} + +inline f32 length(v3 a) +{ + return sqrt(dot(a,a)); +} + +inline f32 length(v4 a) +{ + return sqrt(dot(a,a)); +} + +inline f32 length2(v2 a) +{ + return dot(a,a); +} + +inline f32 length2(v3 a) +{ + return dot(a,a); +} + +inline f32 length2(v4 a) +{ + return dot(a,a); +} + + +inline f32 distance(v2 a, v2 b) +{ + return length(a - b); +} + +inline f32 distance(v3 a, v3 b) +{ + return length(a - b); +} + +inline f32 distance(v4 a, v4 b) +{ + return length(a - b); +} + + +inline f32 distance2(v2 a, v2 b) +{ + return length2(a - b); +} + +inline f32 distance2(v3 a, v3 b) +{ + return length2(a - b); +} + +inline f32 distance2(v4 a, v4 b) +{ + return length2(a - b); +} + + inline v2 normalize(v2 a) { return a / length(a); @@ -1209,10 +1250,6 @@ inline m3 operator*(m3 a, m3 b) result.E[2][1] = dot(a.row[2], c1); result.E[2][2] = dot(a.row[2], c2); - result.E[3][0] = dot(a.row[3], c0); - result.E[3][1] = dot(a.row[3], c1); - result.E[3][2] = dot(a.row[3], c2); - return result; } diff --git a/code/main.cpp b/code/main.cpp index d18ff09..2f26e4b 100644 --- a/code/main.cpp +++ b/code/main.cpp @@ -407,7 +407,7 @@ void system_info_gui(Gui_Layout_Grid *grid) // slider for ram? char ram[128]; snprintf(ram, 128, "%.2f/%.2f GiB", system_info.ram_used / (1024.0*1024.0*1024.0), system_info.ram_total / (1024.0*1024.0*1024.0)); - f32 ram_value = system_info.ram_used; + f32 ram_ratio = (f32)system_info.ram_used / system_info.ram_total; gui_text_aligned(layout.cell(layout.max_cells_count.x), processors, GUI_ALIGN_LEFT); @@ -416,7 +416,7 @@ void system_info_gui(Gui_Layout_Grid *grid) ram_rect.position.x += ram_text_size.x; ram_rect.size.x -= ram_text_size.x; gui_text_aligned(layout.rect(), "RAM: ", GUI_ALIGN_LEFT); - gui_slider_text(ram_rect, 0, system_info.ram_total, &ram_value, ram); + gui_slider_text(ram_rect, &ram_ratio, ram); gui_text_aligned(layout.cell(layout.max_cells_count.x), load, GUI_ALIGN_LEFT); @@ -659,8 +659,8 @@ void fs_gui(Gui_Layout_Grid *grid) layout.row(2); char space[64]; snprintf(space, 64, "%.1f/%.1f GiB", (f32)(fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available) / (1024*1024*1024), (f32)fs_info.fs[i].bytes_total / (1024*1024*1024)); char percentage[64]; snprintf(percentage, 64, "%.1f%%", (f32)(fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available) / fs_info.fs[i].bytes_total * 100); - f32 space_value = fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available; - gui_slider_text(layout.cell(layout.max_cells_count.x), 0, fs_info.fs[i].bytes_total, &space_value, space); + f32 space_ratio = (f32)(fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available) / fs_info.fs[i].bytes_total; + gui_slider_text(layout.cell(layout.max_cells_count.x), &space_ratio, space); gui_text_aligned(layout.cell(layout.max_cells_count.x), percentage, GUI_ALIGN_CENTER); }