2023-09-26 19:40:16 +02:00
# 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 ) ;
}
2023-09-30 02:39:12 +02:00
// 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 ) ;
}
2023-09-26 19:40:16 +02:00
// 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 - - ;
}