font source file
[vg.git] / vg_imgui.h
1 /* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved */
2
3 /*
4 * Principles:
5 *
6 * 1. layout is defined by subdividing
7 * 2. a parent node should never be resized by the content after creation
8 * 3. when the ui is in an interactive state, no controls should ever move
9 * 4. controls directly reference a memory location and use that as their
10 * unique id
11 * 5. a maximum of ONE control per memory location can be drawn at any given
12 * point.
13 */
14
15 #ifndef VG_IMGUI_H
16 #define VG_IMGUI_H
17
18 #define VG_GAME
19 #include "vg/vg.h"
20 #include "vg/vg_tex.h"
21 #include "vg/vg_shader.h"
22
23 typedef i16 ui_px;
24 typedef ui_px ui_rect[4];
25 typedef struct ui_vert ui_vert;
26
27 enum ui_axis {
28 k_ui_axis_h = 0x0u,
29 k_ui_axis_v = 0x1u,
30 };
31
32 /* Relative to cursor p0 */
33 enum ui_align
34 { /* DC BA */
35 k_ui_align_lwr = 0xff,
36 k_ui_align_left = 0x0000| 0x00,
37 k_ui_align_right = 0x0000| 0x01,
38 k_ui_align_center = 0x0000| 0x02,
39
40 k_ui_align_middle = 0x0100,
41 k_ui_align_middle_left = 0x0100| 0x00,
42 k_ui_align_middle_right = 0x0100| 0x01,
43 k_ui_align_middle_center = 0x0100| 0x02,
44
45 k_ui_align_bottom = 0x0200,
46 k_ui_align_bottom_left = 0x0200| 0x00,
47 k_ui_align_bottom_right = 0x0200| 0x01,
48 k_ui_align_bottom_center = 0x0200| 0x02,
49 };
50
51 #pragma pack(push,1)
52 struct ui_vert
53 {
54 ui_px co[2];
55 u8 uv[2];
56 u32 colour;
57 };
58 #pragma pack(pop)
59
60 enum ui_scheme_colour{
61 k_ui_bg = 0,
62 k_ui_fg = 8,
63 k_ui_hue = 16,
64 k_ui_red = 16,
65 k_ui_orange,
66 k_ui_yellow,
67 k_ui_green,
68 k_ui_aqua,
69 k_ui_blue,
70 k_ui_purple,
71 k_ui_gray,
72 k_ui_brighter = 8
73 };
74
75 static ui_px k_ui_widget_height = 28,
76 k_ui_scale = 2,
77 k_ui_padding = 8;
78
79 typedef u32 ui_scheme[8*4];
80
81 #define UI_RGB( STDHEX ) 0xff000000 |\
82 ((STDHEX&0x000000ff)<<16) |\
83 ((STDHEX&0x0000ff00) ) |\
84 ((STDHEX&0x00ff0000)>>16)
85
86 #define UI_TEXTBOX_MULTILINE 0x1
87 #define UI_TEXTBOX_WRAP 0x2
88 #define UI_TEXTBOX_AUTOFOCUS 0x4
89
90 #define UI_MODAL_OK 0x0
91 #define UI_MODAL_GOOD 0x1
92 #define UI_MODAL_BAD 0x2
93 #define UI_MODAL_WARN 0x3
94 #define UI_MODAL_TYPE_BITS 0x3
95
96 #define UI_MOUSE_LEFT (SDL_BUTTON(SDL_BUTTON_LEFT))
97 #define UI_MOUSE_RIGHT (SDL_BUTTON(SDL_BUTTON_RIGHT))
98 #define UI_MOUSE_MIDDLE (SDL_BUTTON(SDL_BUTTON_MIDDLE))
99
100 struct{
101 struct ui_vert *vertex_buffer;
102 u16 *indice_buffer;
103 u32 max_verts, max_indices,
104 cur_vert, cur_indice,
105 vert_start, indice_start;
106
107 union {
108 void *focused_control_id; /* uses the memory location of various locking
109 controls as an id */
110 char *textbuf;
111 i32 *ptr_enum;
112 };
113
114 u32 focused_control_hit;
115 enum ui_control_type{
116 k_ui_control_none,
117 k_ui_control_textbox,
118 k_ui_control_enum,
119 k_ui_control_modal
120 }
121 focused_control_type;
122
123 union{ /* controls that can be focused */
124 struct ui_textbuf{
125 int cursor_user, cursor_pos;
126 u32 len;
127 u32 flags;
128
129 struct ui_textbox_callbacks{
130 void (*enter)( char *, u32 ),
131 (*up)( char *, u32 ),
132 (*down)( char *, u32 ),
133 (*change)( char *, u32 );
134 }
135 callbacks;
136 }
137 textbox;
138
139 struct ui_enum{
140 struct ui_enum_opt{
141 i32 value;
142 const char *alias;
143 }
144 *options;
145 u32 option_count;
146 ui_rect rect;
147 }
148 _enum;
149 };
150
151 struct ui_modal{
152 const char *message;
153 u32 options;
154
155 struct ui_modal_callbacks{
156 void (*close)(u32);
157 }
158 callbacks;
159 }
160 modal;
161
162 GLuint tex_glyphs, vao, vbo, ebo;
163
164 ui_px mouse[2], mouse_click[2];
165 u32 mouse_state[2];
166 u32 ignore_input_frames;
167 int wants_mouse;
168
169 ui_rect click_fader, click_fader_end;
170 float click_fade_opacity;
171
172 ui_scheme scheme;
173
174 enum ui_cursor{
175 k_ui_cursor_default,
176 k_ui_cursor_ibeam,
177 k_ui_cursor_hand,
178 k_ui_cursor_max
179 }
180 cursor;
181
182 SDL_Cursor *cursor_map[ k_ui_cursor_max ];
183 }
184 static vg_ui = {
185 .scheme = {
186 [ k_ui_bg+0 ] = UI_RGB( 0x1d2021 ),
187 [ k_ui_bg+1 ] = UI_RGB( 0x282828 ),
188 [ k_ui_bg+2 ] = UI_RGB( 0x3c3836 ),
189 [ k_ui_bg+3 ] = UI_RGB( 0x504945 ),
190 [ k_ui_bg+4 ] = UI_RGB( 0x665c54 ),
191 [ k_ui_bg+5 ] = UI_RGB( 0x7c6f64 ),
192 [ k_ui_bg+6 ] = UI_RGB( 0x928374 ),
193 [ k_ui_bg+7 ] = UI_RGB( 0xa89984 ),
194
195 [ k_ui_fg+0 ] = UI_RGB( 0xebdbb2 ),
196 [ k_ui_fg+1 ] = UI_RGB( 0xfbf1c7 ),
197 [ k_ui_fg+2 ] = UI_RGB( 0xd5c4a1 ),
198 [ k_ui_fg+3 ] = UI_RGB( 0xbdae93 ),
199 [ k_ui_fg+4 ] = UI_RGB( 0xa89984 ),
200 [ k_ui_fg+5 ] = UI_RGB( 0x000000 ),
201 [ k_ui_fg+6 ] = UI_RGB( 0x000000 ),
202 [ k_ui_fg+7 ] = UI_RGB( 0x000000 ),
203
204 [ k_ui_red ] = UI_RGB( 0xcc241d ),
205 [ k_ui_orange ] = UI_RGB( 0xd65d0e ),
206 [ k_ui_yellow ] = UI_RGB( 0xd79921 ),
207 [ k_ui_green ] = UI_RGB( 0x98971a ),
208 [ k_ui_aqua ] = UI_RGB( 0x689d6a ),
209 [ k_ui_blue ] = UI_RGB( 0x458588 ),
210 [ k_ui_purple ] = UI_RGB( 0xb16286 ),
211 [ k_ui_gray ] = UI_RGB( 0x928374 ),
212 [ k_ui_red + k_ui_brighter ] = UI_RGB( 0xfb4934 ),
213 [ k_ui_orange + k_ui_brighter ] = UI_RGB( 0xfe8019 ),
214 [ k_ui_yellow + k_ui_brighter ] = UI_RGB( 0xfabd2f ),
215 [ k_ui_green + k_ui_brighter ] = UI_RGB( 0xb8bb26 ),
216 [ k_ui_aqua + k_ui_brighter ] = UI_RGB( 0x8ec07c ),
217 [ k_ui_blue + k_ui_brighter ] = UI_RGB( 0x83a598 ),
218 [ k_ui_purple + k_ui_brighter ] = UI_RGB( 0xd3869b ),
219 [ k_ui_gray + k_ui_brighter ] = UI_RGB( 0xa89984 ),
220 }
221 };
222
223 static struct vg_shader _shader_ui =
224 {
225 .name = "[vg] ui",
226 .link = NULL,
227 .vs =
228 {
229 .orig_file = NULL,
230 .static_src =
231 "layout (location=0) in vec2 a_co;"
232 "layout (location=1) in vec2 a_uv;"
233 "layout (location=2) in vec4 a_colour;"
234 "uniform mat3 uPv;"
235 ""
236 "out vec2 aTexCoords;"
237 "out vec4 aColour;"
238 "out vec2 aWsp;"
239 ""
240 "void main()"
241 "{"
242 "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
243 "aTexCoords = a_uv * 0.0078125;"
244 "aColour = a_colour;"
245
246 "aWsp = a_co;"
247 "}",
248 },
249 .fs =
250 {
251 .orig_file = NULL,
252 .static_src =
253 "uniform sampler2D uTexGlyphs;"
254 "out vec4 FragColor;"
255 ""
256 "in vec2 aTexCoords;"
257 "in vec4 aColour;"
258 ""
259 "in vec2 aWsp;"
260
261 "vec2 rand_hash22( vec2 p )"
262 "{"
263 "vec3 p3 = fract(vec3(p.xyx) * 213.8976123);"
264 "p3 += dot(p3, p3.yzx+19.19);"
265 "return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));"
266 "}"
267 ""
268 "void main()"
269 "{"
270 "vec4 diffuse = aColour;"
271
272 "if( diffuse.a == 0.0 )"
273 "{"
274 "diffuse.a = texture( uTexGlyphs, aTexCoords ).r;"
275 "}"
276
277 "FragColor = diffuse;"
278 "}"
279 }
280 };
281
282 static struct vg_shader _shader_ui_image =
283 {
284 .name = "[vg] ui_image",
285 .link = NULL,
286 .vs =
287 {
288 .orig_file = NULL,
289 .static_src =
290 "layout (location=0) in vec2 a_co;"
291 "layout (location=1) in vec2 a_uv;"
292 "layout (location=2) in vec4 a_colour;"
293 "uniform mat3 uPv;"
294
295 "out vec2 aTexCoords;"
296 "out vec4 aColour;"
297 "out vec2 aWsp;"
298
299 "void main()"
300 "{"
301 "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
302 "aTexCoords = a_uv * 0.0078125;"
303 "aColour = a_colour;"
304
305 "aWsp = a_co;"
306 "}",
307 },
308 .fs =
309 {
310 .orig_file = NULL,
311 .static_src =
312 "uniform sampler2D uTexImage;"
313 "out vec4 FragColor;"
314
315 "in vec2 aTexCoords;"
316 "in vec4 aColour;"
317 "in vec2 aWsp;"
318
319 "void main()"
320 "{"
321 "vec4 colour = texture( uTexImage, aTexCoords );"
322 "FragColor = colour;"
323 "}"
324 }
325 };
326 #define UI_GLYPH_SPACING_X 8
327
328 VG_STATIC void _vg_ui_init(void)
329 {
330 if( !vg_shader_compile( &_shader_ui ) ||
331 !vg_shader_compile( &_shader_ui_image ) )
332 vg_fatal_error( "Failed to compile ui shader" );
333
334 /*
335 * Vertex buffer
336 * ----------------------------------------
337 */
338
339 vg_ui.max_indices = 20000;
340 vg_ui.max_verts = 30000;
341
342 /* Generate the buffer we are gonna be drawing to */
343 glGenVertexArrays( 1, &vg_ui.vao );
344 glGenBuffers( 1, &vg_ui.vbo );
345 glGenBuffers( 1, &vg_ui.ebo );
346
347 glBindVertexArray( vg_ui.vao );
348 glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo );
349
350 glBufferData( GL_ARRAY_BUFFER,
351 vg_ui.max_verts * sizeof( struct ui_vert ),
352 NULL, GL_DYNAMIC_DRAW );
353 glBindVertexArray( vg_ui.vao );
354
355 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo );
356 glBufferData( GL_ELEMENT_ARRAY_BUFFER,
357 vg_ui.max_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW );
358
359 VG_CHECK_GL_ERR();
360
361 /* Set pointers */
362 u32 const stride = sizeof( struct ui_vert );
363
364 /* XY */
365 glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride,
366 (void *)offsetof( struct ui_vert, co ) );
367 glEnableVertexAttribArray( 0 );
368
369 /* UV */
370 glVertexAttribPointer( 1, 2, GL_UNSIGNED_BYTE, GL_FALSE, stride,
371 (void *)offsetof( struct ui_vert, uv ) );
372 glEnableVertexAttribArray( 1 );
373
374 /* COLOUR */
375 glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride,
376 (void *)offsetof( struct ui_vert, colour ) );
377 glEnableVertexAttribArray( 2 );
378
379 VG_CHECK_GL_ERR();
380
381 /* Alloc RAM default context */
382 u32 vert_size = vg_ui.max_verts*sizeof(struct ui_vert),
383 inds_size = vg_align8( vg_ui.max_indices*sizeof(u16) );
384
385 vg_ui.vertex_buffer = vg_linear_alloc( vg_mem.rtmemory, vert_size );
386 vg_ui.indice_buffer = vg_linear_alloc( vg_mem.rtmemory, inds_size );
387
388 /* font
389 * -----------------------------------------------------
390 */
391
392 /* Load default font */
393 u32 compressed[] = {
394 #include "vg/vg_pxfont_thin.h"
395 };
396
397 u32 pixels = 0, total = 256*256, data = 0;
398 u8 image[256*256];
399
400 while( pixels < total ){
401 for( int b = 31; b >= 0; b-- ){
402 image[ pixels ++ ] = (compressed[data] & (0x1u << b))? 0xffu: 0x00u;
403
404 if( pixels >= total ){
405 total = 0;
406 break;
407 }
408 }
409 data++;
410 }
411
412 #if 0
413 for( u32 y=1; y<256; y++ ){
414 for( u32 x=0; x<256; x++ ){
415 u32 above = (y-1)*256 + x,
416 below = y*256+x;
417
418 if( (image[ below ] == 0) && (image[ above ] == 255) ){
419 image[ below ] = 128;
420 }
421 }
422 }
423 #endif
424
425 glGenTextures( 1, &vg_ui.tex_glyphs );
426 glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs );
427 glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, 256, 256, 0,
428 GL_RED, GL_UNSIGNED_BYTE, image );
429
430 VG_CHECK_GL_ERR();
431 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
432 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
433 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
434 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
435
436 /*
437 * Cursors
438 * ---------------------------------------------------------------
439 */
440
441 vg_ui.cursor_map[ k_ui_cursor_default ] =
442 SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_ARROW );
443 vg_ui.cursor_map[ k_ui_cursor_hand ] =
444 SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_HAND );
445 vg_ui.cursor_map[ k_ui_cursor_ibeam ] =
446 SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_IBEAM );
447 }
448
449 enum ui_shader {
450 k_ui_shader_colour,
451 k_ui_shader_image
452 };
453
454 static void rect_copy( ui_rect a, ui_rect b )
455 {
456 for( int i=0; i<4; i++ )
457 b[i] = a[i];
458 }
459
460 VG_STATIC void ui_flush( enum ui_shader shader )
461 {
462 u32 vertex_offset = vg_ui.vert_start*sizeof(ui_vert),
463 vertex_count = vg_ui.cur_vert-vg_ui.vert_start,
464 vertex_size = vertex_count*sizeof(ui_vert),
465
466 indice_offset = vg_ui.indice_start*sizeof(u16),
467 indice_count = vg_ui.cur_indice-vg_ui.indice_start,
468 indice_size = indice_count * sizeof(u16);
469
470 if( !vertex_size || !indice_size )
471 return;
472
473 glBindVertexArray( vg_ui.vao );
474 glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo );
475 glBufferSubData( GL_ARRAY_BUFFER, vertex_offset, vertex_size,
476 vg_ui.vertex_buffer+vg_ui.vert_start );
477
478 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo );
479 glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, indice_offset, indice_size,
480 vg_ui.indice_buffer+vg_ui.indice_start );
481
482 glDisable( GL_DEPTH_TEST );
483 glEnable( GL_BLEND );
484 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
485 glBlendEquation( GL_FUNC_ADD );
486 glDisable( GL_CULL_FACE );
487
488 m3x3f view = M3X3_IDENTITY;
489 m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
490 m3x3_scale( view, (v3f){ 1.0f/((float)vg.window_x*0.5f),
491 -1.0f/((float)vg.window_y*0.5f), 1.0f } );
492
493 if( shader == k_ui_shader_colour ){
494 glUseProgram( _shader_ui.id );
495
496 glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1,
497 GL_FALSE, (float *)view );
498
499 glActiveTexture( GL_TEXTURE0 );
500 glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs );
501 glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 );
502 }
503 else if( shader == k_ui_shader_image ){
504 glUseProgram( _shader_ui_image.id );
505 glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1,
506 GL_FALSE, (float *)view );
507 glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 );
508 }
509 else
510 vg_fatal_error( "Invalid UI shader (%d)\n", shader );
511
512 glDrawElements( GL_TRIANGLES, indice_count, GL_UNSIGNED_SHORT,
513 (void *)(vg_ui.indice_start*sizeof(u16)) );
514
515 glDisable( GL_BLEND );
516
517 vg_ui.indice_start = vg_ui.cur_indice;
518 vg_ui.vert_start = vg_ui.cur_vert;
519 }
520
521 static void ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] )
522 {
523 /* this if far from ideal but stops us from crashing */
524 if( (vg_ui.cur_vert + 4 > vg_ui.max_verts) ||
525 (vg_ui.cur_indice + 6 > vg_ui.max_indices))
526 return;
527
528 struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ];
529 u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ];
530
531 for( int i=0; i<4; i++ ){
532 vertices[i].colour = colour;
533 }
534
535 vertices[0].co[0] = rect[0];
536 vertices[0].co[1] = rect[1];
537 vertices[0].uv[0] = uv[0];
538 vertices[0].uv[1] = uv[1];
539 vertices[1].co[0] = rect[0]+rect[2];
540 vertices[1].co[1] = rect[1];
541 vertices[1].uv[0] = uv[2];
542 vertices[1].uv[1] = uv[1];
543 vertices[2].co[0] = rect[0]+rect[2];
544 vertices[2].co[1] = rect[1]+rect[3];
545 vertices[2].uv[0] = uv[2];
546 vertices[2].uv[1] = uv[3];
547 vertices[3].co[0] = rect[0];
548 vertices[3].co[1] = rect[1]+rect[3];
549 vertices[3].uv[0] = uv[0];
550 vertices[3].uv[1] = uv[3];
551 u16 ind_start = vg_ui.cur_vert;
552
553 u16 start = vg_ui.cur_vert;
554 u32 mesh[] = { 0,2,1, 0,3,2 };
555
556 for( u32 i=0; i<vg_list_size(mesh); i++ ){
557 indices[i] = start+mesh[i];
558 }
559
560 vg_ui.cur_indice += 6;
561 vg_ui.cur_vert += 4;
562 }
563
564 static void ui_fill( ui_rect rect, u32 colour )
565 {
566 ui_fill_rect( rect, colour, (ui_px[4]){ 4,4,4,4 } );
567 }
568
569 static void ui_outline( ui_rect rect, ui_px thickness, u32 colour )
570 {
571 /* this if far from ideal but stops us from crashing */
572 if( (vg_ui.cur_vert + 8 > vg_ui.max_verts) ||
573 (vg_ui.cur_indice + 24 > vg_ui.max_indices))
574 return;
575
576 struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ];
577 u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ];
578
579 for( int i=0; i<8; i++ ){
580 vertices[i].uv[0] = 4;
581 vertices[i].uv[1] = 4;
582 vertices[i].colour = colour;
583 }
584
585 vertices[0].co[0] = rect[0];
586 vertices[0].co[1] = rect[1];
587 vertices[1].co[0] = rect[0]+rect[2];
588 vertices[1].co[1] = rect[1];
589 vertices[2].co[0] = rect[0]+rect[2];
590 vertices[2].co[1] = rect[1]+rect[3];
591 vertices[3].co[0] = rect[0];
592 vertices[3].co[1] = rect[1]+rect[3];
593 vertices[4].co[0] = vertices[0].co[0]-thickness;
594 vertices[4].co[1] = vertices[0].co[1]-thickness;
595 vertices[5].co[0] = vertices[1].co[0]+thickness;
596 vertices[5].co[1] = vertices[1].co[1]-thickness;
597 vertices[6].co[0] = vertices[2].co[0]+thickness;
598 vertices[6].co[1] = vertices[2].co[1]+thickness;
599 vertices[7].co[0] = vertices[3].co[0]-thickness;
600 vertices[7].co[1] = vertices[3].co[1]+thickness;
601
602 u16 start = vg_ui.cur_vert;
603 u32 mesh[] = { 0,5,4, 0,1,5, 1,6,5, 1,2,6, 2,7,6, 2,3,7, 3,4,7, 3,0,4 };
604
605 for( u32 i=0; i<vg_list_size(mesh); i++ ){
606 indices[i] = start+mesh[i];
607 }
608
609 vg_ui.cur_indice += 24;
610 vg_ui.cur_vert += 8;
611 }
612
613 static void ui_split( ui_rect rect,
614 enum ui_axis other, ui_px width, ui_px gap,
615 ui_rect l, ui_rect r )
616 {
617 enum ui_axis dir = other ^ 0x1;
618
619 if( width < 0 ) width = rect[ 2+dir ] + width;
620
621 ui_rect temp;
622 rect_copy( rect, temp );
623
624 l[ dir ] = temp[ dir ];
625 r[ dir ] = temp[ dir ] + width + (gap/2);
626 l[ other ] = temp[ other ];
627 r[ other ] = temp[ other ];
628 l[ 2+dir ] = width - (gap/2);
629 r[ 2+dir ] = temp[ 2+dir ] - width - (gap/2);
630 l[ 2+other ] = temp[ 2+other ];
631 r[ 2+other ] = temp[ 2+other ];
632 }
633
634 static ui_px ui_text_line_width( const char *str );
635
636 static void ui_rect_center( ui_rect parent, ui_rect rect )
637 {
638 rect[0] = parent[0] + (parent[2]-rect[2])/2;
639 rect[1] = parent[1] + (parent[3]-rect[3])/2;
640 }
641
642 static void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d )
643 {
644 i32 rp = (i32)rect[2] * (i32)size[1],
645 rc = (i32)size[0] * (i32)rect[3];
646
647 enum ui_axis dir, other;
648 if( rc > rp ) dir = k_ui_axis_h;
649 else dir = k_ui_axis_v;
650 other = dir ^ 0x1;
651
652 d[2+dir] = rect[2+dir];
653 d[2+other] = (rect[2+dir] * size[other]) / size[dir];
654
655 ui_rect_center( rect, d );
656 }
657
658 static void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio,
659 ui_px gap, ui_rect l, ui_rect r )
660 {
661 ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio;
662 ui_split( rect, dir, width, gap, l, r );
663 }
664
665 static void ui_rect_pad( ui_rect rect, ui_px pad[2] )
666 {
667 rect[0] += pad[0];
668 rect[1] += pad[1];
669 rect[2] -= pad[0]*2;
670 rect[3] -= pad[1]*2;
671 }
672
673 static ui_px ui_text_line_width( const char *str )
674 {
675 int length = 0;
676 const char *_c = str;
677 u8 c;
678
679 while( (c = *(_c ++)) ){
680 if( c >= 32 ) length ++;
681 else if( c == '\n' ) break;
682 }
683
684 return length * 8;
685 }
686
687 static ui_px ui_text_string_height( const char *str )
688 {
689 int height = 1;
690 const char *_c = str;
691 u8 c;
692
693 while( (c = *(_c ++)) ){
694 if( c == '\n' ) height ++;
695 }
696
697 return height * 14;
698 }
699
700 static ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale,
701 enum ui_align align )
702 {
703 enum ui_align lwr = k_ui_align_lwr & align;
704 if( lwr == k_ui_align_left ){
705 return rect[0];
706 }
707 else{
708 ui_px width = ui_text_line_width( str ) * scale;
709
710 if( lwr == k_ui_align_right )
711 return rect[0] + rect[2]-width;
712 else
713 return rect[0] + (rect[2]-width)/2;
714 }
715 }
716
717 static ui_px ui_min( ui_px a, ui_px b ){ return a<b?a:b; }
718 static ui_px ui_max( ui_px a, ui_px b ){ return a>b?a:b; }
719 static ui_px ui_clamp( ui_px a, ui_px min, ui_px max )
720 {
721 return ui_min( max, ui_max( a, min ) );
722 }
723
724 static int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped )
725 {
726 ui_px parent_max[2], child_max[2];
727 parent_max[0] = parent[0]+parent[2];
728 parent_max[1] = parent[1]+parent[3];
729 child_max[0] = child[0]+child[2];
730 child_max[1] = child[1]+child[3];
731
732 clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] );
733 clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] );
734 clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] );
735 clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] );
736
737 if( clipped[0] == clipped[2] ||
738 clipped[1] == clipped[3] )
739 return 0;
740
741 clipped[2] -= clipped[0];
742 clipped[3] -= clipped[1];
743
744 return 1;
745 }
746
747 static int ui_inside_rect( ui_rect rect, ui_px co[2] )
748 {
749 if( co[0] >= rect[0] &&
750 co[1] >= rect[1] &&
751 co[0] < rect[0]+rect[2] &&
752 co[1] < rect[1]+rect[3] )
753 {
754 return 1;
755 }
756 else
757 return 0;
758 }
759
760 static int ui_click_down( u32 mask )
761 {
762 if( vg_ui.ignore_input_frames ) return 0;
763 if( (vg_ui.mouse_state[0] & mask) &&
764 !(vg_ui.mouse_state[1] & mask) )
765 return 1;
766 else
767 return 0;
768 }
769
770 static int ui_clicking( u32 mask )
771 {
772 if( vg_ui.ignore_input_frames ) return 0;
773 return vg_ui.mouse_state[0] & mask;
774 }
775
776 static int ui_click_up( u32 mask )
777 {
778 if( vg_ui.ignore_input_frames ) return 0;
779 if( (vg_ui.mouse_state[1] & mask) &&
780 !(vg_ui.mouse_state[0] & mask) )
781 return 1;
782 else
783 return 0;
784 }
785
786 static void ui_prerender(void)
787 {
788 int x, y;
789 vg_ui.mouse_state[1] = vg_ui.mouse_state[0];
790 vg_ui.mouse_state[0] = SDL_GetMouseState( &x, &y );
791 vg_ui.mouse[0] = x;
792 vg_ui.mouse[1] = y;
793
794 vg_ui.cur_vert = 0;
795 vg_ui.cur_indice = 0;
796 vg_ui.vert_start = 0;
797 vg_ui.indice_start = 0;
798 vg_ui.focused_control_hit = 0;
799 vg_ui.cursor = k_ui_cursor_default;
800 vg_ui.wants_mouse = 0;
801
802 if( vg_ui.ignore_input_frames ){
803 vg_ui.ignore_input_frames --;
804 return;
805 }
806
807 if( ui_click_down(UI_MOUSE_LEFT)||ui_click_down(UI_MOUSE_MIDDLE) ){
808 vg_ui.mouse_click[0] = vg_ui.mouse[0];
809 vg_ui.mouse_click[1] = vg_ui.mouse[1];
810 }
811 }
812
813 static u32 ui_colour( enum ui_scheme_colour id )
814 {
815 return vg_ui.scheme[ id ];
816 }
817
818 /* get an appropriately contrasting colour given the base */
819 static u32 ui_colourcont( enum ui_scheme_colour id )
820 {
821 if ( id < k_ui_bg+6 ) return ui_colour( k_ui_fg );
822 else if( id < k_ui_fg ) return ui_colour( k_ui_bg+1 );
823 else if( id < k_ui_hue ) return ui_colour( k_ui_bg+3 );
824 else if( id < k_ui_red+k_ui_brighter ) return ui_colour( k_ui_fg );
825 else return ui_colour( k_ui_fg+1 );
826 }
827
828 static void ui_hex_to_norm( u32 hex, v4f norm )
829 {
830 norm[0] = ((hex ) & 0xff);
831 norm[1] = ((hex>>8 ) & 0xff);
832 norm[2] = ((hex>>16) & 0xff);
833 norm[3] = ((hex>>24) & 0xff);
834 v4_muls( norm, 1.0f/255.0f, norm );
835 }
836
837 static u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale,
838 enum ui_align align, u32 colour )
839 {
840 ui_rect text_cursor;
841 if( colour == 0 ) colour = ui_colour( k_ui_fg );
842
843 colour &= 0x00ffffff;
844
845 const char *_c = str;
846 u8 c;
847
848 text_cursor[0] = ui_text_aligned_x( str, rect, scale, align );
849 text_cursor[1] = rect[1];
850 text_cursor[2] = 8*scale;
851 text_cursor[3] = 14*scale;
852
853 u32 printed_chars = 0;
854
855 if( align & (k_ui_align_middle|k_ui_align_bottom) ){
856 ui_px height = ui_text_string_height( str ) * scale;
857
858 if( align & k_ui_align_bottom )
859 text_cursor[1] += rect[3]-height;
860 else
861 text_cursor[1] += (rect[3]-height)/2;
862 }
863
864 while( (c = *(_c ++)) ){
865 if( printed_chars >= len ){
866 printed_chars = 0;
867 text_cursor[1] += 14*scale;
868 text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align );
869 text_cursor[0] -= UI_GLYPH_SPACING_X*scale;
870
871 u8 glyph_base[2];
872 u8 glyph_index = '\xb6';
873 glyph_base[0] = glyph_index & 0xf;
874 glyph_base[1] = (glyph_index-glyph_base[0])>>4;
875 glyph_base[0] *= 8;
876 glyph_base[1] *= 8;
877
878 ui_fill_rect( text_cursor, 0x00ffffff, (ui_px[4])
879 {
880 glyph_base[0]+2,
881 glyph_base[1]+1,
882 glyph_base[0]+6,
883 glyph_base[1]+8
884 });
885
886 text_cursor[0] += UI_GLYPH_SPACING_X*scale;
887 }
888
889 if( c == '\n' ){
890 text_cursor[1] += 14*scale;
891 text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align );
892 printed_chars = 0;
893 continue;
894 }
895 else if( c >= 33 ){
896 u8 glyph_base[2];
897 u8 glyph_index = c;
898 glyph_base[0] = glyph_index & 0xf;
899 glyph_base[1] = (glyph_index-glyph_base[0])>>4;
900 glyph_base[0] *= 8;
901 glyph_base[1] *= 8;
902
903 ui_rect rect_char;
904
905 if( ui_clip( rect, text_cursor, rect_char ) ){
906 ui_fill_rect( rect_char, colour, (ui_px[4])
907 {
908 glyph_base[0]+2,
909 glyph_base[1]+1,
910 glyph_base[0]+6,
911 glyph_base[1]+8
912 });
913 }
914 }
915 else if( c == '\x1B' ){
916 /* vt codes */
917 _c ++;
918 u16 colour_id = 0;
919 for( int i=0; i<3; i ++ ){
920 if( _c[i] ){
921 if( _c[i] == 'm' ){
922 _c = _c + i + 1;
923
924 switch( colour_id ){
925 case '0': colour = ui_colour( k_ui_fg ); break;
926 case '3'|'0'<<8: colour = ui_colour( k_ui_bg ); break;
927 case '3'|'1'<<8: colour = ui_colour( k_ui_red ); break;
928 case '3'|'2'<<8: colour = ui_colour( k_ui_green ); break;
929 case '3'|'3'<<8: colour = ui_colour( k_ui_yellow ); break;
930 case '3'|'4'<<8: colour = ui_colour( k_ui_blue ); break;
931 case '3'|'5'<<8: colour = ui_colour( k_ui_purple ); break;
932 case '3'|'6'<<8: colour = ui_colour( k_ui_aqua ); break;
933 case '3'|'7'<<8: colour = 0xffffffff; break;
934 }
935
936 colour &= 0x00ffffff;
937 break;
938 }
939
940 colour_id |= _c[i] << (i*8);
941 }
942 else{
943 _c = _c +i;
944 break;
945 }
946 }
947
948 continue;
949 }
950 else if( c == '\t' ){
951 text_cursor[0] += UI_GLYPH_SPACING_X*scale*4;
952 printed_chars += 4;
953 continue;
954 }
955
956 text_cursor[0] += UI_GLYPH_SPACING_X*scale;
957 printed_chars ++;
958 }
959
960 return printed_chars;
961 }
962
963 static void ui_text( ui_rect rect, const char *str, ui_px scale,
964 enum ui_align align, u32 colour )
965 {
966 ui_ntext( rect, str, 1024, scale, align, colour );
967 }
968
969 static void ui_image( ui_rect rect, GLuint image )
970 {
971 ui_flush( k_ui_shader_colour );
972 glActiveTexture( GL_TEXTURE0 );
973 glBindTexture( GL_TEXTURE_2D, image );
974 ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,128, 128, 0 } );
975 ui_flush( k_ui_shader_image );
976 }
977
978 static u32 v4f_u32_colour( v4f colour )
979 {
980 u32 r = colour[0] * 255.0f,
981 g = colour[1] * 255.0f,
982 b = colour[2] * 255.0f,
983 a = colour[3] * 255.0f;
984
985 return r | (g<<8) | (b<<16) | (a<<24);
986 }
987
988 static void ui_defocus_all(void)
989 {
990 if( vg_ui.focused_control_type == k_ui_control_textbox ){
991 SDL_StopTextInput();
992 }
993
994 vg_ui.focused_control_id = NULL;
995 vg_ui.focused_control_hit = 0;
996 vg_ui.focused_control_type = k_ui_control_none;
997 }
998
999 enum ui_button_state {
1000 k_ui_button_none = 0,
1001 k_ui_button_click = 1,
1002 k_ui_button_holding_inside = 2,
1003 k_ui_button_holding_outside = 3,
1004 k_ui_button_hover = 4
1005 };
1006
1007 static enum ui_button_state ui_colourbutton( ui_rect rect,
1008 enum ui_scheme_colour colour ){
1009 int clickup= ui_click_up(UI_MOUSE_LEFT),
1010 click = ui_clicking(UI_MOUSE_LEFT) | clickup,
1011 target = ui_inside_rect( rect, vg_ui.mouse_click ) && click,
1012 hover = ui_inside_rect( rect, vg_ui.mouse );
1013
1014 u32 col_base = vg_ui.scheme[ colour ],
1015 col_highlight = vg_ui.scheme[ k_ui_fg ],
1016 col_hover = vg_ui.scheme[ colour + k_ui_brighter ];
1017
1018 if( vg_ui.focused_control_type != k_ui_control_none ){
1019 clickup = 0;
1020 click = 0;
1021 target = 0;
1022 hover = 0;
1023 }
1024
1025 if( hover ){
1026 vg_ui.cursor = k_ui_cursor_hand;
1027 }
1028
1029 if( click ){
1030 if( target ){
1031 if( hover ){
1032 if( clickup ){
1033 ui_fill( rect, col_highlight );
1034 vg_ui.ignore_input_frames = 2;
1035 rect_copy( rect, vg_ui.click_fader );
1036
1037 rect_copy( rect, vg_ui.click_fader_end );
1038 vg_ui.click_fader_end[3] = 0;
1039 ui_rect_center( rect, vg_ui.click_fader_end );
1040
1041 vg_ui.click_fade_opacity = 1.0f;
1042 ui_defocus_all();
1043 return k_ui_button_click;
1044 }
1045 else{
1046 ui_fill( rect, col_highlight );
1047 return k_ui_button_holding_inside;
1048 }
1049 }
1050 else{
1051 ui_fill( rect, col_base );
1052 ui_outline( rect, 1, col_highlight );
1053 return k_ui_button_holding_outside;
1054 }
1055 }
1056 else{
1057 ui_fill( rect, col_base );
1058 return k_ui_button_none;
1059 }
1060 }
1061 else{
1062 if( hover ){
1063 ui_fill( rect, col_hover );
1064 return k_ui_button_hover;
1065 }
1066 else{
1067 ui_fill( rect, col_base );
1068 return k_ui_button_none;
1069 }
1070 }
1071 }
1072
1073 static enum ui_button_state ui_colourbutton_text(
1074 ui_rect rect, const char *string, ui_px scale,
1075 enum ui_scheme_colour colour ){
1076 enum ui_button_state state = ui_colourbutton( rect, colour );
1077 ui_rect t = { 0,0, ui_text_line_width( string )*scale, 14*scale };
1078 ui_rect_center( rect, t );
1079
1080 u32 text_colour = ui_colourcont(colour);
1081 if( state == k_ui_button_holding_inside )
1082 text_colour = colour;
1083
1084 ui_text( t, string, scale, k_ui_align_left, text_colour );
1085 return state;
1086 }
1087
1088 static enum ui_button_state ui_button_text( ui_rect rect,
1089 const char *string, ui_px scale ){
1090 return ui_colourbutton_text( rect, string, scale, k_ui_bg+4 );
1091 }
1092
1093 static void ui_enum_post(void);
1094 static void ui_postrender(void)
1095 {
1096 if( vg_ui.click_fade_opacity > 0.0f ){
1097 float scale = vg_ui.click_fade_opacity;
1098 scale = vg_maxf( 1.0f/255.0f, scale*scale );
1099
1100 vg_ui.click_fade_opacity -= vg.time_frame_delta * 3.8f;
1101 u32 colour = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000;
1102
1103 v4f begin, end, dest;
1104 for( int i=0; i<4; i++ ){
1105 begin[i] = vg_ui.click_fader[i];
1106 end[i] = vg_ui.click_fader_end[i]+1;
1107 }
1108
1109 v4_lerp( end, begin, scale, dest );
1110
1111 ui_rect rect;
1112 for( int i=0; i<4; i++ ){
1113 rect[i] = dest[i];
1114 }
1115
1116 ui_fill( rect, colour );
1117 }
1118
1119 if( vg_ui.focused_control_type == k_ui_control_enum ){
1120 ui_enum_post();
1121 }
1122 else if( vg_ui.focused_control_type == k_ui_control_modal ){
1123 ui_rect screen = {0,0,vg.window_x,vg.window_y};
1124 ui_fill( screen, 0xa0000000 );
1125 ui_rect box = {0,0,400,200};
1126
1127 u32 colour = ui_colour(k_ui_fg),
1128 type = vg_ui.modal.options & UI_MODAL_TYPE_BITS;
1129 if ( type == 1 ) colour = ui_colour(k_ui_green);
1130 else if( type == 2 ) colour = ui_colour(k_ui_red);
1131 else if( type == 3 ) colour = ui_colour(k_ui_yellow);
1132
1133 ui_rect_center( screen, box );
1134 ui_fill( box, ui_colour(k_ui_bg) );
1135 ui_outline( box, -1, colour );
1136
1137 ui_rect message;
1138 rect_copy( box, message );
1139 message[3] = 100;
1140 ui_rect_center( box, message );
1141
1142 ui_rect row0, row1, btn;
1143 ui_split_ratio( message, k_ui_axis_h, 0.5f, 0, row0, row1 );
1144 row0[0] += UI_GLYPH_SPACING_X;
1145 ui_ntext( row0, vg_ui.modal.message, (box[2]/UI_GLYPH_SPACING_X)-2, 1,
1146 k_ui_align_left, colour );
1147
1148 rect_copy( row1, btn );
1149 btn[2] = 86;
1150 btn[3] = 28;
1151 ui_rect_center( row1, btn );
1152
1153 vg_ui.focused_control_type = k_ui_control_none; /* HACK */
1154 if( ui_button_text( btn, "OK", 1 ) != 1 )
1155 vg_ui.focused_control_hit = 1;
1156 vg_ui.focused_control_type = k_ui_control_modal; /* HACK */
1157 vg_ui.wants_mouse = 1;
1158 }
1159
1160 ui_flush( k_ui_shader_colour );
1161
1162 if( !vg_ui.focused_control_hit ){
1163 ui_defocus_all();
1164 }
1165
1166 if( vg_ui.wants_mouse ){
1167 SDL_SetWindowGrab( vg.window, SDL_FALSE );
1168 SDL_SetRelativeMouseMode( SDL_FALSE );
1169 }
1170 else{
1171 SDL_SetWindowGrab( vg.window, SDL_TRUE );
1172 SDL_SetRelativeMouseMode( SDL_TRUE );
1173 }
1174
1175 SDL_SetCursor( vg_ui.cursor_map[ vg_ui.cursor ] );
1176 SDL_ShowCursor(1);
1177 }
1178
1179 static void ui_dev_colourview(void)
1180 {
1181 ui_rect window = {vg.window_x-256,0,256,vg.window_y}, swatch;
1182
1183 const char *names[vg_list_size(vg_ui.scheme)] = {
1184 [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3",
1185 "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7",
1186
1187 [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3",
1188 "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7",
1189
1190 [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green",
1191 "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray",
1192 "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8",
1193 "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" };
1194
1195 ui_rect col[2];
1196 ui_split_ratio( window, k_ui_axis_v, 0.5f, 0, col[0], col[1] );
1197
1198 for( int i=0; i<vg_list_size(vg_ui.scheme); i++ ){
1199 int which = (i/8)%2;
1200
1201 ui_split( col[which], k_ui_axis_h, 24, 0, swatch, col[which] );
1202 ui_fill( swatch, ui_colour(i) );
1203
1204 if( names[i] )
1205 ui_text(swatch, names[i], 1, k_ui_align_middle_left, ui_colourcont(i));
1206 }
1207 }
1208
1209 static void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect ){
1210 ui_split( inout_panel, k_ui_axis_h, k_ui_widget_height*k_ui_scale,
1211 k_ui_padding, out_rect, inout_panel );
1212 }
1213
1214 static void ui_panel( ui_rect in_rect, ui_rect out_panel ){
1215 ui_fill( in_rect, ui_colour( k_ui_bg+1 ) );
1216 ui_outline( in_rect, 1, ui_colour( k_ui_bg+7 ) );
1217 rect_copy( in_rect, out_panel );
1218 ui_rect_pad( out_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } );
1219 }
1220
1221 /*
1222 * checkbox
1223 * -----------------------------------------------------------------------------
1224 */
1225
1226 static int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ){
1227 ui_rect rect, label, box;
1228 ui_standard_widget( inout_panel, rect );
1229
1230 ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box );
1231 ui_text( label, str_label, k_ui_scale, k_ui_align_middle_left, 0 );
1232
1233 int changed = ui_colourbutton( box, k_ui_bg )==1;
1234 if( changed )
1235 *data = (*data) ^ 0x1;
1236
1237 if( *data ){
1238 ui_rect_pad( box, (ui_px[2]){4,4} );
1239 ui_fill( box, ui_colour( k_ui_orange ) );
1240 }
1241
1242 return changed;
1243 }
1244
1245 /*
1246 * Dropdown / Enum
1247 * -----------------------------------------------------------------------------
1248 */
1249
1250 /*
1251 * unfortunately no return value since we only find out that event in the
1252 * postrender step.
1253 */
1254 static void ui_enum( ui_rect inout_panel, const char *str_label,
1255 struct ui_enum_opt *options, u32 len, i32 *value )
1256 {
1257 ui_rect rect, label, box;
1258 ui_standard_widget( inout_panel, rect );
1259 ui_split( rect, k_ui_axis_v, (ui_text_line_width( str_label )+8)*k_ui_scale,
1260 0, label, box );
1261 ui_text( label, str_label, k_ui_scale, k_ui_align_middle_left, 0 );
1262
1263 const char *display = "OUT OF RANGE";
1264 for( u32 i=0; i<len; i ++ ){
1265 if( *value == options[i].value ){
1266 display = options[i].alias;
1267 break;
1268 }
1269 }
1270
1271 if( ui_button_text( box, display, k_ui_scale ) == 1 ){
1272 vg_ui.focused_control_type = k_ui_control_enum;
1273 vg_ui.ptr_enum = value;
1274 vg_ui._enum.option_count = len;
1275 vg_ui._enum.options = options;
1276 rect_copy( box, vg_ui._enum.rect );
1277 }
1278 }
1279
1280 static void ui_enum_post(void){
1281 ui_rect drawer;
1282 rect_copy( vg_ui._enum.rect, drawer );
1283 drawer[3] *= vg_ui._enum.option_count;
1284
1285 int close = 0;
1286 int clickany= ui_click_up(UI_MOUSE_LEFT|UI_MOUSE_RIGHT|UI_MOUSE_MIDDLE),
1287 hover = ui_inside_rect( drawer, vg_ui.mouse );
1288
1289 if( clickany && !hover ){
1290 return;
1291 }
1292
1293 /* HACK */
1294 vg_ui.focused_control_type = k_ui_control_none;
1295 i32 *value = vg_ui.ptr_enum;
1296
1297 for( u32 i=0; i<vg_ui._enum.option_count; i++ ){
1298 ui_rect button;
1299 ui_split( drawer, k_ui_axis_h, vg_ui._enum.rect[3], 0, button,drawer );
1300
1301 enum ui_scheme_colour colour = k_ui_bg+3;
1302 if( vg_ui._enum.options[i].value == *value )
1303 colour = k_ui_orange;
1304
1305 if( ui_colourbutton_text( button, vg_ui._enum.options[i].alias,
1306 k_ui_scale, colour ) == 1 ){
1307 *value = vg_ui._enum.options[i].value;
1308 close = 1;
1309 }
1310 }
1311
1312 /* HACK */
1313 vg_ui.focused_control_type = k_ui_control_enum;
1314
1315 if( !close )
1316 vg_ui.focused_control_hit = 1;
1317 }
1318
1319 /*
1320 * Textbox chaos
1321 * -----------------------------------------------------------------------------
1322 */
1323
1324 static void _ui_textbox_make_selection( int *start, int *end )
1325 {
1326 *start = VG_MIN( vg_ui.textbox.cursor_pos, vg_ui.textbox.cursor_user );
1327 *end = VG_MAX( vg_ui.textbox.cursor_pos, vg_ui.textbox.cursor_user );
1328 }
1329
1330 static void _ui_textbox_move_cursor( int *cursor0, int *cursor1,
1331 int dir, int snap_together )
1332 {
1333 *cursor0 = VG_MAX( 0, vg_ui.textbox.cursor_user + dir );
1334 *cursor0 =
1335 VG_MIN(
1336 VG_MIN( vg_ui.textbox.len-1, strlen( vg_ui.textbuf )),
1337 *cursor0 );
1338
1339 if( snap_together )
1340 *cursor1 = *cursor0;
1341 }
1342
1343 static int _ui_textbox_makeroom( int datastart, int length )
1344 {
1345 int move_to = VG_MIN( datastart+length, vg_ui.textbox.len-1 );
1346 int move_amount = strlen( vg_ui.textbuf )-datastart;
1347 int move_end = VG_MIN( move_to+move_amount, vg_ui.textbox.len-1 );
1348 move_amount = move_end-move_to;
1349
1350 if( move_amount )
1351 memmove( &vg_ui.textbuf[ move_to ],
1352 &vg_ui.textbuf[ datastart ],
1353 move_end-move_to );
1354
1355 vg_ui.textbuf[ move_end ] = '\0';
1356
1357 return VG_MIN( length, vg_ui.textbox.len-datastart-1 );
1358 }
1359
1360 static int _ui_textbox_delete_char( int direction )
1361 {
1362 int start, end;
1363 _ui_textbox_make_selection( &start, &end );
1364
1365 /* There is no selection */
1366 if( !(end-start) ){
1367 if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_ui.textbuf ) );
1368 else if( direction == -1 ) start = VG_MAX( start-1, 0 );
1369 }
1370
1371 /* Still no selction, no need to do anything */
1372 if( !(end-start) )
1373 return start;
1374
1375 /* Copy the end->terminator to start */
1376 int remaining_length = strlen( vg_ui.textbuf )+1-end;
1377 memmove( &vg_ui.textbuf[ start ],
1378 &vg_ui.textbuf[ end ],
1379 remaining_length );
1380 return start;
1381 }
1382
1383 static void _ui_textbox_to_clipboard(void)
1384 {
1385 int start, end;
1386 _ui_textbox_make_selection( &start, &end );
1387 char buffer[512];
1388
1389 if( end-start ){
1390 memcpy( buffer, &vg_ui.textbuf[ start ], end-start );
1391 buffer[ end-start ] = 0x00;
1392 SDL_SetClipboardText( buffer );
1393 }
1394 }
1395
1396 static void _ui_textbox_clipboard_paste(void)
1397 {
1398 if( !SDL_HasClipboardText() )
1399 return;
1400
1401 char *text = SDL_GetClipboardText();
1402
1403 if( !text )
1404 return;
1405
1406 int datastart = _ui_textbox_delete_char( 0 );
1407 int length = strlen( text );
1408 int cpylength = _ui_textbox_makeroom( datastart, length );
1409
1410 memcpy( vg_ui.textbuf + datastart, text, cpylength);
1411 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
1412 &vg_ui.textbox.cursor_pos, cpylength, 1 );
1413 SDL_free( text );
1414 }
1415
1416 static void _ui_textbox_put_char( char c )
1417 {
1418 vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0);
1419
1420 if( _ui_textbox_makeroom( vg_ui.textbox.cursor_user, 1 ) )
1421 vg_ui.textbuf[ vg_ui.textbox.cursor_user ] = c;
1422
1423 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
1424 &vg_ui.textbox.cursor_pos, 1, 1 );
1425 }
1426
1427 /* Receed secondary cursor */
1428 static void _ui_textbox_left_select(void)
1429 {
1430 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, -1, 0 );
1431 }
1432
1433 /* Match and receed both cursors */
1434 static void _ui_textbox_left(void)
1435 {
1436 int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1;
1437
1438 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
1439 &vg_ui.textbox.cursor_pos, -cursor_diff, 1 );
1440 }
1441
1442 static void _ui_textbox_up(void)
1443 {
1444 if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){
1445 int line_begin = vg_ui.textbox.cursor_user;
1446
1447 while( line_begin ){
1448 if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){
1449 break;
1450 }
1451
1452 line_begin --;
1453 }
1454
1455 if( line_begin ){
1456 int line_above_begin = line_begin-1;
1457
1458 while( line_above_begin ){
1459 if( vg_ui.textbuf[ line_above_begin-1 ] == '\n' ){
1460 break;
1461 }
1462
1463 line_above_begin --;
1464 }
1465
1466 int offset = vg_ui.textbox.cursor_user - line_begin,
1467 line_length_above = line_begin - line_above_begin -1;
1468
1469 offset = VG_MIN( line_length_above, offset );
1470
1471 vg_ui.textbox.cursor_user = line_above_begin+offset;
1472 vg_ui.textbox.cursor_pos = line_above_begin+offset;
1473 }
1474 else{
1475 vg_ui.textbox.cursor_user = line_begin;
1476 vg_ui.textbox.cursor_pos = line_begin;
1477 }
1478 }
1479 else{
1480 if( vg_ui.textbox.callbacks.up ){
1481 vg_ui.textbox.callbacks.up( vg_ui.textbuf, vg_ui.textbox.len );
1482 }
1483 }
1484 }
1485
1486 static void _ui_textbox_down(void)
1487 {
1488 if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){
1489 int line_begin = vg_ui.textbox.cursor_user;
1490
1491 while( line_begin ){
1492 if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){
1493 break;
1494 }
1495
1496 line_begin --;
1497 }
1498
1499 int line_below_begin = vg_ui.textbox.cursor_user;
1500
1501 while(1){
1502 if( vg_ui.textbuf[ line_below_begin ] == '\0' ){
1503 vg_ui.textbox.cursor_user = line_below_begin;
1504 vg_ui.textbox.cursor_pos = line_below_begin;
1505 return;
1506 }
1507
1508 if( vg_ui.textbuf[ line_below_begin ] == '\n' ){
1509 line_below_begin ++;
1510 break;
1511 }
1512
1513 line_below_begin ++;
1514 }
1515
1516 int line_below_end = line_below_begin;
1517 while(1){
1518 if( vg_ui.textbuf[ line_below_end ] == '\0' ||
1519 vg_ui.textbuf[ line_below_end ] == '\n' ){
1520 line_below_end ++;
1521 break;
1522 }
1523 line_below_end ++;
1524 }
1525
1526 int offset = vg_ui.textbox.cursor_user - line_begin,
1527 line_length_below = line_below_end - line_below_begin -1;
1528
1529 offset = VG_MIN( line_length_below, offset );
1530
1531 vg_ui.textbox.cursor_user = line_below_begin+offset;
1532 vg_ui.textbox.cursor_pos = line_below_begin+offset;
1533 }
1534 else{
1535 if( vg_ui.textbox.callbacks.down ){
1536 vg_ui.textbox.callbacks.down( vg_ui.textbuf, vg_ui.textbox.len );
1537 }
1538 }
1539 }
1540
1541 static void _ui_textbox_right_select(void)
1542 {
1543 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 1, 0 );
1544 }
1545
1546 static void _ui_textbox_right(void)
1547 {
1548 int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1;
1549
1550 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
1551 &vg_ui.textbox.cursor_pos, +cursor_diff, 1 );
1552 }
1553
1554 static void _ui_textbox_backspace(void)
1555 {
1556 if( vg_ui.focused_control_type == k_ui_control_textbox ){
1557 vg_ui.textbox.cursor_user = _ui_textbox_delete_char( -1 );
1558 vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user;
1559
1560 if( vg_ui.textbox.callbacks.change ){
1561 vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len );
1562 }
1563 }
1564 }
1565
1566 static void _ui_textbox_delete(void)
1567 {
1568 if( vg_ui.focused_control_type == k_ui_control_textbox ){
1569 vg_ui.textbox.cursor_user = _ui_textbox_delete_char( 1 );
1570 vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user;
1571
1572 if( vg_ui.textbox.callbacks.change ){
1573 vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len );
1574 }
1575 }
1576 }
1577
1578 static void _ui_textbox_home_select(void)
1579 {
1580 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, -10000, 0 );
1581 }
1582
1583 static void _ui_textbox_home(void)
1584 {
1585 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
1586 &vg_ui.textbox.cursor_pos, -10000, 1 );
1587 }
1588
1589 static void _ui_textbox_end_select(void)
1590 {
1591 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 10000, 0 );
1592 }
1593
1594 static void _ui_textbox_end(void)
1595 {
1596 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
1597 &vg_ui.textbox.cursor_pos,
1598 vg_ui.textbox.len-1, 1 );
1599 }
1600
1601 static void _ui_textbox_select_all(void)
1602 {
1603 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 10000, 0);
1604 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_pos, NULL, -10000, 0);
1605 }
1606
1607 static void _ui_textbox_cut(void)
1608 {
1609 _ui_textbox_to_clipboard();
1610 vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0);
1611 vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user;
1612 }
1613
1614 static void _ui_textbox_enter(void)
1615 {
1616 if( vg_ui.focused_control_type == k_ui_control_textbox ){
1617 if( vg_ui.textbox.callbacks.enter )
1618 vg_ui.textbox.callbacks.enter( vg_ui.textbuf, vg_ui.textbox.len );
1619
1620 if( vg_ui.focused_control_type != k_ui_control_textbox ) return;
1621
1622 if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE )
1623 _ui_textbox_put_char( '\n' );
1624 else{
1625 if( !(vg_ui.textbox.flags & UI_TEXTBOX_AUTOFOCUS ) )
1626 ui_defocus_all();
1627 }
1628 }
1629 }
1630
1631 /*
1632 * based on a visual character coordinate relative to the anchor of the textbox,
1633 * this works out the linear place in the buffer that coordinate maps to
1634 *
1635 * input coordinates go in co[0], co[1], and the result index is in co[2]
1636 */
1637 static void _ui_textbox_calc_index_from_grid( int co[3], int wrap_length )
1638 {
1639 int i[3] = {0,0,0};
1640
1641 char c;
1642 while( (c = vg_ui.textbuf[i[2]]) ){
1643 if( i[1]==co[1] && i[0]>=co[0] ) break;
1644
1645 if( i[0] >= wrap_length ){
1646 i[1] ++;
1647 i[0] = 0;
1648 }
1649
1650 if( c >= 32 && c <= 126 ){
1651 i[0] ++;
1652 i[2] ++;
1653 }
1654 else if( c == '\n' ){
1655 i[1] ++;
1656
1657 if( i[1] > co[1] ) break;
1658
1659 i[2] ++;
1660 i[0] = 0;
1661 }
1662 else i[2] ++;
1663 }
1664
1665 co[0] = i[0];
1666 co[1] = i[1];
1667 co[2] = i[2];
1668 }
1669
1670 /*
1671 * based on the index specied in co[2], work out the visual character
1672 * coordinates and store them in co[0], co[1]
1673 */
1674 static void _ui_textbox_index_calc_coords( int co[3], int wrap_length )
1675 {
1676 co[0] = 0;
1677 co[1] = 0;
1678
1679 char c;
1680 int i=0;
1681
1682 while( (c = vg_ui.textbuf[i ++]) ){
1683 if( i > co[2] ) break;
1684 if( co[0] >= wrap_length ){
1685 co[1] ++;
1686 co[0] = 0;
1687 }
1688 if( c >= 32 && c <= 126 ) co[0] ++;
1689 else if( c == '\n' ){
1690 co[1] ++;
1691 co[0] = 0;
1692 }
1693 }
1694 }
1695
1696 /*
1697 * calculate the number of characters remaining until either:
1698 * - the wrap_length limit is hit
1699 * - end of the line/string
1700 *
1701 * index must be fully populated with visual X/Y, and linear index
1702 */
1703 static int _ui_textbox_run_remaining( int index[3], int wrap_length )
1704 {
1705 int i=0, printed_chars=0;
1706 char c;
1707 while( (c = vg_ui.textbuf[index[2] + (i ++)]) ){
1708 if( index[0]+i >= wrap_length ) break;
1709 if( c >= 32 && c <= 126 ) printed_chars ++;
1710 else if( c == '\n' ) break;
1711 }
1712
1713 return printed_chars+1;
1714 }
1715
1716 static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags,
1717 struct ui_textbox_callbacks *callbacks )
1718 {
1719 int clickup= ui_click_up(UI_MOUSE_LEFT),
1720 clickdown = ui_click_down(UI_MOUSE_LEFT),
1721 click = ui_clicking(UI_MOUSE_LEFT) | clickup,
1722 target = ui_inside_rect( rect, vg_ui.mouse_click ) && click,
1723 hover = ui_inside_rect( rect, vg_ui.mouse );
1724
1725 /* allow instant transitions from textbox->textbox */
1726 if( (vg_ui.focused_control_type != k_ui_control_none) &&
1727 (vg_ui.focused_control_type != k_ui_control_textbox) ){
1728 clickup = 0;
1729 clickdown = 0;
1730 click = 0;
1731 target = 0;
1732 hover = 0;
1733 flags &= ~UI_TEXTBOX_AUTOFOCUS;
1734 }
1735
1736 u32 col_base = ui_colour( k_ui_bg ),
1737 col_highlight = ui_colour( k_ui_fg ),
1738 col_cursor = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000;
1739
1740 ui_px border = -1;
1741
1742 ui_rect text_rect;
1743 rect_copy( rect, text_rect );
1744
1745 if( flags & UI_TEXTBOX_MULTILINE ) text_rect[3] = rect[3]-16;
1746 else text_rect[3] = 14;
1747
1748 text_rect[2] -= 16;
1749 ui_rect_center( rect, text_rect );
1750
1751 ui_px wrap_length = 1024;
1752
1753 if( flags & UI_TEXTBOX_WRAP )
1754 wrap_length = text_rect[2] / UI_GLYPH_SPACING_X;
1755
1756 if( hover ){
1757 vg_ui.cursor = k_ui_cursor_ibeam;
1758 }
1759
1760 if( vg_ui.focused_control_id == buf ){
1761 ui_fill( rect, col_base );
1762 ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 );
1763
1764 if( !(flags & UI_TEXTBOX_AUTOFOCUS) && ((clickup||clickdown) && !target)){
1765 ui_defocus_all();
1766 }
1767 else{
1768 vg_ui.focused_control_hit = 1;
1769 if( click && target ){
1770 int p0[3] ={
1771 (vg_ui.mouse_click[0] - text_rect[0]) / UI_GLYPH_SPACING_X,
1772 (vg_ui.mouse_click[1] - text_rect[1]) / 14,
1773 -1
1774 },
1775 p1[3] = {
1776 (vg_ui.mouse[0] - text_rect[0]) / UI_GLYPH_SPACING_X,
1777 (vg_ui.mouse[1] - text_rect[1]) / 14,
1778 -1
1779 };
1780
1781 if( flags & UI_TEXTBOX_MULTILINE ){
1782 _ui_textbox_calc_index_from_grid( p0, wrap_length );
1783 _ui_textbox_calc_index_from_grid( p1, wrap_length );
1784
1785 vg_ui.textbox.cursor_pos = p0[2];
1786 vg_ui.textbox.cursor_user = p1[2];
1787 }
1788 else{
1789 int max = strlen( buf );
1790 vg_ui.textbox.cursor_pos = VG_MAX( 0, VG_MIN( max, p0[0] )),
1791 vg_ui.textbox.cursor_user = VG_MAX( 0, VG_MIN( max, p1[0] ));
1792 }
1793 }
1794
1795 ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ] );
1796
1797 ui_rect cursor;
1798
1799 int c0 = vg_ui.textbox.cursor_pos,
1800 c1 = vg_ui.textbox.cursor_user,
1801 start = VG_MIN( c0, c1 ),
1802 end = VG_MAX( c0, c1 ),
1803 chars = end-start;
1804
1805 if( flags & (UI_TEXTBOX_MULTILINE|UI_TEXTBOX_WRAP) ){
1806
1807 int pos[3], remaining = chars;
1808
1809 pos[2] = start;
1810 _ui_textbox_index_calc_coords( pos, wrap_length );
1811
1812 if( start==end ){
1813 cursor[0] = text_rect[0] + pos[0]*UI_GLYPH_SPACING_X-1;
1814 cursor[1] = text_rect[1] + pos[1]*14;
1815 cursor[2] = 2;
1816 cursor[3] = 13;
1817 ui_fill( cursor, col_cursor );
1818 rect_copy( cursor, vg_ui.click_fader_end );
1819 }
1820 else{
1821 while( remaining ){
1822 int run = _ui_textbox_run_remaining( pos, wrap_length );
1823 run = VG_MIN( run, remaining );
1824
1825 cursor[0] = text_rect[0] + pos[0]*UI_GLYPH_SPACING_X-1;
1826 cursor[1] = text_rect[1] + pos[1]*14;
1827 cursor[2] = (float)(run)*(float)UI_GLYPH_SPACING_X;
1828 cursor[3] = 13;
1829
1830 ui_fill( cursor, col_cursor );
1831
1832 remaining -= run;
1833 pos[0] = 0;
1834 pos[1] ++;
1835 pos[2] += run;
1836 }
1837 rect_copy( cursor, vg_ui.click_fader_end );
1838 }
1839 }
1840 else{
1841 cursor[0] = text_rect[0] + start*UI_GLYPH_SPACING_X-1;
1842 cursor[1] = text_rect[1];
1843 cursor[3] = 13;
1844
1845 if( start==end ){
1846 cursor[2] = 2;
1847 }
1848 else{
1849 cursor[2] = (float)(chars)*(float)UI_GLYPH_SPACING_X;
1850 }
1851
1852 if( (vg_ui.click_fade_opacity<=0.0f) &&
1853 ui_clip( rect, cursor, cursor ) ){
1854 ui_fill( cursor, col_cursor );
1855 }
1856
1857 rect_copy( cursor, vg_ui.click_fader_end );
1858 }
1859 }
1860
1861 return 0;
1862 }
1863
1864 if( click || (flags & UI_TEXTBOX_AUTOFOCUS) ){
1865 if( (target && hover) || (flags & UI_TEXTBOX_AUTOFOCUS) ){
1866 ui_defocus_all();
1867
1868 ui_fill( rect, col_highlight );
1869 vg_ui.ignore_input_frames = 2;
1870 rect_copy( rect, vg_ui.click_fader );
1871 rect_copy( rect, vg_ui.click_fader_end );
1872
1873 vg_ui.click_fade_opacity = 1.0f;
1874 vg_ui.textbuf = buf;
1875 vg_ui.focused_control_hit = 1;
1876 vg_ui.focused_control_type = k_ui_control_textbox;
1877 vg_ui.textbox.len = len;
1878 vg_ui.textbox.flags = flags;
1879 vg_ui.textbox.cursor_pos = 0;
1880 vg_ui.textbox.cursor_user = 0;
1881
1882 if( callbacks ){
1883 vg_ui.textbox.callbacks = *callbacks;
1884 }
1885 else{
1886 vg_ui.textbox.callbacks.change = NULL;
1887 vg_ui.textbox.callbacks.down = NULL;
1888 vg_ui.textbox.callbacks.up = NULL;
1889 vg_ui.textbox.callbacks.enter = NULL;
1890 }
1891
1892 SDL_StartTextInput();
1893 }
1894 }
1895
1896 ui_fill( rect, col_base );
1897
1898 if( hover ){
1899 ui_outline( rect, -1, col_highlight );
1900 }
1901
1902 ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 );
1903 return 0;
1904 }
1905
1906 /*
1907 * Modal UI
1908 * -----------------------------------------------------------------------------
1909 */
1910 static void ui_start_modal( const char *message, u32 options ){
1911 vg_ui.focused_control_type = k_ui_control_modal;
1912 vg_ui.modal.message = message;
1913 vg_ui.modal.callbacks.close = NULL;
1914 vg_ui.modal.options = options;
1915
1916 u32 type = options & UI_MODAL_TYPE_BITS;
1917 if( type == UI_MODAL_OK ) vg_info( message );
1918 else if( type == UI_MODAL_WARN ) vg_warn( message );
1919 else if( type == UI_MODAL_GOOD ) vg_success( message );
1920 else if( type == UI_MODAL_BAD ) vg_error( message );
1921 }
1922
1923 /*
1924 * Input handling
1925 * -----------------------------------------------------------------------------
1926 */
1927
1928 /*
1929 * Handles binds
1930 */
1931 static void _ui_proc_key( SDL_Keysym ev )
1932 {
1933 if( vg_ui.focused_control_type != k_ui_control_textbox ){
1934 return;
1935 }
1936
1937 struct textbox_mapping
1938 {
1939 u16 mod;
1940 SDL_Keycode key;
1941
1942 void (*handler)(void);
1943 }
1944 mappings[] =
1945 {
1946 { 0, SDLK_LEFT, _ui_textbox_left },
1947 { KMOD_SHIFT, SDLK_LEFT, _ui_textbox_left_select },
1948 { 0, SDLK_RIGHT, _ui_textbox_right },
1949 { KMOD_SHIFT, SDLK_RIGHT, _ui_textbox_right_select },
1950 { 0, SDLK_DOWN, _ui_textbox_down },
1951 { 0, SDLK_UP, _ui_textbox_up },
1952 { 0, SDLK_BACKSPACE, _ui_textbox_backspace },
1953 { KMOD_SHIFT, SDLK_BACKSPACE, _ui_textbox_backspace },
1954 { KMOD_CTRL, SDLK_BACKSPACE, _ui_textbox_backspace },
1955 { 0, SDLK_DELETE, _ui_textbox_delete },
1956 { 0, SDLK_HOME, _ui_textbox_home },
1957 { KMOD_SHIFT, SDLK_HOME, _ui_textbox_home_select },
1958 { 0, SDLK_END, _ui_textbox_end },
1959 { KMOD_SHIFT, SDLK_END, _ui_textbox_end_select },
1960 { KMOD_CTRL, SDLK_a, _ui_textbox_select_all },
1961 { KMOD_CTRL, SDLK_c, _ui_textbox_to_clipboard },
1962 { KMOD_CTRL, SDLK_x, _ui_textbox_cut },
1963 { KMOD_CTRL, SDLK_v, _ui_textbox_clipboard_paste },
1964 { 0, SDLK_RETURN, _ui_textbox_enter },
1965 { 0, SDLK_ESCAPE, ui_defocus_all },
1966 };
1967
1968 SDL_Keymod mod = 0;
1969
1970 if( ev.mod & KMOD_SHIFT )
1971 mod |= KMOD_SHIFT;
1972
1973 if( ev.mod & KMOD_CTRL )
1974 mod |= KMOD_CTRL;
1975
1976 if( ev.mod & KMOD_ALT )
1977 mod |= KMOD_ALT;
1978
1979 for( int i=0; i<vg_list_size( mappings ); i++ ){
1980 struct textbox_mapping *mapping = &mappings[i];
1981
1982 if( mapping->key == ev.sym ){
1983 if( mapping->mod == 0 ){
1984 if( mod == 0 ){
1985 mapping->handler();
1986 return;
1987 }
1988 }
1989 else if( (mod & mapping->mod) == mapping->mod ){
1990 mapping->handler();
1991 return;
1992 }
1993 }
1994 }
1995 }
1996
1997 /*
1998 * Callback for text entry mode
1999 */
2000 VG_STATIC void ui_proc_utf8( const char *text )
2001 {
2002 if( vg_ui.focused_control_type == k_ui_control_textbox ){
2003 const char *ptr = text;
2004
2005 while( *ptr ){
2006 if( *ptr != '`' ) _ui_textbox_put_char( *ptr );
2007 ptr ++;
2008 }
2009
2010 if( vg_ui.textbox.callbacks.change ){
2011 vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len );
2012 }
2013 }
2014 }
2015
2016 static void ui_label( ui_rect rect, const char *text, ui_px size,
2017 ui_px gap, ui_rect r )
2018 {
2019 ui_rect l;
2020 ui_px width = (ui_text_line_width(text)+UI_GLYPH_SPACING_X) * size;
2021 ui_split( rect, k_ui_axis_v, width, gap, l, r );
2022 ui_text( l, text, 1, k_ui_align_middle_left, 0 );
2023 }
2024
2025 #endif /* VG_IMGUI_H */