added emitter sprites
[fishladder.git] / vg / vg_console.h
index 1a4813c7d7437ce1dc0ba2eebdfe6b41dcd7238c..76d38007241ef256112d1440159b7dcccc38331c 100644 (file)
@@ -1,3 +1,5 @@
+// Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
+
 struct vg_console
 {
        struct vg_convar
@@ -12,49 +14,138 @@ struct vg_console
                        k_convar_dtype_f32
                } 
                data_type;
+               
+               union
+               {
+                       struct
+                       {
+                               int min, max, clamp;
+                       } opt_i32;
+               };
+
+               int persistent; // Should be stored to cfg/auto.conf
        } 
        *convars;
        
+       struct vg_cmd
+       {
+               int (*function)( int argc, char const *argv[] );
+               const char *name;
+       }
+       *functions;
+       
        char lines[16][512];
        u32 current, len;
+       
+       char input[512];
+       int cursor_user, cursor_pos, string_length;
+       
+       char history[32][512];
+       int history_last, history_pos, history_count;
+       
+       int enabled;
+       int scale;
+}
+vg_console = { .scale = 2 };
+
+// Declerations
+// ------------
+
+// Registration
+static void vg_convar_push( struct vg_convar cv );
+static void vg_function_push( struct vg_cmd cmd );
+
+static void vg_console_draw( void );
+void vg_console_println( const char *str );
+static int vg_console_list( int argc, char const *argv[] );
+static void vg_console_init(void);
+static void vg_console_write_persistent(void);
+static void vg_console_free(void);
+static void execute_console_input( const char *cmd );
+
+// Console interface
+// -----------------
+
+static void console_make_selection( int* start, int* end );
+static void console_move_cursor( int* cursor0, int* cursor1, int dir, int snap_together );
+static int console_makeroom( int datastart, int length );
+static int console_delete_char( int direction );
+static void console_to_clipboard(void);
+static void console_clipboard_paste(void);
+static void console_put_char( char c );
+static void console_history_get( char* buf, int entry_num );
+static void console_proc_key( GLFWwindow* ptrW, int key, int scancode, int action, int mods );
+static void console_proc_wchar( GLFWwindow* ptrW, u32 uWchar );
+static int vg_console_enabled(void);
+
+// =========================================================================================================
+// Implementation
+
+static int vg_console_enabled(void) 
+{ 
+       return vg_console.enabled; 
 }
-vg_console;
 
 static void vg_convar_push( struct vg_convar cv )
 {
        arrpush( vg_console.convars, cv ); 
 }
 
+static void vg_function_push( struct vg_cmd cmd )
+{
+       arrpush( vg_console.functions, cmd );
+}
+
 static void vg_console_draw( void )
