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