poll functions
[vg.git] / 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 #ifndef VG_GAME
7 #define VG_GAME
8 #endif
9
10 #include "vg/vg_ui.h"
11 #include "vg/vg_log.h"
12
13 typedef struct vg_var vg_var;
14 typedef struct vg_cmd vg_cmd;
15
16 struct vg_console
17 {
18 struct vg_var
19 {
20 void *data;
21 const char *name;
22
23 enum vg_var_dtype
24 {
25 k_var_dtype_i32,
26 k_var_dtype_u32,
27 k_var_dtype_f32
28 }
29 data_type;
30
31 union
32 {
33 struct
34 {
35 int min, max, clamp;
36 }
37 opt_i32;
38
39 struct
40 {
41 float min, max;
42 int clamp;
43 }
44 opt_f32;
45 };
46
47 int persistent; /* Should this var be stored to cfg/auto.conf? */
48 }
49 vars[ 32 ];
50
51 struct vg_cmd
52 {
53 int (*function)( int argc, char const *argv[] );
54 void (*poll_suggest)( int argc, char const *argv[] );
55 const char *name;
56 }
57 functions[ 32 ];
58
59 struct
60 {
61 const char *str;
62 int len;
63
64 u32 lev_score;
65 }
66 suggestions[12];
67 u32 suggestion_count;
68 int suggestion_select,
69 suggestion_pastepos,
70 suggestion_maxlen;
71
72 u32 var_count, function_count;
73
74 char input[96],
75 input_copy[96];
76 int cursor_user, cursor_pos, string_length;
77
78 char history[32][96];
79 int history_last, history_pos, history_count;
80
81 int enabled;
82 }
83 vg_console;
84
85 VG_STATIC void vg_var_push( struct vg_var cv );
86 VG_STATIC void vg_function_push( struct vg_cmd cmd );
87
88 VG_STATIC void _vg_console_draw( void );
89 void _vg_console_println( const char *str );
90 VG_STATIC int _vg_console_list( int argc, char const *argv[] );
91 VG_STATIC void _vg_console_init(void);
92 VG_STATIC void _vg_console_write_persistent(void);
93 VG_STATIC void _vg_console_free(void);
94 VG_STATIC void vg_execute_console_input( const char *cmd );
95
96 /*
97 * Console interface
98 */
99 VG_STATIC void console_make_selection( int* start, int* end );
100 VG_STATIC void console_move_cursor( int* cursor0, int* cursor1,
101 int dir, int snap_together );
102 VG_STATIC int console_makeroom( int datastart, int length );
103 VG_STATIC int console_delete_char( int direction );
104 VG_STATIC void console_to_clipboard(void);
105 VG_STATIC void console_clipboard_paste(void);
106 VG_STATIC void console_put_char( char c );
107 VG_STATIC void console_history_get( char* buf, int entry_num );
108 VG_STATIC int _vg_console_enabled(void);
109 VG_STATIC void console_proc_key( SDL_Keysym ev );
110
111 /*
112 * Implementation
113 */
114 VG_STATIC int _vg_console_enabled(void)
115 {
116 return vg_console.enabled;
117 }
118
119 VG_STATIC void vg_var_push( vg_var cv )
120 {
121 if( vg_console.var_count > vg_list_size(vg_console.vars) )
122 vg_fatal_exit_loop( "Too many vars registered" );
123
124 vg_info( "Console variable '%s' registered\n", cv.name );
125 vg_console.vars[ vg_console.var_count ++ ] = cv;
126 }
127
128 VG_STATIC void vg_function_push( struct vg_cmd cmd )
129 {
130 if( vg_console.function_count > vg_list_size(vg_console.functions) )
131 vg_fatal_exit_loop( "Too many functions registered" );
132
133 vg_console.functions[ vg_console.function_count ++ ] = cmd;
134 }
135
136 VG_STATIC void _vg_console_draw( void )
137 {
138 if( !vg_console.enabled )
139 return;
140
141 SDL_AtomicLock( &log_print_sl );
142
143 int ptr = vg_log.buffer_line_current;
144 int const fh = 14,
145 log_lines = 32;
146 int console_lines = VG_MIN( log_lines, vg_log.buffer_line_count );
147
148 vg_uictx.cursor[0] = 0;
149 vg_uictx.cursor[1] = 0;
150 vg_uictx.cursor[3] = log_lines*fh;
151 ui_fill_x();
152
153 /*
154 * log
155 */
156 ui_new_node();
157 {
158 ui_fill_rect( vg_uictx.cursor, 0x77181818 );
159
160 vg_uictx.cursor[3] = fh;
161 ui_align_bottom();
162
163 for( int i=0; i<console_lines; i ++ )
164 {
165 ptr --;
166
167 if( ptr < 0 )
168 ptr = vg_list_size( vg_log.buffer )-1;
169
170 ui_text( vg_uictx.cursor, vg_log.buffer[ptr], 1, 0 );
171 vg_uictx.cursor[1] -= fh;
172 }
173
174 }
175 ui_end_down();
176
177
178 /* Input area */
179 vg_uictx.cursor[1] += 2;
180 vg_uictx.cursor[3] = fh;
181
182 ui_new_node();
183 {
184 ui_fill_rect( vg_uictx.cursor, 0x77111111 );
185 ui_text( vg_uictx.cursor, vg_console.input, 1, 0 );
186
187 int start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user ),
188 end = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
189
190 vg_uictx.cursor[0] = start * UI_GLYPH_SPACING_X;
191 vg_uictx.cursor[2] = (start == end? 0.5f: (float)(end-start))
192 * (float)UI_GLYPH_SPACING_X;
193
194 ui_fill_rect( vg_uictx.cursor, 0x66ffffff );
195 }
196 ui_end_down();
197
198
199 /* suggestions */
200 if( vg_console.suggestion_count )
201 {
202 vg_uictx.cursor[0] += UI_GLYPH_SPACING_X * vg_console.suggestion_pastepos;
203 vg_uictx.cursor[1] += 2;
204 vg_uictx.cursor[3] = vg_console.suggestion_count * fh;
205 vg_uictx.cursor[2] = UI_GLYPH_SPACING_X * vg_console.suggestion_maxlen;
206
207 ui_new_node();
208 {
209 ui_fill_rect( vg_uictx.cursor, 0x77040404 );
210
211 vg_uictx.cursor[3] = fh;
212 for( int i=0; i<vg_console.suggestion_count; i ++ )
213 {
214 if( i == vg_console.suggestion_select )
215 ui_fill_rect( vg_uictx.cursor, 0x66a0e508 );
216
217 ui_text( vg_uictx.cursor, vg_console.suggestions[i].str, 1, 0 );
218 vg_uictx.cursor[1] += fh;
219 }
220 }
221 ui_end_down();
222 }
223
224 SDL_AtomicUnlock( &log_print_sl );
225 }
226
227 VG_STATIC int _vg_console_list( int argc, char const *argv[] )
228 {
229 for( int i=0; i<vg_console.function_count; i ++ )
230 {
231 struct vg_cmd *cmd = &vg_console.functions[ i ];
232 vg_info( "* %s\n", cmd->name );
233 }
234
235 for( int i=0; i<vg_console.var_count; i ++ )
236 {
237 struct vg_var *cv = &vg_console.vars[ i ];
238 vg_info( "%s\n", cv->name );
239 }
240
241 return 0;
242 }
243
244 int _test_break( int argc, const char *argv[] )
245 {
246 vg_fatal_exit_loop( "Test crash from main, after loading (console)" );
247 return 0;
248 }
249
250 VG_STATIC void _vg_console_init(void)
251 {
252 vg_function_push( (struct vg_cmd)
253 {
254 .name = "list",
255 .function = _vg_console_list
256 });
257
258 vg_function_push( (struct vg_cmd)
259 {
260 .name = "crash",
261 .function = _test_break
262 });
263 }
264
265 VG_STATIC void vg_console_load_autos(void)
266 {
267 /* Read and exec persistent commands */
268 FILE *fp = fopen( "cfg/auto.conf", "r" );
269 if( fp )
270 {
271 char line[256];
272
273 while( fgets( line, sizeof( line ), fp ) )
274 {
275 line[ strcspn( line, "\r\n#" ) ] = 0x00;
276
277 if( line[0] != 0x00 )
278 {
279 vg_execute_console_input( line );
280 }
281 }
282
283 fclose( fp );
284 }
285 }
286
287 VG_STATIC void _vg_console_write_persistent(void)
288 {
289 FILE *fp = fopen( "cfg/auto.conf", "w" );
290
291 for( int i=0; i<vg_console.var_count; i ++ )
292 {
293 struct vg_var *cv = &vg_console.vars[i];
294
295 if( cv->persistent )
296 {
297 switch( cv->data_type )
298 {
299 case k_var_dtype_i32:
300 fprintf( fp, "%s %d\n", cv->name, *(i32 *)(cv->data) );
301 break;
302 case k_var_dtype_u32:
303 fprintf( fp, "%s %u\n", cv->name, *(u32 *)(cv->data) );
304 break;
305 case k_var_dtype_f32:
306 fprintf( fp, "%s %.5f\n", cv->name, *(float *)(cv->data ) );
307 break;
308 }
309 }
310 }
311
312 fclose( fp );
313 }
314
315 VG_STATIC void _vg_console_free(void)
316 {
317 _vg_console_write_persistent();
318 }
319
320 /*
321 * splits src into tokens and fills out args as pointers to those tokens
322 * returns number of tokens
323 * dst must be as long as src
324 */
325 VG_STATIC int vg_console_tokenize( const char *src, char *dst,
326 const char *args[8] )
327 {
328 int arg_count = 0,
329 in_token = 0;
330
331 for( int i=0; 1; i ++ )
332 {
333 if( src[i] )
334 {
335 if( src[i] == ' ' || src[i] == '\t' )
336 {
337 if( in_token )
338 dst[i] = '\0';
339
340 in_token = 0;
341
342 if( arg_count == 8 )
343 break;
344 }
345 else
346 {
347 dst[i] = src[i];
348
349 if( !in_token )
350 {
351 args[ arg_count ++ ] = &dst[i];
352 in_token = 1;
353 }
354 }
355 }
356 else
357 {
358 dst[i] = '\0';
359 break;
360 }
361 }
362
363 return arg_count;
364 }
365
366 VG_STATIC vg_var *vg_console_match_var( const char *kw )
367 {
368 for( int i=0; i<vg_console.var_count; i ++ )
369 {
370 struct vg_var *cv = &vg_console.vars[ i ];
371 if( !strcmp( cv->name, kw ) )
372 {
373 return cv;
374 }
375 }
376
377 return NULL;
378 }
379
380 VG_STATIC vg_cmd *vg_console_match_cmd( const char *kw )
381 {
382 for( int i=0; i<vg_console.function_count; i ++ )
383 {
384 struct vg_cmd *cmd = &vg_console.functions[ i ];
385 if( !strcmp( cmd->name, kw ) )
386 {
387 return cmd;
388 }
389 }
390
391 return NULL;
392 }
393
394 VG_STATIC void vg_execute_console_input( const char *cmd )
395 {
396 char temp[512];
397 char const *args[8];
398 int arg_count = vg_console_tokenize( cmd, temp, args );
399
400 if( arg_count == 0 )
401 return;
402
403 int data_int;
404 float data_float;
405
406 vg_var *cv = vg_console_match_var( args[0] );
407 vg_cmd *fn = vg_console_match_cmd( args[0] );
408
409 assert( !(cv && fn) );
410
411 if( cv )
412 {
413 /* Cvar Matched, try get value */
414 if( arg_count >= 2 )
415 {
416 if( (cv->data_type == k_var_dtype_u32) ||
417 (cv->data_type == k_var_dtype_i32) )
418 {
419 data_int = atoi( args[1] );
420
421 *((int *)cv->data) = cv->opt_i32.clamp?
422 VG_MIN( VG_MAX(data_int, cv->opt_i32.min), cv->opt_i32.max ):
423 data_int;
424 }
425 else if( cv->data_type == k_var_dtype_f32 )
426 {
427 data_float = atof( args[1] );
428 *((float *)cv->data) = cv->opt_f32.clamp?
429 vg_minf( vg_maxf( data_float, cv->opt_f32.min),
430 cv->opt_f32.max ):
431 data_float;
432 }
433 }
434 else
435 {
436 if( cv->data_type == k_var_dtype_i32 )
437 vg_info( "= %d\n", *((int *)cv->data) );
438 else if( cv->data_type == k_var_dtype_u32 )
439 vg_info( "= %u\n", *((u32 *)cv->data) );
440 else if( cv->data_type == k_var_dtype_f32 )
441 vg_info( "= %.4f\n", *((float *)cv->data) );
442 }
443
444 return;
445 }
446 else if( fn )
447 {
448 fn->function( arg_count-1, args+1 );
449 return;
450 }
451
452 vg_error( "No command/var named '%s'. Use 'list' to view all\n", args[0] );
453 }
454
455 u32 str_lev_distance( const char *s1, const char *s2 )
456 {
457 u32 m = strlen( s1 ),
458 n = strlen( s2 );
459
460 if( m==0 ) return n;
461 if( n==0 ) return m;
462
463 assert( n+1 <= 256 );
464
465 u32 costs[ 256 ];
466
467 for( u32 k=0; k<=n; k++ )
468 costs[k] = k;
469
470 u32 i = 0;
471 for( u32 i=0; i<m; i++ )
472 {
473 costs[0] = i+1;
474
475 u32 corner = i;
476
477 for( u32 j=0; j<n; j++ )
478 {
479 u32 upper = costs[j+1];
480
481 if( s1[i] == s2[j] )
482 costs[ j+1 ] = corner;
483 else
484 {
485 u32 t = (upper < corner)? upper: corner;
486 costs[j+1] = ((costs[j] < t)? costs[j]: t) + 1;
487 }
488
489 corner = upper;
490 }
491 }
492
493 return costs[n];
494 }
495
496 u32 str_lcs( const char *s1, const char *s2 )
497 {
498 u32 m = VG_MIN( 31, strlen( s1 ) ),
499 n = VG_MIN( 31, strlen( s2 ) );
500
501 int suff[32][32],
502 result = 0;
503
504 for( int i=0; i<=m; i++ )
505 {
506 for( int j=0; j<=n; j++ )
507 {
508 if( i == 0 || j == 0 )
509 suff[i][j] = 0;
510 else if( s1[i-1] == s2[j-1] )
511 {
512 suff[i][j] = suff[i-1][j-1] + 1;
513 result = VG_MAX( result, suff[i][j] );
514 }
515 else
516 suff[i][j] = 0;
517 }
518 }
519
520 return result;
521 }
522
523 /* str must not fuckoff ever! */
524 VG_STATIC void console_suggest_score_text( const char *str, const char *input,
525 int minscore )
526 {
527 /* filter duplicates */
528 for( int i=0; i<vg_console.suggestion_count; i++ )
529 if( !strcmp( vg_console.suggestions[i].str, str ) )
530 return;
531
532 /* calc score */
533 u32 score = str_lcs( str, input );
534
535 if( score < minscore )
536 return;
537
538 int best_pos = vg_console.suggestion_count;
539 for( int j=best_pos-1; j>=0; j -- )
540 if( score > vg_console.suggestions[j].lev_score )
541 best_pos = j;
542
543 /* insert if good score */
544 if( best_pos < vg_list_size( vg_console.suggestions ) )
545 {
546 int start = VG_MIN( vg_console.suggestion_count,
547 vg_list_size( vg_console.suggestions )-1 );
548 for( int j=start; j>best_pos; j -- )
549 vg_console.suggestions[j] = vg_console.suggestions[j-1];
550
551 vg_console.suggestions[ best_pos ].str = str;
552 vg_console.suggestions[ best_pos ].len = strlen( str );
553 vg_console.suggestions[ best_pos ].lev_score = score;
554
555 if( vg_console.suggestion_count <
556 vg_list_size( vg_console.suggestions ) )
557 vg_console.suggestion_count ++;
558 }
559 }
560
561 VG_STATIC void console_update_suggestions(void)
562 {
563 vg_console.suggestion_count = 0;
564 vg_console.suggestion_select = -1;
565 vg_console.suggestion_maxlen = 0;
566
567 /*
568 * - must be typing something
569 * - must be at the end
570 * - prev char must not be a whitespace
571 * - cursors should match
572 */
573
574 if( vg_console.cursor_pos == 0 )
575 return;
576
577 if( vg_console.cursor_pos != vg_console.cursor_user )
578 return;
579
580 if( vg_console.input[ vg_console.cursor_pos ] != '\0' )
581 return;
582
583 if( (vg_console.input[ vg_console.cursor_pos -1 ] == ' ') ||
584 (vg_console.input[ vg_console.cursor_pos -1 ] == '\t') )
585 return;
586
587 char temp[128];
588 const char *args[8];
589
590 int token_count = vg_console_tokenize( vg_console.input, temp, args );
591
592 vg_console.suggestion_pastepos = args[token_count-1]-temp;
593
594 /* Score all our commands and cvars */
595 if( token_count == 1 )
596 {
597 for( int i=0; i<vg_console.var_count; i++ )
598 {
599 vg_var *cvar = &vg_console.vars[i];
600 console_suggest_score_text( cvar->name, args[0], 1 );
601 }
602
603 for( int i=0; i<vg_console.function_count; i++ )
604 {
605 vg_cmd *cmd = &vg_console.functions[i];
606 console_suggest_score_text( cmd->name, args[0], 1 );
607 }
608 }
609 else
610 {
611 vg_cmd *cmd = vg_console_match_cmd( args[0] );
612 vg_var *var = vg_console_match_var( args[0] );
613
614 assert( !( cmd && var ) );
615
616 if( cmd )
617 if( cmd->poll_suggest )
618 cmd->poll_suggest( token_count-1, &args[1] );
619 }
620
621 /* some post processing */
622 for( int i=0; i<vg_console.suggestion_count; i++ )
623 {
624 vg_console.suggestion_maxlen = VG_MAX( vg_console.suggestion_maxlen,
625 vg_console.suggestions[i].len );
626
627 if( vg_console.suggestions[i].lev_score <
628 vg_console.suggestions[0].lev_score/2 )
629 {
630 vg_console.suggestion_count = i;
631 return;
632 }
633 }
634 }
635
636 /*
637 * Console Interface
638 */
639 VG_STATIC void console_make_selection( int* start, int* end )
640 {
641 *start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user );
642 *end = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
643 }
644
645 VG_STATIC void console_move_cursor( int* cursor0, int* cursor1,
646 int dir, int snap_together )
647 {
648 *cursor0 = VG_MAX( 0, vg_console.cursor_user + dir );
649 *cursor0 =
650 VG_MIN(
651 VG_MIN( vg_list_size(vg_console.input)-1, strlen( vg_console.input )),
652 *cursor0 );
653
654 if( snap_together )
655 *cursor1 = *cursor0;
656 }
657
658 VG_STATIC int console_makeroom( int datastart, int length )
659 {
660 int move_to = VG_MIN( datastart+length, vg_list_size( vg_console.input )-1 );
661 int move_amount = strlen( vg_console.input )-datastart;
662 int move_end =
663 VG_MIN( move_to+move_amount, vg_list_size( vg_console.input )-1 );
664 move_amount = move_end-move_to;
665
666 if( move_amount )
667 memmove( &vg_console.input[ move_to ],
668 &vg_console.input[ datastart ],
669 move_end-move_to );
670
671 vg_console.input[ move_end ] = '\0';
672
673 return VG_MIN( length, vg_list_size( vg_console.input )-datastart-1 );
674 }
675
676 VG_STATIC int console_delete_char( int direction )
677 {
678 int start, end;
679 console_make_selection( &start, &end );
680
681 /* There is no selection */
682 if( !(end-start) )
683 {
684 if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_console.input ) );
685 else if( direction == -1 ) start = VG_MAX( start-1, 0 );
686 }
687
688 /* Still no selction, no need to do anything */
689 if( !(end-start) )
690 return start;
691
692 /* Copy the end->terminator to start */
693 int remaining_length = strlen( vg_console.input )+1-end;
694 memmove( &vg_console.input[ start ],
695 &vg_console.input[ end ],
696 remaining_length );
697 return start;
698 }
699
700 VG_STATIC void console_to_clipboard(void)
701 {
702 int start, end;
703 console_make_selection( &start, &end );
704 char buffer[512];
705
706 if( end-start )
707 {
708 memcpy( buffer, &vg_console.input[ start ], end-start );
709 buffer[ end-start ] = 0x00;
710 SDL_SetClipboardText( buffer );
711 }
712 }
713
714 VG_STATIC void console_clipboard_paste(void)
715 {
716 if( !SDL_HasClipboardText() )
717 return;
718
719 char *text = SDL_GetClipboardText();
720
721 if( !text )
722 return;
723
724 int datastart = console_delete_char( 0 );
725 int length = strlen( text );
726 int cpylength = console_makeroom( datastart, length );
727
728 memcpy( vg_console.input + datastart, text, cpylength);
729 console_move_cursor( &vg_console.cursor_user,
730 &vg_console.cursor_pos, cpylength, 1 );
731 SDL_free( text );
732
733 console_update_suggestions();
734 }
735
736 VG_STATIC void console_put_char( char c )
737 {
738 if( !vg_console.enabled )
739 return;
740
741 vg_console.cursor_user = console_delete_char(0);
742
743 if( console_makeroom( vg_console.cursor_user, 1 ) )
744 vg_console.input[ vg_console.cursor_user ] = c;
745
746 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, 1, 1 );
747 }
748
749 VG_STATIC void console_history_get( char* buf, int entry_num )
750 {
751 if( !vg_console.history_count )
752 return;
753
754 int offset = VG_MIN( entry_num, vg_console.history_count -1 ),
755 pick = (vg_console.history_last - offset) %
756 vg_list_size( vg_console.history );
757 strcpy( buf, vg_console.history[ pick ] );
758 }
759
760 /* Receed secondary cursor */
761 VG_STATIC void _console_left_select(void)
762 {
763 console_move_cursor( &vg_console.cursor_user, NULL, -1, 0 );
764 }
765
766 /* Match and receed both cursors */
767 VG_STATIC void _console_left(void)
768 {
769 int cursor_diff = vg_console.cursor_pos - vg_console.cursor_user? 0: 1;
770
771 console_move_cursor( &vg_console.cursor_user,
772 &vg_console.cursor_pos, -cursor_diff, 1 );
773 }
774
775 VG_STATIC void _console_right_select(void)
776 {
777 console_move_cursor( &vg_console.cursor_user, NULL, 1, 0 );
778 }
779
780 VG_STATIC void _console_right(void)
781 {
782 int cursor_diff = vg_console.cursor_pos - vg_console.cursor_user? 0: 1;
783
784 console_move_cursor( &vg_console.cursor_user,
785 &vg_console.cursor_pos, +cursor_diff, 1 );
786 }
787
788 VG_STATIC void _console_down(void)
789 {
790 vg_console.history_pos = VG_MAX( 0, vg_console.history_pos-1 );
791 console_history_get( vg_console.input, vg_console.history_pos );
792
793 console_move_cursor( &vg_console.cursor_user,
794 &vg_console.cursor_pos,
795 vg_list_size(vg_console.input)-1, 1 );
796 }
797
798 VG_STATIC void _console_up(void)
799 {
800 vg_console.history_pos = VG_MAX
801 (
802 0,
803 VG_MIN
804 (
805 vg_console.history_pos+1,
806 VG_MIN
807 (
808 vg_list_size( vg_console.history ),
809 vg_console.history_count - 1
810 )
811 )
812 );
813
814 console_history_get( vg_console.input, vg_console.history_pos );
815 console_move_cursor( &vg_console.cursor_user,
816 &vg_console.cursor_pos,
817 vg_list_size(vg_console.input)-1, 1);
818 }
819
820 VG_STATIC void _console_backspace(void)
821 {
822 vg_console.cursor_user = console_delete_char( -1 );
823 vg_console.cursor_pos = vg_console.cursor_user;
824
825 console_update_suggestions();
826 }
827
828 VG_STATIC void _console_delete(void)
829 {
830 vg_console.cursor_user = console_delete_char( 1 );
831 vg_console.cursor_pos = vg_console.cursor_user;
832
833 console_update_suggestions();
834 }
835
836 VG_STATIC void _console_home_select(void)
837 {
838 console_move_cursor( &vg_console.cursor_user, NULL, -10000, 0 );
839 }
840
841 VG_STATIC void _console_home(void)
842 {
843 console_move_cursor( &vg_console.cursor_user,
844 &vg_console.cursor_pos, -10000, 1 );
845 }
846
847 VG_STATIC void _console_end_select(void)
848 {
849 console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0 );
850 }
851
852 VG_STATIC void _console_end(void)
853 {
854 console_move_cursor( &vg_console.cursor_user,
855 &vg_console.cursor_pos,
856 vg_list_size(vg_console.input)-1, 1 );
857 }
858
859 VG_STATIC void _console_select_all(void)
860 {
861 console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0);
862 console_move_cursor( &vg_console.cursor_pos, NULL, -10000, 0);
863 }
864
865 VG_STATIC void _console_cut(void)
866 {
867 console_to_clipboard();
868 vg_console.cursor_user = console_delete_char(0);
869 vg_console.cursor_pos = vg_console.cursor_user;
870 }
871
872 VG_STATIC void _console_enter(void)
873 {
874 if( !strlen( vg_console.input ) )
875 return;
876
877 vg_info( "%s\n", vg_console.input );
878
879 if( strcmp( vg_console.input,
880 vg_console.history[ vg_console.history_last ]) )
881 {
882 vg_console.history_last = ( vg_console.history_last + 1) %
883 vg_list_size(vg_console.history );
884 vg_console.history_count =
885 VG_MIN( vg_list_size( vg_console.history ),
886 vg_console.history_count + 1 );
887 strcpy( vg_console.history[ vg_console.history_last ],
888 vg_console.input );
889 }
890
891 vg_console.history_pos = -1;
892 vg_execute_console_input( vg_console.input );
893 console_move_cursor( &vg_console.cursor_user,
894 &vg_console.cursor_pos, -10000, 1 );
895 vg_console.input[0] = '\0';
896
897 console_update_suggestions();
898 }
899
900 /*
901 * Suggestion controls
902 */
903 VG_STATIC void _console_fetch_suggestion(void)
904 {
905 char *target = &vg_console.input[ vg_console.suggestion_pastepos ];
906
907 if( vg_console.suggestion_select == -1 )
908 {
909 strcpy( target, vg_console.input_copy );
910 console_move_cursor( &vg_console.cursor_user,
911 &vg_console.cursor_pos, 10000, 1 );
912 }
913 else
914 {
915 strncpy( target,
916 vg_console.suggestions[ vg_console.suggestion_select ].str,
917 vg_list_size( vg_console.input )-1 );
918
919 console_move_cursor( &vg_console.cursor_user,
920 &vg_console.cursor_pos, 10000, 1 );
921 console_put_char( ' ' );
922 }
923 }
924
925 VG_STATIC void _console_suggest_store_normal(void)
926 {
927 if( vg_console.suggestion_select == -1 )
928 {
929 char *target = &vg_console.input[ vg_console.suggestion_pastepos ];
930 strcpy( vg_console.input_copy, target );
931 }
932 }
933
934 VG_STATIC void _console_suggest_next(void)
935 {
936 if( vg_console.suggestion_count )
937 {
938 _console_suggest_store_normal();
939
940 vg_console.suggestion_select ++;
941
942 if( vg_console.suggestion_select >= vg_console.suggestion_count )
943 vg_console.suggestion_select = -1;
944
945 _console_fetch_suggestion();
946 }
947 }
948
949 VG_STATIC void _console_suggest_prev(void)
950 {
951 if( vg_console.suggestion_count )
952 {
953 _console_suggest_store_normal();
954
955 vg_console.suggestion_select --;
956
957 if( vg_console.suggestion_select < -1 )
958 vg_console.suggestion_select = vg_console.suggestion_count-1;
959
960 _console_fetch_suggestion();
961 }
962 }
963
964 /*
965 * Handles binds
966 */
967 VG_STATIC void console_proc_key( SDL_Keysym ev )
968 {
969 /* Open / close console */
970 if( ev.sym == SDLK_BACKQUOTE )
971 {
972 vg_console.enabled = !vg_console.enabled;
973
974 if( vg_console.enabled )
975 SDL_StartTextInput();
976 else
977 SDL_StopTextInput();
978 }
979
980 if( !vg_console.enabled ) return;
981
982 struct console_mapping
983 {
984 u16 mod;
985 SDL_Keycode key;
986
987 void (*handler)(void);
988 }
989 mappings[] =
990 {
991 { 0, SDLK_LEFT, _console_left },
992 { KMOD_SHIFT, SDLK_LEFT, _console_left_select },
993 { 0, SDLK_RIGHT, _console_right },
994 { KMOD_SHIFT, SDLK_RIGHT, _console_right_select },
995 { 0, SDLK_DOWN, _console_down },
996 { 0, SDLK_UP, _console_up },
997 { 0, SDLK_BACKSPACE, _console_backspace },
998 { 0, SDLK_DELETE, _console_delete },
999 { 0, SDLK_HOME, _console_home },
1000 { KMOD_SHIFT, SDLK_HOME, _console_home_select },
1001 { 0, SDLK_END, _console_end },
1002 { KMOD_SHIFT, SDLK_END, _console_end_select },
1003 { KMOD_CTRL, SDLK_a, _console_select_all },
1004 { KMOD_CTRL, SDLK_c, console_to_clipboard },
1005 { KMOD_CTRL, SDLK_x, _console_cut },
1006 { KMOD_CTRL, SDLK_v, console_clipboard_paste },
1007 { 0, SDLK_RETURN, _console_enter },
1008 { KMOD_CTRL, SDLK_n, _console_suggest_next },
1009 { KMOD_CTRL, SDLK_p, _console_suggest_prev }
1010 };
1011
1012 SDL_Keymod mod = 0;
1013
1014 if( ev.mod & KMOD_SHIFT )
1015 mod |= KMOD_SHIFT;
1016
1017 if( ev.mod & KMOD_CTRL )
1018 mod |= KMOD_CTRL;
1019
1020 if( ev.mod & KMOD_ALT )
1021 mod |= KMOD_ALT;
1022
1023 for( int i=0; i<vg_list_size( mappings ); i++ )
1024 {
1025 struct console_mapping *mapping = &mappings[i];
1026
1027 if( mapping->key == ev.sym )
1028 {
1029 if( mapping->mod == 0 )
1030 {
1031 if( mod == 0 )
1032 {
1033 mapping->handler();
1034 return;
1035 }
1036 }
1037 else if( (mod & mapping->mod) == mapping->mod )
1038 {
1039 mapping->handler();
1040 return;
1041 }
1042 }
1043 }
1044 }
1045
1046 /*
1047 * Callback for text entry mode
1048 */
1049 VG_STATIC void console_proc_utf8( const char *text )
1050 {
1051 const char *ptr = text;
1052
1053 while( *ptr )
1054 {
1055 if( *ptr != '`' )
1056 console_put_char( *ptr );
1057 ptr ++;
1058 }
1059
1060 console_update_suggestions();
1061 }
1062
1063 #endif /* VG_CONSOLE_H */