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