-{      
-       int ptr = vg_console.current - vg_console.len;
-       if( ptr <= 0 )
-               ptr += vg_list_size( vg_console.lines );
-       ptr --;
+{
+       if( !vg_console.enabled )
+               return;
+
+       int ptr = vg_console.current-1;
        
        ui_global_ctx.cursor[0] = 0;
        ui_global_ctx.cursor[1] = 0;
-       ui_global_ctx.cursor[3] = vg_console.len*8;
+       ui_global_ctx.cursor[3] = vg_list_size( vg_console.lines )*8*vg_console.scale;
        ui_fill_x( &ui_global_ctx );
        
        ui_new_node( &ui_global_ctx );
-       {               
+       {
                ui_fill_rect( &ui_global_ctx, ui_global_ctx.cursor, 0x77333333 );
        
-               ui_global_ctx.cursor[3] = 8;
+               ui_global_ctx.cursor[3] = 8*vg_console.scale;
                ui_align_bottom( &ui_global_ctx ); 
                
                for( int i = 0; i < vg_console.len; i ++ )
                {
-                       ui_text( &ui_global_ctx, vg_console.lines[ptr], 1, 0 );
-                       ui_global_ctx.cursor[1] -= 8;
-               
-                       ptr --;
                        if( ptr < 0 )
                                ptr = vg_list_size( vg_console.lines )-1;
+         
+                       ui_text( &ui_global_ctx, vg_console.lines[ptr], vg_console.scale );
+                       ui_global_ctx.cursor[1] -= 8*vg_console.scale;
+               
+                       ptr --;
                }
        }
        ui_end_down( &ui_global_ctx );
+       
+       ui_global_ctx.cursor[1] += 2;
+       ui_global_ctx.cursor[3] = 8*vg_console.scale;
+       
+       ui_new_node( &ui_global_ctx );
+       {
+               ui_fill_rect( &ui_global_ctx, ui_global_ctx.cursor, 0x77333333 );
+               
+               ui_text( &ui_global_ctx, vg_console.input, vg_console.scale );
+               
+               int start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user ),
+                        end   = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
+               
+               ui_global_ctx.cursor[0] = (start * ui_glyph_spacing_x * vg_console.scale) + 2;
+               ui_global_ctx.cursor[2] = (start == end? 0.2f: (float)(end-start)) * (float)ui_glyph_spacing_x * (float)vg_console.scale * 0.5f;
+               
+               ui_fill_rect( &ui_global_ctx, ui_global_ctx.cursor, 0x66ffffff );
+       }
+       ui_end_down( &ui_global_ctx );
 }
 
 void vg_console_println( const char *str )
@@ -68,12 +159,443 @@ void vg_console_println( const char *str )
                vg_console.current = 0;
 }
 
