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 ;
2024-09-27 19:46:39 +02:00
ctx - > active = 0 ;
ctx - > hot = 0 ;
ctx - > possibly_hot = 0 ;
2023-09-26 19:40:16 +02:00
ctx - > active_start_time = 0 ;
ctx - > hot_start_time = 0 ;
ctx - > active_status = 0 ;
ctx - > text_cursor_position = 0 ;
ctx - > text_length = 0 ;
2024-09-27 19:46:39 +02:00
ctx - > windows . reserve ( 2 ) ;
2023-09-26 19:40:16 +02:00
ctx - > current_window = NULL ;
2024-09-27 19:46:39 +02:00
ctx - > clipping . reserve ( ) ;
ctx - > id_stack . reserve ( ) ;
2023-09-26 19:40:16 +02:00
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 ' ;
2024-09-27 19:46:39 +02:00
ctx - > input . scroll_move = 0 ;
2023-09-26 19:40:16 +02:00
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 } ;
2024-09-27 19:46:39 +02:00
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 } ;
2023-09-26 19:40:16 +02:00
}
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
2024-09-27 19:46:39 +02:00
for ( u32 i = 0 ; i < ctx - > windows . count ; i + + )
2023-09-26 19:40:16 +02:00
{
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 ;
2024-09-27 19:46:39 +02:00
ctx - > input . scroll_move = 0 ;
ctx - > possibly_hot = 0 ;
2023-09-26 19:40:16 +02:00
}
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 ;
2024-09-27 19:46:39 +02:00
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 ;
2023-09-26 19:40:16 +02:00
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 )
{
2024-09-27 19:46:39 +02:00
if ( gui_is_clipped ( ctx , r ) ) return ;
2023-09-26 19:40:16 +02:00
// @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 )
{
2024-09-27 19:46:39 +02:00
if ( gui_is_clipped ( ctx , r ) ) return ;
2023-09-26 19:40:16 +02:00
// @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 )
{
2024-09-27 19:46:39 +02:00
if ( gui_is_clipped ( ctx , r ) ) return false ;
2023-09-26 19:40:16 +02:00
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
2024-09-27 19:46:39 +02:00
bool gui_slider_range ( Gui_Context * ctx , Rect r , f32 min , f32 max , f32 * value )
2023-09-26 19:40:16 +02:00
{
2024-09-27 19:46:39 +02:00
if ( gui_is_clipped ( ctx , r ) ) return false ;
2023-09-26 19:40:16 +02:00
2024-09-27 19:46:39 +02:00
Gui_Id widget_id = gui_id_from_pointer ( ctx , value ) ;
gui_id_stack_push ( ctx , widget_id ) ;
// Value text
char text [ 64 ] ;
snprintf ( text , 64 , " %f " , * value ) ;
2023-09-26 19:40:16 +02:00
2024-09-27 19:46:39 +02:00
// Convert value from min-max to 0-1 range
2023-09-26 19:40:16 +02:00
f32 ratio = ( * value - min ) / ( max - min ) ;
2024-09-27 19:46:39 +02:00
// Do slider
bool behaviour = gui_slider_text ( ctx , r , & ratio , text ) ;
2023-09-26 19:40:16 +02:00
2024-09-27 19:46:39 +02:00
// Re-convert value from 0-1 to min-max range
* value = clamp ( 0.0f , 1.0f , ratio ) * ( max - min ) + min ;
2023-09-26 19:40:16 +02:00
2024-09-27 19:46:39 +02:00
gui_id_stack_pop ( ctx ) ;
return behaviour ;
2023-09-26 19:40:16 +02:00
}
2024-09-27 19:46:39 +02:00
bool gui_slider_range ( Rect r , f32 min , f32 max , f32 * value )
2023-09-26 19:40:16 +02:00
{
2024-09-27 19:46:39 +02:00
return gui_slider_range ( & global_gui_state . default_context , r , min , max , value ) ;
2023-09-26 19:40:16 +02:00
}
2024-09-27 19:46:39 +02:00
// ratio must be between 0 and 1.
bool gui_slider_text ( Gui_Context * ctx , Rect r , f32 * ratio , const char * text )
2023-09-30 18:28:35 +02:00
{
2024-09-27 19:46:39 +02:00
if ( gui_is_clipped ( ctx , r ) ) return false ;
Gui_Id widget_id = gui_id_from_pointer ( ctx , ratio ) ;
2023-09-30 18:28:35 +02:00
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 ;
2024-09-27 19:46:39 +02:00
* ratio = clamp ( 0.0f , 1.0f , pointer_ratio ) ;
2023-09-30 18:28:35 +02:00
}
// 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
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 ) ;
2024-09-27 19:46:39 +02:00
fill_r . size . x = fill_r . size . x * ( * ratio ) ;
2023-09-30 18:28:35 +02:00
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 ;
}
2024-09-27 19:46:39 +02:00
bool gui_slider_text ( Rect r , f32 * ratio , const char * text )
2023-09-30 18:28:35 +02:00
{
2024-09-27 19:46:39 +02:00
return gui_slider_text ( & global_gui_state . default_context , r , ratio , text ) ;
2023-09-30 18:28:35 +02:00
}
2023-09-26 19:40:16 +02:00
// Images
bool gui_image ( Gui_Context * ctx , Rect r , r_texture * texture )
{
2024-09-27 19:46:39 +02:00
if ( gui_is_clipped ( ctx , r ) ) return false ;
2023-09-26 19:40:16 +02:00
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 )
{
2024-09-27 19:46:39 +02:00
if ( gui_is_clipped ( ctx , r ) ) return false ;
2023-09-26 19:40:16 +02:00
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 )
{
2024-09-27 19:46:39 +02:00
if ( gui_is_clipped ( ctx , r ) ) return false ;
2023-09-26 19:40:16 +02:00
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 )
{
2024-09-27 19:46:39 +02:00
// Panels
// void gui_panel(Gui_Context *ctx, Rect r);
// void gui_panel(Rect r);
//
2023-09-26 19:40:16 +02:00
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 ;
}
2024-09-27 19:46:39 +02:00
gui_clip_start ( ctx , r ) ;
2023-09-26 19:40:16 +02:00
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 ) ;
}
2024-09-27 19:46:39 +02:00
gui_clip_end ( ctx ) ;
2023-09-26 19:40:16 +02:00
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
2024-09-27 19:46:39 +02:00
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 ) ;
2024-09-27 19:46:39 +02:00
u32 window_index = window - ctx - > windows . data ;
2023-09-26 19:40:16 +02:00
bool hovered = gui_is_hovered ( ctx , id , r ) ;
if ( hovered & & ctx - > input . mouse_pressed_this_frame )
{
// Bring window on top
2024-09-27 19:46:39 +02:00
u32 move_count = ctx - > windows . count - 1 - window_index ;
2023-09-26 19:40:16 +02:00
if ( move_count > 0 )
{
Gui_Window tmp = * window ;
2024-09-27 19:46:39 +02:00
memmove ( ctx - > windows . data + window_index , ctx - > windows . data + window_index + 1 , sizeof ( Gui_Window ) * move_count ) ;
2023-09-26 19:40:16 +02:00
2024-09-27 19:46:39 +02:00
ctx - > windows . last ( ) = tmp ;
window_index = ctx - > windows . count - 1 ;
2023-09-26 19:40:16 +02:00
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 ) ;
2024-09-27 19:46:39 +02:00
bool is_inactive = window_index ! = ctx - > windows . count - 1 ;
2023-09-26 19:40:16 +02:00
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 ) ;
}
2024-09-27 19:46:39 +02:00
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 ) ;
}
2023-09-26 19:40:16 +02:00
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 ) ;
}
2024-09-27 19:46:39 +02:00
bool gui_window_titlebar ( Gui_Context * ctx , Rect r , const char * title , Gui_Titlebar_State * state )
2023-09-26 19:40:16 +02:00
{
Gui_Id widget_id = gui_id_from_pointer ( ctx , title ) ;
bool behaviour = gui_button_behaviuor ( ctx , widget_id , r ) ;
2024-09-27 19:46:39 +02:00
if ( state )
{
state - > close = false ;
state - > move = { 0 , 0 } ;
}
2023-09-26 19:40:16 +02:00
// Background
v4 titlebar_color = ctx - > style . window_titlebar_color_inactive ;
2024-09-27 19:46:39 +02:00
if ( ctx - > current_window = = & ctx - > windows . last ( ) )
2023-09-26 19:40:16 +02:00
{
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 , " ⨯ " ) )
{
2024-09-27 19:46:39 +02:00
if ( state )
state - > close = true ;
2023-09-26 19:40:16 +02:00
behaviour = true ;
}
ctx - > style = old_style ;
// Move
2024-09-27 19:46:39 +02:00
if ( state & & ctx - > active = = widget_id )
{
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 , Gui_Titlebar_State * state )
{
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 )
2023-09-26 19:40:16 +02:00
{
2024-09-27 19:46:39 +02:00
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 ) )
2023-09-26 19:40:16 +02:00
{
2024-09-27 19:46:39 +02:00
behaviour = true ;
f32 scroll_amount = relative_h / 3 ;
relative_y + = ctx - > input . scroll_move * scroll_amount ;
2023-09-26 19:40:16 +02:00
}
2024-09-27 19:46:39 +02:00
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 ) ;
2023-09-26 19:40:16 +02:00
}
2024-09-27 19:46:39 +02:00
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 ) ;
2023-09-26 19:40:16 +02:00
2024-09-27 19:46:39 +02:00
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 ;
2023-09-26 19:40:16 +02:00
}
2024-09-27 19:46:39 +02:00
bool gui_scrollable_area_start ( Rect r , v2 area_size , Rect * displayed_r )
2023-09-26 19:40:16 +02:00
{
2024-09-27 19:46:39 +02:00
return gui_scrollable_area_start ( & global_gui_state . default_context , r , area_size , displayed_r ) ;
2023-09-26 19:40:16 +02:00
}
2024-09-27 19:46:39 +02:00
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 ) ;
}
2023-09-26 19:40:16 +02:00
Gui_Window * gui_window_by_id ( Gui_Context * ctx , Rect r , Gui_Id id )
{
Gui_Window * window = NULL ;
2024-09-27 19:46:39 +02:00
for ( u32 i = 0 ; i < ctx - > windows . count ; i + + )
2023-09-26 19:40:16 +02:00
{
if ( ctx - > windows [ i ] . id = = id )
{
window = & ctx - > windows [ i ] ;
break ;
}
}
if ( ! window )
{
2024-09-27 19:46:39 +02:00
Gui_Window w = {
. id = id ,
. r = r ,
. framebuffer = r_framebuffer_create ( V2S ( r . size ) , 0 )
} ;
ctx - > windows . push ( w ) ;
window = & ctx - > windows . last ( ) ;
2023-09-26 19:40:16 +02:00
}
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 ) )
{
2024-09-27 19:46:39 +02:00
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 ;
}
2023-09-26 19:40:16 +02:00
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 )
2024-09-27 19:46:39 +02:00
{
current_window_index = ctx - > current_window - ctx - > windows . data ;
if ( ! is_inside ( ctx - > current_window - > r , ctx - > input . absolute_pointer_position ) )
return false ;
}
2023-09-26 19:40:16 +02:00
// 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.
2024-09-27 19:46:39 +02:00
for ( s32 i = current_window_index + 1 ; i < ctx - > windows . count ; i + + )
2023-09-26 19:40:16 +02:00
{
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
2024-09-27 19:46:39 +02:00
for ( u32 i = current_window_index + 1 ; i < ctx - > windows . count ; i + + )
2023-09-26 19:40:16 +02:00
{
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 )
{
2024-09-27 19:46:39 +02:00
ctx - > active = 0 ;
2023-09-26 19:40:16 +02:00
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 )
{
2024-09-27 19:46:39 +02:00
// ctx->active = 0;
2023-09-26 19:40:16 +02:00
// ctx->active_status = 0;
}
return behaviour ;
}
Gui_Id gui_id_from_pointer ( Gui_Context * ctx , const void * ptr )
{
u32 seed = 0xFFFFFFFF ;
2024-09-27 19:46:39 +02:00
if ( ctx - > id_stack . count )
seed = ctx - > id_stack . last ( ) ;
2023-09-26 19:40:16 +02:00
return hash_crc32 ( & ptr , sizeof ( void * ) , seed ) ;
}
void gui_id_stack_push ( Gui_Context * ctx , Gui_Id id )
{
2024-09-27 19:46:39 +02:00
ctx - > id_stack . push ( id ) ;
2023-09-26 19:40:16 +02:00
}
void gui_id_stack_pop ( Gui_Context * ctx )
{
2024-09-27 19:46:39 +02:00
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 ;
2023-09-26 19:40:16 +02:00
}