summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraura <nw@moneybot.cc>2026-08-11 19:10:16 +0200
committeraura <nw@moneybot.cc>2026-08-11 19:10:16 +0200
commit17c07b5f884453130d8be3a6d91d730d80d4f8ae (patch)
tree6ec11e5e5f7f0be9ed899196c2897aa76a907de2
parent1aa17de9188790f049c0edc4281597cb4e535627 (diff)
parent0a9d7ce6a0764a87766ebb23cc1ece4c26c8c678 (diff)
Merge remote-tracking branch 'origin/day'
-rw-r--r--assets/shaders/2d.fsh2
-rw-r--r--src/editor/editor.cpp6
-rw-r--r--src/editor/editor.h36
-rw-r--r--src/editor/editor_contextmenu.cpp254
-rw-r--r--src/editor/editor_menubar.cpp58
-rw-r--r--src/editor/editor_window.cpp12
-rw-r--r--src/game.cpp39
-rw-r--r--src/game.h3
-rw-r--r--src/game/assets.cpp12
-rw-r--r--src/game/assets.h3
-rw-r--r--src/game/physics/movement.cpp16
-rw-r--r--src/game/player.cpp2
-rw-r--r--src/game/vars.cpp218
-rw-r--r--src/game/vars.h47
-rw-r--r--src/gui/base.cpp70
-rw-r--r--src/gui/base.h86
-rw-r--r--src/gui/console.cpp1779
-rw-r--r--src/gui/console.h67
-rw-r--r--src/gui/contextmenu.cpp437
-rw-r--r--src/gui/textbox.cpp1011
-rw-r--r--src/main.cpp2
-rw-r--r--src/render/gl.cpp41
-rw-r--r--src/util/aabb.h12
-rw-r--r--src/util/allocator.h14
-rw-r--r--src/util/anim.h4
-rw-r--r--src/util/config/config.cpp2
-rw-r--r--src/util/console.cpp12
-rw-r--r--src/util/console.h883
-rw-r--r--src/util/input.cpp20
-rw-r--r--src/util/input.h7
-rw-r--r--src/util/profiler.cpp2
-rw-r--r--src/util/screen.h4
-rw-r--r--src/util/thread.h10
-rw-r--r--src/util/vector.h16
34 files changed, 4587 insertions, 600 deletions
diff --git a/assets/shaders/2d.fsh b/assets/shaders/2d.fsh
index 5702970..ae24dc2 100644
--- a/assets/shaders/2d.fsh
+++ b/assets/shaders/2d.fsh
@@ -15,7 +15,7 @@ void main() {
if( g_sampler != SAMPLER_ID_NONE ) {
vec4 tex_color = batch_sample( g_texcoord );
color *= tex_color;
- if (color.a < 0.01) discard;
+ if(color.a < 0.01) discard;
}
gl_FragColor = color;
}
diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp
index 2e76039..18ae062 100644
--- a/src/editor/editor.cpp
+++ b/src/editor/editor.cpp
@@ -648,6 +648,7 @@ GAME_EDITOR* editor_create( GAME_DATA* game ) {
gui_init( game );
e->wnd = gui_editorwindow( 800, 600 );
+ game->console_window = gui_consolewindow( 10, 10, 256, 128 );
gui_end();
return e;
@@ -663,6 +664,7 @@ STAT editor_close( GAME_EDITOR* e ) {
I32 w = e->wnd->w, h = e->wnd->h;
gui_free( e->wnd );
+ e->wnd = 0;
editor_clear_gui_state_refs( e );
e->wnd = gui_editorwindow( w, h );
} );
@@ -701,8 +703,8 @@ STAT editor_new_map( GAME_EDITOR* e, const char* mapname ) {
m->props.push( { .clr = { 1.f, 1.f, 1.f, 1.f } } );
m->walls.push( {
.propid = 0,
- .start = { 0 , 0, -40.f },
- .end = { 10.f, 0, 80.f },
+ .start = { 0.f, 0.f, -40.f },
+ .end = { 10.f, 0.f, 80.f },
} );
CFG_SECTION* map_cfg = map_serialize( m );
diff --git a/src/editor/editor.h b/src/editor/editor.h
index 8838d8f..682ccc8 100644
--- a/src/editor/editor.h
+++ b/src/editor/editor.h
@@ -74,19 +74,6 @@ enum EditorObjectType_t {
EDITOR_OBJECT_ENTITY = 1
};
-typedef void( *EDITOR_CONTEXTMENU_CALLBACK )( void* data );
-struct EDITOR_CONTEXTMENU_ITEM;
-typedef U8( *EDITOR_CONTEXTMENU_ENABLED_CALLBACK )( EDITOR_CONTEXTMENU_ITEM* item );
-
-struct EDITOR_CONTEXTMENU_ITEM {
- char text[64]{};
- void* data{};
- EDITOR_CONTEXTMENU_CALLBACK cb{};
- EDITOR_CONTEXTMENU_ENABLED_CALLBACK enabled_cb{};
- U8 enabled{1};
- LIST<EDITOR_CONTEXTMENU_ITEM> items{};
-};
-
struct GAME_EDITOR;
typedef void( *EDITOR_GRID_DEP_CALLBACK )( GAME_EDITOR* e );
@@ -189,7 +176,7 @@ struct GAME_EDITOR {
I32 view_mode{};
I32 view2d_type{};
- struct GUI_EDITOR_CONTEXTMENU* contextmenu{};
+ struct GUI_CONTEXTMENU* contextmenu{};
GUI_BASE* header_toolbar{};
GUI_LABEL* header_viewtype_label{};
GUI_BUTTON* header_back{};
@@ -276,17 +263,6 @@ extern void editor_undo_record_change_prop( GAME_EDITOR* e, FNV1A hash, void* ol
extern U8 editor_undo( GAME_EDITOR* e );
extern U8 editor_redo( GAME_EDITOR* e );
-extern void editor_show_contextmenu( I32 x, I32 y, LIST<EDITOR_CONTEXTMENU_ITEM>* items, const char* title = 0 );
-extern void editor_hide_contextmenu();
-extern U8 editor_contextmenu_open();
-extern void editor_contextmenu_set_item(
- EDITOR_CONTEXTMENU_ITEM* item,
- const char* text,
- EDITOR_CONTEXTMENU_CALLBACK cb = 0,
- void* data = 0,
- EDITOR_CONTEXTMENU_ENABLED_CALLBACK enabled_cb = 0
-);
-
struct GUI_EDITORWINDOW : GUI_WINDOW {};
struct GUI_EDITOR_3DVIEW : GUI_VIEW {
U8 heldoutbounds;
@@ -379,19 +355,11 @@ struct GUI_EDITOR_VIEWTYPE_SEGMENT : GUI_BASE {
I32 held_seg{-1};
};
-struct GUI_EDITOR_CONTEXTMENU : GUI_BASE {
- LIST<EDITOR_CONTEXTMENU_ITEM>* items{};
- U8 initheld{1};
- U8 held{};
- I32 held_idx{-1};
- char title[64]{};
-};
-
struct GUI_EDITOR_TOOLBAR : GUI_BASE {
U8 held{};
I32 held_root{-1};
I32 active_root{-1};
- LIST<EDITOR_CONTEXTMENU_ITEM> entries{};
+ LIST<GUI_CONTEXTMENU_ITEM> entries{};
};
extern GUI_EDITORWINDOW* gui_editorwindow( I32 w, I32 h );
diff --git a/src/editor/editor_contextmenu.cpp b/src/editor/editor_contextmenu.cpp
deleted file mode 100644
index 4f9dbe6..0000000
--- a/src/editor/editor_contextmenu.cpp
+++ /dev/null
@@ -1,254 +0,0 @@
-#include "editor_gui_internal.h"
-
-U8 editor_contextmenu_item_enabled( EDITOR_CONTEXTMENU_ITEM* item ) {
- if( !item )
- return 0;
-
- if( item->enabled_cb )
- return item->enabled_cb( item );
-
- return item->enabled;
-}
-
-I32 editor_contextmenu_title_h( GUI_EDITOR_CONTEXTMENU* menu ) {
- return ( menu && menu->title[0] ) ? 18 : 0;
-}
-
-I32 editor_contextmenu_row_h() {
- return 20;
-}
-
-I32 editor_contextmenu_width( LIST<EDITOR_CONTEXTMENU_ITEM>* items, const char* title ) {
- I32 width = EDITOR_TOOLBAR_DROPDOWN_W;
- if( title && title[0] ) {
- I32 text_w = 0;
- gui_draw_get_str_bounds( &text_w, 0, FNT_JPN12, "%s", title );
- width = max( width, text_w + 18 );
- }
-
- if( !items )
- return width;
-
- for( I32 i = 0; i < (I32)items->size; ++i ) {
- I32 text_w = 0;
- gui_draw_get_str_bounds( &text_w, 0, FNT_JPN12, "%s", ( *items )[i].text );
- width = max( width, text_w + 20 );
- }
-
- return width;
-}
-
-I32 editor_contextmenu_height( LIST<EDITOR_CONTEXTMENU_ITEM>* items, const char* title ) {
- I32 height = 4 + editor_contextmenu_row_h() * ( items ? (I32)items->size : 0 );
- if( title && title[0] )
- height += 18 + 1;
- return max( height, editor_contextmenu_row_h() + 4 );
-}
-
-static I32 editor_contextmenu_items_y( GUI_EDITOR_CONTEXTMENU* menu ) {
- I32 y = gui_rely( menu ) + 2;
- if( menu->title[0] )
- y += editor_contextmenu_title_h( menu ) + 1;
- return y;
-}
-
-I32 editor_contextmenu_hit_test( GUI_EDITOR_CONTEXTMENU* menu, I32 mx, I32 my ) {
- if( !menu || !menu->items )
- return -1;
-
- I32 x = gui_relx( menu );
- I32 y = gui_rely( menu );
- if( mx < x || mx >= x + menu->w || my < y || my >= y + menu->h )
- return -1;
-
- I32 items_y = editor_contextmenu_items_y( menu );
- if( my < items_y )
- return -1;
-
- I32 idx = ( my - items_y ) / editor_contextmenu_row_h();
- if( idx < 0 || idx >= (I32)menu->items->size )
- return -1;
-
- return idx;
-}
-
-void gui_editor_contextmenu_draw_fn( void* ptr ) {
- GUI_EDITOR_CONTEXTMENU* menu = (GUI_EDITOR_CONTEXTMENU*)ptr;
- if( !menu || !menu->items || !menu->items->size )
- return;
-
- I32 x = gui_relx( menu );
- I32 y = gui_rely( menu );
- I32 mx = 0, my = 0;
- if( editor_menu_hover_mask_active ) {
- mx = editor_menu_hover_real_x;
- my = editor_menu_hover_real_y;
- } else {
- gui_cursor_pos( &mx, &my );
- }
- I32 hit = editor_contextmenu_hit_test( menu, mx, my );
- U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
- CLR hover_fill = CLR::blend( ui_clr.border, ui_clr.bg, 0.70f );
- CLR active_fill = CLR::blend( ui_clr.bg_sec, ui_clr.border, 0.22f );
-
- CLR border = gui_is_fg_window( menu ) ? ui_clr.border : ui_clr.border_inactive;
- gui_draw_frect( x, y, menu->w, menu->h, border );
- gui_draw_frect( x + 1, y + 1, max( 1, menu->w - 2 ), max( 1, menu->h - 2 ), ui_clr.bg_sec );
-
- I32 cy = y + 2;
- if( menu->title[0] ) {
- gui_draw_str( x + 8, cy + 1, ALIGN_L, FNT_JPN12, ui_clr.txt, "%s", menu->title );
- cy += editor_contextmenu_title_h( menu );
- gui_draw_line( x + 1, cy, x + menu->w - 2, cy, border );
- cy += 1;
- }
-
- for( I32 i = 0; i < (I32)menu->items->size; ++i ) {
- EDITOR_CONTEXTMENU_ITEM* item = &( *menu->items )[i];
- U8 enabled = editor_contextmenu_item_enabled( item );
- U8 hovered = hit == i;
- U8 active = menu->held && menu->held_idx == i && m1 && enabled;
-
- CLR fill = ui_clr.bg_sec;
- if( active )
- fill = active_fill;
- else if( hovered )
- fill = hover_fill;
-
- gui_draw_frect( x + 1, cy + i * editor_contextmenu_row_h(), max( 1, menu->w - 2 ), editor_contextmenu_row_h(), fill );
- gui_draw_str(
- x + 8,
- cy + i * editor_contextmenu_row_h() + 2,
- ALIGN_L,
- FNT_JPN12,
- enabled ? ui_clr.txt : ui_clr.txt_inactive,
- "%s",
- item->text
- );
- }
-}
-
-void gui_editor_contextmenu_input_fn( void* ptr ) {
- GUI_EDITOR_CONTEXTMENU* menu = (GUI_EDITOR_CONTEXTMENU*)ptr;
- if( !menu || !menu->items || !menu->items->size )
- return;
-
- U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
- U8 m2 = gui_mbutton_down( GUI_MBTNRIGHT );
- if( menu->initheld ) {
- if( m1 || m2 )
- return;
- menu->initheld = 0;
- return;
- }
-
- I32 mx = 0, my = 0;
- gui_cursor_pos( &mx, &my );
- I32 hit = editor_contextmenu_hit_test( menu, mx, my );
-
- if( m1 || m2 ) {
- if( !menu->held ) {
- menu->held = 1;
- menu->held_idx = hit;
- }
- return;
- }
-
- if( !menu->held )
- return;
-
- if( hit == -1 ) {
- editor_hide_contextmenu();
- return;
- }
-
- if( hit == menu->held_idx ) {
- EDITOR_CONTEXTMENU_ITEM* item = &( *menu->items )[hit];
- if( editor_contextmenu_item_enabled( item ) && item->cb ) {
- item->cb( item->data );
- editor_hide_contextmenu();
- return;
- }
- }
-
- menu->held = 0;
- menu->held_idx = -1;
-}
-
-void editor_hide_contextmenu() {
- if( !editor || !editor->gui.contextmenu )
- return;
-
- GUI_EDITOR_CONTEXTMENU* menu = editor->gui.contextmenu;
- editor->gui.contextmenu = 0;
-
- if( menu->parent ) {
- I32 idx = menu->parent->children.idx_of( (GUI_BASE*)menu );
- if( idx != -1 )
- menu->parent->children.erase( idx );
- }
-
- GUI_EDITOR_TOOLBAR* bar = (GUI_EDITOR_TOOLBAR*)editor->gui.header_toolbar;
- if( bar )
- bar->active_root = -1;
-
- gui_free( menu );
-}
-
-U8 editor_contextmenu_open() {
- return editor && editor->gui.contextmenu && editor->gui.contextmenu->enabled;
-}
-
-
-void editor_contextmenu_set_item(
- EDITOR_CONTEXTMENU_ITEM* item,
- const char* text,
- EDITOR_CONTEXTMENU_CALLBACK cb,
- void* data,
- EDITOR_CONTEXTMENU_ENABLED_CALLBACK enabled_cb
-) {
- if( !item )
- return;
-
- item->items.clear();
- snprintf( item->text, sizeof( item->text ), "%s", text ? text : "" );
- item->cb = cb;
- item->data = data;
- item->enabled = 1;
- item->enabled_cb = enabled_cb;
-}
-
-void editor_show_contextmenu( I32 x, I32 y, LIST<EDITOR_CONTEXTMENU_ITEM>* items, const char* title ) {
- editor_hide_contextmenu();
- if( !editor || !editor->wnd || !items || !items->size )
- return;
-
- GUI_EDITOR_CONTEXTMENU* menu = new GUI_EDITOR_CONTEXTMENU;
- menu->items = items;
- menu->draw_fn = gui_editor_contextmenu_draw_fn;
- menu->input_fn = gui_editor_contextmenu_input_fn;
- menu->enabled = 1;
- menu->initheld = 1;
- menu->held = 0;
- menu->held_idx = -1;
- menu->parent = editor->wnd;
- snprintf( menu->name, sizeof( menu->name ), "EDITOR_CONTEXTMENU" );
- snprintf( menu->title, sizeof( menu->title ), "%s", title ? title : "" );
-
- menu->w = editor_contextmenu_width( items, title );
- menu->h = editor_contextmenu_height( items, title );
- menu->xbound = menu->w;
- menu->ybound = menu->h;
-
- if( x + menu->w > editor->wnd->w - 2 )
- x = max( 1, editor->wnd->w - menu->w - 2 );
- if( y + menu->h > editor->wnd->h - 2 )
- y = max( 1, editor->wnd->h - menu->h - 2 );
- if( x < 1 ) x = 1;
- if( y < 1 ) y = 1;
-
- menu->x = x;
- menu->y = y;
- editor->wnd->children.push( menu );
- editor->gui.contextmenu = menu;
-}
diff --git a/src/editor/editor_menubar.cpp b/src/editor/editor_menubar.cpp
index cef2650..0a6f661 100644
--- a/src/editor/editor_menubar.cpp
+++ b/src/editor/editor_menubar.cpp
@@ -1,3 +1,4 @@
+#include "editor.h"
#include "editor_gui_internal.h"
U8 editor_menu_hover_mask_active = 0;
@@ -36,7 +37,15 @@ void editor_apply_view_mode( GAME_EDITOR* e ) {
}
void editor_toolbar_close_menu() {
- editor_hide_contextmenu();
+ if( !editor )
+ return;
+
+ GUI_EDITOR_TOOLBAR* tb = (GUI_EDITOR_TOOLBAR*)editor->gui.header_toolbar;
+
+ if( tb )
+ tb->active_root = -1;
+
+ gui_contextmenu_hide( editor->gui.contextmenu );
}
void editor_set_view_mode( I32 mode ) {
@@ -89,11 +98,11 @@ void editor_contextmenu_set_tool_cb( void* data ) {
editor_settool( (U8)(I64)data );
}
-U8 editor_contextmenu_undo_enabled( EDITOR_CONTEXTMENU_ITEM* ) {
+U8 editor_contextmenu_undo_enabled( GUI_CONTEXTMENU_ITEM* ) {
return editor && editor->undo_actions.size > 0;
}
-U8 editor_contextmenu_redo_enabled( EDITOR_CONTEXTMENU_ITEM* ) {
+U8 editor_contextmenu_redo_enabled( GUI_CONTEXTMENU_ITEM* ) {
return editor && editor->redo_actions.size > 0;
}
@@ -104,31 +113,31 @@ void editor_toolbar_init_entries( GUI_EDITOR_TOOLBAR* bar ) {
bar->entries.clear();
bar->entries.resize( 4 );
- editor_contextmenu_set_item( &bar->entries[0], "file" );
+ gui_contextmenu_set_item( &bar->entries[0], "file" );
bar->entries[0].items.resize( 1 );
- editor_contextmenu_set_item( &bar->entries[0].items[0], "save", editor_contextmenu_save_cb );
+ gui_contextmenu_set_item( &bar->entries[0].items[0], "save", editor_contextmenu_save_cb );
- editor_contextmenu_set_item( &bar->entries[1], "edit" );
+ gui_contextmenu_set_item( &bar->entries[1], "edit" );
bar->entries[1].items.resize( 2 );
- editor_contextmenu_set_item( &bar->entries[1].items[0], "undo", editor_contextmenu_undo_cb, 0, editor_contextmenu_undo_enabled );
- editor_contextmenu_set_item( &bar->entries[1].items[1], "redo", editor_contextmenu_redo_cb, 0, editor_contextmenu_redo_enabled );
+ gui_contextmenu_set_item( &bar->entries[1].items[0], "undo", editor_contextmenu_undo_cb, 0, editor_contextmenu_undo_enabled );
+ gui_contextmenu_set_item( &bar->entries[1].items[1], "redo", editor_contextmenu_redo_cb, 0, editor_contextmenu_redo_enabled );
- editor_contextmenu_set_item( &bar->entries[2], "view" );
+ gui_contextmenu_set_item( &bar->entries[2], "view" );
bar->entries[2].items.resize( 3 );
- editor_contextmenu_set_item( &bar->entries[2].items[0], "2d view", editor_contextmenu_set_view_mode_cb, (void*)(I64)EDITOR_VIEWMODE_2D );
- editor_contextmenu_set_item( &bar->entries[2].items[1], "3d view", editor_contextmenu_set_view_mode_cb, (void*)(I64)EDITOR_VIEWMODE_3D );
- editor_contextmenu_set_item( &bar->entries[2].items[2], "simulation", editor_contextmenu_set_view_mode_cb, (void*)(I64)EDITOR_VIEWMODE_SIM );
+ gui_contextmenu_set_item( &bar->entries[2].items[0], "2d view", editor_contextmenu_set_view_mode_cb, (void*)(I64)EDITOR_VIEWMODE_2D );
+ gui_contextmenu_set_item( &bar->entries[2].items[1], "3d view", editor_contextmenu_set_view_mode_cb, (void*)(I64)EDITOR_VIEWMODE_3D );
+ gui_contextmenu_set_item( &bar->entries[2].items[2], "simulation", editor_contextmenu_set_view_mode_cb, (void*)(I64)EDITOR_VIEWMODE_SIM );
- editor_contextmenu_set_item( &bar->entries[3], "tools" );
+ gui_contextmenu_set_item( &bar->entries[3], "tools" );
bar->entries[3].items.resize( 5 );
- editor_contextmenu_set_item( &bar->entries[3].items[0], "none", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_NONE );
- editor_contextmenu_set_item( &bar->entries[3].items[1], "select", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_SELECT );
- editor_contextmenu_set_item( &bar->entries[3].items[2], "wall", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_WALL );
- editor_contextmenu_set_item( &bar->entries[3].items[3], "poly", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_POLY );
- editor_contextmenu_set_item( &bar->entries[3].items[4], "object", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_OBJECT );
+ gui_contextmenu_set_item( &bar->entries[3].items[0], "none", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_NONE );
+ gui_contextmenu_set_item( &bar->entries[3].items[1], "select", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_SELECT );
+ gui_contextmenu_set_item( &bar->entries[3].items[2], "wall", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_WALL );
+ gui_contextmenu_set_item( &bar->entries[3].items[3], "poly", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_POLY );
+ gui_contextmenu_set_item( &bar->entries[3].items[4], "object", editor_contextmenu_set_tool_cb, (void*)(I64)EDITOR_TOOL_OBJECT );
}
-I32 editor_toolbar_root_width( const EDITOR_CONTEXTMENU_ITEM* entry ) {
+I32 editor_toolbar_root_width( const GUI_CONTEXTMENU_ITEM* entry ) {
if( !entry )
return 44;
@@ -196,7 +205,7 @@ void gui_editor_toolbar_draw_fn( void* ptr ) {
U8 hovered = hit.root == i;
U8 active = bar->held && bar->held_root == i && m1;
- U8 open = editor_contextmenu_open() && bar->active_root == i;
+ U8 open = gui_contextmenu_open( editor->gui.contextmenu ) && bar->active_root == i;
CLR fill = ui_clr.bg_sec;
if( active )
@@ -228,13 +237,14 @@ void gui_editor_toolbar_input_fn( void* ptr ) {
}
if( bar->held && bar->held_root == hit.root && hit.root >= 0 ) {
- if( editor_contextmenu_open() && bar->active_root == hit.root ) {
- editor_hide_contextmenu();
+ if( gui_contextmenu_open( editor->gui.contextmenu ) && bar->active_root == hit.root ) {
+ editor_toolbar_close_menu();
} else {
I32 rx = 0;
editor_toolbar_root_rect( bar, hit.root, &rx, 0, 0, 0 );
- editor_show_contextmenu( rx, gui_rely( bar ) + bar->h, &bar->entries[hit.root].items );
- bar->active_root = hit.root;
+ LIST<GUI_CONTEXTMENU_ITEM>* items = &bar->entries[hit.root].items;
+ gui_show_contextmenu( rx, gui_rely( bar ) + bar->h, items, editor->wnd, 0, items->size, &editor->gui.contextmenu );
+ bar->active_root = editor->gui.contextmenu ? hit.root : -1;
}
}
diff --git a/src/editor/editor_window.cpp b/src/editor/editor_window.cpp
index f81b1a8..5cb04c8 100644
--- a/src/editor/editor_window.cpp
+++ b/src/editor/editor_window.cpp
@@ -7,7 +7,7 @@ void gui_editorwindow_draw_fn( void* ptr ) {
editor_raise_header_toolbar( editor );
GUI_BASE* contextmenu = ( editor && editor->map ) ? (GUI_BASE*)editor->gui.contextmenu : 0;
- U8 menu_open = editor_contextmenu_open();
+ U8 menu_open = gui_contextmenu_open( (GUI_CONTEXTMENU*)contextmenu );
F32 saved_mx = input.mouse.pos.x;
F32 saved_my = input.mouse.pos.y;
@@ -34,11 +34,12 @@ void gui_editorwindow_draw_fn( void* ptr ) {
else dlog( "gui_editorwindow_draw_fn(): child %p has no draw_fn", it );
} );
+ input.mouse.pos.x = saved_mx;
+ input.mouse.pos.y = saved_my;
+
if( contextmenu && contextmenu->enabled && contextmenu->draw_fn )
contextmenu->draw_fn( contextmenu );
- input.mouse.pos.x = saved_mx;
- input.mouse.pos.y = saved_my;
editor_menu_hover_mask_active = 0;
}
@@ -48,14 +49,14 @@ static void gui_editorwindow_input_fn( void* ptr ) {
GUI_BASE* toolbar = ( editor && editor->map ) ? editor->gui.header_toolbar : 0;
GUI_BASE* contextmenu = ( editor && editor->map ) ? (GUI_BASE*)editor->gui.contextmenu : 0;
- U8 menu_was_open = editor_contextmenu_open();
+ U8 menu_was_open = gui_contextmenu_open( (GUI_CONTEXTMENU*)contextmenu );
if( toolbar && toolbar->enabled && toolbar->input_fn )
toolbar->input_fn( toolbar );
if( menu_was_open && editor && editor->gui.contextmenu && editor->gui.contextmenu->input_fn )
editor->gui.contextmenu->input_fn( editor->gui.contextmenu );
- if( menu_was_open || editor_contextmenu_open() )
+ if( menu_was_open || gui_contextmenu_open( editor ? editor->gui.contextmenu : 0 ) )
return;
wnd->children.each( fn( GUI_BASE** childptr ) {
@@ -88,6 +89,7 @@ GUI_EDITORWINDOW* gui_editorwindow( I32 w, I32 h ) {
GUI_EDITORWINDOW* wnd = gui_editorwindow_create( w, h );
GAME_EDITOR* e = editor;
LIST<GUI_LIST_ENTRY>* map_list = editor_get_map_list( e );
+
gui_list( wnd->w / 2 - 100, EDITOR_LAYOUT_CONTENT_Y, 200, 200, "map list", map_list, &editor->gui.map_select );
gui_button( wnd->w / 2 - 100, EDITOR_LAYOUT_CONTENT_Y + 220, 95, 25, "new map", editor_new_map_cb );
gui_button( wnd->w / 2 + 5, EDITOR_LAYOUT_CONTENT_Y + 220, 95, 25, "load map", editor_load_map_cb );
diff --git a/src/game.cpp b/src/game.cpp
index 7b526ae..a9cffec 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -3,6 +3,7 @@
#include "game.h"
#include "SDL_keycode.h"
+#include "SDL_mouse.h"
#include "game/physics/movement.h"
#include "game/world/bsp.h"
#include "game/world/draw.h"
@@ -17,24 +18,30 @@
#include "game/world/map.h"
#include "gui.h"
+#include "gui/base.h"
#include "render/gl_batch.h"
#include "util.h"
+#include "util/console.h"
#include "util/input.h"
#include "util/profiler.h"
#include "util/time.h"
-CVAR* g_timescale = var_new( "g_timescale", 1.f );
+CVAR* g_timescale = var_new( "g_timescale", "game timescale", 1.f );
+I32 game_quit( CVAR* self, LIST<STR> cmdline );
+CVAR* g_quit = var_new( "quit", "quit the game", &game_quit );
+U8 quit = 0;
static void game_realtime_resize_repaint( void* userdata );
GAME_DATA* game_init( GL_DATA* gl ) {
- GAME_DATA* game = (GAME_DATA*)malloc( sizeof( GAME_DATA ) );
- memset( game, 0, sizeof(GAME_DATA) );
+ GAME_DATA* game = new GAME_DATA{};
game->gl = gl;
+ console_capture_stdout_begin( &game->console );
+
profiler_init();
- VEC2 screensize = { (F32)gl->canvas_size[0], (F32)gl->canvas_size[1] };
+ VEC2 screensize = { gl->canvas_size[0], gl->canvas_size[1] };
game->shaders.gl2d = gl_2d_init( gl, screensize, "2d" );
game->shaders.gl3d = gl_3d_init( gl, screensize, "3d" );
@@ -172,6 +179,8 @@ U8 game_main_loop( GAME_DATA* game ) { _profiled
if( !OK( gl_poll_events( gl ) ) )
return 1;
+ console_pump_stdout( &game->console );
+
if( !assets_on_frame( game ) )
return 0;
@@ -205,7 +214,7 @@ U8 game_main_loop( GAME_DATA* game ) { _profiled
if( !OK( gl_endframe( gl ) ) )
return 1;
- return input_is_key_down( SDLK_ESCAPE );
+ return quit;
}
void game_on_tick( GAME_DATA* game ) { _profiled
@@ -224,14 +233,30 @@ void game_on_tick( GAME_DATA* game ) { _profiled
}
void game_destroy( GAME_DATA *game ) {
+ if( !game )
+ return;
+
+ console_capture_stdout_end( &game->console );
+ gui_shutdown();
+ game->console_window = 0;
#if IS_EDITOR
- editor_destroy( game->editor );
+ if( game->editor ) {
+ editor_destroy( game->editor );
+ game->editor = 0;
+ }
#endif
if( game->state.map )
map_free( game, game->state.map );
+ assets_destroy( game );
gl_batch_destroy( game->render.batch_2d );
gl_batch_destroy( game->render.batch_3d );
gl_destroy( game->gl );
- free( game );
+ game->gl = 0;
+ delete game;
+}
+
+I32 game_quit( CVAR* self, LIST<STR> cmdline ) {
+ quit = 1;
+ return 1;
}
diff --git a/src/game.h b/src/game.h
index a615e18..bb90a5d 100644
--- a/src/game.h
+++ b/src/game.h
@@ -6,6 +6,7 @@
#include "render/gl.h"
#include "render/gl_2d.h"
#include "render/gl_3d.h"
+#include "util/console.h"
#ifdef IS_EDITOR
#include "editor/editor.h"
@@ -44,6 +45,8 @@ struct GAME_DATA {
GAME_STATE state;
GAME_RENDER render;
+ CONSOLE_DATA console{};
+ GUI_CONSOLEWINDOW* console_window{};
#ifdef IS_EDITOR
GAME_EDITOR* editor;
#endif
diff --git a/src/game/assets.cpp b/src/game/assets.cpp
index 62663a9..6ee7aff 100644
--- a/src/game/assets.cpp
+++ b/src/game/assets.cpp
@@ -9,6 +9,18 @@
void assets_init( GAME_DATA *game ) {
game->assets.test = gl_texture_from_file( game->gl, "uvtest.png" );
game->assets.fonts_init = 0;
+
+ for( I32 idx = 0; idx < SDL_NUM_SYSTEM_CURSORS; ++idx )
+ game->assets.cursors[idx] = SDL_CreateSystemCursor( (SDL_SystemCursor)idx );
+}
+
+void assets_destroy( GAME_DATA* game ) {
+ for( I32 idx = 0; idx < SDL_NUM_SYSTEM_CURSORS; ++idx ) {
+ if( !game->assets.cursors[idx] )
+ continue;
+ SDL_FreeCursor( game->assets.cursors[idx] );
+ game->assets.cursors[idx] = 0;
+ }
}
void assets_create_fonts( struct GAME_DATA *game ) {
diff --git a/src/game/assets.h b/src/game/assets.h
index d0fe270..9841f40 100644
--- a/src/game/assets.h
+++ b/src/game/assets.h
@@ -1,9 +1,11 @@
#pragma once
#include "../util/typedef.h"
#include "../util/file.h"
+#include "SDL_mouse.h"
typedef struct {
+ SDL_Cursor* cursors[SDL_NUM_SYSTEM_CURSORS];
struct GL_FONT* font;
struct GL_FONT* jpn12;
struct GL_FONT* jpn16;
@@ -13,6 +15,7 @@ typedef struct {
} GAME_ASSETS;
extern void assets_init( struct GAME_DATA* game );
+extern void assets_destroy( struct GAME_DATA* game );
extern void assets_create_fonts( struct GAME_DATA* game );
// extensions is a null-terminated array of null-terminated strings.
extern LIST<FILE_ENTRY> assets_get_files_by_ext_dir( const char** extensions, const char* dir );
diff --git a/src/game/physics/movement.cpp b/src/game/physics/movement.cpp
index 4cb669f..ac4ab02 100644
--- a/src/game/physics/movement.cpp
+++ b/src/game/physics/movement.cpp
@@ -6,14 +6,14 @@
#include "../../game/objlist.h"
GAME_MOVEMENT* gmove;
-CVAR* mv_gravity = var_new( "mv_gravity", 800.f );
-CVAR* mv_friction = var_new( "mv_friction", 4.f );
-CVAR* mv_accelerate = var_new( "mv_accelerate", 5.f );
-CVAR* mv_airaccelerate = var_new( "mv_airaccelerate", 12.f );
-CVAR* mv_wallboost = var_new( "mv_wallboost", 0 );
-CVAR* mv_maxspeed = var_new( "mv_maxspeed", 3500.f );
-CVAR* mv_stopspeed = var_new( "mv_stopspeed", 80.f );
-CVAR* mv_jump_impulse = var_new( "mv_jump_impulse", 301.993377f ); // csgo - sqrt(2 * 800 * 57 - jump height)
+CVAR* mv_gravity = var_new( "mv_gravity", "world gravity", 800.f );
+CVAR* mv_friction = var_new( "mv_friction", "ground friction", 4.f );
+CVAR* mv_accelerate = var_new( "mv_accelerate", "ground acceleration", 5.f );
+CVAR* mv_airaccelerate = var_new( "mv_airaccelerate", "air acceleration", 12.f );
+CVAR* mv_wallboost = var_new( "mv_wallboost", "preserve speed when clipping vel against wall", 0 );
+CVAR* mv_maxspeed = var_new( "mv_maxspeed", "max vel per axis", 3500.f );
+CVAR* mv_stopspeed = var_new( "mv_stopspeed", "min speed to calc ground friction", 80.f );
+CVAR* mv_jump_impulse = var_new( "mv_jump_impulse", "jump force", 301.993377f ); // csgo - sqrt(2 * 800 * 57 - jump height)
const U32 NON_JUMP_VELOCITY = 140.f;
diff --git a/src/game/player.cpp b/src/game/player.cpp
index e4c0390..a1dc3b8 100644
--- a/src/game/player.cpp
+++ b/src/game/player.cpp
@@ -34,7 +34,7 @@ void capture_mouse( PLAYER* p ) {
F32* yaw = &p->input.cam.pos.y;
F32* pitch = &p->input.cam.pos.x;
- if( input.keys[SDL_SCANCODE_F1] ) {
+ if( input.mouselock && input.keys[SDLK_ESCAPE] ) {
input_capture_mouse( 0 );
}
diff --git a/src/game/vars.cpp b/src/game/vars.cpp
index 443699d..bc12887 100644
--- a/src/game/vars.cpp
+++ b/src/game/vars.cpp
@@ -1,5 +1,6 @@
#include "vars.h"
#include "../util.h"
+
#include <string.h>
VAR_LIST* varl;
@@ -14,12 +15,35 @@ VAR_LIST* vars_init() {
return ret;
}
-CVAR* var_new( const char* name, F32 v ) {
+inline F32 var_clamp( CVAR* var, F32 value ) {
+ if( !( var->flags & CVAR_FLAG_MINMAX ) )
+ return value;
+
+ return m_clamp(
+ value,
+ var->min_v.fl_v,
+ var->max_v.fl_v
+ );
+}
+
+inline I32 var_clamp( CVAR* var, I32 value ) {
+ if( !( var->flags & CVAR_FLAG_MINMAX ) )
+ return value;
+
+ return m_clamp(
+ value,
+ var->min_v.i_v,
+ var->max_v.i_v
+ );
+}
+
+CVAR* var_new( const char* name, STR desc, F32 v ) {
vars_check_global_list();
- CVAR* ret = new CVAR;
- memset( ret, 0 , sizeof( CVAR ) );
+ CVAR* ret = new CVAR{};
ret->type = CVAR_TYPE_FLOAT;
+ ret->default_v.fl_v = v;
+ ret->desc = desc;
ret->name = name;
ret->fl_v = v;
@@ -27,25 +51,85 @@ CVAR* var_new( const char* name, F32 v ) {
return ret;
}
-CVAR* var_new( const char* name, I32 i ) {
+CVAR* var_new(
+ const char* name,
+ STR desc,
+ F32 v,
+ F32 min_v,
+ F32 max_v
+) {
+ if( min_v > max_v ) {
+ dlog( "var_new() : invalid bounds for %s\n", name );
+ return 0;
+ }
+
vars_check_global_list();
- CVAR* ret = new CVAR;
- memset( ret, 0 , sizeof( CVAR ) );
+ CVAR* ret = new CVAR{};
+ ret->flags = CVAR_FLAG_MINMAX;
+ ret->type = CVAR_TYPE_FLOAT;
+ ret->min_v.fl_v = min_v;
+ ret->max_v.fl_v = max_v;
+ ret->desc = desc;
+ ret->name = name;
+
+ ret->fl_v = var_clamp( ret, v );
+ ret->default_v.fl_v = ret->fl_v;
+
+ varl->vars.push( ret );
+ return ret;
+}
+
+CVAR* var_new( const char* name, STR desc, I32 v ) {
+ vars_check_global_list();
+
+ CVAR* ret = new CVAR{};
ret->type = CVAR_TYPE_INT;
+ ret->default_v.i_v = v;
+ ret->desc = desc;
ret->name = name;
- ret->i_v = i;
+ ret->i_v = v;
varl->vars.push( ret );
return ret;
}
-CVAR* var_new( const char* name, CLR c ) {
+CVAR* var_new(
+ const char* name,
+ STR desc,
+ I32 v,
+ I32 min_v,
+ I32 max_v
+) {
+ if( min_v > max_v ) {
+ dlog( "var_new() : invalid bounds for %s\n", name );
+ return 0;
+ }
+
vars_check_global_list();
- CVAR* ret = new CVAR;
- memset( ret, 0 , sizeof( CVAR ) );
+ CVAR* ret = new CVAR{};
+ ret->flags = CVAR_FLAG_MINMAX;
+ ret->type = CVAR_TYPE_INT;
+ ret->min_v.i_v = min_v;
+ ret->max_v.i_v = max_v;
+ ret->desc = desc;
+ ret->name = name;
+
+ ret->i_v = var_clamp( ret, v );
+ ret->default_v.i_v = ret->i_v;
+
+ varl->vars.push( ret );
+ return ret;
+}
+
+CVAR* var_new( const char* name, STR desc, CLR c ) {
+ vars_check_global_list();
+
+ CVAR* ret = new CVAR{};
ret->type = CVAR_TYPE_COLOR;
+ ret->default_v.clr_v = c;
+ ret->desc = desc;
ret->name = name;
ret->clr_v = c;
@@ -53,25 +137,34 @@ CVAR* var_new( const char* name, CLR c ) {
return ret;
}
-CVAR* var_new( const char* name, CVAR_FUNC func ) {
+CVAR* var_new( const char* name, STR desc, CVAR_FUNC func ) {
vars_check_global_list();
- CVAR* ret = new CVAR;
- memset( ret, 0 , sizeof( CVAR ) );
+ CVAR* ret = new CVAR{};
ret->type = CVAR_TYPE_FN;
ret->name = name;
ret->fn_v = func;
+ ret->desc = desc;
varl->vars.push( ret );
return ret;
}
-CVAR* var_new( const char* name, const char* v ) {
+CVAR* var_new( const char* name, STR desc, const char* v ) {
vars_check_global_list();
- CVAR* ret = new CVAR;
- memset( ret, 0, sizeof( CVAR ) );
+ CVAR* ret = new CVAR{};
+
+ if( !v || strlen( v ) >= sizeof( ret->str_v ) ) {
+ dlog( "var_new() : invalid initial string for %s\n", name );
+ delete ret;
+ return 0;
+ }
+
+ strcpy( ret->default_v.str_v, v );
ret->type = CVAR_TYPE_STRING;
+ strcpy( ret->str_v, v );
+ ret->desc = desc;
ret->name = name;
varl->vars.push( ret );
@@ -80,22 +173,22 @@ CVAR* var_new( const char* name, const char* v ) {
CVAR* var_find( const char* name ) {
if( !varl ) {
- dlog( "var_new() : error: varl null\n" );
+ dlog( "var_find() : error: varl null\n" );
return 0;
}
- U32 len = strlen( name );
+ U32 len = (U32)strlen( name );
for( U32 i = 0; i < varl->vars.size; ++i ) {
- U32 len2 = strlen( varl->vars[i]->name );
- if( strncmp( varl->vars[i]->name, name, min( len, len2 ) ) == 0 ) {
- return varl->vars[i];
- }
+ CVAR* cvar = varl->vars[i];
+ U32 len2 = (U32)strlen( cvar->name );
+ if( strncmp( cvar->name, name, min( len, len2 ) ) )
+ continue;
+ return cvar;
}
-
return 0;
}
-I32 var_call( CVAR *cmdvar, const char *cmdline ) {
+I32 var_call( CVAR *cmdvar, LIST<STR> cmdline ) {
if( cmdvar->type != CVAR_TYPE_FN ) {
dlog( "var_call() : error: not a function\n" );
return -1;
@@ -128,29 +221,34 @@ CLR var_getclr( CVAR *var ) {
}
}
-const char* var_gets( CVAR *var ) {
+STR var_gets( const CVAR *var ) {
+ if( !var )
+ return {};
+
switch( var->type ) {
- case CVAR_TYPE_STRING: return var->str_v;
- case CVAR_TYPE_FLOAT:
- sprintf( var->str_v + sizeof(F32), "%f", var->fl_v );
- return var->str_v + sizeof(F32);
- case CVAR_TYPE_INT:
- sprintf( var->str_v + sizeof(I32), "%d", var->i_v );
- return var->str_v + sizeof(I32);
+ case CVAR_TYPE_STRING: return STR( "%s", var->str_v );
case CVAR_TYPE_COLOR:
- sprintf( var->str_v + sizeof(CLR), "%f %f %f %f", var->clr_v.r, var->clr_v.g, var->clr_v.b, var->clr_v.a );
- return var->str_v + sizeof(CLR);
- default:
- return "";
+ return STR(
+ "%#.9g %#.9g %#.9g %#.9g",
+ (F64)var->clr_v.r,
+ (F64)var->clr_v.g,
+ (F64)var->clr_v.b,
+ (F64)var->clr_v.a
+ );
+ case CVAR_TYPE_FLOAT: return STR( "%#.9g", var->fl_v );
+ case CVAR_TYPE_INT: return STR( "%d", var->i_v );
+ case CVAR_TYPE_FN:
+ return STR( "<function %s>", var->name.data );
+ default: return {};
}
}
void var_set( CVAR* var, F32 v ) {
switch( var->type ) {
case CVAR_TYPE_FLOAT:
- var->fl_v = v; break;
+ var->fl_v = var_clamp( var, v ); break;
case CVAR_TYPE_INT:
- var->i_v = (I32)v; break;
+ var->i_v = var_clamp( var, (I32)v ); break;
case CVAR_TYPE_COLOR:
var->clr_v = (CLR){ v, v, v, 1.f }; break;
case CVAR_TYPE_STRING:
@@ -162,9 +260,9 @@ void var_set( CVAR* var, F32 v ) {
void var_set( CVAR* var, I32 v ) {
switch( var->type ) {
case CVAR_TYPE_FLOAT:
- var->fl_v = (F32)v; break;
+ var->fl_v = var_clamp( var, (F32)v ); break;
case CVAR_TYPE_INT:
- var->i_v = v; break;
+ var->i_v = var_clamp( var, v ); break;
case CVAR_TYPE_COLOR:
var->clr_v = (CLR){ ((F32)v / 255), ((F32)v / 255), ((F32)v / 255), 1.f }; break;
case CVAR_TYPE_STRING:
@@ -179,35 +277,29 @@ void var_set( CVAR* var, CLR v ) {
var->clr_v = v; break;
case CVAR_TYPE_STRING:
strcpy( var->str_v, to_str( v ) ); break;
- default: dlog( "attempted to set color to %s, but it's not a color value", var->name.data ); break;
+ default: dlog( "attempted to set color to %s, but it's not a color value\n", var->name.data ); break;
}
}
void var_set( CVAR* var, STR v ) {
- if( v.size >= 64 ) {
- dlog( "attempted to set string to %s, but it's too long (%d/64 bytes)", var->name.data, v.size );
+ if( var->type != CVAR_TYPE_STRING ) {
+ dlog(
+ "attempted to set string to %s, "
+ "but it is not a string value\n",
+ var->name.data
+ );
return;
}
- switch( var->type ) {
- case CVAR_TYPE_FLOAT:
- F32 f; sscanf( v.data, "%f", &f );
- var->fl_v = f; break;
- case CVAR_TYPE_INT:
- var->i_v = atoi( v.data ); break;
- case CVAR_TYPE_STRING:
- strcpy( var->str_v, v.data ); break;
- case CVAR_TYPE_COLOR: {
- LIST<STR> strings = v.split( " " );
- if( strings.size < 3 || strings.size > 4 )
- break;
-
- for( U32 i = 0; i < strings.size; ++i ) {
- F32 f{}; sscanf( strings[i].data, "%f", &f );
- ( (F32*)( &var->clr_v ) )[i] = f;
- } break;
- }
- default:
- break;
+ if( v.size >= sizeof( var->str_v ) ) {
+ dlog(
+ "attempted to set string to %s, "
+ "but it's too long (%u/64 bytes)\n",
+ var->name.data,
+ v.size
+ );
+ return;
}
+
+ memcpy( var->str_v, v.data, v.size + 1 );
}
diff --git a/src/game/vars.h b/src/game/vars.h
index 253effb..754979c 100644
--- a/src/game/vars.h
+++ b/src/game/vars.h
@@ -10,11 +10,18 @@ enum CvarTypes_t {
CVAR_TYPE_LAST
};
-typedef I32(*CVAR_FUNC)( struct CVAR* self, const char* cmdline );
+enum CvarFlags_t {
+ CVAR_FLAG_NONE = 0,
+ CVAR_FLAG_MINMAX = 1 << 1,
+};
+
+typedef I32(*CVAR_FUNC)( struct CVAR* self, LIST<STR> cmdline );
struct CVAR {
STR name;
U8 type;
+ STR desc;
+ U32 flags;
union {
F32 fl_v;
I32 i_v;
@@ -30,6 +37,16 @@ struct CVAR {
CVAR_FUNC fn_v;
char str_v[64];
} default_v;
+
+ union {
+ F32 fl_v;
+ I32 i_v;
+ } min_v;
+
+ union {
+ F32 fl_v;
+ I32 i_v;
+ } max_v;
};
struct VAR_LIST {
@@ -38,18 +55,32 @@ struct VAR_LIST {
extern VAR_LIST* vars_init();
-extern CVAR* var_new( const char* name, F32 v );
-extern CVAR* var_new( const char* name, I32 v );
-extern CVAR* var_new( const char* name, CLR v );
-extern CVAR* var_new( const char* name, const char* v );
-extern CVAR* var_new( const char* name, CVAR_FUNC fn );
+extern CVAR* var_new( const char* name, STR desc, F32 v );
+extern CVAR* var_new(
+ const char* name,
+ STR desc,
+ F32 v,
+ F32 min_v,
+ F32 max_v
+);
+extern CVAR* var_new( const char* name, STR desc, I32 v );
+extern CVAR* var_new(
+ const char* name,
+ STR desc,
+ I32 v,
+ I32 min_v,
+ I32 max_v
+);
+extern CVAR* var_new( const char* name, STR desc, CLR v );
+extern CVAR* var_new( const char* name, STR desc, const char* v );
+extern CVAR* var_new( const char* name, STR desc, CVAR_FUNC fn );
extern CVAR* var_find( const char* name );
-extern I32 var_call( CVAR* cmdvar, const char* cmdline );
+extern I32 var_call( CVAR* cmdvar, LIST<STR> cmdline );
extern F32 var_getf( CVAR* var );
extern I32 var_geti( CVAR* var );
extern CLR var_getclr( CVAR* var );
-extern const char* var_gets( CVAR* var );
+extern STR var_gets( const CVAR* var );
extern void var_set( CVAR* var, F32 v );
extern void var_set( CVAR* var, I32 v );
diff --git a/src/gui/base.cpp b/src/gui/base.cpp
index 79299de..153d82b 100644
--- a/src/gui/base.cpp
+++ b/src/gui/base.cpp
@@ -110,6 +110,23 @@ void gui_free( GUI_BASE* node ) {
delete node;
}
+void gui_shutdown() {
+ _gui.callbacks.clear();
+ _gui.clip_rects.clear();
+ _gui.cur_view = 0;
+ _gui.cur_window = 0;
+
+ while( _gui.windows.size ) {
+ GUI_WINDOW* wnd = _gui.windows[
+ _gui.windows.size - 1
+ ];
+ gui_free( wnd );
+ }
+ _gui.batch = 0;
+ _gui.fonts.jpn12.glfnt = 0;
+ _gui.fonts.jpn16.glfnt = 0;
+}
+
I32 gui_relx( GUI_BASE* node ) {
I32 x = node->x;
for( GUI_BASE* p = node->parent; !!p; p = p->parent ) {
@@ -283,47 +300,62 @@ GL_FONT* gui_font_from_idx( U32 fnt ) {
return pfnt->glfnt;
}
-void gui_draw_str_internal( I32 x, I32 y, U8 align, U32 fnt, CLR col, const char* str ) {
+void gui_draw_get_str_bounds_internal( I32* w, I32* h, U32 fnt, const char* buf ) {
GL_FONT* pfnt = gui_font_from_idx( fnt );
if( !pfnt ) return;
- I32 w;
- gui_draw_get_str_bounds( &w, 0, fnt, str );
- switch( align ) {
- case ALIGN_R: x -= w; break;
- case ALIGN_C: x -= (I32)( w * 0.5f ); break;
- }
-
- gl_font_draw( pfnt, _gui.batch, { (F32)x, (F32)y }, str, col );
+ VEC2 dim = gl_font_dim( pfnt, buf );
+ if( w ) *w = dim.x;
+ if( h ) *h = dim.y;
}
-void gui_draw_str( I32 x, I32 y, U8 align, U32 fnt, CLR col, const char* fmt, ... ) {
+void gui_draw_get_str_bounds( I32* w, I32* h, U32 fnt, const char *fmt, ... ) {
va_list args;
va_start( args, fmt );
char buf[GUI_STR_BUF_MAX];
- vsprintf( buf, fmt, args );
+ I32 result = vsnprintf(
+ buf, sizeof( buf ),
+ fmt, args
+ );
va_end( args );
- gui_draw_str_internal( x, y, align, fnt, col, buf );
+ if( result < 0 )
+ return;
+
+ buf[sizeof( buf ) - 1] = 0;
+ gui_draw_get_str_bounds_internal( w, h, fnt, buf );
}
-void gui_draw_get_str_bounds_internal( I32* w, I32* h, U32 fnt, const char* buf ) {
+void gui_draw_str_internal( I32 x, I32 y, U8 align, U32 fnt, CLR col, const char* str ) {
GL_FONT* pfnt = gui_font_from_idx( fnt );
if( !pfnt ) return;
- VEC2 dim = gl_font_dim( pfnt, buf );
- if( w ) *w = dim.x;
- if( h ) *h = dim.y;
+ I32 w;
+ gui_draw_get_str_bounds_internal( &w, 0, fnt, str );
+ switch( align ) {
+ case ALIGN_R: x -= w; break;
+ case ALIGN_C: x -= (I32)( w * 0.5f ); break;
+ }
+
+ gl_font_draw( pfnt, _gui.batch, { (F32)x, (F32)y }, str, col );
}
-void gui_draw_get_str_bounds( I32* w, I32* h, U32 fnt, const char *fmt, ... ) {
+void gui_draw_str( I32 x, I32 y, U8 align, U32 fnt, CLR col, const char* fmt, ... ) {
va_list args;
va_start( args, fmt );
char buf[GUI_STR_BUF_MAX];
- vsprintf( buf, fmt, args );
+ I32 result = vsnprintf(
+ buf, sizeof( buf ),
+ fmt, args
+ );
va_end( args );
- gui_draw_get_str_bounds_internal( w, h, fnt, buf );
+ if( result < 0 )
+ return;
+
+ buf[sizeof( buf ) - 1] = 0;
+
+ gui_draw_str_internal( x, y, align, fnt, col, buf );
}
void gui_draw_get_clip( I32 *x, I32 *y, I32 *w, I32 *h ) {
diff --git a/src/gui/base.h b/src/gui/base.h
index 108988d..c2c09d1 100644
--- a/src/gui/base.h
+++ b/src/gui/base.h
@@ -11,6 +11,8 @@ static const struct {
CLR bg = CLR::blend( CLR::WHITE(), CLR::BLACK(), 0.92f );
CLR bg_sec = CLR::blend( CLR::WHITE(), CLR::BLACK(), 0.97f );
CLR bg_alt = CLR::blend( CLR::WHITE(), CLR::BLACK(), 0.985f );
+ CLR shadow = CLR::blend_root( CLR::BLACK(), CLR::WHITE(), 0.05f );
+ CLR selection = CLR::blend( CLR::WHITE(), CLR::BLACK(), 0.7f );
} ui_clr;
// ========================================== [ base ] ============================================
@@ -35,6 +37,7 @@ extern struct GUI_WINDOW* gui_get_parent_wnd( struct GUI_BASE* node );
extern void gui_empty_children( struct GUI_BASE* node );
extern void gui_free( struct GUI_BASE* node );
+extern void gui_shutdown();
extern struct GUI_VIEW* gui_get_view(); // gets currently edited view
extern void gui_set_view( struct GUI_VIEW* view ); // sets currently edited view
@@ -58,6 +61,18 @@ struct GUI_CONTEXT_ENTRY {
LIST<GUI_CONTEXT_ENTRY> subentries;
};
+typedef void( *GUI_CONTEXTMENU_CALLBACK )( void* data );
+struct GUI_CONTEXTMENU_ITEM;
+typedef U8( *GUI_CONTEXTMENU_ENABLED_CALLBACK )( GUI_CONTEXTMENU_ITEM* item );
+
+struct GUI_CONTEXTMENU_ITEM {
+ char text[64]{};
+ void* data{};
+ GUI_CONTEXTMENU_CALLBACK cb{};
+ GUI_CONTEXTMENU_ENABLED_CALLBACK enabled_cb{};
+ U8 enabled{1};
+ LIST<GUI_CONTEXTMENU_ITEM> items{};
+};
extern struct GUI_VIEW* gui_view( I32 x, I32 y, I32 w, I32 h ); // sets current view
extern struct GUI_WINDOW* gui_window( I32 w, I32 h ); // sets current view and window
@@ -76,6 +91,39 @@ extern struct GUI_LIST_ENTRY* gui_list_get_selected( struct GUI_LIST* list );
extern struct GUI_CONTEXTBOX* gui_contextbox( I32 x, I32 y, I32 w, I32 h, const char* title, LIST<GUI_CONTEXT_ENTRY>* entries );
extern struct GUI_CONTEXTBOX* gui_contextbox( I32 x, I32 y, I32 w, I32 h, LIST<GUI_CONTEXT_ENTRY>* entries );
+extern struct GUI_CONTEXTMENU* gui_show_contextmenu(
+ I32 x, I32 y,
+ LIST<GUI_CONTEXTMENU_ITEM>* items,
+ GUI_BASE* parent,
+ const char* title = 0,
+ I32 max_visible_rows = 0,
+ struct GUI_CONTEXTMENU** owner_ref = 0
+);
+extern void gui_contextmenu_hide( GUI_CONTEXTMENU* menu );
+extern U8 gui_contextmenu_open( GUI_CONTEXTMENU* menu );
+extern I32 gui_contextmenu_height(
+ I32 rows,
+ const char* title = 0
+);
+extern void gui_contextmenu_move_selection(
+ GUI_CONTEXTMENU* menu,
+ I32 delta,
+ U8 wrap = 1
+);
+extern void gui_contextmenu_reveal_selection(
+ GUI_CONTEXTMENU* menu
+);
+extern void gui_contextmenu_set_item(
+ GUI_CONTEXTMENU_ITEM* item,
+ const char* text,
+ GUI_CONTEXTMENU_CALLBACK cb = 0,
+ void* data = 0,
+ GUI_CONTEXTMENU_ENABLED_CALLBACK enabled_cb = 0
+);
+extern struct GUI_CONTEXTMENU_ITEM* gui_contextmenu_selected(
+ GUI_CONTEXTMENU* menu
+);
+
extern struct GUI_FLOATINPUT* gui_floatinput(
I32 x, I32 y, I32 w,
const char* title,
@@ -108,6 +156,16 @@ extern struct GUI_COLORINPUT* gui_colorinput(
U8 showalpha = 1
);
+extern struct GUI_CONSOLEWINDOW* gui_consolewindow(
+ I32 x,
+ I32 y,
+ I32 w,
+ I32 h
+);
+extern U8 gui_consolewindow_is_open();
+extern void gui_consolewindow_toggle();
+extern U8 gui_consolewindow_input_focused();
+
// ======================================= [ definitions ] ========================================
enum GuiTxtAlign_t {
@@ -201,16 +259,22 @@ struct GUI_DROPDOWN : GUI_BASE {
struct GUI_TEXTBOX : GUI_BASE {
char value[GUI_TEXTBOX_MAX];
- U32 len;
U32 maxlen;
- U8 keys[0xff];
- U8 prevkeys[0xff];
+ U8 keys[0x100];
+ U8 prevkeys[0x100];
U8 heldkey;
F32 heldtime;
F32 repeattime;
+ U32 cursorin, cursorout;
+ I32 cursorpxin, cursorpxout;
+ I32 cursorpy;
+ I32 scrollpx, scrollpy;
+ U8 cursor_goal_valid;
+ I32 cursor_goal_px;
+
F32 blinktime;
U8 blink;
@@ -223,6 +287,22 @@ struct GUI_TEXTBOX : GUI_BASE {
GUI_CALLBACK pre_cb;
};
+struct GUI_CONTEXTMENU : GUI_BASE {
+ LIST<GUI_CONTEXTMENU_ITEM>* items{};
+ GUI_CONTEXTMENU** owner_ref{};
+
+ I32 selected_idx{-1};
+ I32 first_visible{};
+ I32 visible_rows{};
+ I32 max_visible_rows{};
+
+ U8 initheld{1};
+ U8 held{};
+ I32 held_idx{-1};
+
+ char title[64]{};
+};
+
struct GUI_CONTEXTBOX : GUI_BASE {
LIST<GUI_CONTEXT_ENTRY>* entries;
};
diff --git a/src/gui/console.cpp b/src/gui/console.cpp
new file mode 100644
index 0000000..cf6cc4d
--- /dev/null
+++ b/src/gui/console.cpp
@@ -0,0 +1,1779 @@
+#include "SDL_clipboard.h"
+#include "SDL_keyboard.h"
+#include "SDL_keycode.h"
+#include "SDL_mouse.h"
+
+#include "console.h"
+
+#include "../render/gl_2d_font.h"
+#include "../util/console.h"
+#include "../util/math.h"
+#include "../game.h"
+#include "base.h"
+
+#include <string.h>
+
+struct CONSOLE_INPUT_SEGMENT {
+ U32 length{};
+ U32 start{};
+ U8 valid{};
+};
+
+struct CONSOLE_WRAP_RANGE {
+ U32 next;
+ U32 end;
+};
+
+extern GAME_DATA* game;
+extern VAR_LIST* varl;
+
+constexpr I32 GUI_CONSOLE_HISTORY_MENU_ROWS = 4;
+constexpr I32 GUI_CONSOLE_MINIMUM_HEIGHT = 128;
+constexpr I32 GUI_CONSOLE_MINIMUM_WIDTH = 256;
+constexpr I32 GUI_CONSOLE_INPUT_HEIGHT = 20;
+constexpr I32 GUI_TEXTBOX_TITLE_OFFSET = 15;
+constexpr I32 GUI_CONSOLE_BORDER_WIDTH = 1;
+constexpr I32 GUI_CONSOLE_PADDING = 4;
+constexpr I32 GUI_TITLE_HEIGHT = 26;
+constexpr I32 GUI_BUTTON_SIZE = 16;
+constexpr I32 GUI_GRAB_SIZE = 8;
+
+inline I32 get_input_y( I32 wnd_h ) {
+ return wnd_h
+ - ( GUI_CONSOLE_BORDER_WIDTH * 2 )
+ - GUI_CONSOLE_PADDING
+ - GUI_TEXTBOX_TITLE_OFFSET
+ - GUI_CONSOLE_INPUT_HEIGHT;
+}
+
+inline I32 get_input_w( I32 wnd_w ) {
+ return wnd_w
+ - ( GUI_CONSOLE_PADDING * 2 )
+ - ( GUI_CONSOLE_BORDER_WIDTH * 2 );
+}
+
+inline I32 get_close_x( I32 wnd_w ) {
+ return wnd_w
+ - GUI_BUTTON_SIZE
+ - GUI_CONSOLE_PADDING
+ - ( GUI_CONSOLE_BORDER_WIDTH * 2 );
+}
+
+inline VEC2 get_console_output_pos( GUI_CONSOLEWINDOW* wnd ) {
+ I32 x = gui_relx( wnd ) + GUI_CONSOLE_BORDER_WIDTH + GUI_CONSOLE_PADDING;
+ I32 y = gui_rely( wnd ) + GUI_TITLE_HEIGHT + GUI_CONSOLE_PADDING;
+ return { x, y };
+}
+
+inline VEC2 get_console_output_sz( GUI_CONSOLEWINDOW* wnd ) {
+ I32 bottom = gui_rely( wnd->input ) + GUI_TEXTBOX_TITLE_OFFSET - GUI_CONSOLE_PADDING;
+ I32 width = wnd->w - ( GUI_CONSOLE_BORDER_WIDTH + GUI_CONSOLE_PADDING ) * 2;
+ I32 top = gui_rely( wnd ) + GUI_TITLE_HEIGHT + GUI_CONSOLE_PADDING;
+ return {
+ (F32)max( 0, width ),
+ (F32)max( 0, bottom - top )
+ };
+}
+
+inline U8 constrain_console( GUI_CONSOLEWINDOW* wnd, I32 max_x, I32 max_y ) {
+ max_x = max( 1, max_x );
+ max_y = max( 1, max_y );
+
+ I32 oldx = wnd->x;
+ I32 oldy = wnd->y;
+ I32 oldw = wnd->w;
+ I32 oldh = wnd->h;
+
+ I32 minh = min( GUI_CONSOLE_MINIMUM_HEIGHT, max_y );
+ I32 minw = min( GUI_CONSOLE_MINIMUM_WIDTH, max_x );
+
+ wnd->w = m_clamp( wnd->w, minw, max_x );
+ wnd->h = m_clamp( wnd->h, minh, max_y );
+ wnd->x = m_clamp( wnd->x, 0, max_x - wnd->w );
+ wnd->y = m_clamp( wnd->y, 0, max_y - wnd->h );
+
+ return oldx != wnd->x
+ || oldy != wnd->y
+ || oldw != wnd->w
+ || oldh != wnd->h;
+}
+
+inline GUI_VIEW* get_console_view( GUI_CONSOLEWINDOW* wnd ) {
+ if( !wnd->children.size )
+ return 0;
+ return (GUI_VIEW*)wnd->children.at( 0 );
+}
+
+inline void layout_console_window( GUI_CONSOLEWINDOW* wnd ) {
+ GUI_VIEW* view = get_console_view( wnd );
+ if( !view )
+ return;
+
+ view->w = max( 1, wnd->w - GUI_CONSOLE_BORDER_WIDTH * 2 );
+ view->h = max( 1, wnd->h - GUI_CONSOLE_BORDER_WIDTH * 2 );
+
+ if( wnd->input ) {
+ wnd->input->y = get_input_y( wnd->h );
+ wnd->input->w = get_input_w( wnd->w );
+ }
+
+ if( wnd->close )
+ wnd->close->x = get_close_x( wnd->w );
+
+ if( wnd->title )
+ wnd->title->w = view->w;
+}
+
+inline void store_console_drag_origin( GUI_CONSOLEWINDOW* wnd ) {
+ wnd->drag_start_mouse = input.mouse.pos;
+ wnd->drag_start_pos = VEC2( wnd->x, wnd->y );
+ wnd->drag_start_sz = VEC2(
+ wnd->x + wnd->w,
+ wnd->y + wnd->h
+ );
+}
+
+inline RESIZE_EDGES get_console_active_edges(
+ GUI_CONSOLEWINDOW* wnd,
+ VEC2 mouse,
+ I32 max_x,
+ I32 max_y
+) {
+ F32 radius = GUI_GRAB_SIZE * 0.5f;
+
+ if( mouse.x < 0 || mouse.x >= max_x
+ || mouse.y < 0 || mouse.y >= max_y
+ ) return RESIZE_NONE;
+
+ F32 top = (F32)wnd->y;
+ F32 left = (F32)wnd->x;
+ F32 right = (F32)( wnd->x + wnd->w );
+ F32 bottom = (F32)( wnd->y + wnd->h );
+
+ U8 within_vert = mouse.y >= top + GUI_TITLE_HEIGHT
+ && mouse.y < bottom + radius;
+ U8 within_horz = mouse.x >= left - radius
+ && mouse.x < right + radius;
+ U8 near_left = within_vert
+ && mouse.x >= left - radius
+ && mouse.x < left + radius;
+ U8 near_right = within_vert
+ && mouse.x >= right - radius
+ && mouse.x < right + radius;
+ U8 near_bottom = within_horz
+ && mouse.y >= bottom - radius
+ && mouse.y < bottom + radius;
+
+ if( near_left && near_right ) {
+ F32 d_left = fabsf( mouse.x - left );
+ F32 d_right = fabsf( mouse.x - right );
+
+ if( d_left <= d_right )
+ near_right = 0;
+ else
+ near_left = 0;
+ }
+
+ U8 mask = RESIZE_NONE;
+ if( near_left )
+ mask |= RESIZE_LEFT;
+ if( near_right )
+ mask |= RESIZE_RIGHT;
+ if( near_bottom )
+ mask |= RESIZE_BOTTOM;
+ return (RESIZE_EDGES)mask;
+}
+
+inline U8 is_over_console_input( GUI_CONSOLEWINDOW* wnd, VEC2 mouse ) {
+ if( !wnd->input )
+ return 0;
+ I32 x = gui_relx( wnd->input );
+ I32 y = gui_rely( wnd->input ) + GUI_TEXTBOX_TITLE_OFFSET;
+
+ return mouse.x >= x
+ && mouse.x < x + wnd->input->w
+ && mouse.y >= y
+ && mouse.y < y + wnd->input->h;
+}
+
+inline SDL_SystemCursor get_console_cursor( RESIZE_EDGES edges, U8 over_input ) {
+ if( ( edges & RESIZE_BOTTOM ) && ( edges & RESIZE_LEFT ) )
+ return SDL_SYSTEM_CURSOR_SIZENESW;
+ if( ( edges & RESIZE_BOTTOM ) && ( edges & RESIZE_RIGHT ) )
+ return SDL_SYSTEM_CURSOR_SIZENWSE;
+ if( edges & RESIZE_BOTTOM )
+ return SDL_SYSTEM_CURSOR_SIZENS;
+ if( edges & ( RESIZE_LEFT | RESIZE_RIGHT ) )
+ return SDL_SYSTEM_CURSOR_SIZEWE;
+ if( over_input )
+ return SDL_SYSTEM_CURSOR_IBEAM;
+ return SDL_SYSTEM_CURSOR_ARROW;
+}
+
+inline void set_console_cursor( RESIZE_EDGES edges, U8 over_input = 0 ) {
+ SDL_SystemCursor type = get_console_cursor( edges, over_input );
+ SDL_Cursor* cursor = game->assets.cursors[type];
+ if( cursor && SDL_GetCursor() != cursor )
+ SDL_SetCursor( cursor );
+}
+
+inline void console_resize_begin( GUI_CONSOLEWINDOW* wnd, RESIZE_EDGES edges ) {
+ wnd->active_edges = edges;
+ wnd->locked = 1;
+
+ store_console_drag_origin( wnd );
+ SDL_CaptureMouse( SDL_TRUE );
+}
+
+inline void console_resize_end( GUI_CONSOLEWINDOW* wnd ) {
+ if( wnd->active_edges != RESIZE_NONE )
+ SDL_CaptureMouse( SDL_FALSE );
+ wnd->active_edges = RESIZE_NONE;
+ wnd->locked = 0;
+}
+
+inline void resize_console_window( GUI_CONSOLEWINDOW* wnd, I32 max_x, I32 max_y ) {
+ F32 bottom = wnd->drag_start_sz.y;
+ F32 right = wnd->drag_start_sz.x;
+ F32 left = wnd->drag_start_pos.x;
+ F32 top = wnd->drag_start_pos.y;
+
+ F32 dm_x = input.mouse.pos.x - wnd->drag_start_mouse.x;
+ F32 dm_y = input.mouse.pos.y - wnd->drag_start_mouse.y;
+
+ F32 min_h = (F32)min( GUI_CONSOLE_MINIMUM_HEIGHT, max_y );
+ F32 min_w = (F32)min( GUI_CONSOLE_MINIMUM_WIDTH, max_x );
+
+ U8 hit_constraint = 0;
+
+ if( wnd->active_edges & RESIZE_LEFT ) {
+ F32 req = wnd->drag_start_pos.x + dm_x;
+ F32 applied = m_clamp( req, 0.0f, wnd->drag_start_sz.x - min_w );
+ left = applied;
+ hit_constraint |= req != applied;
+ } else if( wnd->active_edges & RESIZE_RIGHT ) {
+ F32 req = wnd->drag_start_sz.x + dm_x;
+ F32 applied = m_clamp( req,
+ wnd->drag_start_pos.x + min_w,
+ (F32)max_x
+ );
+ right = applied;
+ hit_constraint |= req != applied;
+ }
+
+ if( wnd->active_edges & RESIZE_BOTTOM ) {
+ F32 req = wnd->drag_start_sz.y + dm_y;
+ F32 applied = m_clamp( req,
+ wnd->drag_start_pos.y + min_h,
+ (F32)max_y
+ );
+ bottom = applied;
+ hit_constraint |= req != applied;
+ }
+
+ wnd->y = (I32)top;
+ wnd->x = (I32)left;
+ wnd->w = max( 1, (I32)( right - left ) );
+ wnd->h = max( 1, (I32)( bottom - top ) );
+ layout_console_window( wnd );
+ if( hit_constraint )
+ store_console_drag_origin( wnd );
+}
+
+inline I32 console_output_line_height() {
+ I32 height = 0;
+ I32 width = 0;
+
+ gui_draw_get_str_bounds(
+ &width, &height,
+ FNT_JPN12, "Ag"
+ );
+
+ return height > 0
+ ? height + 1
+ : 0;
+}
+
+inline U8 console_output_id_bounds(
+ const CONSOLE_DATA* state,
+ U64* first,
+ U64* last
+) {
+ if( !state )
+ return 0;
+
+ U8 has_partial = state->partial_stdout_line.size > 0;
+ U8 has_complete = state->lines.count > 0;
+
+ if( !has_complete && !has_partial )
+ return 0;
+
+ if( first ) {
+ *first = has_complete
+ ? state->lines.total_pushed - state->lines.count
+ : state->lines.total_pushed;
+ }
+
+ if( last ) {
+ *last = has_partial
+ ? state->lines.total_pushed
+ : state->lines.total_pushed - 1;
+ }
+
+ return 1;
+}
+
+inline U8 console_validate_output_selection(
+ GUI_CONSOLEWINDOW* wnd,
+ const CONSOLE_DATA* state
+) {
+ if( !wnd || !wnd->output_selection_active )
+ return 0;
+
+ U64 first = 0;
+ U64 last = 0;
+
+ if( !console_output_id_bounds( state, &first, &last ) ) {
+ wnd->output_selection_active = 0;
+ wnd->output_selecting = 0;
+ return 0;
+ }
+
+ U64 selection_start = min(
+ wnd->output_selection_anchor,
+ wnd->output_selection_cursor
+ );
+
+ U64 selection_end = max(
+ wnd->output_selection_anchor,
+ wnd->output_selection_cursor
+ );
+
+ if( selection_end < first || selection_start > last ) {
+ wnd->output_selection_active = 0;
+ wnd->output_selecting = 0;
+ return 0;
+ }
+
+ wnd->output_selection_anchor = max( first,
+ min( wnd->output_selection_anchor, last )
+ );
+ wnd->output_selection_cursor = max( first,
+ min( wnd->output_selection_cursor, last )
+ );
+ return 1;
+}
+
+inline U8 console_output_line_selected(
+ const GUI_CONSOLEWINDOW* wnd,
+ U64 source_id
+) {
+ if( !wnd || !wnd->output_selection_active )
+ return 0;
+
+ U64 start = min(
+ wnd->output_selection_anchor,
+ wnd->output_selection_cursor
+ );
+ U64 end = max(
+ wnd->output_selection_anchor,
+ wnd->output_selection_cursor
+ );
+ return source_id >= start && source_id <= end;
+}
+
+inline void console_push_wrapped_range(
+ GUI_CONSOLEWINDOW* wnd,
+ const char* text,
+ U32 start, U32 end,
+ U64 source_id
+) {
+ CONSOLE_WRAPPED_LINE wrapped = {
+ .text = STR( end - start, text + start ),
+ .source_id = source_id
+ };
+ wnd->wrapped_lines.push( wrapped );
+}
+
+inline CONSOLE_WRAP_RANGE console_next_wrap_range(
+ const STR& line,
+ U32 start,
+ I32 max_w,
+ GL_FONT* font
+) {
+ CONSOLE_WRAP_RANGE range = {
+ line.size,
+ line.size
+ };
+
+ I32 last_space = -1;
+ U32 cursor = start;
+ F32 width = 0.f;
+
+ while( cursor < line.size ) {
+ U8 c = (U8)line.data[cursor];
+ F32 advance = font->glyphs[c].advance_x;
+
+ if( width + advance > max_w )
+ break;
+
+ width += advance;
+
+ if( c == ' ' && cursor > start )
+ last_space = (I32)cursor;
+ ++cursor;
+ }
+
+ if( cursor == line.size )
+ return range;
+
+ if( cursor == start ) {
+ range.next = start + 1;
+ range.end = start + 1;
+ return range;
+ }
+
+ if( last_space > (I32)start ) {
+ range.end = (U32)last_space;
+ range.next = range.end + 1;
+
+ while( range.next < line.size
+ && line.data[range.next] == ' '
+ ) ++range.next;
+
+ return range;
+ }
+
+ range.next = cursor;
+ range.end = cursor;
+ return range;
+}
+
+void console_wrap_line(
+ GUI_CONSOLEWINDOW* wnd,
+ const STR& line,
+ U64 source_id,
+ I32 max_w,
+ GL_FONT* font
+) {
+ if( !wnd || !font || max_w <= 0 )
+ return;
+
+ if( !line.size ) {
+ console_push_wrapped_range(
+ wnd, line.data,
+ 0, 0, source_id
+ );
+ return;
+ }
+
+ for( U32 start = 0; start < line.size; ) {
+ CONSOLE_WRAP_RANGE range = console_next_wrap_range(
+ line, start,
+ max_w, font
+ );
+ console_push_wrapped_range(
+ wnd, line.data,
+ start, range.end,
+ source_id
+ );
+
+ start = range.next;
+ }
+}
+
+void console_rebuild_wrapped_lines(
+ GUI_CONSOLEWINDOW* wnd,
+ const CONSOLE_DATA* state,
+ I32 width
+) {
+ if( !wnd || !state || width <= 0 )
+ return;
+
+ U8 lines_removed = wnd->seen_revision != state->lines.revision;
+
+ if( !lines_removed
+ && wnd->wrapped_width == width
+ && wnd->wrapped_revision == state->revision
+ ) return;
+
+ GL_FONT* font = _gui.fonts.jpn12.glfnt;
+ if( !font )
+ return;
+
+ I32 old_count = (I32)wnd->wrapped_lines.size;
+ I32 old_offset = wnd->scroll_offset;
+ U8 width_changed = wnd->wrapped_width != width;
+
+ wnd->wrapped_lines.clear();
+
+ U64 first_source_id = state->lines.total_pushed - state->lines.count;
+ for( U32 idx = 0; idx < state->lines.count; ++idx ) {
+ console_wrap_line(
+ wnd,
+ state->lines[idx],
+ first_source_id + idx,
+ width,
+ font
+ );
+ }
+
+ if( state->partial_stdout_line.size ) {
+ console_wrap_line(
+ wnd,
+ state->partial_stdout_line,
+ state->lines.total_pushed,
+ width,
+ font
+ );
+ }
+
+ I32 new_count = (I32)wnd->wrapped_lines.size;
+
+ if( lines_removed )
+ wnd->scroll_offset = 0;
+ else if( old_offset > 0 && !width_changed ) {
+ wnd->scroll_offset = max( 0,
+ old_offset + new_count - old_count
+ );
+ }
+ wnd->wrapped_width = width;
+ wnd->wrapped_revision = state->revision;
+ wnd->seen_revision = state->lines.revision;
+}
+
+void draw_console_output( GUI_CONSOLEWINDOW* wnd ) {
+ VEC2 pos = get_console_output_pos( wnd );
+ VEC2 sz = get_console_output_sz( wnd );
+ if( sz.x <= 0 || sz.y <= 0 )
+ return;
+
+ console_rebuild_wrapped_lines( wnd, &game->console, (I32)sz.x );
+ console_validate_output_selection( wnd, &game->console );
+
+ I32 line_height = console_output_line_height();
+ if( line_height <= 0 )
+ return;
+
+ wnd->visible_rows = (I32)sz.y / line_height;
+ I32 row_count = (I32)wnd->wrapped_lines.size;
+ I32 max_scroll = max( 0, row_count - wnd->visible_rows );
+ wnd->scroll_offset = m_clamp( wnd->scroll_offset, 0, max_scroll );
+ gui_draw_push_clip(
+ (I32)pos.x, (I32)pos.y,
+ (I32)sz.x, (I32)sz.y
+ );
+ I32 y = (I32)( pos.y + sz.y ) - line_height;
+ I32 index = row_count - 1 - wnd->scroll_offset;
+
+ while( index >= 0 && y >= pos.y ) {
+ CONSOLE_WRAPPED_LINE* row = &wnd->wrapped_lines[index];
+
+ if( console_output_line_selected( wnd, row->source_id ) )
+ gui_draw_frect( (I32)pos.x, y, (I32)sz.x, line_height, ui_clr.selection );
+ gui_draw_str(
+ pos.x, y, ALIGN_L,
+ FNT_JPN12, ui_clr.txt,
+ "%s", row->text.data
+ );
+ y -= line_height;
+ --index;
+ }
+ gui_draw_pop_clip();
+}
+
+inline LIST<CVAR*> console_cvar_list( const char* query, U32 query_length ) {
+ LIST<CVAR*> matches;
+
+ if( !varl || !query || !query_length )
+ return matches;
+
+ matches.reserve( varl->vars.size );
+ for( U32 idx = 0; idx < varl->vars.size; ++idx ) {
+ CVAR* var = varl->vars[idx];
+ if( !var || !var->name.data )
+ continue;
+
+ U32 name_length = (U32)strlen( var->name.data );
+
+ if( name_length >= query_length
+ && !strncmp( var->name.data, query, query_length )
+ ) matches.push( var );
+ }
+ return matches;
+}
+
+inline void console_close_input_menu( GUI_CONSOLEWINDOW* wnd ) {
+ if( !wnd )
+ return;
+
+ if( wnd->menus.input.menu ) {
+ gui_contextmenu_hide( wnd->menus.input.menu );
+ wnd->menus.input.menu = 0;
+ }
+
+ if( wnd->menus.input.items.size )
+ wnd->menus.input.items.clear();
+
+ if( wnd->menus.input.cvars.size )
+ wnd->menus.input.cvars.clear();
+
+ wnd->menus.input.mode = CONSOLE_MENU_INPUT_NONE;
+ wnd->menus.input.history_size = 0;
+ wnd->menus.input.cvar_count = 0;
+ wnd->menus.input.query[0] = 0;
+ wnd->menus.input.suppressed = 0;
+ wnd->menus.input.suppressed_value[0] = 0;
+}
+
+inline void console_close_output_menu( GUI_CONSOLEWINDOW* wnd ) {
+ if( wnd && wnd->menus.output.menu )
+ gui_contextmenu_hide( wnd->menus.output.menu );
+}
+
+inline U8 console_mouse_over_menu(
+ GUI_CONTEXTMENU* menu,
+ VEC2 mouse
+) {
+ if( !menu || !gui_contextmenu_open( menu ) )
+ return 0;
+ I32 x = gui_relx( menu );
+ I32 y = gui_rely( menu );
+ return mouse.x >= x
+ && mouse.x < x + menu->w
+ && mouse.y >= y
+ && mouse.y < y + menu->h;
+}
+
+void console_copy_output_selection( void* data ) {
+ GUI_CONSOLEWINDOW* wnd = (GUI_CONSOLEWINDOW*)data;
+ CONSOLE_DATA* state = game ? &game->console : 0;
+
+ if( !console_validate_output_selection( wnd, state ) )
+ return;
+
+ U64 selection_start = min(
+ wnd->output_selection_anchor,
+ wnd->output_selection_cursor
+ );
+ U64 selection_end = max(
+ wnd->output_selection_anchor,
+ wnd->output_selection_cursor
+ );
+
+ U64 first_complete = state->lines.total_pushed - state->lines.count;
+ U8 copied_line = 0;
+ STR copied;
+
+ for( U64 source_id = selection_start;; ++source_id ) {
+ const STR* line = 0;
+
+ if( source_id < state->lines.total_pushed
+ && source_id >= first_complete
+ ) {
+ U64 logical_index = source_id - first_complete;
+ if( logical_index < state->lines.count )
+ line = &state->lines[(U32)logical_index];
+ }
+ else if( source_id == state->lines.total_pushed
+ && state->partial_stdout_line.size
+ ) line = &state->partial_stdout_line;
+
+ if( line ) {
+ if( copied_line )
+ copied.push( '\n' );
+ copied.append( *line );
+ copied_line = 1;
+ }
+
+ if( source_id == selection_end )
+ break;
+ }
+
+ if( copied_line )
+ SDL_SetClipboardText( copied.data );
+}
+
+inline void console_open_output_menu(
+ GUI_CONSOLEWINDOW* wnd,
+ I32 mouse_x, I32 mouse_y
+) {
+ if( !wnd || !wnd->output_selection_active )
+ return;
+
+ console_close_input_menu( wnd );
+ console_close_output_menu( wnd );
+ wnd->menus.output.menu = gui_show_contextmenu(
+ mouse_x, mouse_y,
+ &wnd->menus.output.items,
+ wnd, 0, 1, &wnd->menus.output.menu
+ );
+
+ if( !wnd->menus.output.menu )
+ return;
+
+ I32 canvas_w = game->gl->canvas_size[0];
+ I32 canvas_h = game->gl->canvas_size[1];
+
+ I32 screen_x = m_clamp( mouse_x,
+ 0,
+ max( 0,
+ canvas_w - wnd->menus.output.menu->w
+ )
+ );
+ I32 screen_y = m_clamp( mouse_y,
+ 0,
+ max( 0,
+ canvas_h - wnd->menus.output.menu->h
+ )
+ );
+ wnd->menus.output.menu->x = screen_x - gui_relx( wnd );
+ wnd->menus.output.menu->y = screen_y - gui_rely( wnd );
+}
+
+inline I32 console_input_menu_visible_rows(
+ GUI_CONTEXTMENU* menu,
+ I32 item_count,
+ I32 available_h
+) {
+ I32 rows = min(
+ menu->max_visible_rows,
+ item_count
+ );
+
+ while( rows > 0
+ && gui_contextmenu_height(
+ rows, menu->title
+ ) > available_h
+ ) --rows;
+ return rows;
+}
+
+inline void console_reset_input_menu_view(
+ GUI_CONSOLEWINDOW* wnd,
+ GUI_CONTEXTMENU* menu,
+ I32 item_count
+) {
+ if( menu->selected_idx >= 0 ) {
+ gui_contextmenu_reveal_selection( menu );
+ return;
+ }
+ menu->first_visible = wnd->menus.input.mode == CONSOLE_MENU_INPUT_HIST
+ ? max( 0, item_count - menu->visible_rows )
+ : 0;
+}
+
+inline void console_position_input_menu( GUI_CONSOLEWINDOW* wnd ) {
+ if( !wnd || !wnd->input || !wnd->menus.input.menu )
+ return;
+
+ GUI_CONTEXTMENU* menu = wnd->menus.input.menu;
+ if( !menu->items || !menu->items->size )
+ return;
+
+ I32 canvas_w = max( 1, game->gl->canvas_size[0] );
+ I32 canvas_h = max( 1, game->gl->canvas_size[1] );
+
+ I32 input_x = gui_relx( wnd->input );
+ I32 input_bottom = gui_rely( wnd->input )
+ + GUI_TEXTBOX_TITLE_OFFSET
+ + wnd->input->h;
+ I32 item_count = (I32)menu->items->size;
+
+ menu->visible_rows = console_input_menu_visible_rows(
+ menu,
+ item_count,
+ max( 0, canvas_h - input_bottom )
+ );
+ menu->enabled = menu->visible_rows > 0;
+ if( !menu->enabled )
+ return;
+
+ menu->h = gui_contextmenu_height( menu->visible_rows, menu->title );
+
+ console_reset_input_menu_view(
+ wnd, menu,
+ item_count
+ );
+
+ menu->w = min(
+ max( 1, wnd->input->w ),
+ max( 1, canvas_w - input_x )
+ );
+ menu->y = input_bottom - gui_rely( wnd );
+ menu->x = input_x - gui_relx( wnd );
+ menu->xbound = menu->w;
+ menu->ybound = menu->h;
+}
+
+inline U8 console_show_input_menu( GUI_CONSOLEWINDOW* wnd ) {
+ if( !wnd || !wnd->input || !wnd->menus.input.items.size )
+ return 0;
+
+ I32 max_rows = min(
+ GUI_CONSOLE_HISTORY_MENU_ROWS,
+ (I32)wnd->menus.input.items.size
+ );
+
+ I32 input_x = gui_relx( wnd->input );
+ I32 input_bottom = gui_rely( wnd->input )
+ + GUI_TEXTBOX_TITLE_OFFSET
+ + wnd->input->h;
+ wnd->menus.input.menu = gui_show_contextmenu(
+ input_x, input_bottom,
+ &wnd->menus.input.items,
+ wnd, 0, max_rows,
+ &wnd->menus.input.menu
+ );
+
+ if( !wnd->menus.input.menu )
+ return 0;
+
+ console_position_input_menu( wnd );
+ return 1;
+}
+
+inline CONSOLE_INPUT_SEGMENT console_input_segment( GUI_TEXTBOX* textbox ) {
+ CONSOLE_INPUT_SEGMENT segment{};
+
+ if( !textbox )
+ return segment;
+
+ U32 total_length = (U32)strlen( textbox->value );
+ U8 in_string = 0;
+
+ for( U32 idx = 0; idx < total_length; ++idx ) {
+ char c = textbox->value[idx];
+
+ if( c == '"' )
+ in_string ^= 1;
+ else if( c == ';' && !in_string )
+ segment.start = idx + 1;
+ }
+
+ while( segment.start < total_length
+ && textbox->value[segment.start] == ' '
+ ) ++segment.start;
+
+ segment.length = total_length - segment.start;
+ segment.valid = !in_string;
+ return segment;
+}
+
+inline const char* console_input_menu_completion(
+ GUI_CONSOLEWINDOW* wnd,
+ const CONSOLE_INPUT_SEGMENT& segment,
+ CONSOLE_MENU_INPUT_MODE* selected_mode
+) {
+ if( !wnd || !wnd->input || !wnd->menus.input.menu || !selected_mode )
+ return 0;
+
+ I32 selected = wnd->menus.input.menu->selected_idx;
+ *selected_mode = wnd->menus.input.mode;
+
+ if( *selected_mode == CONSOLE_MENU_INPUT_HIST ) {
+ if( segment.length
+ || !game
+ || selected < 0
+ || selected >= (I32)game->console.history.size
+ ) return 0;
+
+ return game->console.history[selected].data;
+ }
+
+ if( *selected_mode != CONSOLE_MENU_INPUT_CVAR
+ || !segment.length
+ || selected < 0
+ || selected >= (I32)wnd->menus.input.cvars.size
+ ) return 0;
+
+ U32 cached_length = (U32)strlen( wnd->menus.input.query );
+
+ if( cached_length != segment.length
+ || strncmp(
+ wnd->menus.input.query,
+ wnd->input->value + segment.start,
+ segment.length
+ )
+ ) return 0;
+
+ CVAR* var = wnd->menus.input.cvars[selected];
+ return var && var->name.data
+ ? var->name.data
+ : 0;
+}
+
+inline U8 console_apply_input_completion(
+ GUI_TEXTBOX* textbox,
+ U32 start,
+ const char* completion
+) {
+ if( !textbox || !completion )
+ return 0;
+
+ U32 capacity = min(
+ textbox->maxlen,
+ GUI_TEXTBOX_MAX
+ );
+
+ if( !capacity || start >= capacity - 1 )
+ return 0;
+
+ snprintf(
+ textbox->value + start,
+ capacity - start,
+ "%s", completion
+ );
+
+ U32 length = (U32)strlen( textbox->value );
+
+ textbox->active = 1;
+ textbox->blink = 1;
+ textbox->blinktime = gui_time();
+ textbox->cursorpxin = 0;
+ textbox->cursorpxout = 0;
+ textbox->cursorin = length;
+ textbox->cursorout = length;
+ textbox->cursor_goal_valid = 0;
+ return 1;
+}
+
+inline void console_suppress_input_menu(
+ GUI_CONSOLEWINDOW* wnd,
+ const char* value
+) {
+ wnd->menus.input.suppressed = 1;
+ snprintf(
+ wnd->menus.input.suppressed_value,
+ sizeof( wnd->menus.input.suppressed_value ),
+ "%s", value
+ );
+}
+
+inline U8 console_apply_input_menu_selection(
+ GUI_CONSOLEWINDOW* wnd
+) {
+ if( !wnd || !wnd->input || !wnd->menus.input.menu )
+ return 0;
+
+ if( !gui_contextmenu_selected( wnd->menus.input.menu ) )
+ return 0;
+
+ CONSOLE_INPUT_SEGMENT segment = console_input_segment( wnd->input );
+ if( !segment.valid )
+ return 0;
+
+ CONSOLE_MENU_INPUT_MODE selected_mode = wnd->menus.input.mode;
+ const char* completion = console_input_menu_completion(
+ wnd, segment,
+ &selected_mode
+ );
+ if( !completion )
+ return 0;
+
+ U8 applied = console_apply_input_completion(
+ wnd->input,
+ segment.start,
+ completion
+ );
+
+ char completed_value[GUI_TEXTBOX_MAX]{};
+
+ if( applied && selected_mode == CONSOLE_MENU_INPUT_CVAR ) {
+ snprintf(
+ completed_value,
+ sizeof( completed_value ),
+ "%s", wnd->input->value
+ );
+ }
+
+ console_close_input_menu( wnd );
+
+ if( applied && selected_mode == CONSOLE_MENU_INPUT_CVAR )
+ console_suppress_input_menu( wnd, completed_value );
+
+ return 1;
+}
+
+void console_input_menu_item_cb( void* data ) {
+ console_apply_input_menu_selection(
+ (GUI_CONSOLEWINDOW*)data
+ );
+}
+
+inline void console_open_cvar_menu(
+ GUI_CONSOLEWINDOW* wnd,
+ const char* query,
+ U32 query_length
+) {
+ console_close_input_menu( wnd );
+
+ if( !wnd || !wnd->input || !query || !query_length )
+ return;
+
+ wnd->menus.input.mode = CONSOLE_MENU_INPUT_CVAR;
+ wnd->menus.input.cvar_count = varl
+ ? varl->vars.size
+ : 0;
+
+ I32 copy_length = (I32)min(
+ query_length,
+ GUI_TEXTBOX_MAX - 1
+ );
+
+ snprintf(
+ wnd->menus.input.query,
+ sizeof( wnd->menus.input.query ),
+ "%.*s", copy_length, query
+ );
+
+ wnd->menus.input.cvars = console_cvar_list( query, query_length );
+ if( !wnd->menus.input.cvars.size )
+ return;
+
+ wnd->menus.input.items.reserve(
+ wnd->menus.input.cvars.size
+ );
+
+ for( U32 idx = 0; idx < wnd->menus.input.cvars.size; ++idx ) {
+ CVAR* var = wnd->menus.input.cvars[idx];
+ if( !var || !var->name.data )
+ continue;
+
+ GUI_CONTEXTMENU_ITEM* item = wnd->menus.input.items.push( {} );
+ gui_contextmenu_set_item(
+ item,
+ var->name.data,
+ console_input_menu_item_cb,
+ wnd
+ );
+ }
+ console_show_input_menu( wnd );
+}
+
+inline void console_open_history_menu( GUI_CONSOLEWINDOW* wnd ) {
+ console_close_input_menu( wnd );
+
+ if( !wnd || !wnd->input || !game || !game->console.history.size )
+ return;
+
+ wnd->menus.input.mode = CONSOLE_MENU_INPUT_HIST;
+ wnd->menus.input.history_size = game->console.history.size;
+ wnd->menus.input.items.reserve( game->console.history.size );
+
+ for( U32 idx = 0; idx < game->console.history.size; ++idx ) {
+ GUI_CONTEXTMENU_ITEM* item = wnd->menus.input.items.push( {} );
+ gui_contextmenu_set_item(
+ item,
+ game->console.history[idx].data,
+ console_input_menu_item_cb,
+ wnd
+ );
+ }
+
+ console_show_input_menu( wnd );
+}
+
+inline U8 console_input_menu_suppressed(
+ GUI_CONSOLEWINDOW* wnd,
+ GUI_TEXTBOX* textbox
+) {
+ if( !wnd->menus.input.suppressed )
+ return 0;
+
+ if( !strcmp(
+ wnd->menus.input.suppressed_value,
+ textbox->value
+ )
+ ) return 1;
+
+ wnd->menus.input.suppressed = 0;
+ wnd->menus.input.suppressed_value[0] = 0;
+ return 0;
+}
+
+inline void console_sync_history_menu(
+ GUI_CONSOLEWINDOW* wnd
+) {
+ if( !game->console.history.size ) {
+ console_close_input_menu( wnd );
+ return;
+ }
+
+ U8 stale = wnd->menus.input.mode != CONSOLE_MENU_INPUT_HIST
+ || wnd->menus.input.history_size != game->console.history.size
+ || !wnd->menus.input.items.size;
+
+ if( stale ) {
+ console_open_history_menu( wnd );
+ return;
+ }
+
+ if( !wnd->menus.input.menu )
+ console_show_input_menu( wnd );
+ console_position_input_menu( wnd );
+}
+
+inline void console_sync_cvar_menu(
+ GUI_CONSOLEWINDOW* wnd,
+ const CONSOLE_INPUT_SEGMENT& segment
+) {
+ const char* query = wnd->input->value + segment.start;
+ U32 cvar_count = varl ? varl->vars.size : 0;
+ U32 cached_length = (U32)strlen( wnd->menus.input.query );
+
+ U8 stale = wnd->menus.input.mode != CONSOLE_MENU_INPUT_CVAR
+ || wnd->menus.input.cvar_count != cvar_count
+ || cached_length != segment.length;
+
+ if( !stale
+ && strncmp(
+ wnd->menus.input.query,
+ query,
+ segment.length
+ )
+ ) stale = 1;
+
+ if( stale ) {
+ console_open_cvar_menu(
+ wnd,
+ query,
+ segment.length
+ );
+ return;
+ }
+
+ if( !wnd->menus.input.menu && wnd->menus.input.items.size )
+ console_show_input_menu( wnd );
+ console_position_input_menu( wnd );
+}
+
+inline void console_sync_input_menu( GUI_CONSOLEWINDOW* wnd ) {
+ if( !wnd )
+ return;
+
+ U8 menu_interacting = wnd->menus.input.menu
+ && wnd->menus.input.menu->held;
+ if( !wnd->enabled
+ || !wnd->input
+ || ( !wnd->input->active && !menu_interacting )
+ ) {
+ console_close_input_menu( wnd );
+ return;
+ }
+
+ if( console_input_menu_suppressed( wnd, wnd->input ) )
+ return;
+
+ CONSOLE_INPUT_SEGMENT segment = console_input_segment( wnd->input );
+ if( !segment.valid ) {
+ console_close_input_menu( wnd );
+ return;
+ }
+
+ if( !segment.length )
+ console_sync_history_menu( wnd );
+ else
+ console_sync_cvar_menu( wnd, segment );
+}
+
+inline U8 console_textbox_key_pressed( GUI_TEXTBOX* textbox, U32 key ) {
+ U32 idx = key & 0xff;
+ return textbox->keys[idx] && !textbox->prevkeys[idx];
+}
+
+inline U8 console_handle_input_menu_keys( GUI_CONSOLEWINDOW* wnd ) {
+ if( !wnd || !wnd->input || !wnd->menus.input.menu )
+ return 0;
+
+ GUI_CONTEXTMENU* menu = wnd->menus.input.menu;
+ GUI_TEXTBOX* textbox = wnd->input;
+
+ if( !gui_contextmenu_open( menu ) )
+ return 0;
+
+ CONSOLE_INPUT_SEGMENT segment = console_input_segment( textbox );
+ if( !segment.valid )
+ return 0;
+
+ U8 valid_mode = 0;
+ if( wnd->menus.input.mode == CONSOLE_MENU_INPUT_HIST )
+ valid_mode = !segment.length;
+ else if( wnd->menus.input.mode == CONSOLE_MENU_INPUT_CVAR
+ && segment.length
+ ) {
+ U32 cached_length = (U32)strlen( wnd->menus.input.query );
+ valid_mode = cached_length == segment.length
+ && !strncmp(
+ wnd->menus.input.query,
+ textbox->value + segment.start,
+ segment.length
+ );
+ }
+
+ if( !valid_mode )
+ return 0;
+
+ U8 moved = 0;
+ if( console_textbox_key_pressed( textbox, SDLK_UP ) ) {
+ gui_contextmenu_move_selection( menu, -1, 1 );
+ moved = 1;
+ }
+ else if( console_textbox_key_pressed( textbox, SDLK_DOWN ) ) {
+ gui_contextmenu_move_selection( menu, 1, 1 );
+ moved = 1;
+ }
+
+ if( moved ) {
+ textbox->active = 1;
+ textbox->blink = 1;
+ textbox->blinktime = gui_time();
+ }
+
+ if( !console_textbox_key_pressed( textbox, SDLK_RETURN )
+ && !console_textbox_key_pressed( textbox, SDLK_KP_ENTER )
+ ) return moved;
+ return console_apply_input_menu_selection( wnd );
+}
+
+void gui_consolewindow_draw_fn( void* ptr ) {
+ GUI_CONSOLEWINDOW* wnd = (GUI_CONSOLEWINDOW*)ptr;
+ I32 max_x = game->gl->canvas_size[0];
+ I32 max_y = game->gl->canvas_size[1];
+
+ if( constrain_console( wnd, max_x, max_y )
+ && wnd->active_edges != RESIZE_NONE
+ ) store_console_drag_origin( wnd );
+ layout_console_window( wnd );
+ console_position_input_menu( wnd );
+
+ I32 x = gui_relx( wnd );
+ I32 y = gui_rely( wnd );
+
+ CLR clr = gui_is_fg_window( wnd ) ? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( x + wnd->w, y + 4, 4, wnd->h, ui_clr.shadow );
+ gui_draw_frect( x + 4, y + wnd->h, wnd->w - 2, 4, CLR::BLACK() );
+ gui_draw_frect( x, y, wnd->w, wnd->h, clr );
+ gui_draw_frect(
+ x + GUI_CONSOLE_BORDER_WIDTH,
+ y + GUI_CONSOLE_BORDER_WIDTH,
+ wnd->w - GUI_CONSOLE_BORDER_WIDTH * 2,
+ wnd->h - GUI_CONSOLE_BORDER_WIDTH * 2,
+ ui_clr.bg
+ );
+ gui_draw_frect( x, y, wnd->w, GUI_TITLE_HEIGHT, clr );
+ gui_draw_frect(
+ x + GUI_CONSOLE_BORDER_WIDTH,
+ y + GUI_CONSOLE_BORDER_WIDTH,
+ wnd->w - GUI_CONSOLE_BORDER_WIDTH * 2,
+ GUI_TITLE_HEIGHT - GUI_CONSOLE_BORDER_WIDTH * 2,
+ ui_clr.bg
+ );
+
+ draw_console_output( wnd );
+
+ wnd->children.each( fn( GUI_BASE** ptr ) {
+ GUI_BASE* it = *ptr;
+ if( !it->enabled ) return;
+ if( it->draw_fn ) it->draw_fn( it );
+ else dlog( "gui_editorwindow_draw_fn(): child %p has no draw_fn", it );
+ } );
+}
+
+inline void console_accept_input( GUI_CONSOLEWINDOW* wnd ) {
+ GUI_TEXTBOX* textbox = wnd->input;
+ if( !textbox )
+ return;
+ U32 enter = SDLK_RETURN & 0xff;
+ U32 kp_enter = SDLK_KP_ENTER & 0xff;
+
+ U8 enter_pressed =
+ ( textbox->keys[enter] && !textbox->prevkeys[enter] ) ||
+ ( textbox->keys[kp_enter] && !textbox->prevkeys[kp_enter] );
+
+ if( !enter_pressed )
+ return;
+
+ if( !console_submit( &game->console, textbox->value ) ) {
+ textbox->active = 1;
+ return;
+ }
+
+ textbox->blinktime = gui_time();
+ textbox->cursorpxout = 0;
+ textbox->cursorpxin = 0;
+ textbox->cursorout = 0;
+ textbox->cursorin = 0;
+ textbox->value[0] = 0;
+ textbox->active = 1;
+ textbox->blink = 1;
+
+ wnd->scroll_offset = 0;
+}
+
+inline I32 console_max_scroll( GUI_CONSOLEWINDOW* wnd ) {
+ if( !wnd )
+ return 0;
+ I32 row_count = (I32)wnd->wrapped_lines.size;
+ I32 visible = max( 1, wnd->visible_rows );
+ return max( 0, row_count - visible );
+}
+
+inline U8 console_mouse_over_output(
+ GUI_CONSOLEWINDOW* wnd,
+ VEC2 mouse
+) {
+ if( !wnd )
+ return 0;
+
+ VEC2 pos = get_console_output_pos( wnd );
+ VEC2 sz = get_console_output_sz( wnd );
+
+ return sz.x > 0
+ && sz.y > 0
+ && mouse.x >= pos.x
+ && mouse.x < pos.x + sz.x
+ && mouse.y >= pos.y
+ && mouse.y < pos.y + sz.y;
+}
+
+inline I32 console_output_hit_test(
+ GUI_CONSOLEWINDOW* wnd,
+ VEC2 mouse
+) {
+ if( !console_mouse_over_output( wnd, mouse ) )
+ return -1;
+
+ VEC2 pos = get_console_output_pos( wnd );
+ VEC2 sz = get_console_output_sz( wnd );
+ I32 line_height = console_output_line_height();
+ if( line_height <= 0 )
+ return -1;
+
+ console_rebuild_wrapped_lines(
+ wnd,
+ &game->console,
+ (I32)sz.x
+ );
+
+ wnd->visible_rows = (I32)sz.y / line_height;
+ if( wnd->visible_rows <= 0 )
+ return -1;
+
+ wnd->scroll_offset = m_clamp(
+ wnd->scroll_offset,
+ 0,
+ console_max_scroll( wnd )
+ );
+
+ I32 from_bottom = (
+ (I32)( pos.y + sz.y )
+ - 1
+ - (I32)mouse.y
+ ) / line_height;
+
+ if( from_bottom < 0 || from_bottom >= wnd->visible_rows )
+ return -1;
+
+ I32 index = (I32)wnd->wrapped_lines.size
+ - 1
+ - wnd->scroll_offset
+ - from_bottom;
+ if( index < 0 || index >= (I32)wnd->wrapped_lines.size )
+ return -1;
+ return index;
+}
+
+inline U8 console_handle_output_left_mouse(
+ GUI_CONSOLEWINDOW* wnd,
+ U8 left_down,
+ U8 left_pressed,
+ U8 over_output,
+ I32 hit
+) {
+ if( !left_down )
+ wnd->output_selecting = 0;
+
+ if( left_pressed && !over_output ) {
+ console_close_output_menu( wnd );
+ wnd->output_selection_active = 0;
+ wnd->output_selecting = 0;
+ return 1;
+ }
+
+ if( !left_pressed ) {
+ if( left_down && wnd->output_selecting && hit >= 0 )
+ wnd->output_selection_cursor = wnd->wrapped_lines[hit].source_id;
+ return 0;
+ }
+
+ console_close_output_menu( wnd );
+ console_close_input_menu( wnd );
+
+ if( wnd->input )
+ wnd->input->active = 0;
+
+ if( hit < 0 ) {
+ wnd->output_selection_active = 0;
+ wnd->output_selecting = 0;
+ return 1;
+ }
+
+ U64 source_id = wnd->wrapped_lines[hit].source_id;
+ U8 extend = input.keys[SDLK_LSHIFT & 0xff]
+ || input.keys[SDLK_RSHIFT & 0xff];
+
+ if( !extend || !wnd->output_selection_active )
+ wnd->output_selection_anchor = source_id;
+
+ wnd->output_selection_cursor = source_id;
+ wnd->output_selection_active = 1;
+ wnd->output_selecting = 1;
+ return 0;
+}
+
+inline void console_handle_output_right_mouse(
+ GUI_CONSOLEWINDOW* wnd,
+ U8 right_pressed,
+ U8 over_output,
+ I32 hit,
+ VEC2 mouse
+) {
+ if( !right_pressed || !over_output || hit < 0 )
+ return;
+
+ U64 source_id = wnd->wrapped_lines[hit].source_id;
+
+ if( !console_output_line_selected( wnd, source_id ) ) {
+ wnd->output_selection_anchor = source_id;
+ wnd->output_selection_cursor = source_id;
+ wnd->output_selection_active = 1;
+ }
+
+ wnd->output_selecting = 0;
+
+ if( wnd->input )
+ wnd->input->active = 0;
+
+ console_open_output_menu(
+ wnd,
+ (I32)mouse.x,
+ (I32)mouse.y
+ );
+}
+
+inline void console_handle_output_mouse(
+ GUI_CONSOLEWINDOW* wnd,
+ U8 left_down,
+ U8 left_pressed,
+ U8 right_pressed
+) {
+ if( !wnd )
+ return;
+
+ if( wnd->output_selection_active
+ && !console_validate_output_selection( wnd, &game->console )
+ ) console_close_output_menu( wnd );
+
+ VEC2 mouse = input.mouse.pos;
+
+ if( console_mouse_over_menu( wnd->menus.input.menu, mouse )
+ || console_mouse_over_menu( wnd->menus.output.menu, mouse )
+ ) {
+ if( !left_down )
+ wnd->output_selecting = 0;
+ return;
+ }
+
+ U8 over_output = console_mouse_over_output( wnd, mouse );
+ I32 hit = over_output
+ ? console_output_hit_test( wnd, mouse )
+ : -1;
+
+ if( console_handle_output_left_mouse(
+ wnd,
+ left_down,
+ left_pressed,
+ over_output,
+ hit
+ )
+ ) return;
+
+ console_handle_output_right_mouse(
+ wnd,
+ right_pressed,
+ over_output,
+ hit,
+ mouse
+ );
+}
+
+inline void console_handle_output_copy_key( GUI_CONSOLEWINDOW* wnd ) {
+ if( !wnd )
+ return;
+
+ U8 c_down = input.keys['c'];
+ U8 c_pressed = c_down && !wnd->copy_key_held;
+
+ wnd->copy_key_held = c_down;
+
+ U8 ctrl_down = input.keys[SDLK_LCTRL & 0xff]
+ || input.keys[SDLK_RCTRL & 0xff];
+
+ if( c_pressed && ctrl_down && wnd->output_selection_active )
+ console_copy_output_selection( wnd );
+}
+
+void console_handle_scroll( GUI_CONSOLEWINDOW* wnd ) {
+ if( !wnd
+ || !input.mouse.wheel
+ || !console_mouse_over_output( wnd, input.mouse.pos )
+ ) return;
+
+ I32 delta = 0;
+
+ if( input.mouse.wheel == 255 )
+ delta = -1;
+ else if( input.mouse.wheel == 1 )
+ delta = 1;
+ else
+ return;
+
+ wnd->scroll_offset = m_clamp(
+ wnd->scroll_offset + delta,
+ 0,
+ console_max_scroll( wnd )
+ );
+}
+
+inline U8 console_handle_resize_input(
+ GUI_CONSOLEWINDOW* wnd,
+ I32 max_x,
+ I32 max_y,
+ U8 left_down,
+ U8 left_pressed,
+ RESIZE_EDGES* hover_edges
+) {
+ *hover_edges = RESIZE_NONE;
+
+ if( constrain_console( wnd, max_x, max_y )
+ && wnd->active_edges != RESIZE_NONE
+ ) store_console_drag_origin( wnd );
+ layout_console_window( wnd );
+
+ if( wnd->active_edges != RESIZE_NONE ) {
+ U8 lost_focus = SDL_GetKeyboardFocus() != game->gl->window;
+
+ if( !left_down || lost_focus )
+ console_resize_end( wnd );
+ else {
+ resize_console_window( wnd, max_x, max_y );
+ set_console_cursor( wnd->active_edges );
+ return 1;
+ }
+ }
+ *hover_edges = get_console_active_edges(
+ wnd, input.mouse.pos,
+ max_x, max_y
+ );
+ if( left_pressed && *hover_edges != RESIZE_NONE ) {
+ console_resize_begin( wnd, *hover_edges );
+ set_console_cursor( *hover_edges );
+ return 1;
+ }
+ return 0;
+}
+
+void gui_consolewindow_input_fn( void* ptr ) {
+ GUI_CONSOLEWINDOW* wnd = (GUI_CONSOLEWINDOW*)ptr;
+ I32 max_x = game->gl->canvas_size[0];
+ I32 max_y = game->gl->canvas_size[1];
+
+ U8 left_down = input.mouse.left;
+ U8 left_pressed = left_down && !wnd->lmb_held;
+ wnd->lmb_held = left_down;
+
+ U8 right_down = input.mouse.right;
+ U8 right_pressed = right_down && !wnd->rmb_held;
+ wnd->rmb_held = right_down;
+
+ console_handle_output_copy_key( wnd );
+
+ if( input.mouselock )
+ input_capture_mouse( 0 );
+ else
+ input_reset_mouse_accumulator();
+
+ RESIZE_EDGES active_edges = RESIZE_NONE;
+ if( console_handle_resize_input( wnd,
+ max_x, max_y,
+ left_down, left_pressed,
+ &active_edges
+ )
+ ) return;
+
+ U8 over_input = is_over_console_input( wnd, input.mouse.pos );
+ console_handle_scroll( wnd );
+ console_handle_output_mouse( wnd, left_down, left_pressed, right_pressed );
+
+ RESIZE_EDGES cursor_edges =
+ !left_down || left_pressed
+ ? active_edges
+ : RESIZE_NONE;
+ set_console_cursor( cursor_edges, over_input );
+
+ console_sync_input_menu( wnd );
+
+ gui_base_input_fn( wnd );
+
+ if( !wnd->enabled )
+ return;
+
+ U8 menu_consumed = console_handle_input_menu_keys( wnd );
+ if( !menu_consumed )
+ console_accept_input( wnd );
+
+ console_sync_input_menu( wnd );
+
+ constrain_console( wnd, max_x, max_y );
+ layout_console_window( wnd );
+ console_position_input_menu( wnd );
+}
+
+inline void console_set_input_focused(
+ GUI_CONSOLEWINDOW* wnd,
+ U8 focused
+) {
+ if( !wnd || !wnd->input )
+ return;
+
+ GUI_TEXTBOX* textbox = wnd->input;
+ textbox->active = focused;
+ textbox->heldkey = 0;
+ textbox->m1held = 0;
+
+ memcpy(
+ textbox->keys,
+ input.keys,
+ sizeof( textbox->keys )
+ );
+ memcpy(
+ textbox->prevkeys,
+ input.keys,
+ sizeof( textbox->prevkeys )
+ );
+ if( focused ) {
+ textbox->blink = 1;
+ textbox->blinktime = gui_time();
+ }
+}
+
+void console_close_cb( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ GUI_VIEW* view = (GUI_VIEW*)btn->parent;
+ GUI_CONSOLEWINDOW* wnd = (GUI_CONSOLEWINDOW*)view->parent;
+ console_close_output_menu( wnd );
+ console_close_input_menu( wnd );
+ console_resize_end( wnd );
+ wnd->output_selecting = 0;
+ wnd->copy_key_held = 0;
+ console_set_input_focused( wnd, 0 );
+ wnd->enabled = 0;
+}
+
+GUI_CONSOLEWINDOW* gui_consolewindow_create( I32 x, I32 y, I32 w, I32 h ) {
+ GUI_CONSOLEWINDOW* wnd = new GUI_CONSOLEWINDOW{};
+ wnd->x = x;
+ wnd->y = y;
+ wnd->w = w;
+ wnd->h = h;
+ wnd->locked = 0;
+ wnd->draw_fn = gui_consolewindow_draw_fn;
+ wnd->input_fn = gui_consolewindow_input_fn;
+ strcpy( wnd->name, "CONSOLE" );
+ _gui.windows.push( wnd );
+ gui_set_view( wnd );
+ gui_view(
+ GUI_CONSOLE_BORDER_WIDTH,
+ GUI_CONSOLE_BORDER_WIDTH,
+ w - GUI_CONSOLE_BORDER_WIDTH * 2,
+ h - GUI_CONSOLE_BORDER_WIDTH * 2
+ );
+ return wnd;
+}
+
+GUI_CONSOLEWINDOW* gui_consolewindow( I32 x, I32 y, I32 w, I32 h ) {
+ GUI_CONSOLEWINDOW* wnd = gui_consolewindow_create( x, y, w, h );
+
+ wnd->close = gui_button(
+ get_close_x( wnd->w ),
+ GUI_CONSOLE_PADDING,
+ GUI_BUTTON_SIZE,
+ GUI_BUTTON_SIZE,
+ "X",
+ console_close_cb
+ );
+ wnd->input = gui_textbox(
+ GUI_CONSOLE_PADDING,
+ get_input_y( wnd->h ),
+ get_input_w( wnd->w ),
+ GUI_CONSOLE_INPUT_HEIGHT,
+ "", 0x400, 0
+ );
+ wnd->input->active = 1;
+ wnd->title = gui_title( "console" );
+
+ GUI_CONTEXTMENU_ITEM* copy = wnd->menus.output.items.push( {} );
+ gui_contextmenu_set_item( copy, "copy", console_copy_output_selection, wnd );
+
+ layout_console_window( wnd );
+ return wnd;
+}
+
+U8 gui_consolewindow_input_focused() {
+ GUI_CONSOLEWINDOW* wnd = game
+ ? game->console_window
+ : 0;
+
+ return wnd
+ && wnd->input
+ && wnd->enabled
+ && wnd->input->active;
+}
+
+U8 gui_consolewindow_is_open() {
+ GUI_CONSOLEWINDOW* wnd = game
+ ? game->console_window
+ : 0;
+ return wnd
+ && wnd->enabled;
+}
+
+void gui_consolewindow_toggle() {
+ GUI_CONSOLEWINDOW* wnd = game
+ ? game->console_window
+ : 0;
+ if( !wnd )
+ return;
+
+ set_console_cursor( RESIZE_NONE );
+
+ if( wnd->enabled ) {
+ console_close_output_menu( wnd );
+ console_close_input_menu( wnd );
+ console_resize_end( wnd );
+ wnd->output_selecting = 0;
+ wnd->copy_key_held = 0;
+ console_set_input_focused( wnd, 0 );
+ }
+
+ wnd->enabled ^= 1;
+
+ if( wnd->enabled ) {
+ gui_bring_to_top( wnd );
+ console_set_input_focused( wnd, 1 );
+ wnd->copy_key_held = input.keys['c'];
+ wnd->rmb_held = input.mouse.right;
+ wnd->lmb_held = input.mouse.left;
+ }
+}
diff --git a/src/gui/console.h b/src/gui/console.h
index e69de29..0730f15 100644
--- a/src/gui/console.h
+++ b/src/gui/console.h
@@ -0,0 +1,67 @@
+#pragma once
+#include "base.h"
+
+enum RESIZE_EDGES : U8 {
+ RESIZE_NONE = 0,
+ RESIZE_LEFT = 1 << 0,
+ RESIZE_RIGHT = 1 << 1,
+ RESIZE_BOTTOM = 1 << 2
+};
+
+enum CONSOLE_MENU_INPUT_MODE : U8 {
+ CONSOLE_MENU_INPUT_NONE = 0,
+ CONSOLE_MENU_INPUT_HIST = 1,
+ CONSOLE_MENU_INPUT_CVAR = 2
+};
+
+struct CVAR;
+
+struct CONSOLE_WRAPPED_LINE {
+ STR text{};
+ U64 source_id{};
+};
+
+struct GUI_CONSOLEWINDOW : GUI_WINDOW {
+ struct GUI_CONSOLEWINDOW_MENU {
+ struct GUI_CONSOLEWINDOW_INPUT_MENU {
+ char suppressed_value[GUI_TEXTBOX_MAX]{};
+ LIST<GUI_CONTEXTMENU_ITEM> items{};
+ CONSOLE_MENU_INPUT_MODE mode{};
+ char query[GUI_TEXTBOX_MAX]{};
+ GUI_CONTEXTMENU* menu{};
+ LIST<CVAR*> cvars{};
+ U32 history_size{};
+ U32 cvar_count{};
+ U8 suppressed{};
+ } input;
+
+ struct GUI_CONSOLEWINDOW_OUTPUT_MENU {
+ LIST<GUI_CONTEXTMENU_ITEM> items{};
+ GUI_CONTEXTMENU* menu{};
+ } output;
+ } menus;
+
+ LIST<CONSOLE_WRAPPED_LINE> wrapped_lines{};
+ U64 wrapped_revision{(U64)-1};
+ I32 wrapped_width{-1};
+ U64 seen_revision{};
+ I32 scroll_offset{};
+ I32 visible_rows{};
+
+ U64 output_selection_anchor{};
+ U64 output_selection_cursor{};
+ U8 output_selection_active{};
+ U8 output_selecting{};
+ U8 copy_key_held{};
+
+ GUI_TEXTBOX* input{};
+ GUI_BUTTON* close{};
+ GUI_TITLE* title{};
+
+ RESIZE_EDGES active_edges = RESIZE_NONE;
+ VEC2 drag_start_mouse{};
+ VEC2 drag_start_pos{};
+ VEC2 drag_start_sz{};
+ U8 lmb_held{};
+ U8 rmb_held{};
+};
diff --git a/src/gui/contextmenu.cpp b/src/gui/contextmenu.cpp
new file mode 100644
index 0000000..0775651
--- /dev/null
+++ b/src/gui/contextmenu.cpp
@@ -0,0 +1,437 @@
+#include "base.h"
+
+U8 gui_contextmenu_item_enabled( GUI_CONTEXTMENU_ITEM* item ) {
+ if( !item )
+ return 0;
+
+ if( item->enabled_cb )
+ return item->enabled_cb( item );
+
+ return item->enabled;
+}
+
+I32 gui_contextmenu_title_h( GUI_CONTEXTMENU* menu ) {
+ return ( menu && menu->title[0] ) ? 18 : 0;
+}
+
+I32 gui_contextmenu_row_h() {
+ return 20;
+}
+
+I32 gui_contextmenu_width( LIST<GUI_CONTEXTMENU_ITEM>* items, const char* title ) {
+ I32 width = 0;
+ if( title && title[0] ) {
+ I32 text_w = 0;
+ gui_draw_get_str_bounds( &text_w, 0, FNT_JPN12, "%s", title );
+ width = max( width, text_w + 18 );
+ }
+
+ if( !items )
+ return width;
+
+ for( I32 i = 0; i < (I32)items->size; ++i ) {
+ I32 text_w = 0;
+ gui_draw_get_str_bounds( &text_w, 0, FNT_JPN12, "%s", ( *items )[i].text );
+ width = max( width, text_w + 20 );
+ }
+
+ return width;
+}
+
+I32 gui_contextmenu_height( I32 rows, const char* title ) {
+ I32 height = 4 + gui_contextmenu_row_h() * rows;
+ if( title && title[0] )
+ height += 18 + 1;
+ return max( height, gui_contextmenu_row_h() + 4 );
+}
+
+static I32 gui_contextmenu_items_y( GUI_CONTEXTMENU* menu ) {
+ if( !menu )
+ return 0;
+ I32 y = gui_rely( menu ) + 2;
+ if( menu->title[0] )
+ y += gui_contextmenu_title_h( menu ) + 1;
+ return y;
+}
+
+I32 gui_contextmenu_hit_test( GUI_CONTEXTMENU* menu, I32 mx, I32 my ) {
+ if( !menu || !menu->items )
+ return -1;
+
+ I32 x = gui_relx( menu );
+ I32 y = gui_rely( menu );
+ if( mx < x || mx >= x + menu->w || my < y || my >= y + menu->h )
+ return -1;
+
+ I32 items_y = gui_contextmenu_items_y( menu );
+ if( my < items_y )
+ return -1;
+
+ I32 row = ( my - items_y ) / gui_contextmenu_row_h();
+ if( row < 0 || row >= menu->visible_rows )
+ return -1;
+
+ I32 idx = menu->first_visible + row;
+ if( idx < 0 || idx >= (I32)menu->items->size )
+ return -1;
+
+ return idx;
+}
+
+static I32 gui_contextmenu_draw_frame(
+ GUI_CONTEXTMENU* menu,
+ I32 x, I32 y,
+ CLR border
+) {
+ gui_draw_frect( x, y, menu->w, menu->h, border );
+ gui_draw_frect(
+ x + 1,
+ y + 1,
+ max( 1, menu->w - 2 ),
+ max( 1, menu->h - 2 ),
+ ui_clr.bg_sec
+ );
+
+ I32 item_y = y + 2;
+
+ if( menu->title[0] ) {
+ gui_draw_str(
+ x + 8, item_y + 1,
+ ALIGN_L, FNT_JPN12,
+ ui_clr.txt,
+ "%s", menu->title
+ );
+ item_y += gui_contextmenu_title_h( menu );
+ gui_draw_line(
+ x + 1, item_y,
+ x + menu->w - 2, item_y,
+ border
+ );
+ ++item_y;
+ }
+ return item_y;
+}
+
+static void gui_contextmenu_draw_rows(
+ GUI_CONTEXTMENU* menu,
+ I32 x, I32 y,
+ I32 hit,
+ U8 left_down
+) {
+ I32 item_count = (I32)menu->items->size;
+ I32 first = m_clamp(
+ menu->first_visible,
+ 0, item_count
+ );
+ I32 rows = min(
+ menu->visible_rows,
+ item_count - first
+ );
+
+ CLR hover = CLR::blend( ui_clr.border, ui_clr.bg, 0.7f );
+ CLR active = CLR::blend( ui_clr.bg_sec, ui_clr.border, 0.22f );
+
+ gui_draw_push_clip(
+ x + 1, y,
+ max( 0, menu->w - 2 ),
+ max( 0, rows * gui_contextmenu_row_h() )
+ );
+
+ for( I32 row = 0; row < rows; ++row ) {
+ I32 idx = first + row;
+ GUI_CONTEXTMENU_ITEM* item = &( *menu->items )[idx];
+ U8 enabled = gui_contextmenu_item_enabled( item );
+ U8 pressed = menu->held
+ && menu->held_idx == idx
+ && left_down
+ && enabled;
+ CLR fill = pressed
+ ? active
+ : ( hit == idx || menu->selected_idx == idx )
+ ? hover
+ : ui_clr.bg_sec;
+ I32 row_y = y + row * gui_contextmenu_row_h();
+
+ gui_draw_frect(
+ x + 1, row_y,
+ max( 1, menu->w - 2 ),
+ gui_contextmenu_row_h(),
+ fill
+ );
+ gui_draw_str(
+ x + 8, row_y + 2,
+ ALIGN_L, FNT_JPN12,
+ enabled ? ui_clr.txt : ui_clr.txt_inactive,
+ "%s", item->text
+ );
+ }
+ gui_draw_pop_clip();
+}
+
+void gui_contextmenu_draw_fn( void* ptr ) {
+ GUI_CONTEXTMENU* menu = (GUI_CONTEXTMENU*)ptr;
+ if( !menu || !menu->items || !menu->items->size )
+ return;
+
+ I32 x = gui_relx( menu );
+ I32 y = gui_rely( menu );
+ I32 mx = 0, my = 0;
+ gui_cursor_pos( &mx, &my );
+
+ CLR border = gui_is_fg_window( menu )
+ ? ui_clr.border
+ : ui_clr.border_inactive;
+
+ I32 item_y = gui_contextmenu_draw_frame(
+ menu,
+ x, y,
+ border
+ );
+
+ gui_contextmenu_draw_rows(
+ menu, x, item_y,
+ gui_contextmenu_hit_test( menu, mx, my ),
+ gui_mbutton_down( GUI_MBTNLEFT )
+ );
+}
+
+void gui_contextmenu_input_fn( void* ptr ) {
+ GUI_CONTEXTMENU* menu = (GUI_CONTEXTMENU*)ptr;
+ if( !menu || !menu->items || !menu->items->size )
+ return;
+
+ U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
+ U8 m2 = gui_mbutton_down( GUI_MBTNRIGHT );
+ if( menu->initheld ) {
+ if( m1 || m2 )
+ return;
+ menu->initheld = 0;
+ return;
+ }
+
+ I32 mx = 0, my = 0;
+ gui_cursor_pos( &mx, &my );
+ I32 hit = gui_contextmenu_hit_test( menu, mx, my );
+
+ if( m1 || m2 ) {
+ if( !menu->held ) {
+ menu->held = 1;
+ menu->held_idx = hit;
+ }
+ return;
+ }
+
+ if( !menu->held )
+ return;
+
+ if( hit == -1 ) {
+ gui_contextmenu_hide( menu );
+ return;
+ }
+
+ if( hit == menu->held_idx ) {
+ GUI_CONTEXTMENU_ITEM* item = &( *menu->items )[hit];
+ if( gui_contextmenu_item_enabled( item ) && item->cb ) {
+ menu->selected_idx = hit;
+
+ GUI_BASE* parent = menu->parent;
+ item->cb( item->data );
+
+ if( parent
+ && parent->children.idx_of( (GUI_BASE*)menu ) != -1
+ ) gui_contextmenu_hide( menu );
+ return;
+ }
+ }
+
+ menu->held = 0;
+ menu->held_idx = -1;
+}
+
+void gui_contextmenu_hide( GUI_CONTEXTMENU* menu ) {
+ if( !menu )
+ return;
+
+ if( menu->owner_ref && *menu->owner_ref == menu )
+ *menu->owner_ref = 0;
+
+ if( menu->parent ) {
+ I32 idx = menu->parent->children.idx_of( (GUI_BASE*)menu );
+ if( idx != -1 )
+ menu->parent->children.erase( idx );
+ }
+
+ gui_free( menu );
+}
+
+U8 gui_contextmenu_open( GUI_CONTEXTMENU *menu ) {
+ return !menu ? 0 : menu->enabled;
+}
+
+void gui_contextmenu_set_item(
+ GUI_CONTEXTMENU_ITEM* item,
+ const char* text,
+ GUI_CONTEXTMENU_CALLBACK cb,
+ void* data,
+ GUI_CONTEXTMENU_ENABLED_CALLBACK enabled_cb
+) {
+ if( !item )
+ return;
+
+ item->items.clear();
+ snprintf( item->text, sizeof( item->text ), "%s", text ? text : "" );
+ item->cb = cb;
+ item->data = data;
+ item->enabled = 1;
+ item->enabled_cb = enabled_cb;
+}
+
+GUI_CONTEXTMENU_ITEM* gui_contextmenu_selected( GUI_CONTEXTMENU* menu ) {
+ if( !menu || !menu->items )
+ return 0;
+
+ if( menu->selected_idx < 0 || menu->selected_idx >= (I32)menu->items->size )
+ return 0;
+
+ return &( *menu->items )[menu->selected_idx];
+}
+
+static I32 gui_contextmenu_next_enabled(
+ GUI_CONTEXTMENU* menu,
+ I32 delta,
+ U8 wrap
+) {
+ I32 item_count = (I32)menu->items->size;
+ I32 direction = delta < 0 ? -1 : 1;
+ I32 candidate = menu->selected_idx;
+ U8 has_candidate = candidate >= 0
+ && candidate < item_count;
+ for( I32 scanned = 0; scanned < item_count; ++scanned ) {
+ if( !has_candidate ) {
+ candidate = direction < 0
+ ? item_count - 1
+ : 0;
+ has_candidate = 1;
+ }
+ else {
+ candidate += direction;
+
+ if( candidate < 0 || candidate >= item_count ) {
+ if( !wrap )
+ return -1;
+
+ candidate = direction < 0
+ ? item_count - 1
+ : 0;
+ }
+ }
+
+ if( gui_contextmenu_item_enabled(
+ &( *menu->items )[candidate]
+ ) ) return candidate;
+ }
+ return -1;
+}
+
+void gui_contextmenu_reveal_selection(
+ GUI_CONTEXTMENU* menu
+) {
+ if( !menu || !menu->items || menu->visible_rows <= 0 )
+ return;
+
+ I32 selected = menu->selected_idx;
+
+ if( selected < 0 || selected >= (I32)menu->items->size )
+ return;
+
+ if( selected < menu->first_visible )
+ menu->first_visible = selected;
+ else if( selected >= menu->first_visible + menu->visible_rows )
+ menu->first_visible = selected - menu->visible_rows + 1;
+
+ I32 max_first = max( 0,
+ (I32)menu->items->size - menu->visible_rows
+ );
+
+ menu->first_visible = m_clamp(
+ menu->first_visible,
+ 0, max_first
+ );
+}
+
+void gui_contextmenu_move_selection(
+ GUI_CONTEXTMENU* menu,
+ I32 delta,
+ U8 wrap
+) {
+ if( !menu || !menu->items || !menu->items->size || !delta )
+ return;
+
+ I32 selected = gui_contextmenu_next_enabled(
+ menu,
+ delta,
+ wrap
+ );
+
+ if( selected < 0 )
+ return;
+
+ menu->selected_idx = selected;
+ gui_contextmenu_reveal_selection( menu );
+}
+
+GUI_CONTEXTMENU* gui_show_contextmenu(
+ I32 x, I32 y,
+ LIST<GUI_CONTEXTMENU_ITEM>* items,
+ GUI_BASE* parent,
+ const char* title,
+ I32 max_visible_rows,
+ GUI_CONTEXTMENU** owner_ref
+) {
+ if( !parent || !items || !items->size )
+ return 0;
+
+ GUI_WINDOW* owner_window = gui_get_parent_wnd( parent );
+ if( !owner_window )
+ return 0;
+
+ if( owner_ref && *owner_ref )
+ gui_contextmenu_hide( *owner_ref );
+
+ GUI_CONTEXTMENU* menu = new GUI_CONTEXTMENU;
+ menu->items = items;
+ menu->draw_fn = gui_contextmenu_draw_fn;
+ menu->input_fn = gui_contextmenu_input_fn;
+ menu->enabled = 1;
+ menu->initheld = 1;
+ menu->held = 0;
+ menu->held_idx = -1;
+ menu->selected_idx = -1;
+ menu->parent = owner_window;
+ menu->owner_ref = owner_ref;
+ I32 item_count = (I32)items->size;
+ menu->max_visible_rows = max_visible_rows > 0
+ ? min( max_visible_rows, item_count )
+ : item_count;
+ menu->visible_rows = menu->max_visible_rows;
+ menu->first_visible = max( 0,
+ item_count - menu->visible_rows
+ );
+
+ snprintf( menu->name, sizeof( menu->name ), "GUI_CONTEXTMENU" );
+ snprintf( menu->title, sizeof( menu->title ), "%s", title ? title : "" );
+
+ menu->w = gui_contextmenu_width( items, title );
+ menu->h = gui_contextmenu_height( menu->visible_rows, title );
+ menu->xbound = menu->w;
+ menu->ybound = menu->h;
+
+ menu->x = x - gui_relx( owner_window );
+ menu->y = y - gui_rely( owner_window );
+ owner_window->children.push( menu );
+
+ if( owner_ref )
+ *owner_ref = menu;
+
+ return menu;
+}
diff --git a/src/gui/textbox.cpp b/src/gui/textbox.cpp
index c367193..edf2a30 100644
--- a/src/gui/textbox.cpp
+++ b/src/gui/textbox.cpp
@@ -1,51 +1,363 @@
#include "base.h"
+#include "../render/gl_2d_font.h"
#include "../util/input.h"
#include <SDL_keycode.h>
const U32 TEXTBOX_TITLE_OFFSET = 15;
-U8 is_printable_char( U8 key ) {
- return key >= 32 && key < 127;
+struct GUI_TEXTBOX_KEY_RESULT {
+ U8 value_changed{};
+ U8 cursor_moved{};
+};
+
+static I32 gui_textbox_line_height() {
+ GL_FONT* font = _gui.fonts.jpn12.glfnt;
+ return font
+ ? max( 1, (I32)font->char_height )
+ : 1;
}
-void gui_textbox_draw_newline_str( GUI_TEXTBOX* tb, I32 x, I32 y, CLR clr, I32* endposx, I32* endposy ) {
- char linebuf[GUI_TEXTBOX_MAX];
- char* start = tb->value;
- I32 tw, th;
+static U32 gui_textbox_line_start( const char* text, U32 cursor ) {
+ while( cursor > 0 && text[cursor - 1] != '\n' )
+ --cursor;
+ return cursor;
+}
- for( char* c = start; !!*c; ++c ) {
- if( *c == '\n' ) {
- U32 len = (U32)( c - start );
- memcpy( linebuf, start, len );
- linebuf[len] = 0;
+static U32 gui_textbox_line_end( const char* text, U32 length, U32 cursor ) {
+ while( cursor < length && text[cursor] != '\n' )
+ ++cursor;
+ return cursor;
+}
+
+static U8 gui_textbox_has_selection( const GUI_TEXTBOX* tb ) {
+ return tb->cursorin != tb->cursorout;
+}
- gui_draw_str( x, y, ALIGN_L, FNT_JPN12, clr, linebuf );
- gui_draw_get_str_bounds( &tw, &th, FNT_JPN12, linebuf );
- y += th + 1;
+static U32 gui_textbox_selection_start( const GUI_TEXTBOX* tb ) {
+ return min( tb->cursorin, tb->cursorout );
+}
- *endposx = x + tw;
- *endposy = y + th;
+static U32 gui_textbox_selection_end( const GUI_TEXTBOX* tb ) {
+ return max( tb->cursorin, tb->cursorout );
+}
- if( *(c+1) == 0 )
- break;
+static U8 gui_textbox_shift_down( const GUI_TEXTBOX* tb ) {
+ return tb->keys[SDLK_LSHIFT & 0xff]
+ || tb->keys[SDLK_RSHIFT & 0xff];
+}
- start = ++c;
- }
- else if( (*c+1) == 0 ) {
- U32 len = (U32)( c - start );
- memcpy( linebuf, start, len );
- linebuf[len] = 0;
+static I32 gui_textbox_range_width( const char* text, U32 start, U32 end ) {
+ GL_FONT* font = _gui.fonts.jpn12.glfnt;
+ if( !font )
+ return 0;
- gui_draw_str( x, y, ALIGN_L, FNT_JPN12, clr, linebuf );
- gui_draw_get_str_bounds( &tw, &th, FNT_JPN12, linebuf );
- y += th + 1;
- *endposx = x + tw;
- *endposy = y + th;
+ F32 width = 0.f;
+ for( U32 idx = start; idx < end; ++idx ) {
+ U8 c = (U8)text[idx];
+ if( !c || c == '\n' )
break;
- }
+ width += font->glyphs[c].advance_x;
+ }
+
+ return (I32)( width + 0.5f );
+}
+
+static I32 gui_textbox_cursor_row( const char* text, U32 cursor ) {
+ I32 row = 0;
+ for( U32 idx = 0; idx < cursor; ++idx ) {
+ if( text[idx] == '\n' )
+ ++row;
+ }
+ return row;
+}
+
+static I32 gui_textbox_line_count( const char* text, U32 length ) {
+ I32 count = 1;
+ for( U32 idx = 0; idx < length; ++idx ) {
+ if( text[idx] == '\n' )
+ ++count;
+ }
+ return count;
+}
+
+static U32 gui_textbox_line_start_from_row(
+ const char* text,
+ U32 length,
+ I32 wanted_row
+) {
+ U32 start = 0;
+ for( I32 row = 0; row < wanted_row; ++row ) {
+ U32 end = gui_textbox_line_end( text, length, start );
+ if( end >= length )
+ return length;
+ start = end + 1;
+ }
+ return start;
+}
+
+static U32 gui_textbox_nearest_cursor_x(
+ const char* text,
+ U32 line_start, U32 line_end,
+ I32 wanted_x
+) {
+ GL_FONT* font = _gui.fonts.jpn12.glfnt;
+ if( !font || wanted_x <= 0 )
+ return line_start;
+
+ F32 x = 0.f;
+
+ for( U32 idx = line_start; idx < line_end; ++idx ) {
+ F32 advance = font->glyphs[(U8)text[idx]].advance_x;
+ if( (F32)wanted_x < x + advance * 0.5f )
+ return idx;
+ x += advance;
+ }
+ return line_end;
+}
+
+static void gui_textbox_update_cursor_metrics( GUI_TEXTBOX* tb ) {
+ U32 length = (U32)strlen( tb->value );
+
+ tb->cursorin = min( tb->cursorin, length );
+ tb->cursorout = min( tb->cursorout, length );
+
+ U32 line_start_in = gui_textbox_line_start( tb->value, tb->cursorin );
+ U32 line_start_out = gui_textbox_line_start( tb->value, tb->cursorout );
+
+ tb->cursorpxin = gui_textbox_range_width(
+ tb->value,
+ line_start_in,
+ tb->cursorin
+ );
+ tb->cursorpxout = gui_textbox_range_width(
+ tb->value,
+ line_start_out,
+ tb->cursorout
+ );
+
+ tb->cursorpy = gui_textbox_cursor_row(
+ tb->value,
+ tb->cursorout
+ ) * gui_textbox_line_height();
+}
+
+static void gui_textbox_set_cursor(
+ GUI_TEXTBOX* tb,
+ U32 cursor,
+ U8 extend_selection = 0,
+ U8 preserve_vertical_goal = 0
+) {
+ U32 length = (U32)strlen( tb->value );
+ tb->cursorout = min( cursor, length );
+
+ if( !extend_selection )
+ tb->cursorin = tb->cursorout;
+
+ gui_textbox_update_cursor_metrics( tb );
+
+ if( !preserve_vertical_goal )
+ tb->cursor_goal_valid = 0;
+}
+
+static U32 gui_textbox_cursor_from_point(
+ GUI_TEXTBOX* tb,
+ I32 local_x, I32 local_y
+) {
+ U32 length = (U32)strlen( tb->value );
+ I32 line_height = gui_textbox_line_height();
+ I32 line_count = gui_textbox_line_count( tb->value, length );
+ I32 row = local_y / line_height;
+ row = m_clamp( row, 0, line_count - 1 );
+ U32 line_start = gui_textbox_line_start_from_row(
+ tb->value, length, row
+ );
+ U32 line_end = gui_textbox_line_end(
+ tb->value, length, line_start
+ );
+ return gui_textbox_nearest_cursor_x(
+ tb->value,
+ line_start,
+ line_end,
+ max( 0, local_x )
+ );
+}
+
+static U8 gui_textbox_move_vertical(
+ GUI_TEXTBOX* tb,
+ I32 direction,
+ U8 extend_selection = 0
+) {
+ if( !direction )
+ return 0;
+
+ U32 length = (U32)strlen( tb->value );
+ gui_textbox_update_cursor_metrics( tb );
+
+ if( !tb->cursor_goal_valid ) {
+ tb->cursor_goal_px = tb->cursorpxout;
+ tb->cursor_goal_valid = 1;
+ }
+
+ U32 current_start = gui_textbox_line_start( tb->value, tb->cursorout );
+ U32 target_start = 0;
+ U32 target_end = 0;
+
+ if( direction < 0 ) {
+ if( current_start == 0 )
+ return 0;
+
+ target_end = current_start - 1;
+ target_start = gui_textbox_line_start( tb->value, target_end );
+ }
+ else {
+ U32 current_end = gui_textbox_line_end(
+ tb->value,
+ length,
+ tb->cursorout
+ );
+ if( current_end >= length )
+ return 0;
+
+ target_start = current_end + 1;
+ target_end = gui_textbox_line_end( tb->value, length, target_start );
+ }
+
+ U32 target = gui_textbox_nearest_cursor_x(
+ tb->value,
+ target_start,
+ target_end,
+ tb->cursor_goal_px
+ );
+ gui_textbox_set_cursor( tb, target, extend_selection, 1 );
+ return 1;
+}
+
+static U8 gui_textbox_delete_selection( GUI_TEXTBOX* tb ) {
+ if( !gui_textbox_has_selection( tb ) )
+ return 0;
+
+ U32 length = (U32)strlen( tb->value );
+ U32 start = min(
+ gui_textbox_selection_start( tb ),
+ length
+ );
+ U32 end = min(
+ gui_textbox_selection_end( tb ),
+ length
+ );
+
+ if( start >= end ) {
+ gui_textbox_set_cursor( tb, start );
+ return 0;
}
+
+ if( tb->pre_cb )
+ tb->pre_cb( tb );
+
+ memmove(
+ tb->value + start,
+ tb->value + end,
+ length - end + 1
+ );
+
+ gui_textbox_set_cursor( tb, start );
+ return 1;
+}
+
+static U32 gui_textbox_insert_bytes(
+ GUI_TEXTBOX* tb,
+ const char* source,
+ U32 count
+) {
+ if( !tb || !source || !count || !tb->maxlen )
+ return 0;
+
+ U32 length = (U32)strlen( tb->value );
+ U32 limit = min( tb->maxlen, GUI_TEXTBOX_MAX );
+
+ U32 start = min(
+ gui_textbox_selection_start( tb ),
+ length
+ );
+ U32 end = min(
+ gui_textbox_selection_end( tb ),
+ length
+ );
+
+ U32 selected = end - start;
+ U32 remaining = length - selected;
+
+ if( limit <= 1 || remaining >= limit - 1 )
+ return 0;
+
+ U32 available = limit - 1 - remaining;
+ count = min( count, available );
+
+ if( !count )
+ return 0;
+
+ if( tb->pre_cb )
+ tb->pre_cb( tb );
+
+ memmove(
+ tb->value + start + count,
+ tb->value + end,
+ length - end + 1
+ );
+
+ memcpy(
+ tb->value + start,
+ source, count
+ );
+
+ gui_textbox_set_cursor( tb, start + count );
+ return count;
+}
+
+static U8 gui_textbox_backspace( GUI_TEXTBOX* tb ) {
+ if( gui_textbox_delete_selection( tb ) )
+ return 1;
+
+ U32 length = (U32)strlen( tb->value );
+ U32 cursor = min( tb->cursorout, length );
+ if( !cursor )
+ return 0;
+
+ if( tb->pre_cb )
+ tb->pre_cb( tb );
+
+ memmove(
+ tb->value + cursor - 1,
+ tb->value + cursor,
+ length - cursor + 1
+ );
+
+ gui_textbox_set_cursor( tb, cursor - 1 );
+ return 1;
+}
+
+static U8 gui_textbox_delete_forward( GUI_TEXTBOX* tb ) {
+ if( gui_textbox_delete_selection( tb ) )
+ return 1;
+
+ U32 length = (U32)strlen( tb->value );
+ U32 cursor = min( tb->cursorout, length );
+ if( cursor >= length )
+ return 0;
+
+ if( tb->pre_cb )
+ tb->pre_cb( tb );
+
+ memmove(
+ tb->value + cursor,
+ tb->value + cursor + 1,
+ length - cursor
+ );
+
+ gui_textbox_set_cursor( tb, cursor );
+ return 1;
}
void gui_textbox_draw_blinker( GUI_TEXTBOX* tb, I32 x, I32 y, CLR clr ) {
@@ -56,154 +368,574 @@ void gui_textbox_draw_blinker( GUI_TEXTBOX* tb, I32 x, I32 y, CLR clr ) {
tb->blinktime = gui_time();
}
- if( tb->blink )
- gui_draw_str( x, y, ALIGN_L, FNT_JPN12, clr, "|" );
+ if( tb->blink ) {
+ I32 caret_h = max( 1,
+ gui_textbox_line_height() - 2
+ );
+ gui_draw_frect(
+ x, y + 1,
+ 1, caret_h,
+ clr
+ );
+ }
}
-void gui_textbox_draw_fn( void* ptr ) {
- GUI_TEXTBOX* tb = (GUI_TEXTBOX*)ptr;
+static I32 gui_textbox_newline_width() {
+ GL_FONT* font = _gui.fonts.jpn12.glfnt;
- I32 x = gui_relx( tb );
- I32 y = gui_rely( tb );
+ if( !font )
+ return 1;
- gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, tb->name );
- y += TEXTBOX_TITLE_OFFSET;
+ return max( 1,
+ (I32)( font->glyphs[(U8)' '].advance_x + 0.5f )
+ );
+}
- CLR col = gui_is_fg_window( tb )? ui_clr.border : ui_clr.border_inactive;
+static void gui_textbox_draw_selection(
+ GUI_TEXTBOX* tb,
+ I32 draw_x, I32 draw_y,
+ I32 line_height,
+ CLR color
+) {
+ if( !gui_textbox_has_selection( tb ) )
+ return;
- gui_draw_frect( x, y, tb->w, tb->h, col );
- gui_draw_frect( x+1, y+1, tb->w-2, tb->h-2, ui_clr.bg_sec );
+ U32 length = (U32)strlen( tb->value );
+ U32 selection_start = min(
+ gui_textbox_selection_start( tb ), length
+ );
+ U32 selection_end = min(
+ gui_textbox_selection_end( tb ), length
+ );
+
+ U32 line_start = gui_textbox_line_start(
+ tb->value, selection_start
+ );
+
+ I32 row = gui_textbox_cursor_row( tb->value, line_start );
+
+ I32 newline_width = gui_textbox_newline_width();
+
+ for( ;; ) {
+ U32 line_end = gui_textbox_line_end(
+ tb->value, length, line_start
+ );
+
+ U32 range_start = max(
+ selection_start, line_start
+ );
+ U32 range_end = min(
+ selection_end, line_end
+ );
+
+ I32 x0 = gui_textbox_range_width(
+ tb->value, line_start, range_start
+ );
+ I32 x1 = gui_textbox_range_width(
+ tb->value, line_start, range_end
+ );
+ I32 width = x1 - x0;
+
+ if( line_end < length && selection_end > line_end )
+ width += newline_width;
+
+ if( width > 0 ) {
+ gui_draw_frect(
+ draw_x + x0,
+ draw_y + row * line_height,
+ width, line_height, color
+ );
+ }
- CLR clr = tb->active? ui_clr.txt : ui_clr.txt_inactive;
- I32 ypos = y + 3;
- I32 xpos = x + 2;
+ if( line_end >= length || line_end >= selection_end )
+ break;
- I32 endposx, endposy;
+ line_start = line_end + 1;
+ ++row;
+ }
+}
- if( tb->allow_newl ) {
- gui_textbox_draw_newline_str( tb, xpos, ypos, clr, &endposx, &endposy );
- } else {
- gui_draw_str( xpos, ypos, ALIGN_L, FNT_JPN12, clr, tb->value );
+static void gui_textbox_draw_frame(
+ GUI_TEXTBOX* tb,
+ I32 x, I32 y
+) {
+ CLR border = gui_is_fg_window( tb )
+ ? ui_clr.border
+ : ui_clr.border_inactive;
+
+ gui_draw_frect( x, y, tb->w, tb->h, border );
+ gui_draw_frect(
+ x + 1, y + 1,
+ max( 0, tb->w - 2 ),
+ max( 0, tb->h - 2 ),
+ ui_clr.bg_sec
+ );
+}
- I32 tw, th;
- gui_draw_get_str_bounds( &tw, &th, FNT_JPN12, tb->value );
- endposx = xpos + tw;
- endposy = ypos;
+static void gui_textbox_update_scroll(
+ GUI_TEXTBOX* tb,
+ I32 content_w,
+ I32 content_h,
+ I32 line_height
+) {
+ I32 text_h = line_height;
+ I32 text_w = 0;
+
+ gui_draw_get_str_bounds(
+ &text_w, &text_h, FNT_JPN12,
+ "%s", tb->value
+ );
+
+ text_h = max( text_h,
+ tb->cursorpy + line_height
+ );
+
+ if( tb->cursorpxout < tb->scrollpx )
+ tb->scrollpx = tb->cursorpxout;
+ else if( tb->cursorpxout + 1 > tb->scrollpx + content_w )
+ tb->scrollpx = tb->cursorpxout + 1 - content_w;
+
+ I32 max_scroll_x = max( 0,
+ max( text_w, tb->cursorpxout + 1 ) - content_w
+ );
+
+ tb->scrollpx = m_clamp(
+ tb->scrollpx, 0, max_scroll_x
+ );
+
+ if( !tb->allow_newl ) {
+ tb->cursorpy = 0;
+ tb->scrollpy = 0;
+ return;
}
- gui_textbox_draw_blinker( tb, endposx, endposy, clr );
+ if( tb->cursorpy < tb->scrollpy )
+ tb->scrollpy = tb->cursorpy;
+ else if( tb->cursorpy + line_height > tb->scrollpy + content_h )
+ tb->scrollpy = tb->cursorpy + line_height - content_h;
+
+ tb->scrollpy = m_clamp(
+ tb->scrollpy,
+ 0,
+ max( 0, text_h - content_h )
+ );
+}
+
+static void gui_textbox_draw_content(
+ GUI_TEXTBOX* tb,
+ I32 x, I32 y,
+ I32 line_height,
+ CLR text_color
+) {
+ I32 draw_x = x + 2 - tb->scrollpx;
+ I32 draw_y = y + 3 - tb->scrollpy;
+
+ gui_draw_push_clip(
+ x + 1, y + 1,
+ max( 0, tb->w - 2 ),
+ max( 0, tb->h - 2 )
+ );
+
+ gui_textbox_draw_selection( tb,
+ draw_x, draw_y,
+ line_height,
+ ui_clr.selection
+ );
+
+ gui_draw_str(
+ draw_x, draw_y, ALIGN_L, FNT_JPN12,
+ text_color, "%s", tb->value
+ );
+
+ gui_textbox_draw_blinker( tb,
+ draw_x + tb->cursorpxout,
+ draw_y + tb->cursorpy,
+ text_color
+ );
+
+ gui_draw_pop_clip();
+}
+
+void gui_textbox_draw_fn( void* ptr ) {
+ GUI_TEXTBOX* tb = (GUI_TEXTBOX*)ptr;
+ I32 x = gui_relx( tb );
+ I32 title_y = gui_rely( tb );
+
+ gui_draw_str(
+ x, title_y, ALIGN_L,
+ FNT_JPN12, ui_clr.txt,
+ "%s", tb->name
+ );
+
+ I32 y = title_y + TEXTBOX_TITLE_OFFSET;
+ I32 line_height = gui_textbox_line_height();
+
+ gui_textbox_draw_frame( tb, x, y );
+ gui_textbox_update_cursor_metrics( tb );
+ gui_textbox_update_scroll( tb,
+ max( 1, tb->w - 4 ),
+ max( 1, tb->h - 4 ),
+ line_height
+ );
+
+ CLR text_color = tb->active
+ ? ui_clr.txt
+ : ui_clr.txt_inactive;
+
+ gui_textbox_draw_content( tb, x, y, line_height, text_color );
}
void gui_textbox_handle_mouse( GUI_TEXTBOX* tb ) {
I32 x = gui_relx( tb );
I32 y = gui_rely( tb ) + TEXTBOX_TITLE_OFFSET;
- I32 w = tb->w;
- I32 h = tb->h;
- U8 m1 = gui_mbutton_down( 0 );
- I32 mx, my;
+ U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
+ I32 mx = 0;
+ I32 my = 0;
gui_cursor_pos( &mx, &my );
- U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h;
+ U8 inbounds = mx >= x
+ && mx < x + tb->w
+ && my >= y
+ && my < y + tb->h;
- if( m1 ) {
- if( inbounds )
- tb->m1held = 1;
- else
- tb->active = 0;
- } else {
- if( inbounds && tb->m1held ) {
- tb->active = 1;
- tb->blink = 1;
- tb->blinktime = gui_time();
- }
+ if( !m1 ) {
tb->m1held = 0;
+ return;
+ }
+
+ if( !tb->m1held ) {
+ if( !inbounds ) {
+ tb->m1held = 2;
+ tb->active = 0;
+ return;
+ }
+
+ I32 local_x = mx - ( x + 2 ) + tb->scrollpx;
+ I32 local_y = my - ( y + 3 ) + tb->scrollpy;
+
+ U32 cursor = gui_textbox_cursor_from_point(
+ tb, local_x, local_y
+ );
+
+ gui_textbox_set_cursor( tb, cursor );
+ tb->m1held = 1;
+ tb->active = 1;
+ }
+ else if( tb->m1held == 1 ) {
+ I32 local_x = mx - ( x + 2 ) + tb->scrollpx;
+ I32 local_y = my - ( y + 3 ) + tb->scrollpy;
+
+ U32 cursor = gui_textbox_cursor_from_point(
+ tb, local_x, local_y
+ );
+
+ gui_textbox_set_cursor( tb, cursor, 1 );
+ }
+
+ if( tb->m1held == 1 ) {
+ tb->blink = 1;
+ tb->blinktime = gui_time();
+ }
+}
+
+static U8 gui_textbox_move_horizontal_key(
+ GUI_TEXTBOX* tb,
+ I32 direction,
+ U8 extend_selection
+) {
+ if( !extend_selection && gui_textbox_has_selection( tb ) ) {
+ U32 target = direction < 0
+ ? gui_textbox_selection_start( tb )
+ : gui_textbox_selection_end( tb );
+ gui_textbox_set_cursor( tb, target );
+ return 1;
+ }
+
+ U32 length = (U32)strlen( tb->value );
+
+ if( direction < 0 && tb->cursorout > 0 ) {
+ gui_textbox_set_cursor( tb,
+ tb->cursorout - 1,
+ extend_selection
+ );
+ return 1;
+ }
+
+ if( direction > 0 && tb->cursorout < length ) {
+ gui_textbox_set_cursor( tb,
+ tb->cursorout + 1,
+ extend_selection
+ );
+ return 1;
+ }
+ return 0;
+}
+
+static U8 gui_textbox_move_vertical_key(
+ GUI_TEXTBOX* tb,
+ I32 direction,
+ U8 extend_selection
+) {
+ U8 had_selection = gui_textbox_has_selection( tb );
+ U8 moved = gui_textbox_move_vertical( tb,
+ direction,
+ extend_selection
+ );
+
+ if( !extend_selection && had_selection && !moved ) {
+ gui_textbox_set_cursor( tb, tb->cursorout );
+ return 1;
+ }
+ return moved;
+}
+
+static U8 gui_textbox_move_line_edge(
+ GUI_TEXTBOX* tb,
+ U8 move_to_end,
+ U8 extend_selection
+) {
+ U32 length = (U32)strlen( tb->value );
+ U32 target = move_to_end
+ ? gui_textbox_line_end( tb->value, length, tb->cursorout )
+ : gui_textbox_line_start( tb->value, tb->cursorout );
+ U8 moved = target != tb->cursorout
+ || ( !extend_selection
+ && gui_textbox_has_selection( tb )
+ );
+ gui_textbox_set_cursor( tb,
+ target, extend_selection
+ );
+ return moved;
+}
+
+static U8 gui_textbox_apply_edit_key(
+ GUI_TEXTBOX* tb,
+ U32 key,
+ GUI_TEXTBOX_KEY_RESULT* result
+) {
+ constexpr U32 kp_enter = SDLK_KP_ENTER & 0xff;
+ constexpr U32 del = SDLK_DELETE & 0xff;
+
+ if( key == '\b' ) {
+ result->value_changed = gui_textbox_backspace( tb );
+ result->cursor_moved = result->value_changed;
+ return 1;
}
+
+ if( key == del ) {
+ result->value_changed = gui_textbox_delete_forward( tb );
+ return 1;
+ }
+
+ if( key == '\r' || key == kp_enter ) {
+ if( tb->allow_newl ) {
+ result->value_changed = gui_textbox_insert_bytes( tb, "\n", 1 ) == 1;
+ result->cursor_moved = result->value_changed;
+ }
+ else
+ tb->active = 0;
+ return 1;
+ }
+
+ if( key == '\x1b' ) {
+ tb->active = 0;
+ return 1;
+ }
+ return 0;
+}
+
+static U8 gui_textbox_apply_move_key(
+ GUI_TEXTBOX* tb,
+ U32 key,
+ U8 extend_selection,
+ GUI_TEXTBOX_KEY_RESULT* result
+) {
+ constexpr U32 right = SDLK_RIGHT & 0xff;
+ constexpr U32 down = SDLK_DOWN & 0xff;
+ constexpr U32 home = SDLK_HOME & 0xff;
+ constexpr U32 left = SDLK_LEFT & 0xff;
+ constexpr U32 end = SDLK_END & 0xff;
+ constexpr U32 up = SDLK_UP & 0xff;
+
+ if( key == left || key == right ) {
+ result->cursor_moved = gui_textbox_move_horizontal_key(
+ tb,
+ key == left ? -1 : 1,
+ extend_selection
+ );
+ return 1;
+ }
+
+ if( key == up || key == down ) {
+ if( !tb->allow_newl )
+ return 0;
+ result->cursor_moved = gui_textbox_move_vertical_key(
+ tb,
+ key == up ? -1 : 1,
+ extend_selection
+ );
+ return 1;
+ }
+
+ if( key == home || key == end ) {
+ result->cursor_moved = gui_textbox_move_line_edge(
+ tb,
+ key == end,
+ extend_selection
+ );
+ return 1;
+ }
+ return 0;
+}
+
+static void gui_textbox_apply_key_result(
+ GUI_TEXTBOX* tb,
+ const GUI_TEXTBOX_KEY_RESULT& result,
+ F32 now
+) {
+ if( result.value_changed && tb->cb )
+ tb->cb( tb );
+
+ if( result.value_changed || result.cursor_moved ) {
+ tb->blink = 1;
+ tb->blinktime = now;
+ }
+}
+
+static U8 gui_textbox_key_repeatable(
+ const GUI_TEXTBOX* tb,
+ U32 key
+) {
+ constexpr U32 right = SDLK_RIGHT & 0xff;
+ constexpr U32 down = SDLK_DOWN & 0xff;
+ constexpr U32 left = SDLK_LEFT & 0xff;
+ constexpr U32 del = SDLK_DELETE & 0xff;
+ constexpr U32 up = SDLK_UP & 0xff;
+
+ return key == '\b'
+ || key == del
+ || key == left
+ || key == right
+ || ( tb->allow_newl && ( key == up || key == down ) );
}
void gui_textbox_loop_keys( GUI_TEXTBOX* tb ) {
- U32 len = strlen( tb->value );
-
- for( U32 i = 0; i < 0xff; ++i ) {
- U8 key = tb->keys[i];
- U8 prev = tb->prevkeys[i];
-
- if( !key && prev && i == tb->heldkey )
- tb->heldkey = 0;
- else if( key && !prev ) {
- if( is_printable_char( i ) && len < tb->maxlen - 1 ) {
- if( tb->keys[SDLK_LSHIFT & 0xff] || tb->keys[SDLK_RSHIFT & 0xff] )
- tb->value[len] = (char)i + ('a' - 'A');
- else
- tb->value[len] = (char)i;
- tb->value[++len] = 0;
- }
- else if( i == '\b' ) {
- if( len > 0 ) {
- tb->value[--len] = 0;
- }
- }
- else if( i == '\r' ) {
- if( tb->allow_newl ) {
- tb->value[len] = '\n';
- tb->value[++len] = 0;
- }
- else {
- tb->active = 0;
- }
- } else {
- if( i == '\x1b' )
- tb->active = 0;
- if( tb->cb )
- tb->cb( tb );
- continue;
- }
-
- tb->heldkey = i;
- tb->heldtime = gui_time();
- if( tb->cb )
- tb->cb( tb );
+ U32 length = strlen( tb->value );
+ tb->cursorin = min( tb->cursorin, length );
+ tb->cursorout = min( tb->cursorout, length );
+ U8 extend_selection = gui_textbox_shift_down( tb );
+
+ for( U32 idx = 0; idx < 0x100; ++idx ) {
+ U8 prev = tb->prevkeys[idx];
+ U8 key = tb->keys[idx];
+
+ if( !key ) {
+ if( prev && idx == tb->heldkey )
+ tb->heldkey = 0;
+ continue;
+ }
+
+ if( prev )
+ continue;
+
+ GUI_TEXTBOX_KEY_RESULT result{};
+ U8 handled = gui_textbox_apply_edit_key(
+ tb,
+ idx,
+ &result
+ );
+ if( !handled ) {
+ handled = gui_textbox_apply_move_key(
+ tb, idx,
+ extend_selection,
+ &result
+ );
}
+ if( !handled )
+ continue;
+
+ F32 now = gui_time();
+ gui_textbox_apply_key_result( tb, result, now );
+
+ tb->heldtime = now;
+ tb->repeattime = now;
+ tb->heldkey = (U8)idx;
}
}
void gui_textbox_handle_repeat( GUI_TEXTBOX* tb ) {
- if( !tb->heldkey )
+ if( !gui_textbox_key_repeatable( tb, tb->heldkey ) )
return;
- F32 delta = gui_time() - tb->heldtime;
- if( delta <= 0.75f )
+ F32 now = gui_time();
+ if( now - tb->heldtime <= 0.75f
+ || now - tb->repeattime <= 0.05f )
return;
- if( gui_time() - tb->repeattime > 0.05f ) {
- U32 len = strlen( tb->value );
- if( is_printable_char( tb->heldkey ) && len < (tb->maxlen - 1) ) {
- if( tb->pre_cb ) tb->pre_cb( tb );
+ GUI_TEXTBOX_KEY_RESULT result{};
+ U8 extend_selection = gui_textbox_shift_down( tb );
+ U8 handled = gui_textbox_apply_edit_key(
+ tb,
+ tb->heldkey,
+ &result
+ );
+ if( !handled ) {
+ handled = gui_textbox_apply_move_key(
+ tb, tb->heldkey,
+ extend_selection,
+ &result
+ );
+ }
- if( tb->keys[SDLK_LSHIFT & 0xff] || tb->keys[SDLK_RSHIFT & 0xff] )
- tb->value[len] = tb->heldkey + ('a' - 'A');
- else
- tb->value[len] = tb->heldkey;
- tb->value[++len] = 0;
+ if( handled )
+ gui_textbox_apply_key_result( tb, result, now );
- if( tb->cb ) tb->cb( tb );
- }
- else if( tb->heldkey == '\b' && len > 0 ) {
- if( tb->pre_cb ) tb->pre_cb( tb );
- tb->value[--len] = 0;
- if( tb->cb ) tb->cb( tb );
+ tb->repeattime = now;
+}
+
+void gui_textbox_append_input_text( GUI_TEXTBOX* tb ) {
+ if( !tb->active || !input.textlen || !tb->maxlen )
+ return;
+
+ if( !tb->allow_newl ) {
+ U32 filtered = 0;
+ for( U32 idx = 0; idx < input.textlen; ++idx ) {
+ char c = input.text[idx];
+
+ if( c != '\n' && c != '\r' )
+ input.text[filtered++] = c;
}
+ input.textlen = filtered;
+ input.text[filtered] = 0;
+ }
- tb->repeattime = gui_time();
+ U32 inserted = gui_textbox_insert_bytes(
+ tb,
+ input.text,
+ input.textlen
+ );
+
+ if( inserted ) {
+ tb->blink = 1;
+ tb->blinktime = gui_time();
+
+ if( tb->cb )
+ tb->cb( tb );
}
+
+ input.textlen = 0;
+ input.text[0] = 0;
}
void gui_textbox_handle_keyboard( GUI_TEXTBOX* tb ) {
if( !tb->active )
return;
- memcpy( tb->prevkeys, tb->keys, 0xff );
- memcpy( tb->keys, input.keys, 0xff );
+ gui_textbox_append_input_text( tb );
+
+ memcpy( tb->prevkeys, tb->keys, 0x100 );
+ memcpy( tb->keys, input.keys, 0x100 );
gui_textbox_loop_keys( tb );
gui_textbox_handle_repeat( tb );
@@ -217,7 +949,7 @@ void gui_textbox_input_fn( void* ptr ) {
}
GUI_TEXTBOX* gui_textbox( I32 x, I32 y, I32 w, I32 h, const char* title, U32 maxlen, U8 allow_newl ) {
- GUI_TEXTBOX* tb = new GUI_TEXTBOX;
+ GUI_TEXTBOX* tb = new GUI_TEXTBOX{};
tb->x = x;
tb->y = y;
@@ -234,13 +966,22 @@ GUI_TEXTBOX* gui_textbox( I32 x, I32 y, I32 w, I32 h, const char* title, U32 max
tb->active = 0;
tb->m1held = 0;
tb->heldkey = 0;
+ tb->cursorpy = 0;
tb->heldtime = 0;
tb->repeattime = 0;
tb->maxlen = maxlen;
+ tb->cursor_goal_px = 0;
+ tb->cursor_goal_valid = 0;
tb->allow_newl = allow_newl;
+ tb->scrollpx = tb->scrollpy = 0;
+ tb->cursorin = tb->cursorout = 0;
+ tb->cursorpxin = tb->cursorpxout = 0;
memset( tb->value, 0, GUI_TEXTBOX_MAX );
- memset( tb->keys, 0, 0xff );
- memset( tb->prevkeys, 0, 0xff );
+ memset( tb->keys, 0, 0x100 );
+ memset( tb->prevkeys, 0, 0x100 );
+
+ tb->blink = 1;
+ tb->blinktime = gui_time();
gui_get_view()->children.push( tb );
diff --git a/src/main.cpp b/src/main.cpp
index 451c014..b4d5a88 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -44,5 +44,7 @@ int main(int argc, char* argv[]) {
}
game_destroy( game );
+ free( canvas );
+ canvas = 0;
return 0;
}
diff --git a/src/render/gl.cpp b/src/render/gl.cpp
index 7425ff4..c4eee26 100644
--- a/src/render/gl.cpp
+++ b/src/render/gl.cpp
@@ -1,3 +1,5 @@
+#include "SDL_keyboard.h"
+#include "SDL_keycode.h"
#include "SDL_video.h"
#include <unistd.h>
#define STB_IMAGE_IMPLEMENTATION
@@ -5,6 +7,7 @@
#include "gl.h"
#include "gl_2d_font.h"
+#include "../gui/base.h"
#include "../util.h"
I32 SAMPLER_INDICES[255];
@@ -224,6 +227,7 @@ GL_DATA* gl_create( I32* _canvas ) {
gl_handle_window_size_change( gl, start_w, start_h );
SDL_SetEventFilter( gl_event_filter, gl );
+ SDL_StartTextInput();
gl_inst = gl;
return gl;
}
@@ -240,6 +244,7 @@ void gl_gen_buffers( GL_DATA* gl ) {
void gl_destroy( GL_DATA *gl ) {
SDL_SetEventFilter( 0, 0 );
+ SDL_StopTextInput();
while( gl->programs.size ) { gl_program_destroy( gl, gl->programs.pop() ); }
while( gl->fonts.size ) { gl_font_destroy( gl, gl->fonts.pop() ); }
@@ -486,22 +491,50 @@ STAT gl_beginframe( GL_DATA* gl ) {
}
STAT gl_poll_events( GL_DATA* gl ) {
+ U8 suppress_console = 0;
SDL_Event e;
+
while( SDL_PollEvent( &e ) ) {
+ if( suppress_console && e.type == SDL_TEXTINPUT ) {
+ suppress_console = 0;
+ continue;
+ }
+
input_on_event( &e );
switch( e.type ) {
case SDL_QUIT:
return STAT_BREAK;
+
case SDL_WINDOWEVENT: {
if( e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED
|| e.window.event == SDL_WINDOWEVENT_RESIZED ) {
- gl_handle_window_size_change( gl, e.window.data1, e.window.data2 );
+ gl_handle_window_size_change(
+ gl,
+ e.window.data1,
+ e.window.data2
+ );
}
} break;
- case SDL_KEYDOWN:
- return e.key.keysym.sym == SDLK_ESCAPE ? STAT_BREAK : STAT_OK;
- default: break;
+
+ case SDL_KEYDOWN: {
+ if( e.key.keysym.sym == SDLK_BACKQUOTE
+ && !e.key.repeat
+ ) {
+ gui_consolewindow_toggle();
+ U16 modifiers = e.key.keysym.mod;
+ suppress_console = !(
+ modifiers & (
+ KMOD_CTRL |
+ KMOD_ALT |
+ KMOD_GUI
+ )
+ );
+ }
+ } break;
+
+ default:
+ break;
}
}
diff --git a/src/util/aabb.h b/src/util/aabb.h
index 9dc4b20..6e895fc 100644
--- a/src/util/aabb.h
+++ b/src/util/aabb.h
@@ -15,21 +15,21 @@ inline F32 aabb_support_radius( const AABB& aabb, const VEC3& dir ) {
}
inline U8 aabb_intersects( const AABB& aabb, const AABB& other ) {
- if (aabb.min.x > other.max.x || aabb.max.x < other.min.x)
+ if(aabb.min.x > other.max.x || aabb.max.x < other.min.x)
return 0;
- if (aabb.min.y > other.max.y || aabb.max.y < other.min.y)
+ if(aabb.min.y > other.max.y || aabb.max.y < other.min.y)
return 0;
- if (aabb.min.z > other.max.z || aabb.max.z < other.min.z)
+ if(aabb.min.z > other.max.z || aabb.max.z < other.min.z)
return 0;
return 1;
}
inline U8 aabb_contains_point( const AABB& aabb, const VEC3& point ) {
- if (point.x < aabb.min.x || point.x > aabb.max.x)
+ if(point.x < aabb.min.x || point.x > aabb.max.x)
return 0;
- if (point.y < aabb.min.y || point.y > aabb.max.y)
+ if(point.y < aabb.min.y || point.y > aabb.max.y)
return 0;
- if (point.z < aabb.min.z || point.z > aabb.max.z)
+ if(point.z < aabb.min.z || point.z > aabb.max.z)
return 0;
return 1;
}
diff --git a/src/util/allocator.h b/src/util/allocator.h
index 63fc94a..5fc5aa6 100644
--- a/src/util/allocator.h
+++ b/src/util/allocator.h
@@ -312,14 +312,16 @@ struct LIST {
if( idx >= size )
return;
- if constexpr( !__is_trivially_destructible(T) ) {
- data[idx].~T();
- memset( &data[idx], 0, sizeof(T) );
- }
-
- for( U32 i = idx; i < size - 1; i++ )
+ for( U32 i = idx; i + 1 < size; ++i )
data[i] = data[i + 1];
+ U32 last = size - 1;
+
+ if constexpr( !__is_trivially_destructible(T) )
+ data[last].~T();
+
+ memset( &data[last], 0, sizeof(T) );
+
if( --size < capacity / 4 )
shrink();
}
diff --git a/src/util/anim.h b/src/util/anim.h
index dbc2c11..4c314d9 100644
--- a/src/util/anim.h
+++ b/src/util/anim.h
@@ -9,8 +9,8 @@ inline T lerp( T a, T b, F32 t ) {
template < typename t >
inline t clamp( t a, t min, t max ) {
- if ( a < min ) return min;
- if ( a > max ) return max;
+ if( a < min ) return min;
+ if( a > max ) return max;
return a;
}
diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp
index 00a2512..63e042a 100644
--- a/src/util/config/config.cpp
+++ b/src/util/config/config.cpp
@@ -16,7 +16,7 @@ inline U8 is_whitespace( char c ) {
}
inline void trim_whitespace( char* buf ) {
- if (!buf || !*buf) return;
+ if(!buf || !*buf) return;
U32 start = 0;
while( buf[start] && is_whitespace( buf[start] ) ) start++;
diff --git a/src/util/console.cpp b/src/util/console.cpp
new file mode 100644
index 0000000..eee9030
--- /dev/null
+++ b/src/util/console.cpp
@@ -0,0 +1,12 @@
+#include "console.h"
+#include "../game.h"
+
+extern GAME_DATA* game;
+
+I32 console_clear_cb( CVAR *self, LIST<STR> cmdline ) {
+ if( !game )
+ return 0;
+ return console_clear_output( &game->console );
+}
+
+CVAR* g_clear = var_new( "clear", "clear console output window", &console_clear_cb );
diff --git a/src/util/console.h b/src/util/console.h
new file mode 100644
index 0000000..4a62561
--- /dev/null
+++ b/src/util/console.h
@@ -0,0 +1,883 @@
+#pragma once
+
+#include "../game/vars.h"
+#include "string.h"
+
+#include <math.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+enum CONSOLE_ROUTE : U8 {
+ CONSOLE_ROUTE_NONE = 0,
+ CONSOLE_ROUTE_VIEW = 1 << 0,
+ CONSOLE_ROUTE_STDOUT = 1 << 1,
+ CONSOLE_ROUTE_BOTH = CONSOLE_ROUTE_VIEW | CONSOLE_ROUTE_STDOUT
+};
+
+inline constexpr U64 CONSOLE_BYTES_MAX = 10 * 1024 * 1024;
+
+struct CONSOLE_DATA_RING {
+ LIST<STR> slots{64};
+
+ U32 first{};
+ U32 count{};
+
+ U64 max_bytes{CONSOLE_BYTES_MAX};
+ U64 revision{};
+ U64 total_pushed{};
+ U64 bytes{};
+
+ static U64 line_bytes( const STR& line ) {
+ return (U64)line.size + 1;
+ }
+
+ STR& operator[]( U32 logical_idx ) {
+ U32 physical_idx = ( first + logical_idx ) % slots.capacity;
+ return slots.data[physical_idx];
+ }
+
+ const STR& operator[]( U32 logical_idx ) const {
+ U32 physical_idx = ( first + logical_idx ) % slots.capacity;
+ return slots.data[physical_idx];
+ }
+
+ void grow() {
+ U32 new_capacity = slots.capacity
+ ? slots.capacity * 2
+ : 64;
+
+ LIST<STR> grown{new_capacity};
+
+ for( U32 idx = 0; idx < count; ++idx )
+ grown.push( (*this)[idx] );
+
+ slots = grown;
+ first = 0;
+ }
+
+ void pop_front() {
+ if( !count )
+ return;
+
+ STR& oldest = slots.data[first];
+ bytes -= line_bytes( oldest );
+ oldest = STR();
+
+ first = ( first + 1 ) % slots.capacity;
+
+ --count;
+ if( !count )
+ first = 0;
+ }
+
+ U8 make_room_for( U64 required_bytes ) {
+ if( required_bytes > max_bytes )
+ return 0;
+
+ U8 removed = 0;
+
+ while( count && bytes > max_bytes - required_bytes ) {
+ pop_front();
+ removed = 1;
+ }
+
+ if( removed )
+ ++revision;
+
+ return bytes <= max_bytes - required_bytes;
+ }
+
+ void push( const STR& src ) {
+ if( !max_bytes )
+ return;
+
+ U64 src_bytes = line_bytes( src );
+
+ if( src_bytes > max_bytes ) {
+ U32 keep = max_bytes > 1
+ ? (U32)( max_bytes - 1 )
+ : 0;
+
+ keep = keep < src.size
+ ? keep
+ : src.size;
+
+ STR clipped( keep, src.data + src.size - keep );
+ push( clipped );
+ return;
+ }
+
+ if( !make_room_for( src_bytes ) )
+ return;
+
+ if( count == slots.capacity )
+ grow();
+
+ U32 tail = ( first + count ) % slots.capacity;
+
+ if( tail < slots.size )
+ slots[tail] = src;
+ else
+ slots.push( src );
+
+ ++count;
+ ++total_pushed;
+ bytes += src_bytes;
+ }
+
+ void clear() {
+ slots.clear();
+ slots.reserve( 64 );
+ bytes = 0;
+ count = 0;
+ first = 0;
+ }
+};
+
+struct CONSOLE_STDOUT_CAPTURE {
+ I32 saved_stdout{-1};
+ I32 read_fd{-1};
+ U8 active{};
+};
+
+struct CONSOLE_DATA {
+ CONSOLE_STDOUT_CAPTURE stdout_capture{};
+ CONSOLE_DATA_RING lines{};
+ STR partial_stdout_line{};
+ U8 pending_carriage{};
+ LIST<STR> history{};
+ U64 revision{};
+};
+
+struct CONSOLE_COMMAND {
+ U8 unterminated;
+ LIST<STR> args;
+};
+
+inline U8 console_append_partial_bytes(
+ CONSOLE_DATA* state,
+ const char* text,
+ U32 length
+) {
+ if( !state || !text )
+ return 0;
+
+ if( !length )
+ return 1;
+
+ STR& partial = state->partial_stdout_line;
+ U64 required_bytes = (U64)partial.size + (U64)length + 1;
+
+ if( !state->lines.make_room_for( required_bytes ) )
+ return 0;
+
+ for( U32 idx = 0; idx < length; ++idx )
+ partial.push( text[idx] );
+
+ return 1;
+}
+
+inline void console_clear_partial_line( CONSOLE_DATA* state ) {
+ if( !state )
+ return;
+
+ STR& partial = state->partial_stdout_line;
+ partial.size = 0;
+
+ if( partial.data )
+ partial.data[0] = 0;
+}
+
+inline I32 console_clear_output( CONSOLE_DATA* state ) {
+ if( !state )
+ return 0;
+ state->lines.clear();
+ console_clear_partial_line( state );
+ state->pending_carriage = 0;
+ ++state->revision;
+ return 1;
+}
+extern I32 console_clear_cb( CVAR* self, LIST<STR> cmdline );
+
+inline void console_finish_partial_line( CONSOLE_DATA* state ) {
+ if( !state )
+ return;
+
+ state->lines.push( state->partial_stdout_line );
+ console_clear_partial_line( state );
+}
+
+inline STR console_command_token(
+ const char* text,
+ U32 start,
+ U32 end
+) {
+ STR token;
+ token.reserve( end - start + 1 );
+
+ for( U32 idx = start; idx < end; ++idx ) {
+ if( text[idx] == '"' )
+ continue;
+ token.push( text[idx] );
+ }
+ return token;
+}
+
+inline void console_parse_command(
+ const char* text,
+ CONSOLE_COMMAND* command
+) {
+ if( !command )
+ return;
+
+ command->args.clear();
+ command->unterminated = 0;
+
+ if( !text )
+ return;
+
+ U32 token_start = 0;
+ U8 in_string = 0;
+ U32 cursor = 0;
+
+ while( text[cursor] ) {
+ char c = text[cursor];
+
+ if( c == '\"' )
+ in_string ^= 1;
+
+ if( c == ' ' && !in_string ) {
+ if( cursor > token_start ) {
+ command->args.push(
+ console_command_token(
+ text, token_start, cursor
+ )
+ );
+ }
+
+ do {
+ ++cursor;
+ } while( text[cursor] == ' ' );
+
+ token_start = cursor;
+ continue;
+ }
+
+ ++cursor;
+ }
+
+ if( cursor > token_start ) {
+ command->args.push(
+ console_command_token(
+ text, token_start, cursor
+ )
+ );
+ }
+
+ command->unterminated = in_string;
+}
+
+inline void console_append_text(
+ CONSOLE_DATA* state,
+ const char* text,
+ U32 len
+) {
+ if( !state || !text || !len )
+ return;
+
+ ++state->revision;
+
+ for( U32 idx = 0; idx < len; ++idx ) {
+ char c = text[idx];
+
+ if( state->pending_carriage ) {
+ state->pending_carriage = 0;
+
+ if( c == '\n' ) {
+ console_finish_partial_line( state );
+ continue;
+ }
+ console_clear_partial_line( state );
+ }
+
+ if( c == '\r' ) {
+ state->pending_carriage = 1;
+ continue;
+ }
+
+ if( c == '\n' ) {
+ console_finish_partial_line( state );
+ continue;
+ }
+
+ if( c == '\t' ) {
+ console_append_partial_bytes(
+ state, " ", 2
+ );
+ continue;
+ }
+
+ console_append_partial_bytes( state, &c, 1 );
+ }
+}
+
+inline void console_write_fd( I32 fd, const char* text, U32 len ) {
+ if( fd < 0 || !text )
+ return;
+ U32 written = 0;
+ while( written < len ) {
+ ssize_t result = write( fd, text + written, len - written );
+ if( result > 0 ) {
+ written += (U32)result;
+ continue;
+ }
+ if( result < 0 && errno == EINTR )
+ continue;
+ break;
+ }
+}
+
+inline void console_write_original_stdout(
+ CONSOLE_DATA* state,
+ const char* text,
+ U32 len
+) {
+ I32 fd = state && state->stdout_capture.active
+ ? state->stdout_capture.saved_stdout
+ : STDOUT_FILENO;
+ console_write_fd( fd, text, len );
+}
+
+inline void console_write(
+ CONSOLE_DATA* state,
+ U8 routes,
+ const char* text,
+ U32 len
+) {
+ if( !text || !len )
+ return;
+ if( routes & CONSOLE_ROUTE_VIEW )
+ console_append_text( state, text, len );
+ if( routes & CONSOLE_ROUTE_STDOUT )
+ console_write_original_stdout( state, text, len );
+}
+
+inline void console_print(
+ CONSOLE_DATA* state,
+ U8 routes,
+ const char* fmt,
+ ...
+) {
+ if( !state || !fmt )
+ return;
+
+ va_list args;
+ va_list count_args;
+ va_start( args, fmt );
+ va_copy( count_args, args );
+ I32 len = vsnprintf( 0, 0, fmt, count_args );
+ va_end( count_args );
+
+ if( len < 0 ) {
+ va_end( args );
+ return;
+ }
+
+ STR text;
+ text.reserve( (U32)len + 1 );
+ vsnprintf(
+ text.data, (U32)len + 1,
+ fmt, args
+ );
+ va_end( args );
+
+ text.size = (U32)len;
+ text.data[text.size] = 0;
+ console_write(
+ state,
+ routes,
+ text.data,
+ text.size
+ );
+}
+
+inline U8 console_parse_i32(
+ const STR& text,
+ I32* output
+) {
+ if( !output || !text.data || !text.size )
+ return 0;
+
+ char* end = 0;
+ errno = 0;
+ long parsed = strtol( text.data, &end, 10 );
+
+ if( errno == ERANGE
+ || end == text.data
+ || *end
+ || parsed < INT_MIN
+ || parsed > INT_MAX
+ ) return 0;
+
+ *output = (I32)parsed;
+ return 1;
+}
+
+inline U8 console_parse_f32(
+ const STR& text,
+ F32* output
+) {
+ if( !output || !text.data || !text.size )
+ return 0;
+
+ char* end = 0;
+ errno = 0;
+ F32 parsed = strtof( text.data, &end );
+
+ if( errno == ERANGE
+ || end == text.data
+ || *end
+ || !isfinite( parsed )
+ ) return 0;
+
+ *output = parsed;
+ return 1;
+}
+
+inline U8 console_parse_color_component(
+ const STR& text,
+ F32* output
+) {
+ I32 integer = 0;
+ if( console_parse_i32( text, &integer ) ) {
+ if( integer < 0 || integer > 255 )
+ return 0;
+ *output = (F32)integer / 255.f;
+ return 1;
+ }
+
+ F32 decimal = 0.f;
+ if( !console_parse_f32( text, &decimal )
+ || decimal < 0.f
+ || decimal > 1.f
+ ) return 0;
+
+ *output = decimal;
+ return 1;
+}
+
+inline U8 console_execute_cvar(
+ CONSOLE_DATA* state,
+ CVAR* cvar,
+ LIST<STR>& args
+) {
+ if( cvar->type != CVAR_TYPE_FN && args.size == 0 )
+ return 0;
+
+ switch( cvar->type ) {
+ case CVAR_TYPE_FN: {
+ if( var_call( cvar, args ) )
+ return 1;
+
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "%s", "[error] failed to run cvar\n"
+ );
+ return 0;
+ }
+ case CVAR_TYPE_INT: {
+ I32 value = 0;
+
+ if( args.size != 1
+ || !console_parse_i32( args[0], &value )
+ ) break;
+
+ var_set( cvar, value );
+ return 1;
+ }
+ case CVAR_TYPE_FLOAT: {
+ F32 value = 0.f;
+
+ if( args.size != 1
+ || !console_parse_f32( args[0], &value )
+ ) break;
+
+ var_set( cvar, value );
+ return 1;
+ } break;
+ case CVAR_TYPE_COLOR: {
+ if( args.size == 1 ) {
+ F32 value = 0.f;
+
+ if( !console_parse_color_component( args[0], &value ) )
+ break;
+
+ var_set( cvar, value );
+ return 1;
+ }
+
+ if( args.size != 3 && args.size != 4 )
+ break;
+
+ F32 components[4] = {
+ cvar->clr_v.r,
+ cvar->clr_v.g,
+ cvar->clr_v.b,
+ cvar->clr_v.a
+ };
+
+ for( U32 idx = 0; idx < args.size; ++idx ) {
+ if(
+ !console_parse_color_component(
+ args[idx],
+ &components[idx]
+ )
+ ) break;
+
+ if( idx + 1 != args.size )
+ continue;
+
+ var_set( cvar,
+ CLR(
+ components[0],
+ components[1],
+ components[2],
+ components[3]
+ )
+ );
+ return 1;
+ }
+ } break;
+ case CVAR_TYPE_STRING: {
+ if( args.size != 1
+ || args[0].size >= sizeof( cvar->str_v )
+ ) break;
+
+ var_set( cvar, args[0] );
+ return 1;
+ }
+ default: {
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "%s", "[error] unhandled cvar type\n"
+ );
+ return 0;
+ }
+ }
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "[error] failed to set %s\n",
+ cvar->name.data
+ );
+ return 0;
+}
+
+inline U8 console_print_i32_help(
+ CONSOLE_DATA* state,
+ CVAR* cvar
+) {
+ if( !state || !cvar || cvar->type != CVAR_TYPE_INT )
+ return 0;
+
+ if( cvar->flags & CVAR_FLAG_MINMAX ) {
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "%s - %s. default(%d) current(%d) min(%d) max(%d)\n",
+ cvar->name.data,
+ cvar->desc.data,
+ cvar->default_v.i_v,
+ cvar->i_v,
+ cvar->min_v.i_v,
+ cvar->max_v.i_v
+ );
+ } else {
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "%s - %s. default(%d) current(%d)\n",
+ cvar->name.data,
+ cvar->desc.data,
+ cvar->default_v.i_v,
+ cvar->i_v
+ );
+ }
+ return 1;
+}
+inline U8 console_print_f32_help(
+ CONSOLE_DATA* state,
+ CVAR* cvar
+) {
+ if( !state || !cvar || cvar->type != CVAR_TYPE_FLOAT )
+ return 0;
+
+ if( cvar->flags & CVAR_FLAG_MINMAX ) {
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "%s - %s. default(%f) current(%f) min(%f) max(%f)\n",
+ cvar->name.data,
+ cvar->desc.data,
+ cvar->default_v.fl_v,
+ cvar->fl_v,
+ cvar->min_v.fl_v,
+ cvar->max_v.fl_v
+ );
+ } else {
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "%s - %s. default(%f) current(%f)\n",
+ cvar->name.data,
+ cvar->desc.data,
+ cvar->default_v.fl_v,
+ cvar->fl_v
+ );
+ }
+ return 1;
+}
+
+inline U8 console_print_cvar_help(
+ CONSOLE_DATA* state,
+ CVAR* cvar
+) {
+ switch( cvar->type ) {
+ case CVAR_TYPE_FN: return 1;
+ case CVAR_TYPE_INT:
+ return console_print_i32_help( state, cvar );
+ case CVAR_TYPE_FLOAT:
+ return console_print_f32_help( state, cvar );
+ case CVAR_TYPE_COLOR: {
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "%s - %s. default(%f %f %f %f) current(%f %f %f %f)\n",
+ cvar->name.data,
+ cvar->desc.data,
+ cvar->default_v.clr_v.r,
+ cvar->default_v.clr_v.g,
+ cvar->default_v.clr_v.b,
+ cvar->default_v.clr_v.a,
+ cvar->clr_v.r,
+ cvar->clr_v.g,
+ cvar->clr_v.b,
+ cvar->clr_v.a
+ );
+ } break;
+ case CVAR_TYPE_STRING: {
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "%s - %s. default(%s) current(%s)\n",
+ cvar->name.data,
+ cvar->desc.data,
+ cvar->default_v.str_v,
+ cvar->str_v
+ );
+ } break;
+ default: {
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "%s", "[error] unhandled cvar type\n"
+ );
+ return 0;
+ }
+ }
+ return 1;
+}
+
+inline U8 console_execute_command(
+ CONSOLE_DATA* state,
+ CONSOLE_COMMAND* command
+) {
+ if( !command || !command->args.size )
+ return 1;
+
+ if( command->unterminated ) {
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "%s", "[error] unterminated string\n"
+ );
+ return 0;
+ }
+
+ STR command_name = command->args.pop_front();
+ CVAR* cvar = var_find( command_name.data );
+
+ if( !cvar ) {
+ console_print(
+ state, CONSOLE_ROUTE_BOTH,
+ "[error] unknown command: %s\n",
+ command_name.data
+ );
+ return 0;
+ }
+
+ if( !command->args.size && cvar->type != CVAR_TYPE_FN )
+ return console_print_cvar_help( state, cvar );
+ return console_execute_cvar(
+ state,
+ cvar,
+ command->args
+ );
+}
+
+inline U8 console_execute_segment(
+ CONSOLE_DATA* state,
+ const char* text,
+ U32 length
+) {
+ if( !length )
+ return 1;
+
+ STR segment( length, text );
+ CONSOLE_COMMAND command{};
+
+ console_parse_command(
+ segment.data,
+ &command
+ );
+
+ return console_execute_command( state, &command );
+}
+
+inline void console_execute( CONSOLE_DATA* state, const char* cmd ) {
+ if( !state || !cmd )
+ return;
+
+ U32 command_start = 0;
+ U8 in_string = 0;
+
+ for( U32 cursor = 0;; ++cursor ) {
+ char c = cmd[cursor];
+
+ if( c == '"' )
+ in_string ^= 1;
+
+ U8 at_end = !c;
+ U8 at_separator = c == ';' && !in_string;
+
+ if( !at_end && !at_separator )
+ continue;
+
+ if(
+ !console_execute_segment(
+ state,
+ cmd + command_start,
+ cursor - command_start
+ )
+ ) return;
+
+ if( at_end )
+ return;
+
+ command_start = cursor + 1;
+ }
+}
+
+inline U8 console_submit(
+ CONSOLE_DATA* state,
+ const char* cmd
+) {
+ if( !state || !cmd || !cmd[0] )
+ return 0;
+
+ STR submitted( "%s", cmd );
+ state->history.push( submitted );
+
+ console_print(
+ state,
+ CONSOLE_ROUTE_BOTH,
+ "] %s\n",
+ submitted.data
+ );
+
+ console_execute( state, submitted.data );
+ return 1;
+}
+
+inline U8 console_capture_stdout_begin( CONSOLE_DATA* state ) {
+ if( !state )
+ return 0;
+
+ CONSOLE_STDOUT_CAPTURE& capture = state->stdout_capture;
+
+ if( capture.active )
+ return 1;
+
+ I32 saved_stdout = dup( STDOUT_FILENO );
+ if( saved_stdout < 0 )
+ return 0;
+
+ I32 pipe_fds[2];
+ if( pipe( pipe_fds ) ) {
+ close( saved_stdout );
+ return 0;
+ }
+
+ I32 flags = fcntl( pipe_fds[0], F_GETFL, 0 );
+ if( flags < 0
+ || fcntl( pipe_fds[0], F_SETFL, flags | O_NONBLOCK ) < 0 ) {
+ close( pipe_fds[0] );
+ close( pipe_fds[1] );
+ close( saved_stdout );
+ return 0;
+ }
+
+ fflush( stdout );
+ setvbuf( stdout, 0, _IONBF, 0 );
+ if( dup2( pipe_fds[1], STDOUT_FILENO ) < 0 ) {
+ close( pipe_fds[0] );
+ close( pipe_fds[1] );
+ close( saved_stdout );
+ return 0;
+ }
+ close( pipe_fds[1] );
+ capture.saved_stdout = saved_stdout;
+ capture.read_fd = pipe_fds[0];
+ capture.active = 1;
+ return 1;
+}
+
+inline void console_pump_stdout( CONSOLE_DATA* state ) {
+ if( !state )
+ return;
+
+ CONSOLE_STDOUT_CAPTURE& capture = state->stdout_capture;
+ if( !capture.active || capture.read_fd < 0 )
+ return;
+ char buffer[4096];
+ for( ;; ) {
+ ssize_t count = read( capture.read_fd, buffer, sizeof( buffer ) );
+ if( count > 0 ) {
+ console_write_fd(
+ capture.saved_stdout,
+ buffer, (U32)count
+ );
+ console_append_text( state, buffer, (U32)count );
+ continue;
+ }
+ if( count < 0 && errno == EINTR )
+ continue;
+ if( count < 0
+ && ( errno == EAGAIN || errno == EWOULDBLOCK )
+ ) break;
+ break;
+ }
+}
+
+inline void console_capture_stdout_end( CONSOLE_DATA* state ) {
+ if( !state )
+ return;
+
+ CONSOLE_STDOUT_CAPTURE& capture = state->stdout_capture;
+ if( !capture.active )
+ return;
+
+ fflush( stdout );
+ dup2( capture.saved_stdout, STDOUT_FILENO );
+ console_pump_stdout( state );
+ close( capture.read_fd );
+ close( capture.saved_stdout );
+ capture.read_fd = capture.saved_stdout = -1;
+ capture.active = 0;
+}
diff --git a/src/util/input.cpp b/src/util/input.cpp
index fa308e2..46bc5f9 100644
--- a/src/util/input.cpp
+++ b/src/util/input.cpp
@@ -1,6 +1,8 @@
#include "input.h"
#include "SDL_events.h"
+#include <string.h>
+
INPUT_DATA input;
void input_on_event( SDL_Event* e ) {
@@ -30,6 +32,22 @@ void input_on_event( SDL_Event* e ) {
input.mouse.pos_delta.x += (F32)e->motion.xrel * input.mpitch;
input.mouse.pos_delta.y += (F32)e->motion.yrel * input.myaw;
} break;
+ case SDL_TEXTINPUT: {
+ U32 incoming = strlen( e->text.text );
+ U32 available = (U32)sizeof( input.text ) - 1 - input.textlen;
+ U32 count = incoming < available
+ ? incoming
+ : available;
+ if( !count )
+ break;
+ memcpy(
+ input.text + input.textlen,
+ e->text.text,
+ count
+ );
+ input.textlen += count;
+ input.text[input.textlen] = 0;
+ } break;
case SDL_KEYDOWN: {
input.keys[e->key.keysym.sym & 0xff] = 1;
} break;
@@ -77,6 +95,8 @@ void input_on_mouse( I32 type, I32 x, I32 y ) {
void input_frame_end() {
input.mouse.wheel = 0;
+ input.textlen = 0;
+ input.text[0] = 0;
}
U8 input_is_key_down( U32 key ) {
diff --git a/src/util/input.h b/src/util/input.h
index af5e258..f982844 100644
--- a/src/util/input.h
+++ b/src/util/input.h
@@ -34,9 +34,11 @@ struct INPUT_KEYBINDS {
using ON_INPUT_FN = FN<void( SDL_Event* )>;
+constexpr U32 INPUT_TEXT_MAX = 1024;
+
struct INPUT_DATA {
MOUSE_DATA mouse;
- U8 keys[0xff];
+ U8 keys[0x100];
U8 mouselock;
F32 msens = .3f;
F32 myaw = 1.f;
@@ -44,7 +46,8 @@ struct INPUT_DATA {
INPUT_KEYBINDS binds;
LIST<ON_INPUT_FN> on_input;
-
+ char text[INPUT_TEXT_MAX];
+ U32 textlen;
};
extern INPUT_DATA input;
diff --git a/src/util/profiler.cpp b/src/util/profiler.cpp
index 86c1810..5f9cd25 100644
--- a/src/util/profiler.cpp
+++ b/src/util/profiler.cpp
@@ -4,7 +4,7 @@
#include "../game/vars.h"
#include "string.h"
-CVAR* prof_overlay = var_new( "prof_overlay", 0 );
+CVAR* prof_overlay = var_new( "prof_overlay", "show profiler overlay", 0, 0, 1 );
PROFILER_GLOBAL gprof;
void __profiler_intern_draw_tree( PROFILER_LIST_ENTRY* e, GL_FONT* font, I32* x, I32* y ) {
diff --git a/src/util/screen.h b/src/util/screen.h
index 34effb3..6d30231 100644
--- a/src/util/screen.h
+++ b/src/util/screen.h
@@ -25,7 +25,7 @@ inline VEC2 s_c( VEC2 offset = { 0.f, 0.f } ) {
}
inline VEC2 s_tc( VEC2 offset = { 0.f, 0.f } ) {
- return { offset.x + (F32)canvas[0] / 2, 0 };
+ return { offset.x + (F32)canvas[0] / 2, 0.0f };
}
inline VEC2 s_bc( VEC2 offset = { 0.f, 0.f } ) {
@@ -33,7 +33,7 @@ inline VEC2 s_bc( VEC2 offset = { 0.f, 0.f } ) {
}
inline VEC2 s_cl( VEC2 offset = { 0.f, 0.f } ) {
- return { 0, offset.y + (F32)canvas[1] / 2 };
+ return { 0.0f, offset.y + (F32)canvas[1] / 2 };
}
inline VEC2 s_cr( VEC2 offset = { 0.f, 0.f } ) {
diff --git a/src/util/thread.h b/src/util/thread.h
index 72ef5fb..b46ade0 100644
--- a/src/util/thread.h
+++ b/src/util/thread.h
@@ -544,13 +544,13 @@ get the count, it might have changed by another thread calling consume or produc
union thread_mutex_t
{
void* align;
- char data[ 64 ];
+ char data[64];
};
union thread_signal_t
{
void* align;
- char data[ 116 ];
+ char data[116];
};
union thread_atomic_int_t
@@ -567,7 +567,7 @@ union thread_atomic_ptr_t
union thread_timer_t
{
void* data;
- char d[ 8 ];
+ char d[8];
};
struct thread_queue_t
@@ -1418,7 +1418,7 @@ int thread_queue_produce( thread_queue_t* queue, void* value, int timeout_ms )
return 0;
}
int tail = thread_atomic_int_inc( &queue->tail );
- queue->values[ tail % queue->size ] = value;
+ queue->values[tail % queue->size] = value;
if( thread_atomic_int_inc( &queue->count ) == 0 )
thread_signal_raise( &queue->data_ready );
return 1;
@@ -1439,7 +1439,7 @@ void* thread_queue_consume( thread_queue_t* queue, int timeout_ms )
return NULL;
}
int head = thread_atomic_int_inc( &queue->head );
- void* retval = queue->values[ head % queue->size ];
+ void* retval = queue->values[head % queue->size];
if( thread_atomic_int_dec( &queue->count ) == queue->size )
thread_signal_raise( &queue->space_open );
return retval;
diff --git a/src/util/vector.h b/src/util/vector.h
index 294b627..c88ccee 100644
--- a/src/util/vector.h
+++ b/src/util/vector.h
@@ -12,8 +12,9 @@ struct VEC2 {
F32 y;
VEC2() { x = y = 0.0f; }
- VEC2( F32 X, F32 Y ) { x = X; y = Y; }
VEC2( const VEC2& v ) = default;
+ template<typename T1, typename T2>
+ VEC2( T1 X, T2 Y ) { x = (F32)X; y = (F32)Y; }
bool operator==( const VEC2& v ) const { return ( x == v.x && y == v.y ); }
F32& operator[]( I32 i ) { return ( (F32*)this )[i]; }
@@ -46,9 +47,9 @@ struct VEC3 {
F32 z;
VEC3() { x = y = z = 0.0f; }
- VEC3( F32 X, F32 Y, F32 Z ) { x = X; y = Y; z = Z; }
VEC3( const VEC3& v ) = default;
-
+ template<typename T1, typename T2, typename T3>
+ VEC3( T1 X, T2 Y, T3 Z ) { x = (F32)X; y = (F32)Y; z = (F32)Z; }
VEC3( const VEC2& v ) { x = v.x; y = v.y; z = 0.f; }
bool operator==( const VEC3& v ) const { return ( x == v.x && y == v.y && z == v.z ); }
@@ -81,8 +82,9 @@ struct VEC4 {
F32 x, y, z, w;
VEC4() { x = y = z = w = 0; }
- VEC4( F32 X, F32 Y, F32 Z, F32 W ) { x = X; y = Y; z = Z; w = W; }
VEC4( const VEC4& v ) = default;
+ template <typename T1, typename T2, typename T3, typename T4>
+ VEC4( T1 X, T2 Y, T3 Z, T4 W ) { x = (F32)X; y = (F32)Y; z = (F32)Z; w = (F32)W; }
VEC4( const VEC3& v ) { x = v.x; y = v.y; z = v.z; w = 1.f; }
VEC4( const VEC2& v ) { x = v.x; y = v.y; z = 0.f; w = 1.f; }
@@ -227,19 +229,19 @@ inline void angle_vectors( const VEC3& angles, VEC3* forward, VEC3* right, VEC3*
sr = sinf( angles.z * PIRAD );
cr = cosf( angles.z * PIRAD );
- if ( forward ) {
+ if( forward ) {
forward->x = cp * cy;
forward->y = cp * sy;
forward->z = -sp;
}
- if ( right ) {
+ if( right ) {
right->x = -1 * sr * sp * cy + -1 * cr * -sy;
right->y = -1 * sr * sp * sy + -1 * cr * cy;
right->z = -1 * sr * cp;
}
- if ( up ) {
+ if( up ) {
up->x = cr * sp * cy + -sr * -sy;
up->y = cr * sp * sy + -sr * cy;
up->z = cr * cp;