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