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