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