a1ce4063c0b63b5617b606e3c28695824fe413e8
[fishladder.git] / vg / vg_console.h
1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
2
3 struct vg_console
4 {
5 struct vg_convar
6 {
7 void *data;
8 const char *name;
9
10 enum vg_convar_dtype
11 {
12 k_convar_dtype_i32,
13 k_convar_dtype_u32,
14 k_convar_dtype_f32
15 }
16 data_type;
17
18 union
19 {
20 struct
21 {
22 int min, max, clamp;
23 } opt_i32;
24 };
25
26 int persistent; // Should be stored to cfg/auto.conf
27 }
28 *convars;
29
30 struct vg_cmd
31 {
32 int (*function)( int argc, char const *argv[] );
33 const char *name;
34 }
35 *functions;
36
37 char lines[16][512];
38 u32 current, len;
39
40 char input[512];
41 int cursor_user, cursor_pos, string_length;
42
43 char history[32][512];
44 int history_last, history_pos, history_count;
45
46 int enabled;
47 int scale;
48 }
49 vg_console = { .scale = 2 };
50
51 // Declerations
52 // ------------
53
54 // Registration
55 static void vg_convar_push( struct vg_convar cv );
56 static void vg_function_push( struct vg_cmd cmd );
57
58 static void vg_console_draw( void );
59 void vg_console_println( const char *str );
60 static int vg_console_list( int argc, char const *argv[] );
61 static void vg_console_init(void);
62 static void vg_console_write_persistent(void);
63 static void vg_console_free(void);
64 static void execute_console_input( const char *cmd );
65
66 // Console interface
67 // -----------------
68
69 static void console_make_selection( int* start, int* end );
70 static void console_move_cursor( int* cursor0, int* cursor1, int dir, int snap_together );
71 static int console_makeroom( int datastart, int length );
72 static int console_delete_char( int direction );
73 static void console_to_clipboard(void);
74 static void console_clipboard_paste(void);
75 static void console_put_char( char c );
76 static void console_history_get( char* buf, int entry_num );
77 static void console_proc_key( GLFWwindow* ptrW, int key, int scancode, int action, int mods );
78 static void console_proc_wchar( GLFWwindow* ptrW, u32 uWchar );
79 static int vg_console_enabled(void);
80
81 // =========================================================================================================
82 // Implementation
83
84 static int vg_console_enabled(void)
85 {
86 return vg_console.enabled;
87 }
88
89 static void vg_convar_push( struct vg_convar cv )
90 {
91 arrpush( vg_console.convars, cv );
92 }
93
94 static void vg_function_push( struct vg_cmd cmd )
95 {
96 arrpush( vg_console.functions, cmd );
97 }
98
99 static void vg_console_draw( void )
100 {
101 if( !vg_console.enabled )
102 return;
103
104 int ptr = vg_console.current-1;
105
106 int const fh = 14;
107
108 ui_global_ctx.cursor[0] = 0;
109 ui_global_ctx.cursor[1] = 0;
110 ui_global_ctx.cursor[3] = vg_list_size( vg_console.lines )*fh*vg_console.scale;
111 ui_fill_x( &ui_global_ctx );
112
113 ui_new_node( &ui_global_ctx );
114 {
115 ui_fill_rect( &ui_global_ctx, ui_global_ctx.cursor, 0x77333333 );
116
117 ui_global_ctx.cursor[3] = fh*vg_console.scale;
118 ui_align_bottom( &ui_global_ctx );
119
120 for( int i = 0; i < vg_console.len; i ++ )
121 {
122 if( ptr < 0 )
123 ptr = vg_list_size( vg_console.lines )-1;
124
125 ui_text( &ui_global_ctx, vg_console.lines[ptr], vg_console.scale );
126 ui_global_ctx.cursor[1] -= fh*vg_console.scale;
127
128 ptr --;
129 }
130 }
131 ui_end_down( &ui_global_ctx );
132
133 ui_global_ctx.cursor[1] += 2;
134 ui_global_ctx.cursor[3] = fh*vg_console.scale;
135
136 ui_new_node( &ui_global_ctx );
137 {
138 ui_fill_rect( &ui_global_ctx, ui_global_ctx.cursor, 0x77333333 );
139
140 ui_text( &ui_global_ctx, vg_console.input, vg_console.scale );
141
142 int start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user ),
143 end = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
144
145 ui_global_ctx.cursor[0] = (start * ui_glyph_spacing_x * vg_console.scale);
146 ui_global_ctx.cursor[2] = (start == end? 0.5f: (float)(end-start)) * (float)ui_glyph_spacing_x * (float)vg_console.scale;
147
148 ui_fill_rect( &ui_global_ctx, ui_global_ctx.cursor, 0x66ffffff );
149 }
150 ui_end_down( &ui_global_ctx );
151 }
152
153 void vg_console_println( const char *str )
154 {
155 if( vg_console.len < vg_list_size( vg_console.lines ) )
156 vg_console.len ++;
157
158 strcpy( vg_console.lines[ vg_console.current ++ ], str );
159
160 if( vg_console.current >= vg_list_size( vg_console.lines ) )
161 vg_console.current = 0;
162 }
163
164 static int vg_console_list( int argc, char const *argv[] )
165 {
166 for( int i = 0; i < arrlen( vg_console.functions ); i ++ )
167 {
168 struct vg_cmd *cmd = &vg_console.functions[ i ];
169 vg_info( "* %s\n", cmd->name );
170 }
171
172 vg_info( "* snowsound\n" );
173
174 for( int i = 0; i < arrlen( vg_console.convars ); i ++ )
175 {
176 struct vg_convar *cv = &vg_console.convars[ i ];
177 vg_info( "%s\n", cv->name );
178 }
179
180 return 0;
181 }
182
183 static int vg_console_chartest( int argc, char const *argv[] )
184 {
185 vg_info( "\"THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG\"\n" );
186 vg_info( "'the quick brown fox jumps over the lazy dog'\n" );
187 vg_info( ":;!@#$%^& 0123456789 +-*/=~ ()[]{}<>\n" );
188 return 0;
189 }
190
191 static void vg_console_init(void)
192 {
193 vg_log_callback = vg_console_println;
194
195 vg_convar_push( (struct vg_convar)
196 { .name = "console_scale", .data = &vg_console.scale, .data_type = k_convar_dtype_i32,
197 .opt_i32 = { .clamp = 1, .min = 1, .max = 7 } } );
198
199 vg_function_push( (struct vg_cmd){
200 .name = "list",
201 .function = vg_console_list
202 });
203
204 vg_function_push( (struct vg_cmd){
205 .name = "chartest",
206 .function = vg_console_chartest
207 });
208
209 // Read and exec persistent commands
210 FILE *fp = fopen( "cfg/auto.conf", "r" );
211 if( fp )
212 {
213 char line[256];
214
215 while( fgets( line, sizeof( line ), fp ) )
216 {
217 line[ strcspn( line, "\r\n#" ) ] = 0x00;
218
219 if( line[0] != 0x00 )
220 {
221 execute_console_input( line );
222 }
223 }
224
225 fclose( fp );
226 }
227 }
228
229 static void vg_console_write_persistent(void)
230 {
231 FILE *fp = fopen( "cfg/auto.conf", "w" );
232
233 for( int i = 0; i < arrlen( vg_console.convars ); i ++ )
234 {
235 struct vg_convar *cv = &vg_console.convars[i];
236
237 if( cv->persistent )
238 {
239 switch( cv->data_type )
240 {
241 case k_convar_dtype_i32:
242 fprintf( fp, "%s %d\n", cv->name, *(i32 *)(cv->data) );
243 break;
244 case k_convar_dtype_u32:
245 fprintf( fp, "%s %u\n", cv->name, *(u32 *)(cv->data) );
246 break;
247 case k_convar_dtype_f32:
248 fprintf( fp, "%s %.5f\n", cv->name, *(float *)(cv->data ) );
249 break;
250 }
251 }
252 }
253
254 fclose( fp );
255 }
256
257 static void vg_console_free(void)
258 {
259 vg_console_write_persistent();
260
261 arrfree( vg_console.convars );
262 arrfree( vg_console.functions );
263 }
264
265 static void execute_console_input( const char *cmd )
266 {
267 char temp[512];
268 char const *args[9];
269 int arg_count = 0;
270
271 int in_token = 0;
272
273 // Split string into tokens
274 for( int i = 0; i < vg_list_size( temp ); i ++ )
275 {
276 if( cmd[i] )
277 {
278 if( isspace( cmd[i] ) )
279 {
280 temp[i] = '\0';
281 in_token = 0;
282
283 if( arg_count == vg_list_size( args ) )
284 break;
285 }
286 else
287 {
288 temp[i] = cmd[i];
289
290 if( !in_token )
291 {
292 args[ arg_count ++ ] = temp + i;
293 in_token = 1;
294 }
295 }
296 }
297 else
298 {
299 temp[i] = '\0';
300 break;
301 }
302 }
303
304 if( arg_count == 0 )
305 return;
306
307 int data_int;
308
309 for( int i = 0; i < arrlen( vg_console.convars ); i ++ )
310 {
311 struct vg_convar *cv = &vg_console.convars[ i ];
312 if( !strcmp( cv->name, args[0] ) )
313 {
314 // Cvar Matched, try get value
315 if( arg_count >= 2 )
316 {
317 switch( cv->data_type )
318 {
319 case k_convar_dtype_u32:
320 case k_convar_dtype_i32:
321
322 data_int = atoi( args[1] );
323 *((int *)cv->data) = cv->opt_i32.clamp? VG_MIN( VG_MAX(data_int, cv->opt_i32.min), cv->opt_i32.max ): data_int;
324
325 break;
326 case k_convar_dtype_f32: *((float *)cv->data) = atof( temp ); break;
327 }
328 }
329 else
330 {
331 switch( cv->data_type )
332 {
333 case k_convar_dtype_i32: vg_info( "= %d\n", *((int *)cv->data) ); break;
334 case k_convar_dtype_u32: vg_info( "= %u\n", *((u32 *)cv->data) ); break;
335 case k_convar_dtype_f32: vg_info( "= %.4f\n", *((float *)cv->data) ); break;
336 }
337 }
338
339 return;
340 }
341 }
342
343 // Try commands
344 for( int i = 0; i < arrlen( vg_console.functions ); i ++ )
345 {
346 struct vg_cmd *cmd = &vg_console.functions[ i ];
347 if( !strcmp( cmd->name, args[0] ) )
348 {
349 // Call function
350 cmd->function( arg_count-1, args+1 );
351 return;
352 }
353 }
354
355 vg_error( "No command/variable named '%s'. Use 'list' to view all\n", args[0] );
356 }
357
358 // =============================================================================================================================
359 // Console interface
360
361 static void console_make_selection( int* start, int* end )
362 {
363 *start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user );
364 *end = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
365 }
366
367 static void console_move_cursor( int* cursor0, int* cursor1, int dir, int snap_together )
368 {
369 *cursor0 = VG_MAX( 0, vg_console.cursor_user + dir );
370 *cursor0 = VG_MIN( VG_MIN( vg_list_size( vg_console.input ), strlen( vg_console.input )), *cursor0 );
371 if( snap_together )
372 *cursor1 = *cursor0;
373 }
374
375 static int console_makeroom( int datastart, int length )
376 {
377 int move_to = VG_MIN( datastart+length, vg_list_size( vg_console.input ) );
378 int move_amount = strlen( vg_console.input )-datastart;
379 int move_end = VG_MIN( move_to+move_amount, vg_list_size( vg_console.input ) );
380 move_amount = move_end-move_to;
381
382 if( move_amount )
383 memmove( &vg_console.input[ move_to ], &vg_console.input[ datastart ], move_end-move_to );
384
385 vg_console.input[ move_end ] = '\0';
386
387 return VG_MIN( length, vg_list_size( vg_console.input )-datastart );
388 }
389
390 static int console_delete_char( int direction )
391 {
392 int start, end;
393 console_make_selection( &start, &end );
394
395 // There is no selection
396 if( !(end-start) )
397 {
398 if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_console.input ) );
399 else if( direction == -1 ) start = VG_MAX( start-1, 0 );
400 }
401
402 // Still no selction, no need to do anything
403 if( !(end-start) )
404 return start;
405
406 // Copy the end->terminator to start
407 int remaining_length = strlen( vg_console.input )+1-end;
408 memmove( &vg_console.input[ start ], &vg_console.input[ end ], remaining_length );
409 return start;
410 }
411
412 static void console_to_clipboard(void)
413 {
414 int start, end;
415 console_make_selection( &start, &end );
416 char buffer[512];
417
418 if( end-start )
419 {
420 memcpy( buffer, &vg_console.input[ start ], end-start );
421 buffer[ end-start ] = 0x00;
422 glfwSetClipboardString( NULL, buffer );
423 }
424 }
425
426 static void console_clipboard_paste(void)
427 {
428 int datastart = console_delete_char(0);
429 const char* clipboard = glfwGetClipboardString(NULL);
430 int length = strlen(clipboard);
431
432 int cpylength = console_makeroom(datastart, length);
433
434 memcpy( vg_console.input + datastart, clipboard, cpylength);
435
436 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, cpylength, 1 );
437 }
438
439 static void console_put_char( char c )
440 {
441 if( !vg_console.enabled )
442 return;
443
444 vg_console.cursor_user = console_delete_char(0);
445
446 if( console_makeroom( vg_console.cursor_user, 1 ) )
447 vg_console.input[ vg_console.cursor_user ] = c;
448
449 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, 1, 1 );
450 }
451
452 static void console_history_get( char* buf, int entry_num )
453 {
454 if( !vg_console.history_count )
455 return;
456
457 int pick = (vg_console.history_last - VG_MIN( entry_num, vg_console.history_count -1 )) % vg_list_size( vg_console.history );
458 strcpy( buf, vg_console.history[ pick ] );
459 }
460
461 static void console_proc_key( GLFWwindow* ptrW, int key, int scancode, int action, int mods )
462 {
463 if( action )
464 {
465 int cursor_diff = vg_console.cursor_pos - vg_console.cursor_user? 0: 1;
466
467 if( key == GLFW_KEY_GRAVE_ACCENT )
468 {
469 vg_console.enabled = !vg_console.enabled;
470 return;
471 }
472
473 if( !vg_console.enabled )
474 return;
475
476 if( key == GLFW_KEY_LEFT )
477 {
478 if( mods & GLFW_MOD_SHIFT ) // Receed secondary cursor
479 {
480 console_move_cursor( &vg_console.cursor_user, NULL, -1, 0 );
481 }
482 else // Match and receed both cursors
483 {
484 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, -cursor_diff, 1 );
485 }
486 }
487 else if( key == GLFW_KEY_RIGHT ) // Advance secondary cursor
488 {
489 if( mods & GLFW_MOD_SHIFT )
490 {
491 console_move_cursor( &vg_console.cursor_user, NULL, 1, 0 );
492 }
493 else // Match and advance both cursors
494 {
495 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, +cursor_diff, 1 );
496 }
497 }
498 else if( key == GLFW_KEY_DOWN )
499 {
500 if( mods & GLFW_MOD_SHIFT ){}
501 else
502 {
503 vg_console.history_pos = VG_MAX( 0, vg_console.history_pos-1 );
504 console_history_get( vg_console.input, vg_console.history_pos );
505 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, vg_list_size( vg_console.input ), 1 );
506 }
507 }
508 else if( key == GLFW_KEY_UP )
509 {
510 if( mods & GLFW_MOD_SHIFT ){}
511 else
512 {
513 vg_console.history_pos = VG_MAX
514 (
515 0,
516 VG_MIN
517 (
518 vg_console.history_pos+1,
519 VG_MIN
520 (
521 vg_list_size( vg_console.history ),
522 vg_console.history_count - 1
523 )
524 )
525 );
526
527 console_history_get( vg_console.input, vg_console.history_pos );
528 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, vg_list_size( vg_console.input ), 1);
529 }
530 }
531 else if( key == GLFW_KEY_BACKSPACE ) // Lookback delete
532 {
533 vg_console.cursor_user = console_delete_char( -1 );
534 vg_console.cursor_pos = vg_console.cursor_user;
535 }
536 else if( key == GLFW_KEY_DELETE ) // Lookforward delete
537 {
538 vg_console.cursor_user = console_delete_char( 1 );
539 vg_console.cursor_pos = vg_console.cursor_user;
540 }
541 else if( key == GLFW_KEY_HOME ) // Home key
542 {
543 if( mods & GLFW_MOD_SHIFT )
544 console_move_cursor( &vg_console.cursor_user, NULL, -10000, 0 );
545 else
546 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, -10000, 1 );
547 }
548 else if( key == GLFW_KEY_END ) // End key
549 {
550 if( mods & GLFW_MOD_SHIFT )
551 console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0 );
552 else
553 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, vg_list_size( vg_console.input ), 1 );
554 }
555 else if( key == GLFW_KEY_A )
556 {
557 if( mods & GLFW_MOD_CONTROL ) // Select all
558 {
559 console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0);
560 console_move_cursor( &vg_console.cursor_pos, NULL, -10000, 0);
561 }
562 }
563 else if( key == GLFW_KEY_C ) // Copy
564 {
565 if( mods & GLFW_MOD_CONTROL )
566 {
567 console_to_clipboard();
568 }
569 }
570 else if( key == GLFW_KEY_X ) // Cut
571 {
572 if( mods & GLFW_MOD_CONTROL )
573 {
574 console_to_clipboard();
575 vg_console.cursor_user = console_delete_char(0);
576 vg_console.cursor_pos = vg_console.cursor_user;
577 }
578 }
579 else if( key == GLFW_KEY_V ) // Paste
580 {
581 if( mods & GLFW_MOD_CONTROL )
582 {
583 console_clipboard_paste();
584 }
585 }
586 else if( key == GLFW_KEY_ENTER )
587 {
588 if( !strlen( vg_console.input ) )
589 return;
590
591 vg_info( "%s\n", vg_console.input );
592
593 if( strcmp( vg_console.input, vg_console.history[ vg_console.history_last ]) )
594 {
595 vg_console.history_last = ( vg_console.history_last + 1) % vg_list_size(vg_console.history );
596 vg_console.history_count = VG_MIN( vg_list_size( vg_console.history ), vg_console.history_count + 1 );
597 strcpy( vg_console.history[ vg_console.history_last ], vg_console.input );
598 }
599
600 vg_console.history_pos = -1;
601 execute_console_input( vg_console.input );
602 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, -10000, 1 );
603 vg_console.input[0] = '\0';
604 }
605 }
606 }
607
608 // Handle an OS based input of UTF32 character from the keyboard or such
609 static void console_proc_wchar( GLFWwindow* ptrW, u32 uWchar )
610 {
611 //LOG_INFO("Recieved wchar: %u\n", uWchar);
612 if( uWchar <= 0x7F && (char)uWchar != 0x60)
613 {
614 console_put_char((char)uWchar);
615 }
616 }