Files
Server_Monitor/code/gui/gui.cpp

829 lines
24 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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);
}
// 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--;
}