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