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