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