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