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