+static int vg_console_list( int argc, char const *argv[] )
+{
+       for( int i = 0; i < arrlen( vg_console.functions ); i ++ )
+       {
+               struct vg_cmd *cmd = &vg_console.functions[ i ];
+               vg_info( "* %s\n", cmd->name );
+       }
+       
+       vg_info( "* snowsound\n" );
+       
+       for( int i = 0; i < arrlen( vg_console.convars ); i ++ )
+       {
+               struct vg_convar *cv = &vg_console.convars[ i ];
+               vg_info( "%s\n", cv->name );
+       }
+       
+       return 0;
+}
+
 static void vg_console_init(void)
 {
        vg_log_callback = vg_console_println;
+       
+       vg_convar_push( (struct vg_convar)
+       { .name = "console_scale", .data = &vg_console.scale, .data_type = k_convar_dtype_i32, 
+               .opt_i32 = { .clamp = 1, .min = 1, .max = 7 } } );
+       
+       vg_function_push( (struct vg_cmd){
+               .name = "list",
+               .function = vg_console_list
+       });
+
+       // Read and exec persistent commands
+       FILE *fp = fopen( "cfg/auto.conf", "r" );
+       if( fp )
+       {
+               char line[256];
+
+               while( fgets( line, sizeof( line ), fp ) )
+               {
+                       line[ strcspn( line, "\r\n#" ) ] = 0x00;
+
+                       if( line[0] != 0x00 )
+                       {
+                               execute_console_input( line );
+                       }
+               }
+
+               fclose( fp );
+       }
+}
+
+static void vg_console_write_persistent(void)
+{
+       FILE *fp = fopen( "cfg/auto.conf", "w" );
+       
+       for( int i = 0; i < arrlen( vg_console.convars ); i ++ )
+       {
+               struct vg_convar *cv = &vg_console.convars[i];
+
+               if( cv->persistent )
+               {
+                       switch( cv->data_type )
+                       {
+                               case k_convar_dtype_i32:
+                                       fprintf( fp, "%s %d\n", cv->name, *(i32 *)(cv->data) );
+                               break;
+                               case k_convar_dtype_u32:
+                                       fprintf( fp, "%s %u\n", cv->name, *(u32 *)(cv->data) );
+                               break;
+                               case k_convar_dtype_f32:
+                                       fprintf( fp, "%s %.5f\n", cv->name, *(float *)(cv->data ) );
+                               break;
+                       }
+               }
+       }
+
+       fclose( fp );
 }
 
 static void vg_console_free(void)
 {
+       vg_console_write_persistent();
+
        arrfree( vg_console.convars );
+       arrfree( vg_console.functions );
+}
+
+static void execute_console_input( const char *cmd )
+{
+       char temp[512];
+       char const *args[9];
+       int arg_count = 0;
+       
+       int in_token = 0;
+       
+       // Split string into tokens
+       for( int i = 0; i < vg_list_size( temp ); i ++ )
+       {
+               if( cmd[i] )
+               {
+                       if( isspace( cmd[i] ) )
+                       {
+                               temp[i] = '\0';
+                               in_token = 0;
+                               
+                               if( arg_count == vg_list_size( args ) )
+                                       break;
+                       }
+                       else
+                       {
+                               temp[i] = cmd[i];
+                               
+                               if( !in_token )
+                               {
+                                       args[ arg_count ++ ] = temp + i;
+                                       in_token = 1;
+                               }
+                       }
+               }
+               else
+               {
+                       temp[i] = '\0';
+                       break;
+               }
+       }
+       
+       if( arg_count == 0 )
+               return;
+       
+       int data_int;
+       
+       for( int i = 0; i < arrlen( vg_console.convars ); i ++ )
+       {
+               struct vg_convar *cv = &vg_console.convars[ i ];
+               if( !strcmp( cv->name, args[0] ) )
+               {
+                       // Cvar Matched, try get value
+                       if( arg_count >= 2 )
+                       {
+                               switch( cv->data_type )
+                               {
+                                       case k_convar_dtype_u32:
+                                       case k_convar_dtype_i32: 
+                                               
+                                               data_int = atoi( args[1] ); 
+                                               *((int *)cv->data) = cv->opt_i32.clamp? VG_MIN( VG_MAX(data_int, cv->opt_i32.min), cv->opt_i32.max ): data_int;
+                                       
+                                       break;
+                                       case k_convar_dtype_f32: *((float *)cv->data) = atof( temp ); break;
+                               }
+                       }
+                       else
+                       {
+                               switch( cv->data_type )
+                               {
+                                       case k_convar_dtype_i32: vg_info( "= %d\n", *((int *)cv->data) ); break;
+                                       case k_convar_dtype_u32: vg_info( "= %u\n", *((u32 *)cv->data) ); break;
+                                       case k_convar_dtype_f32: vg_info( "= %.4f\n", *((float *)cv->data) ); break;
+                               }
+                       }
+               
+                       return;
+               }
+       }
+       
+       // Try commands
+       for( int i = 0; i < arrlen( vg_console.functions ); i ++ )
+       {
+               struct vg_cmd *cmd = &vg_console.functions[ i ];
+               if( !strcmp( cmd->name, args[0] ) )
+               {
+                       // Call function
+                       cmd->function( arg_count-1, args+1 );
+                       return;
+               }
+       }
+       
+       vg_error( "No command/variable named '%s'. Use 'list' to view all\n", args[0] );
+}
+
+// =============================================================================================================================
+// Console interface
+
+static void console_make_selection( int* start, int* end )
+{
+       *start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user );
+       *end   = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
+}
+
+static void console_move_cursor( int* cursor0, int* cursor1, int dir, int snap_together )
+{
+       *cursor0 = VG_MAX( 0, vg_console.cursor_user + dir );
+       *cursor0 = VG_MIN( VG_MIN( vg_list_size( vg_console.input ), strlen( vg_console.input )), *cursor0 );
+       if( snap_together ) 
+               *cursor1 = *cursor0;
+}
+
+static int console_makeroom( int datastart, int length )
+{
+       int move_to = VG_MIN( datastart+length, vg_list_size( vg_console.input ) );
+       int move_amount = strlen( vg_console.input )-datastart;
+       int move_end = VG_MIN( move_to+move_amount, vg_list_size( vg_console.input ) );
+       move_amount = move_end-move_to;
+       
+       if( move_amount )
+               memmove( &vg_console.input[ move_to ], &vg_console.input[ datastart ], move_end-move_to );
+       
+       vg_console.input[ move_end ] = '\0';
+       
+       return VG_MIN( length, vg_list_size( vg_console.input )-datastart );
+}
+
+static int console_delete_char( int direction )
+{
+       int start, end;
+       console_make_selection( &start, &end );
+       
+       // There is no selection
+       if( !(end-start) )
+       {
+               if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_console.input ) );
+               else if( direction == -1 ) start = VG_MAX( start-1, 0 );
+       }
+       
+       // Still no selction, no need to do anything
+       if( !(end-start) ) 
+               return start;
+       
+       // Copy the end->terminator to start
+       int remaining_length = strlen( vg_console.input )+1-end;
+       memmove( &vg_console.input[ start ], &vg_console.input[ end ], remaining_length );
+       return start;
+}
+
+static void console_to_clipboard(void)
+{
+       int start, end;
+       console_make_selection( &start, &end );
+       char buffer[512];
+       
+       if( end-start )
+       {
+               memcpy( buffer, &vg_console.input[ start ], end-start );
+               buffer[ end-start ] = 0x00;
+               glfwSetClipboardString( NULL, buffer );
+       }
+}
+
+static void console_clipboard_paste(void)
+{
+       int datastart = console_delete_char(0);
+       const char* clipboard = glfwGetClipboardString(NULL);
+       int length = strlen(clipboard);
+       
+       int cpylength = console_makeroom(datastart, length);
+
+       memcpy( vg_console.input + datastart, clipboard, cpylength);
+       
+       console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, cpylength, 1 );
+}
+
+static void console_put_char( char c )
+{
+       if( !vg_console.enabled ) 
+               return;
+       
+       vg_console.cursor_user = console_delete_char(0);
+       
+       if( console_makeroom( vg_console.cursor_user, 1 ) )
+               vg_console.input[ vg_console.cursor_user ] = c;
+       
+       console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, 1, 1 );
+}
+
+static void console_history_get( char* buf, int entry_num )
+{
+       if( !vg_console.history_count )
+               return;
+       
+       int pick = (vg_console.history_last - VG_MIN( entry_num, vg_console.history_count -1 )) % vg_list_size( vg_console.history );
+       strcpy( buf, vg_console.history[ pick ] );
+}
+
+static void console_proc_key( GLFWwindow* ptrW, int key, int scancode, int action, int mods )
+{
+       if( action )
+       {
+               int cursor_diff = vg_console.cursor_pos - vg_console.cursor_user? 0: 1;
+               
+               if( key == GLFW_KEY_GRAVE_ACCENT )
+               {
+                       vg_console.enabled = !vg_console.enabled;
+                       return;
+               }
+               
+               if( !vg_console.enabled ) 
+                       return;
+               
+               if( key == GLFW_KEY_LEFT )
+               {
+                       if( mods & GLFW_MOD_SHIFT ) // Receed secondary cursor
+                       { 
+                               console_move_cursor( &vg_console.cursor_user, NULL, -1, 0 );
+                       } 
+                       else // Match and receed both cursors
+                       {
+                               console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, -cursor_diff, 1 );
+                       }
+               }
+               else if( key == GLFW_KEY_RIGHT ) // Advance secondary cursor
+               {
+                       if( mods & GLFW_MOD_SHIFT )
+                       {
+                               console_move_cursor( &vg_console.cursor_user, NULL, 1, 0 );
+                       } 
+                       else // Match and advance both cursors 
+                       {
+                               console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, +cursor_diff, 1 );
+                       }
+               } 
+               else if( key == GLFW_KEY_DOWN )
+               {
+                       if( mods & GLFW_MOD_SHIFT ){} 
+                       else 
+                       {
+                               vg_console.history_pos = VG_MAX( 0, vg_console.history_pos-1 );
+                               console_history_get( vg_console.input, vg_console.history_pos );
+                               console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, vg_list_size( vg_console.input ), 1 );
+                       }
+               } 
+               else if( key == GLFW_KEY_UP )
+               {
+                       if( mods & GLFW_MOD_SHIFT ){} 
+                       else 
+                       {
+                               vg_console.history_pos = VG_MAX
+                               ( 
+                                       0, 
+                                       VG_MIN
+                                       (
+                                               vg_console.history_pos+1, 
+                                               VG_MIN
+                                               ( 
+                                                       vg_list_size( vg_console.history ), 
+                                                       vg_console.history_count - 1 
+                                               )
+                                       )
+                               );
+                               
+                               console_history_get( vg_console.input, vg_console.history_pos );
+                               console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, vg_list_size( vg_console.input ), 1);
+                       }
+               } 
+               else if( key == GLFW_KEY_BACKSPACE ) // Lookback delete
+               {
+                       vg_console.cursor_user = console_delete_char( -1 );
+                       vg_console.cursor_pos = vg_console.cursor_user;
+               } 
+               else if( key == GLFW_KEY_DELETE ) // Lookforward delete
+               {
+                       vg_console.cursor_user = console_delete_char( 1 );
+                       vg_console.cursor_pos = vg_console.cursor_user;
+               } 
+               else if( key == GLFW_KEY_HOME ) // Home key
+               {
+                       if( mods & GLFW_MOD_SHIFT ) 
+                               console_move_cursor( &vg_console.cursor_user, NULL, -10000, 0 );
+                       else 
+                               console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, -10000, 1 );
+               } 
+               else if( key == GLFW_KEY_END ) // End key
+               {
+                       if( mods & GLFW_MOD_SHIFT ) 
+                               console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0 );
+                       else 
+                               console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, vg_list_size( vg_console.input ), 1 );
+               }
+               else if( key == GLFW_KEY_A )
+               {
+                       if( mods & GLFW_MOD_CONTROL ) // Select all
+                       {
+                               console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0);
+                               console_move_cursor( &vg_console.cursor_pos, NULL, -10000, 0);
+                       }
+               } 
+               else if( key == GLFW_KEY_C ) // Copy
+               {
+                       if( mods & GLFW_MOD_CONTROL )
+                       {
+                               console_to_clipboard();
+                       }
+               } 
+               else if( key == GLFW_KEY_X ) // Cut
+               {
+                       if( mods & GLFW_MOD_CONTROL )
+                       {
+                               console_to_clipboard();
+                               vg_console.cursor_user = console_delete_char(0);
+                               vg_console.cursor_pos = vg_console.cursor_user;
+                       }
+               } 
+               else if( key == GLFW_KEY_V ) // Paste
+               {
+                       if( mods & GLFW_MOD_CONTROL )
+                       {
+                               console_clipboard_paste();
+                       }
+               } 
+               else if( key == GLFW_KEY_ENTER )
+               {
+                       if( !strlen( vg_console.input ) ) 
+                               return;
+                       
+                       vg_info( "%s\n", vg_console.input );
+                       
+                       if( strcmp( vg_console.input, vg_console.history[ vg_console.history_last ]) )
+                       {
+                               vg_console.history_last = ( vg_console.history_last + 1) % vg_list_size(vg_console.history );
+                               vg_console.history_count = VG_MIN( vg_list_size( vg_console.history ), vg_console.history_count + 1 );
+                               strcpy( vg_console.history[ vg_console.history_last ], vg_console.input );
+                       }
+                       
+                       vg_console.history_pos = -1;
+                       execute_console_input( vg_console.input );
+                       console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, -10000, 1 );
+                       vg_console.input[0] = '\0';
+               }
+       }
+}
+
+// Handle an OS based input of UTF32 character from the keyboard or such
+static void console_proc_wchar( GLFWwindow* ptrW, u32 uWchar )
+{
+       //LOG_INFO("Recieved wchar: %u\n", uWchar);
+       if( uWchar <= 0x7F && (char)uWchar != 0x60)
+       {
+               console_put_char((char)uWchar);
+       }
 }