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