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