e18515d45cd029e3d422da5c09881994193a7fc0
[vg.git] / vg_console.h
1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
2
3 #ifndef VG_CONSOLE_H
4 #define VG_CONSOLE_H
5
6 #ifndef VG_GAME
7 #define VG_GAME
8 #endif
9
10 #include "vg/vg_ui.h"
11 #include "vg/vg_log.h"
12
13 typedef struct vg_convar vg_convar;
14 typedef struct vg_cmd vg_cmd;
15
16 struct vg_console
17 {
18 struct vg_convar
19 {
20 void *data;
21 void (*update)(void);
22 const char *name;
23
24 enum vg_convar_dtype
25 {
26 k_convar_dtype_i32,
27 k_convar_dtype_u32,
28 k_convar_dtype_f32
29 }
30 data_type;
31
32 union
33 {
34 struct
35 {
36 int min, max, clamp;
37 }
38 opt_i32;
39
40 struct
41 {
42 float min, max;
43 int clamp;
44 }
45 opt_f32;
46 };
47
48 int persistent; /* Should this var be stored to cfg/auto.conf? */
49 }
50 convars[ 32 ];
51
52 struct vg_cmd
53 {
54 int (*function)( int argc, char const *argv[] );
55 const char *name;
56 }
57 functions[ 32 ];
58
59 u32 convar_count, function_count;
60
61 char input[96];
62 int cursor_user, cursor_pos, string_length;
63
64 char history[32][96];
65 int history_last, history_pos, history_count;
66
67 int enabled;
68 }
69 vg_console;
70
71 VG_STATIC void vg_convar_push( struct vg_convar cv );
72 VG_STATIC void vg_function_push( struct vg_cmd cmd );
73
74 VG_STATIC void _vg_console_draw( void );
75 void _vg_console_println( const char *str );
76 VG_STATIC int _vg_console_list( int argc, char const *argv[] );
77 VG_STATIC void _vg_console_init(void);
78 VG_STATIC void _vg_console_write_persistent(void);
79 VG_STATIC void _vg_console_free(void);
80 VG_STATIC void vg_execute_console_input( const char *cmd );
81
82 /*
83 * Console interface
84 */
85 VG_STATIC void console_make_selection( int* start, int* end );
86 VG_STATIC void console_move_cursor( int* cursor0, int* cursor1,
87 int dir, int snap_together );
88 VG_STATIC int console_makeroom( int datastart, int length );
89 VG_STATIC int console_delete_char( int direction );
90 VG_STATIC void console_to_clipboard(void);
91 VG_STATIC void console_clipboard_paste(void);
92 VG_STATIC void console_put_char( char c );
93 VG_STATIC void console_history_get( char* buf, int entry_num );
94 VG_STATIC int _vg_console_enabled(void);
95 VG_STATIC void console_proc_key( SDL_Keysym ev );
96
97 /*
98 * Implementation
99 */
100 VG_STATIC int _vg_console_enabled(void)
101 {
102 return vg_console.enabled;
103 }
104
105 VG_STATIC void vg_convar_push( vg_convar cv )
106 {
107 if( vg_console.convar_count > vg_list_size(vg_console.convars) )
108 vg_fatal_exit_loop( "Too many convars registered" );
109
110 vg_info( "Console variable '%s' registered\n", cv.name );
111 vg_console.convars[ vg_console.convar_count ++ ] = cv;
112 }
113
114 VG_STATIC void vg_function_push( struct vg_cmd cmd )
115 {
116 if( vg_console.function_count > vg_list_size(vg_console.functions) )
117 vg_fatal_exit_loop( "Too many functions registered" );
118
119 vg_console.functions[ vg_console.function_count ++ ] = cmd;
120 }
121
122 VG_STATIC void _vg_console_draw( void )
123 {
124 if( !vg_console.enabled )
125 return;
126
127 SDL_AtomicLock( &log_print_sl );
128
129 int ptr = vg_log.buffer_line_current;
130 int const fh = 14,
131 log_lines = 32;
132 int console_lines = VG_MIN( log_lines, vg_log.buffer_line_count );
133
134 vg_uictx.cursor[0] = 0;
135 vg_uictx.cursor[1] = 0;
136 vg_uictx.cursor[3] = log_lines*fh;
137 ui_fill_x();
138
139 ui_new_node();
140 {
141 ui_fill_rect( vg_uictx.cursor, 0x77333333 );
142
143 vg_uictx.cursor[3] = fh;
144 ui_align_bottom();
145
146 for( int i=0; i<console_lines; i ++ )
147 {
148 ptr --;
149
150 if( ptr < 0 )
151 ptr = vg_list_size( vg_log.buffer )-1;
152
153 ui_text( vg_uictx.cursor, vg_log.buffer[ptr], 1, 0 );
154 vg_uictx.cursor[1] -= fh;
155 }
156
157 }
158 ui_end_down();
159
160 vg_uictx.cursor[1] += 2;
161 vg_uictx.cursor[3] = fh;
162
163 ui_new_node();
164 {
165 ui_fill_rect( vg_uictx.cursor, 0x77333333 );
166 ui_text( vg_uictx.cursor, vg_console.input, 1, 0 );
167
168 int start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user ),
169 end = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
170
171 vg_uictx.cursor[0] = start * UI_GLYPH_SPACING_X;
172 vg_uictx.cursor[2] = (start == end? 0.5f: (float)(end-start))
173 * (float)UI_GLYPH_SPACING_X;
174
175 ui_fill_rect( vg_uictx.cursor, 0x66ffffff );
176 }
177 ui_end_down();
178 SDL_AtomicUnlock( &log_print_sl );
179 }
180
181 VG_STATIC int _vg_console_list( int argc, char const *argv[] )
182 {
183 for( int i=0; i<vg_console.function_count; i ++ )
184 {
185 struct vg_cmd *cmd = &vg_console.functions[ i ];
186 vg_info( "* %s\n", cmd->name );
187 }
188
189 for( int i=0; i<vg_console.convar_count; i ++ )
190 {
191 struct vg_convar *cv = &vg_console.convars[ i ];
192 vg_info( "%s\n", cv->name );
193 }
194
195 return 0;
196 }
197
198 int _test_break( int argc, const char *argv[] )
199 {
200 vg_fatal_exit_loop( "Test crash from main, after loading (console)" );
201 return 0;
202 }
203
204 VG_STATIC void _vg_console_init(void)
205 {
206 vg_function_push( (struct vg_cmd)
207 {
208 .name = "list",
209 .function = _vg_console_list
210 });
211
212 vg_function_push( (struct vg_cmd)
213 {
214 .name = "crash",
215 .function = _test_break
216 });
217 }
218
219 VG_STATIC void vg_console_load_autos(void)
220 {
221 /* Read and exec persistent commands */
222 FILE *fp = fopen( "cfg/auto.conf", "r" );
223 if( fp )
224 {
225 char line[256];
226
227 while( fgets( line, sizeof( line ), fp ) )
228 {
229 line[ strcspn( line, "\r\n#" ) ] = 0x00;
230
231 if( line[0] != 0x00 )
232 {
233 vg_execute_console_input( line );
234 }
235 }
236
237 fclose( fp );
238 }
239 }
240
241 VG_STATIC void _vg_console_write_persistent(void)
242 {
243 FILE *fp = fopen( "cfg/auto.conf", "w" );
244
245 for( int i=0; i<vg_console.convar_count; i ++ )
246 {
247 struct vg_convar *cv = &vg_console.convars[i];
248
249 if( cv->persistent )
250 {
251 switch( cv->data_type )
252 {
253 case k_convar_dtype_i32:
254 fprintf( fp, "%s %d\n", cv->name, *(i32 *)(cv->data) );
255 break;
256 case k_convar_dtype_u32:
257 fprintf( fp, "%s %u\n", cv->name, *(u32 *)(cv->data) );
258 break;
259 case k_convar_dtype_f32:
260 fprintf( fp, "%s %.5f\n", cv->name, *(float *)(cv->data ) );
261 break;
262 }
263 }
264 }
265
266 fclose( fp );
267 }
268
269 VG_STATIC void _vg_console_free(void)
270 {
271 _vg_console_write_persistent();
272 }
273
274 VG_STATIC void vg_execute_console_input( const char *cmd )
275 {
276 char temp[512];
277 char const *args[9];
278 int arg_count = 0;
279
280 int in_token = 0;
281
282 /* Split string into tokens */
283 for( int i = 0; i < vg_list_size( temp ); i ++ )
284 {
285 if( cmd[i] )
286 {
287 if( cmd[i] == ' ' || cmd[i] == '\t' )
288 {
289 temp[i] = '\0';
290 in_token = 0;
291
292 if( arg_count == vg_list_size( args ) )
293 break;
294 }
295 else
296 {
297 temp[i] = cmd[i];
298
299 if( !in_token )
300 {
301 args[ arg_count ++ ] = temp + i;
302 in_token = 1;
303 }
304 }
305 }
306 else
307 {
308 temp[i] = '\0';
309 break;
310 }
311 }
312
313 if( arg_count == 0 )
314 return;
315
316 int data_int;
317 float data_float;
318
319 for( int i=0; i<vg_console.convar_count; i ++ )
320 {
321 struct vg_convar *cv = &vg_console.convars[ i ];
322 if( !strcmp( cv->name, args[0] ) )
323 {
324 /* Cvar Matched, try get value */
325 if( arg_count >= 2 )
326 {
327 switch( cv->data_type )
328 {
329 case k_convar_dtype_u32:
330 case k_convar_dtype_i32:
331
332 data_int = atoi( args[1] );
333
334 *((int *)cv->data) = cv->opt_i32.clamp?
335 VG_MIN( VG_MAX(data_int, cv->opt_i32.min), cv->opt_i32.max ):
336 data_int;
337
338 break;
339 case k_convar_dtype_f32:
340 data_float = atof( args[1] );
341 *((float *)cv->data) = cv->opt_f32.clamp?
342 vg_minf( vg_maxf( data_float, cv->opt_f32.min),
343 cv->opt_f32.max ):
344 data_float;
345 break;
346 }
347
348 if( cv->update )
349 cv->update();
350 }
351 else
352 {
353 switch( cv->data_type )
354 {
355 case k_convar_dtype_i32:
356 vg_info( "= %d\n", *((int *)cv->data) );
357 break;
358 case k_convar_dtype_u32:
359 vg_info( "= %u\n", *((u32 *)cv->data) );
360 break;
361 case k_convar_dtype_f32:
362 vg_info( "= %.4f\n", *((float *)cv->data) );
363 break;
364 }
365 }
366
367 return;
368 }
369 }
370
371 /*
372 * Find and excecute command
373 */
374 for( int i=0; i<vg_console.function_count; i ++ )
375 {
376 struct vg_cmd *cmd = &vg_console.functions[ i ];
377 if( !strcmp( cmd->name, args[0] ) )
378 {
379 cmd->function( arg_count-1, args+1 );
380 return;
381 }
382 }
383
384 vg_error( "No command/var named '%s'. Use 'list' to view all\n", args[0] );
385 }
386
387 /*
388 * Console Interface
389 */
390 VG_STATIC void console_make_selection( int* start, int* end )
391 {
392 *start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user );
393 *end = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
394 }
395
396 VG_STATIC void console_move_cursor( int* cursor0, int* cursor1,
397 int dir, int snap_together )
398 {
399 *cursor0 = VG_MAX( 0, vg_console.cursor_user + dir );
400 *cursor0 =
401 VG_MIN(
402 VG_MIN( vg_list_size( vg_console.input ), strlen( vg_console.input )),
403 *cursor0 );
404
405 if( snap_together )
406 *cursor1 = *cursor0;
407 }
408
409 VG_STATIC int console_makeroom( int datastart, int length )
410 {
411 int move_to = VG_MIN( datastart+length, vg_list_size( vg_console.input ) );
412 int move_amount = strlen( vg_console.input )-datastart;
413 int move_end =
414 VG_MIN( move_to+move_amount, vg_list_size( vg_console.input ) );
415 move_amount = move_end-move_to;
416
417 if( move_amount )
418 memmove( &vg_console.input[ move_to ],
419 &vg_console.input[ datastart ],
420 move_end-move_to );
421
422 vg_console.input[ move_end ] = '\0';
423
424 return VG_MIN( length, vg_list_size( vg_console.input )-datastart );
425 }
426
427 VG_STATIC int console_delete_char( int direction )
428 {
429 int start, end;
430 console_make_selection( &start, &end );
431
432 /* There is no selection */
433 if( !(end-start) )
434 {
435 if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_console.input ) );
436 else if( direction == -1 ) start = VG_MAX( start-1, 0 );
437 }
438
439 /* Still no selction, no need to do anything */
440 if( !(end-start) )
441 return start;
442
443 /* Copy the end->terminator to start */
444 int remaining_length = strlen( vg_console.input )+1-end;
445 memmove( &vg_console.input[ start ],
446 &vg_console.input[ end ],
447 remaining_length );
448 return start;
449 }
450
451 VG_STATIC void console_to_clipboard(void)
452 {
453 int start, end;
454 console_make_selection( &start, &end );
455 char buffer[512];
456
457 if( end-start )
458 {
459 memcpy( buffer, &vg_console.input[ start ], end-start );
460 buffer[ end-start ] = 0x00;
461 SDL_SetClipboardText( buffer );
462 }
463 }
464
465 VG_STATIC void console_clipboard_paste(void)
466 {
467 if( !SDL_HasClipboardText() )
468 return;
469
470 char *text = SDL_GetClipboardText();
471
472 if( !text )
473 return;
474
475 int datastart = console_delete_char( 0 );
476 int length = strlen( text );
477 int cpylength = console_makeroom( datastart, length );
478
479 memcpy( vg_console.input + datastart, text, cpylength);
480 console_move_cursor( &vg_console.cursor_user,
481 &vg_console.cursor_pos, cpylength, 1 );
482
483 SDL_free( text );
484 }
485
486 VG_STATIC void console_put_char( char c )
487 {
488 if( !vg_console.enabled )
489 return;
490
491 vg_console.cursor_user = console_delete_char(0);
492
493 if( console_makeroom( vg_console.cursor_user, 1 ) )
494 vg_console.input[ vg_console.cursor_user ] = c;
495
496 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, 1, 1 );
497 }
498
499 VG_STATIC void console_history_get( char* buf, int entry_num )
500 {
501 if( !vg_console.history_count )
502 return;
503
504 int offset = VG_MIN( entry_num, vg_console.history_count -1 ),
505 pick = (vg_console.history_last - offset) %
506 vg_list_size( vg_console.history );
507 strcpy( buf, vg_console.history[ pick ] );
508 }
509
510 /* Receed secondary cursor */
511 VG_STATIC void _console_left_select(void)
512 {
513 console_move_cursor( &vg_console.cursor_user, NULL, -1, 0 );
514 }
515
516 /* Match and receed both cursors */
517 VG_STATIC void _console_left(void)
518 {
519 int cursor_diff = vg_console.cursor_pos - vg_console.cursor_user? 0: 1;
520
521 console_move_cursor( &vg_console.cursor_user,
522 &vg_console.cursor_pos, -cursor_diff, 1 );
523 }
524
525 VG_STATIC void _console_right_select(void)
526 {
527 console_move_cursor( &vg_console.cursor_user, NULL, 1, 0 );
528 }
529
530 VG_STATIC void _console_right(void)
531 {
532 int cursor_diff = vg_console.cursor_pos - vg_console.cursor_user? 0: 1;
533
534 console_move_cursor( &vg_console.cursor_user,
535 &vg_console.cursor_pos, +cursor_diff, 1 );
536 }
537
538 VG_STATIC void _console_down(void)
539 {
540 vg_console.history_pos = VG_MAX( 0, vg_console.history_pos-1 );
541 console_history_get( vg_console.input, vg_console.history_pos );
542
543 console_move_cursor( &vg_console.cursor_user,
544 &vg_console.cursor_pos,
545 vg_list_size( vg_console.input ), 1 );
546 }
547
548 VG_STATIC void _console_up(void)
549 {
550 vg_console.history_pos = VG_MAX
551 (
552 0,
553 VG_MIN
554 (
555 vg_console.history_pos+1,
556 VG_MIN
557 (
558 vg_list_size( vg_console.history ),
559 vg_console.history_count - 1
560 )
561 )
562 );
563
564 console_history_get( vg_console.input, vg_console.history_pos );
565 console_move_cursor( &vg_console.cursor_user,
566 &vg_console.cursor_pos,
567 vg_list_size( vg_console.input ), 1);
568 }
569
570 VG_STATIC void _console_backspace(void)
571 {
572 vg_console.cursor_user = console_delete_char( -1 );
573 vg_console.cursor_pos = vg_console.cursor_user;
574 }
575
576 VG_STATIC void _console_delete(void)
577 {
578 vg_console.cursor_user = console_delete_char( 1 );
579 vg_console.cursor_pos = vg_console.cursor_user;
580 }
581
582 VG_STATIC void _console_home_select(void)
583 {
584 console_move_cursor( &vg_console.cursor_user, NULL, -10000, 0 );
585 }
586
587 VG_STATIC void _console_home(void)
588 {
589 console_move_cursor( &vg_console.cursor_user,
590 &vg_console.cursor_pos, -10000, 1 );
591 }
592
593 VG_STATIC void _console_end_select(void)
594 {
595 console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0 );
596 }
597
598 VG_STATIC void _console_end(void)
599 {
600 console_move_cursor( &vg_console.cursor_user,
601 &vg_console.cursor_pos,
602 vg_list_size( vg_console.input ), 1 );
603 }
604
605 VG_STATIC void _console_select_all(void)
606 {
607 console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0);
608 console_move_cursor( &vg_console.cursor_pos, NULL, -10000, 0);
609 }
610
611 VG_STATIC void _console_cut(void)
612 {
613 console_to_clipboard();
614 vg_console.cursor_user = console_delete_char(0);
615 vg_console.cursor_pos = vg_console.cursor_user;
616 }
617
618 VG_STATIC void _console_enter(void)
619 {
620 if( !strlen( vg_console.input ) )
621 return;
622
623 vg_info( "%s\n", vg_console.input );
624
625 if( strcmp( vg_console.input,
626 vg_console.history[ vg_console.history_last ]) )
627 {
628 vg_console.history_last = ( vg_console.history_last + 1) %
629 vg_list_size(vg_console.history );
630 vg_console.history_count =
631 VG_MIN( vg_list_size( vg_console.history ),
632 vg_console.history_count + 1 );
633 strcpy( vg_console.history[ vg_console.history_last ],
634 vg_console.input );
635 }
636
637 vg_console.history_pos = -1;
638 vg_execute_console_input( vg_console.input );
639 console_move_cursor( &vg_console.cursor_user,
640 &vg_console.cursor_pos, -10000, 1 );
641 vg_console.input[0] = '\0';
642 }
643
644 VG_STATIC void console_proc_key( SDL_Keysym ev )
645 {
646 /* Open / close console */
647 if( ev.sym == SDLK_BACKQUOTE )
648 {
649 vg_console.enabled = !vg_console.enabled;
650
651 if( vg_console.enabled )
652 SDL_StartTextInput();
653 else
654 SDL_StopTextInput();
655 }
656
657 if( !vg_console.enabled ) return;
658
659 struct console_mapping
660 {
661 u16 mod;
662 SDL_Keycode key;
663
664 void (*handler)(void);
665 }
666 mappings[] =
667 {
668 { 0, SDLK_LEFT, _console_left },
669 { KMOD_SHIFT, SDLK_LEFT, _console_left_select },
670 { 0, SDLK_RIGHT, _console_right },
671 { KMOD_SHIFT, SDLK_RIGHT, _console_right_select },
672 { 0, SDLK_DOWN, _console_down },
673 { 0, SDLK_UP, _console_up },
674 { 0, SDLK_BACKSPACE, _console_backspace },
675 { 0, SDLK_DELETE, _console_delete },
676 { 0, SDLK_HOME, _console_home },
677 { KMOD_SHIFT, SDLK_HOME, _console_home_select },
678 { 0, SDLK_END, _console_end },
679 { KMOD_SHIFT, SDLK_END, _console_end_select },
680 { KMOD_CTRL, SDLK_a, _console_select_all },
681 { KMOD_CTRL, SDLK_c, console_to_clipboard },
682 { KMOD_CTRL, SDLK_x, _console_cut },
683 { KMOD_CTRL, SDLK_v, console_clipboard_paste },
684 { 0, SDLK_RETURN, _console_enter }
685 };
686
687 SDL_Keymod mod = ev.mod & (KMOD_SHIFT|KMOD_CTRL|KMOD_ALT);
688
689 for( int i=0; i<vg_list_size( mappings ); i++ )
690 {
691 struct console_mapping *mapping = &mappings[i];
692
693 if( mapping->key == ev.sym )
694 {
695 if( mapping->mod == 0 )
696 {
697 if( mod == 0 )
698 {
699 mapping->handler();
700 return;
701 }
702 }
703 else if( (mod & mapping->mod) == mapping->mod )
704 {
705 mapping->handler();
706 return;
707 }
708 }
709 }
710 }
711
712 VG_STATIC void console_proc_utf8( const char *text )
713 {
714 const char *ptr = text;
715
716 while( *ptr )
717 {
718 if( *ptr != '`' )
719 console_put_char( *ptr );
720 ptr ++;
721 }
722 }
723
724 #endif /* VG_CONSOLE_H */