48dee1a4a0fbd36d4f625fdab7c0f25dfc9da7f4
[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;
106 ui_global_ctx.cursor[2] = (start == end? 0.2f: (float)(end-start)) * (float)ui_glyph_spacing_x * (float)vg_console.scale;
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 else
182 {
183 temp[i] = cmd[i];
184
185 if( !in_token )
186 {
187 args[ arg_count ++ ] = temp + i;
188 in_token = 1;
189 }
190 }
191 }
192 else
193 {
194 temp[i] = '\0';
195 break;
196 }
197 }
198
199 if( arg_count == 0 )
200 return;
201
202 int data_int;
203
204 for( int i = 0; i < arrlen( vg_console.convars ); i ++ )
205 {
206 struct vg_convar *cv = &vg_console.convars[ i ];
207 if( !strcmp( cv->name, args[0] ) )
208 {
209 // Cvar Matched, try get value
210 if( arg_count >= 2 )
211 {
212 switch( cv->data_type )
213 {
214 case k_convar_dtype_u32:
215 case k_convar_dtype_i32:
216
217 data_int = atoi( args[1] );
218 *((int *)cv->data) = cv->opt_i32.clamp? VG_MIN( VG_MAX(data_int, cv->opt_i32.min), cv->opt_i32.max ): data_int;
219
220 break;
221 case k_convar_dtype_f32: *((float *)cv->data) = atof( temp ); break;
222 }
223 }
224 else
225 {
226 switch( cv->data_type )
227 {
228 case k_convar_dtype_i32: vg_info( "= %d\n", *((int *)cv->data) ); break;
229 case k_convar_dtype_u32: vg_info( "= %u\n", *((u32 *)cv->data) ); break;
230 case k_convar_dtype_f32: vg_info( "= %.4f\n", *((float *)cv->data) ); break;
231 }
232 }
233
234 return;
235 }
236 }
237
238 // Try commands
239 for( int i = 0; i < arrlen( vg_console.functions ); i ++ )
240 {
241 struct vg_cmd *cmd = &vg_console.functions[ i ];
242 if( !strcmp( cmd->name, args[0] ) )
243 {
244 // Call function
245 cmd->function( arg_count-1, args+1 );
246 return;
247 }
248 }
249
250 vg_error( "No command/variable named '%s'. Use 'list' to view all\n", args[0] );
251 }
252
253 // =============================================================================================================================
254 // Console interface
255
256 static void console_make_selection( int* start, int* end )
257 {
258 *start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user );
259 *end = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
260 }
261
262 static void console_move_cursor( int* cursor0, int* cursor1, int dir, int snap_together )
263 {
264 *cursor0 = VG_MAX( 0, vg_console.cursor_user + dir );
265 *cursor0 = VG_MIN( VG_MIN( vg_list_size( vg_console.input ), strlen( vg_console.input )), *cursor0 );
266 if( snap_together )
267 *cursor1 = *cursor0;
268 }
269
270 static int console_makeroom( int datastart, int length )
271 {
272 int move_to = VG_MIN( datastart+length, vg_list_size( vg_console.input ) );
273 int move_amount = strlen( vg_console.input )-datastart;
274 int move_end = VG_MIN( move_to+move_amount, vg_list_size( vg_console.input ) );
275 move_amount = move_end-move_to;
276
277 if( move_amount )
278 memmove( &vg_console.input[ move_to ], &vg_console.input[ datastart ], move_end-move_to );
279
280 vg_console.input[ move_end ] = '\0';
281
282 return VG_MIN( length, vg_list_size( vg_console.input )-datastart );
283 }
284
285 static int console_delete_char( int direction )
286 {
287 int start, end;
288 console_make_selection( &start, &end );
289
290 // There is no selection
291 if( !(end-start) )
292 {
293 if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_console.input ) );
294 else if( direction == -1 ) start = VG_MAX( start-1, 0 );
295 }
296
297 // Still no selction, no need to do anything
298 if( !(end-start) )
299 return start;
300
301 // Copy the end->terminator to start
302 int remaining_length = strlen( vg_console.input )+1-end;
303 memmove( &vg_console.input[ start ], &vg_console.input[ end ], remaining_length );
304 return start;
305 }
306
307 static void console_to_clipboard(void)
308 {
309 int start, end;
310 console_make_selection( &start, &end );
311 char buffer[512];
312
313 if( end-start )
314 {
315 memcpy( buffer, &vg_console.input[ start ], end-start );
316 buffer[ end-start ] = 0x00;
317 glfwSetClipboardString( NULL, buffer );
318 }
319 }
320
321 static void console_clipboard_paste(void)
322 {
323 int datastart = console_delete_char(0);
324 const char* clipboard = glfwGetClipboardString(NULL);
325 int length = strlen(clipboard);
326
327 int cpylength = console_makeroom(datastart, length);
328
329 memcpy( vg_console.input + datastart, clipboard, cpylength);
330
331 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, cpylength, 1 );
332 }
333
334 static void console_put_char( char c )
335 {
336 if( !vg_console.enabled )
337 return;
338
339 vg_console.cursor_user = console_delete_char(0);
340
341 if( console_makeroom( vg_console.cursor_user, 1 ) )
342 vg_console.input[ vg_console.cursor_user ] = c;
343
344 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, 1, 1 );
345 }
346
347 static void console_history_get( char* buf, int entry_num )
348 {
349 if( !vg_console.history_count )
350 return;
351
352 int pick = (vg_console.history_last - VG_MIN( entry_num, vg_console.history_count -1 )) % vg_list_size( vg_console.history );
353 strcpy( buf, vg_console.history[ pick ] );
354 }
355
356 static void console_proc_key( GLFWwindow* ptrW, int key, int scancode, int action, int mods )
357 {
358 if( action )
359 {
360 int cursor_diff = vg_console.cursor_pos - vg_console.cursor_user? 0: 1;
361
362 if( key == GLFW_KEY_GRAVE_ACCENT )
363 {
364 vg_console.enabled = !vg_console.enabled;
365 return;
366 }
367
368 if( !vg_console.enabled )
369 return;
370
371 if( key == GLFW_KEY_LEFT )
372 {
373 if( mods & GLFW_MOD_SHIFT ) // Receed secondary cursor
374 {
375 console_move_cursor( &vg_console.cursor_user, NULL, -1, 0 );
376 }
377 else // Match and receed both cursors
378 {
379 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, -cursor_diff, 1 );
380 }
381 }
382 else if( key == GLFW_KEY_RIGHT ) // Advance secondary cursor
383 {
384 if( mods & GLFW_MOD_SHIFT )
385 {
386 console_move_cursor( &vg_console.cursor_user, NULL, 1, 0 );
387 }
388 else // Match and advance both cursors
389 {
390 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, +cursor_diff, 1 );
391 }
392 }
393 else if( key == GLFW_KEY_DOWN )
394 {
395 if( mods & GLFW_MOD_SHIFT ){}
396 else
397 {
398 vg_console.history_pos = VG_MAX( 0, vg_console.history_pos-1 );
399 console_history_get( vg_console.input, vg_console.history_pos );
400 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, vg_list_size( vg_console.input ), 1 );
401 }
402 }
403 else if( key == GLFW_KEY_UP )
404 {
405 if( mods & GLFW_MOD_SHIFT ){}
406 else
407 {
408 vg_console.history_pos = VG_MAX
409 (
410 0,
411 VG_MIN
412 (
413 vg_console.history_pos+1,
414 VG_MIN
415 (
416 vg_list_size( vg_console.history ),
417 vg_console.history_count - 1
418 )
419 )
420 );
421
422 console_history_get( vg_console.input, vg_console.history_pos );
423 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, vg_list_size( vg_console.input ), 1);
424 }
425 }
426 else if( key == GLFW_KEY_BACKSPACE ) // Lookback delete
427 {
428 vg_console.cursor_user = console_delete_char( -1 );
429 vg_console.cursor_pos = vg_console.cursor_user;
430 }
431 else if( key == GLFW_KEY_DELETE ) // Lookforward delete
432 {
433 vg_console.cursor_user = console_delete_char( 1 );
434 vg_console.cursor_pos = vg_console.cursor_user;
435 }
436 else if( key == GLFW_KEY_HOME ) // Home key
437 {
438 if( mods & GLFW_MOD_SHIFT )
439 console_move_cursor( &vg_console.cursor_user, NULL, -10000, 0 );
440 else
441 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, -10000, 1 );
442 }
443 else if( key == GLFW_KEY_END ) // End key
444 {
445 if( mods & GLFW_MOD_SHIFT )
446 console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0 );
447 else
448 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, vg_list_size( vg_console.input ), 1 );
449 }
450 else if( key == GLFW_KEY_A )
451 {
452 if( mods & GLFW_MOD_CONTROL ) // Select all
453 {
454 console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0);
455 console_move_cursor( &vg_console.cursor_pos, NULL, -10000, 0);
456 }
457 }
458 else if( key == GLFW_KEY_C ) // Copy
459 {
460 if( mods & GLFW_MOD_CONTROL )
461 {
462 console_to_clipboard();
463 }
464 }
465 else if( key == GLFW_KEY_X ) // Cut
466 {
467 if( mods & GLFW_MOD_CONTROL )
468 {
469 console_to_clipboard();
470 vg_console.cursor_user = console_delete_char(0);
471 vg_console.cursor_pos = vg_console.cursor_user;
472 }
473 }
474 else if( key == GLFW_KEY_V ) // Paste
475 {
476 if( mods & GLFW_MOD_CONTROL )
477 {
478 console_clipboard_paste();
479 }
480 }
481 else if( key == GLFW_KEY_ENTER )
482 {
483 if( !strlen( vg_console.input ) )
484 return;
485
486 vg_info( "%s\n", vg_console.input );
487
488 if( strcmp( vg_console.input, vg_console.history[ vg_console.history_last ]) )
489 {
490 vg_console.history_last = ( vg_console.history_last + 1) % vg_list_size(vg_console.history );
491 vg_console.history_count = VG_MIN( vg_list_size( vg_console.history ), vg_console.history_count + 1 );
492 strcpy( vg_console.history[ vg_console.history_last ], vg_console.input );
493 }
494
495 vg_console.history_pos = -1;
496 execute_console_input( vg_console.input );
497 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, -10000, 1 );
498 vg_console.input[0] = '\0';
499 }
500 }
501 }
502
503 // Handle an OS based input of UTF32 character from the keyboard or such
504 static void console_proc_wchar( GLFWwindow* ptrW, u32 uWchar )
505 {
506 //LOG_INFO("Recieved wchar: %u\n", uWchar);
507 if( uWchar <= 0x7F && (char)uWchar != 0x60)
508 {
509 console_put_char((char)uWchar);
510 }
511 }