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