deda2fce05ca1b2e955984c0516835c39632bc87
[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 */
10
11
12 #ifndef VG_IMGUI_H
13 #define VG_IMGUI_H
14
15 #define VG_GAME
16 #include "vg/vg.h"
17 #include "vg/vg_tex.h"
18 #include "vg/vg_shader.h"
19
20 typedef i16 ui_px;
21 typedef ui_px ui_rect[4];
22 typedef struct ui_vert ui_vert;
23
24 enum ui_axis {
25 k_ui_axis_h = 0x0u,
26 k_ui_axis_v = 0x1u,
27 };
28
29 /* Relative to cursor p0 */
30 enum ui_align
31 { /* DC BA */
32 k_ui_align_left = 0x0000| 0x00,
33 k_ui_align_right = 0x0000| 0x01,
34 k_ui_align_center = 0x0000| 0x02,
35
36 k_ui_align_middle = 0x0100,
37 k_ui_align_middle_left = 0x0100| 0x00,
38 k_ui_align_middle_right = 0x0100| 0x01,
39 k_ui_align_middle_center = 0x0100| 0x02,
40
41 k_ui_align_bottom = 0x0200,
42 k_ui_align_bottom_left = 0x0200| 0x00,
43 k_ui_align_bottom_right = 0x0200| 0x01,
44 k_ui_align_bottom_center = 0x0200| 0x02,
45 };
46
47 #pragma pack(push,1)
48 struct ui_vert
49 {
50 ui_px co[2];
51 u8 uv[2];
52 u32 colour;
53 };
54 #pragma pack(pop)
55
56 enum ui_scheme_colour{
57 k_ui_bg = 0,
58 k_ui_fg = 8,
59 k_ui_hue = 16,
60 k_ui_red = 16,
61 k_ui_orange,
62 k_ui_yellow,
63 k_ui_green,
64 k_ui_aqua,
65 k_ui_blue,
66 k_ui_purple,
67 k_ui_gray,
68 k_ui_brighter = 8
69 };
70
71 typedef u32 ui_scheme[8*4];
72
73 #define UI_RGB( STDHEX ) 0xff000000 |\
74 ((STDHEX&0x000000ff)<<16) |\
75 ((STDHEX&0x0000ff00) ) |\
76 ((STDHEX&0x00ff0000)>>16)
77
78 struct{
79 struct ui_vert *vertex_buffer;
80 u16 *indice_buffer;
81 u32 max_verts, max_indices,
82 cur_vert, cur_indice,
83 vert_start, indice_start;
84
85 void *focused_control_id; /* uses the memory location of various locking
86 controls as an id */
87 u32 focused_control_hit;
88 enum ui_control_type{
89 k_ui_control_none,
90 k_ui_control_textbox,
91 }
92 focused_control_type;
93
94 GLuint tex_glyphs, vao, vbo, ebo;
95
96 ui_px mouse[2], mouse_click[2];
97 u32 mouse_state[2];
98 u32 ignore_input_frames;
99
100 ui_rect click_fader;
101 float click_fade_opacity;
102
103 ui_scheme scheme;
104 }
105 static vg_ui = {
106 .scheme = {
107 [ k_ui_bg+0 ] = UI_RGB( 0x1d2021 ),
108 [ k_ui_bg+1 ] = UI_RGB( 0x282828 ),
109 [ k_ui_bg+2 ] = UI_RGB( 0x3c3836 ),
110 [ k_ui_bg+3 ] = UI_RGB( 0x504945 ),
111 [ k_ui_bg+4 ] = UI_RGB( 0x665c54 ),
112 [ k_ui_bg+5 ] = UI_RGB( 0x7c6f64 ),
113 [ k_ui_bg+6 ] = UI_RGB( 0x928374 ),
114 [ k_ui_bg+7 ] = UI_RGB( 0xa89984 ),
115
116 [ k_ui_fg+0 ] = UI_RGB( 0xebdbb2 ),
117 [ k_ui_fg+1 ] = UI_RGB( 0xfbf1c7 ),
118 [ k_ui_fg+2 ] = UI_RGB( 0xd5c4a1 ),
119 [ k_ui_fg+3 ] = UI_RGB( 0xbdae93 ),
120 [ k_ui_fg+4 ] = UI_RGB( 0xa89984 ),
121 [ k_ui_fg+5 ] = UI_RGB( 0x000000 ),
122 [ k_ui_fg+6 ] = UI_RGB( 0x000000 ),
123 [ k_ui_fg+7 ] = UI_RGB( 0x000000 ),
124
125 [ k_ui_red ] = UI_RGB( 0xcc241d ),
126 [ k_ui_orange ] = UI_RGB( 0xd65d0e ),
127 [ k_ui_yellow ] = UI_RGB( 0xd79921 ),
128 [ k_ui_green ] = UI_RGB( 0x98971a ),
129 [ k_ui_aqua ] = UI_RGB( 0x689d6a ),
130 [ k_ui_blue ] = UI_RGB( 0x458588 ),
131 [ k_ui_purple ] = UI_RGB( 0xb16286 ),
132 [ k_ui_gray ] = UI_RGB( 0x928374 ),
133 [ k_ui_red + k_ui_brighter ] = UI_RGB( 0xfb4934 ),
134 [ k_ui_orange + k_ui_brighter ] = UI_RGB( 0xfe8019 ),
135 [ k_ui_yellow + k_ui_brighter ] = UI_RGB( 0xfabd2f ),
136 [ k_ui_green + k_ui_brighter ] = UI_RGB( 0xb8bb26 ),
137 [ k_ui_aqua + k_ui_brighter ] = UI_RGB( 0x8ec07c ),
138 [ k_ui_blue + k_ui_brighter ] = UI_RGB( 0x83a598 ),
139 [ k_ui_purple + k_ui_brighter ] = UI_RGB( 0xd3869b ),
140 [ k_ui_gray + k_ui_brighter ] = UI_RGB( 0xa89984 ),
141 }
142 };
143
144 static struct vg_shader _shader_ui =
145 {
146 .name = "[vg] ui",
147 .link = NULL,
148 .vs =
149 {
150 .orig_file = NULL,
151 .static_src =
152 "layout (location=0) in vec2 a_co;"
153 "layout (location=1) in vec2 a_uv;"
154 "layout (location=2) in vec4 a_colour;"
155 "uniform mat3 uPv;"
156 ""
157 "out vec2 aTexCoords;"
158 "out vec4 aColour;"
159 "out vec2 aWsp;"
160 ""
161 "void main()"
162 "{"
163 "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
164 "aTexCoords = a_uv * 0.0078125;"
165 "aColour = a_colour;"
166
167 "aWsp = a_co;"
168 "}",
169 },
170 .fs =
171 {
172 .orig_file = NULL,
173 .static_src =
174 "uniform sampler2D uTexGlyphs;"
175 "out vec4 FragColor;"
176 ""
177 "in vec2 aTexCoords;"
178 "in vec4 aColour;"
179 ""
180 "in vec2 aWsp;"
181
182 "vec2 rand_hash22( vec2 p )"
183 "{"
184 "vec3 p3 = fract(vec3(p.xyx) * 213.8976123);"
185 "p3 += dot(p3, p3.yzx+19.19);"
186 "return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));"
187 "}"
188 ""
189 "void main()"
190 "{"
191 "vec4 diffuse = aColour;"
192
193 "if( diffuse.a == 0.0 )"
194 "{"
195 "diffuse.a = texture( uTexGlyphs, aTexCoords ).r;"
196 "}"
197
198 "FragColor = diffuse;"
199 "}"
200 }
201 };
202
203 static struct vg_shader _shader_ui_image =
204 {
205 .name = "[vg] ui_image",
206 .link = NULL,
207 .vs =
208 {
209 .orig_file = NULL,
210 .static_src =
211 "layout (location=0) in vec2 a_co;"
212 "layout (location=1) in vec2 a_uv;"
213 "layout (location=2) in vec4 a_colour;"
214 "uniform mat3 uPv;"
215
216 "out vec2 aTexCoords;"
217 "out vec4 aColour;"
218 "out vec2 aWsp;"
219
220 "void main()"
221 "{"
222 "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
223 "aTexCoords = a_uv * 0.0078125;"
224 "aColour = a_colour;"
225
226 "aWsp = a_co;"
227 "}",
228 },
229 .fs =
230 {
231 .orig_file = NULL,
232 .static_src =
233 "uniform sampler2D uTexImage;"
234 "out vec4 FragColor;"
235
236 "in vec2 aTexCoords;"
237 "in vec4 aColour;"
238 "in vec2 aWsp;"
239
240 "void main()"
241 "{"
242 "vec4 colour = texture( uTexImage, aTexCoords );"
243
244 /* wtf is this?? */
245 #if 0
246 "float value = dot(vec4(1.0),colour)*0.25;"
247
248 "vec3 col = vec3(pow(cos(value*3.14159265*2.0)*0.5+0.5,0.5))"
249 "* vec3(step(value,0.5),0.3,step(1.0-value,0.5));"
250 "FragColor = vec4( col*4.0, 1.0 );"
251 #endif
252
253 "FragColor = colour;"
254 "}"
255 }
256 };
257 #define UI_GLYPH_SPACING_X 8
258
259 VG_STATIC void _vg_ui_init(void)
260 {
261 if( !vg_shader_compile( &_shader_ui ) ||
262 !vg_shader_compile( &_shader_ui_image ) )
263 vg_fatal_error( "Failed to compile ui shader" );
264
265 /*
266 * Vertex buffer
267 * ----------------------------------------
268 */
269
270 vg_ui.max_indices = 20000;
271 vg_ui.max_verts = 30000;
272
273 /* Generate the buffer we are gonna be drawing to */
274 glGenVertexArrays( 1, &vg_ui.vao );
275 glGenBuffers( 1, &vg_ui.vbo );
276 glGenBuffers( 1, &vg_ui.ebo );
277
278 glBindVertexArray( vg_ui.vao );
279 glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo );
280
281 glBufferData( GL_ARRAY_BUFFER,
282 vg_ui.max_verts * sizeof( struct ui_vert ),
283 NULL, GL_DYNAMIC_DRAW );
284 glBindVertexArray( vg_ui.vao );
285
286 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo );
287 glBufferData( GL_ELEMENT_ARRAY_BUFFER,
288 vg_ui.max_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW );
289
290 VG_CHECK_GL_ERR();
291
292 /* Set pointers */
293 u32 const stride = sizeof( struct ui_vert );
294
295 /* XY */
296 glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride,
297 (void *)offsetof( struct ui_vert, co ) );
298 glEnableVertexAttribArray( 0 );
299
300 /* UV */
301 glVertexAttribPointer( 1, 2, GL_UNSIGNED_BYTE, GL_FALSE, stride,
302 (void *)offsetof( struct ui_vert, uv ) );
303 glEnableVertexAttribArray( 1 );
304
305 /* COLOUR */
306 glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride,
307 (void *)offsetof( struct ui_vert, colour ) );
308 glEnableVertexAttribArray( 2 );
309
310 VG_CHECK_GL_ERR();
311
312 /* Alloc RAM default context */
313 u32 vert_size = vg_ui.max_verts*sizeof(struct ui_vert),
314 inds_size = vg_align8( vg_ui.max_indices*sizeof(u16) );
315
316 vg_ui.vertex_buffer = vg_linear_alloc( vg_mem.rtmemory, vert_size );
317 vg_ui.indice_buffer = vg_linear_alloc( vg_mem.rtmemory, inds_size );
318
319 /* font
320 * -----------------------------------------------------
321 */
322
323 /* Load default font */
324 u32 compressed[] = {
325 #include "vg/vg_pxfont_thin.h"
326 };
327
328 u32 pixels = 0, total = 256*256, data = 0;
329 u8 image[256*256];
330
331 while( pixels < total ){
332 for( int b = 31; b >= 0; b-- ){
333 image[ pixels ++ ] = (compressed[data] & (0x1u << b))? 0xffu: 0x00u;
334
335 if( pixels >= total ){
336 total = 0;
337 break;
338 }
339 }
340 data++;
341 }
342
343 glGenTextures( 1, &vg_ui.tex_glyphs );
344 glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs );
345 glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, 256, 256, 0,
346 GL_RED, GL_UNSIGNED_BYTE, image );
347
348 VG_CHECK_GL_ERR();
349 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
350 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
351 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
352 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
353 }
354
355 enum ui_shader {
356 k_ui_shader_colour,
357 k_ui_shader_image
358 };
359
360 static void rect_copy( ui_rect a, ui_rect b )
361 {
362 for( int i=0; i<4; i++ )
363 b[i] = a[i];
364 }
365
366 VG_STATIC void ui_flush( enum ui_shader shader )
367 {
368 u32 vertex_offset = vg_ui.vert_start*sizeof(ui_vert),
369 vertex_count = vg_ui.cur_vert-vg_ui.vert_start,
370 vertex_size = vertex_count*sizeof(ui_vert),
371
372 indice_offset = vg_ui.indice_start*sizeof(u16),
373 indice_count = vg_ui.cur_indice-vg_ui.indice_start,
374 indice_size = indice_count * sizeof(u16);
375
376 if( !vertex_size || !indice_size )
377 return;
378
379 glBindVertexArray( vg_ui.vao );
380 glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo );
381 glBufferSubData( GL_ARRAY_BUFFER, vertex_offset, vertex_size,
382 vg_ui.vertex_buffer+vg_ui.vert_start );
383
384 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo );
385 glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, indice_offset, indice_size,
386 vg_ui.indice_buffer+vg_ui.indice_start );
387
388 glEnable( GL_BLEND );
389 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
390 glBlendEquation( GL_FUNC_ADD );
391
392 m3x3f view = M3X3_IDENTITY;
393 m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
394 m3x3_scale( view, (v3f){ 1.0f/((float)vg.window_x*0.5f),
395 -1.0f/((float)vg.window_y*0.5f), 1.0f } );
396
397 if( shader == k_ui_shader_colour ){
398 glUseProgram( _shader_ui.id );
399
400 glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1,
401 GL_FALSE, (float *)view );
402
403 glActiveTexture( GL_TEXTURE0 );
404 glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs );
405 glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 );
406 }
407 else if( shader == k_ui_shader_image ){
408 glUseProgram( _shader_ui_image.id );
409 glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1,
410 GL_FALSE, (float *)view );
411 glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 );
412 }
413 else
414 vg_fatal_error( "Invalid UI shader (%d)\n", shader );
415
416 glDrawElements( GL_TRIANGLES, indice_count, GL_UNSIGNED_SHORT,
417 (void *)(vg_ui.indice_start*sizeof(u16)) );
418
419 glDisable( GL_BLEND );
420
421 vg_ui.indice_start = vg_ui.cur_indice;
422 vg_ui.vert_start = vg_ui.cur_vert;
423 }
424
425 static void ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] )
426 {
427 /* this if far from ideal but stops us from crashing */
428 if( (vg_ui.cur_vert + 4 > vg_ui.max_verts) ||
429 (vg_ui.cur_indice + 6 > vg_ui.max_indices))
430 return;
431
432 struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ];
433 u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ];
434
435 for( int i=0; i<4; i++ ){
436 vertices[i].colour = colour;
437 }
438
439 vertices[0].co[0] = rect[0];
440 vertices[0].co[1] = rect[1];
441 vertices[0].uv[0] = uv[0];
442 vertices[0].uv[1] = uv[1];
443 vertices[1].co[0] = rect[0]+rect[2];
444 vertices[1].co[1] = rect[1];
445 vertices[1].uv[0] = uv[2];
446 vertices[1].uv[1] = uv[1];
447 vertices[2].co[0] = rect[0]+rect[2];
448 vertices[2].co[1] = rect[1]+rect[3];
449 vertices[2].uv[0] = uv[2];
450 vertices[2].uv[1] = uv[3];
451 vertices[3].co[0] = rect[0];
452 vertices[3].co[1] = rect[1]+rect[3];
453 vertices[3].uv[0] = uv[0];
454 vertices[3].uv[1] = uv[3];
455 u16 ind_start = vg_ui.cur_vert;
456
457 u16 start = vg_ui.cur_vert;
458 u32 mesh[] = { 0,2,1, 0,3,2 };
459
460 for( u32 i=0; i<vg_list_size(mesh); i++ ){
461 indices[i] = start+mesh[i];
462 }
463
464 vg_ui.cur_indice += 6;
465 vg_ui.cur_vert += 4;
466 }
467
468 static void ui_fill( ui_rect rect, u32 colour )
469 {
470 ui_fill_rect( rect, colour, (ui_px[4]){ 4,4,4,4 } );
471 }
472
473 static void ui_outline( ui_rect rect, ui_px thickness, u32 colour )
474 {
475 /* this if far from ideal but stops us from crashing */
476 if( (vg_ui.cur_vert + 8 > vg_ui.max_verts) ||
477 (vg_ui.cur_indice + 24 > vg_ui.max_indices))
478 return;
479
480 struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ];
481 u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ];
482
483 for( int i=0; i<8; i++ ){
484 vertices[i].uv[0] = 4;
485 vertices[i].uv[1] = 4;
486 vertices[i].colour = colour;
487 }
488
489 vertices[0].co[0] = rect[0];
490 vertices[0].co[1] = rect[1];
491 vertices[1].co[0] = rect[0]+rect[2];
492 vertices[1].co[1] = rect[1];
493 vertices[2].co[0] = rect[0]+rect[2];
494 vertices[2].co[1] = rect[1]+rect[3];
495 vertices[3].co[0] = rect[0];
496 vertices[3].co[1] = rect[1]+rect[3];
497 vertices[4].co[0] = vertices[0].co[0]-thickness;
498 vertices[4].co[1] = vertices[0].co[1]-thickness;
499 vertices[5].co[0] = vertices[1].co[0]+thickness;
500 vertices[5].co[1] = vertices[1].co[1]-thickness;
501 vertices[6].co[0] = vertices[2].co[0]+thickness;
502 vertices[6].co[1] = vertices[2].co[1]+thickness;
503 vertices[7].co[0] = vertices[3].co[0]-thickness;
504 vertices[7].co[1] = vertices[3].co[1]+thickness;
505
506 u16 start = vg_ui.cur_vert;
507 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 };
508
509 for( u32 i=0; i<vg_list_size(mesh); i++ ){
510 indices[i] = start+mesh[i];
511 }
512
513 vg_ui.cur_indice += 24;
514 vg_ui.cur_vert += 8;
515 }
516
517 static void ui_split_px( ui_rect rect,
518 enum ui_axis other, ui_px width, ui_px pad,
519 ui_rect l, ui_rect r )
520 {
521 enum ui_axis dir = other ^ 0x1;
522
523 ui_rect temp;
524 rect_copy( rect, temp );
525
526 l[ dir ] = temp[ dir ] + pad;
527 r[ dir ] = temp[ dir ] + width + (pad/2);
528 l[ other ] = temp[ other ] + pad;
529 r[ other ] = temp[ other ] + pad;
530 l[ 2+dir ] = width - ((3*pad)/2);
531 r[ 2+dir ] = temp[ 2+dir ] - width - ((3*pad)/2);
532 l[ 2+other ] = temp[ 2+other ] - pad*2;
533 r[ 2+other ] = temp[ 2+other ] - pad*2;
534 }
535
536 static void ui_rect_center( ui_rect parent, ui_rect rect )
537 {
538 rect[0] = parent[0] + (parent[2]-rect[2])/2;
539 rect[1] = parent[1] + (parent[3]-rect[3])/2;
540 }
541
542 static void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d )
543 {
544 i32 rp = (i32)rect[2] * (i32)size[1],
545 rc = (i32)size[0] * (i32)rect[3];
546
547 enum ui_axis dir, other;
548 if( rc > rp ) dir = k_ui_axis_h;
549 else dir = k_ui_axis_v;
550 other = dir ^ 0x1;
551
552 d[2+dir] = rect[2+dir];
553 d[2+other] = (rect[2+dir] * size[other]) / size[dir];
554
555 ui_rect_center( rect, d );
556 }
557
558 static void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio,
559 ui_px pad, ui_rect l, ui_rect r )
560 {
561 ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio;
562 ui_split_px( rect, dir, width, pad, l, r );
563 }
564
565 static void ui_rect_pad( ui_rect rect, ui_px pad )
566 {
567 rect[0] += pad;
568 rect[1] += pad;
569 rect[2] -= pad*2;
570 rect[3] -= pad*2;
571 }
572
573 static ui_px ui_text_line_width( const char *str )
574 {
575 int length = 0;
576 const char *_c = str;
577 char c;
578
579 while( (c = *(_c ++)) ){
580 if( c >= 32 && c <= 126 )
581 length ++;
582 else if( c == '\n' )
583 break;
584 }
585
586 return length * 8;
587 }
588
589 static ui_px ui_text_string_height( const char *str )
590 {
591 int height = 1;
592 const char *_c = str;
593 char c;
594
595 while( (c = *(_c ++)) ){
596 if( c == '\n' ) height ++;
597 }
598
599 return height * 14;
600 }
601
602 static ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale,
603 enum ui_align align )
604 {
605 if( align == k_ui_align_left ){
606 return rect[0];
607 }
608 else{
609 ui_px width = ui_text_line_width( str ) * scale;
610
611 if( align == k_ui_align_right )
612 return rect[0] + rect[2]-width;
613 else
614 return rect[0] + (rect[2]-width)/2;
615 }
616 }
617
618 static ui_px ui_min( ui_px a, ui_px b ){ return a<b?a:b; }
619 static ui_px ui_max( ui_px a, ui_px b ){ return a>b?a:b; }
620 static ui_px ui_clamp( ui_px a, ui_px min, ui_px max )
621 {
622 return ui_min( max, ui_max( a, min ) );
623 }
624
625 static int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped )
626 {
627 ui_px parent_max[2], child_max[2];
628 parent_max[0] = parent[0]+parent[2];
629 parent_max[1] = parent[1]+parent[3];
630 child_max[0] = child[0]+child[2];
631 child_max[1] = child[1]+child[3];
632
633 clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] );
634 clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] );
635 clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] );
636 clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] );
637
638 if( clipped[0] == clipped[2] ||
639 clipped[1] == clipped[3] )
640 return 0;
641
642 clipped[2] -= clipped[0];
643 clipped[3] -= clipped[1];
644
645 return 1;
646 }
647
648 static int ui_inside_rect( ui_rect rect, ui_px co[2] )
649 {
650 if( co[0] >= rect[0] &&
651 co[1] >= rect[1] &&
652 co[0] <= rect[0]+rect[2] &&
653 co[1] <= rect[1]+rect[3] )
654 {
655 return 1;
656 }
657 else
658 return 0;
659 }
660
661 static int ui_click_down(void)
662 {
663 if( vg_ui.ignore_input_frames ) return 0;
664
665 if( (vg_ui.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT)) &&
666 !(vg_ui.mouse_state[1] & SDL_BUTTON(SDL_BUTTON_LEFT)) )
667 return 1;
668 else
669 return 0;
670 }
671
672 static int ui_clicking(void)
673 {
674 if( vg_ui.ignore_input_frames ) return 0;
675 return vg_ui.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT);
676 }
677
678 static int ui_click_up(void)
679 {
680 if( vg_ui.ignore_input_frames ) return 0;
681 if( (vg_ui.mouse_state[1] & SDL_BUTTON(SDL_BUTTON_LEFT)) &&
682 !(vg_ui.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT)) )
683 return 1;
684 else
685 return 0;
686 }
687
688 static void ui_prerender(void)
689 {
690 int x, y;
691 vg_ui.mouse_state[1] = vg_ui.mouse_state[0];
692 vg_ui.mouse_state[0] = SDL_GetMouseState( &x, &y );
693 vg_ui.mouse[0] = x;
694 vg_ui.mouse[1] = y;
695
696 vg_ui.cur_vert = 0;
697 vg_ui.cur_indice = 0;
698 vg_ui.vert_start = 0;
699 vg_ui.indice_start = 0;
700 vg_ui.focused_control_hit = 0;
701
702 if( vg_ui.ignore_input_frames ){
703 vg_ui.ignore_input_frames --;
704 return;
705 }
706
707 if( ui_click_down() ){
708 vg_ui.mouse_click[0] = vg_ui.mouse[0];
709 vg_ui.mouse_click[1] = vg_ui.mouse[1];
710 }
711 }
712
713 static u32 ui_colour( enum ui_scheme_colour id )
714 {
715 return vg_ui.scheme[ id ];
716 }
717
718 static void ui_text( ui_rect rect, const char *str, ui_px scale,
719 enum ui_align align )
720 {
721 ui_rect text_cursor;
722 u32 colour = ui_colour( k_ui_fg ) & 0x00ffffff;
723
724 const char *_c = str;
725 u8 c;
726
727 text_cursor[0] = ui_text_aligned_x( str, rect, scale, align );
728 text_cursor[1] = rect[1];
729 text_cursor[2] = 8*scale;
730 text_cursor[3] = 14*scale;
731
732 if( align & (k_ui_align_middle|k_ui_align_bottom) ){
733 ui_px height = ui_text_string_height( str ) * scale;
734
735 if( align & k_ui_align_bottom )
736 text_cursor[1] += rect[3]-height;
737 else
738 text_cursor[1] += (rect[3]-height)/2;
739 }
740
741 while( (c = *(_c ++)) ){
742 if( c == '\n' ){
743 text_cursor[1] += 14*scale;
744 text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align );
745 continue;
746 }
747 else if( c >= 33 ){
748 u8 glyph_base[2];
749 u8 glyph_index = c;
750 glyph_base[0] = glyph_index & 0xf;
751 glyph_base[1] = (glyph_index-glyph_base[0])>>4;
752
753 glyph_base[0] *= 8;
754 glyph_base[1] *= 8;
755
756 ui_rect rect_char;
757
758 if( ui_clip( rect, text_cursor, rect_char ) ){
759 ui_fill_rect( rect_char, colour, (ui_px[4])
760 {
761 glyph_base[0]+2,
762 glyph_base[1]+1,
763 glyph_base[0]+6,
764 glyph_base[1]+8
765 });
766 }
767 }
768 else if( c == '\x1B' ){
769 /* vt codes */
770 _c ++;
771 u16 colour_id = 0;
772 for( int i=0; i<3; i ++ ){
773 if( _c[i] ){
774 if( _c[i] == 'm' ){
775 _c = _c + i + 1;
776
777 switch( colour_id ){
778 case '0': colour = ui_colour( k_ui_fg ); break;
779 case '3'|'0'<<8: colour = ui_colour( k_ui_bg ); break;
780 case '3'|'1'<<8: colour = ui_colour( k_ui_red ); break;
781 case '3'|'2'<<8: colour = ui_colour( k_ui_green ); break;
782 case '3'|'3'<<8: colour = ui_colour( k_ui_yellow ); break;
783 case '3'|'4'<<8: colour = ui_colour( k_ui_blue ); break;
784 case '3'|'5'<<8: colour = ui_colour( k_ui_purple ); break;
785 case '3'|'6'<<8: colour = ui_colour( k_ui_aqua ); break;
786 case '3'|'7'<<8: colour = ui_colour( 0xffffffff ); break;
787 }
788
789 colour &= 0x00ffffff;
790 break;
791 }
792
793 colour_id |= _c[i] << (i*8);
794 }
795 else{
796 _c = _c +i;
797 break;
798 }
799 }
800
801 continue;
802 }
803 else if( c == '\t' ){
804 text_cursor[0] += UI_GLYPH_SPACING_X*scale*4;
805 continue;
806 }
807
808 text_cursor[0] += UI_GLYPH_SPACING_X*scale;
809 }
810 }
811
812 static void ui_image( ui_rect rect, GLuint image )
813 {
814 ui_flush( k_ui_shader_colour );
815
816 glActiveTexture( GL_TEXTURE0 );
817 glBindTexture( GL_TEXTURE_2D, image );
818 ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,0, 255,255 } );
819 ui_flush( k_ui_shader_image );
820 }
821
822 static u32 v4f_u32_colour( v4f colour )
823 {
824 u32 r = colour[0] * 255.0f,
825 g = colour[1] * 255.0f,
826 b = colour[2] * 255.0f,
827 a = colour[3] * 255.0f;
828
829 return r | (g<<8) | (b<<16) | (a<<24);
830 }
831
832 static void ui_defocus_all(void)
833 {
834 if( vg_ui.focused_control_type == k_ui_control_textbox ){
835 SDL_StopTextInput();
836 }
837
838 vg_ui.focused_control_id = NULL;
839 vg_ui.focused_control_hit = 0;
840 vg_ui.focused_control_type = k_ui_control_none;
841 }
842
843 static int ui_button( ui_rect rect, enum ui_scheme_colour colour )
844 {
845 int clickup= ui_click_up(),
846 click = ui_clicking() | clickup,
847 target = ui_inside_rect( rect, vg_ui.mouse_click ) && click,
848 hover = ui_inside_rect( rect, vg_ui.mouse );
849
850 u32 col_base = vg_ui.scheme[ colour ],
851 col_highlight = vg_ui.scheme[ k_ui_fg ],
852 col_hover = vg_ui.scheme[ colour + k_ui_brighter ];
853
854 if( click ){
855 if( target ){
856 if( hover ){
857 if( clickup ){
858 ui_fill( rect, col_highlight );
859 vg_ui.ignore_input_frames = 2;
860 rect_copy( rect, vg_ui.click_fader );
861 vg_ui.click_fade_opacity = 1.0f;
862 ui_defocus_all();
863 return 1;
864 }
865 else{
866 ui_fill( rect, col_highlight );
867 return 0;
868 }
869 }
870 else{
871 ui_fill( rect, col_base );
872 ui_outline( rect, 1, col_highlight );
873 return 0;
874 }
875 }
876 else{
877 ui_fill( rect, col_base );
878 return 0;
879 }
880 }
881 else{
882 if( hover ){
883 ui_fill( rect, col_hover );
884 return 0;
885 }
886 else{
887 ui_fill( rect, col_base );
888 return 0;
889 }
890 }
891 }
892
893 static int ui_button_text( ui_rect rect, const char *string, ui_px scale,
894 enum ui_scheme_colour colour )
895 {
896 int result = ui_button( rect, colour );
897 ui_rect t = { 0,0, ui_text_line_width( string )*scale, 14*scale };
898 ui_rect_center( rect, t );
899 ui_text( t, string, scale, k_ui_align_left );
900 return result;
901 }
902
903 static void ui_postrender(void)
904 {
905 if( vg_ui.click_fade_opacity > 0.0f ){
906
907 float scale = vg_ui.click_fade_opacity;
908 scale = vg_maxf( 1.0f/255.0f, scale*scale );
909
910 vg_ui.click_fade_opacity -= vg.time_frame_delta * 1.8f;
911 u32 colour = v4f_u32_colour( (v4f){ 1.0f,1.0f,1.0f, scale } );
912
913 ui_rect rect;
914 rect[3] = (float)(vg_ui.click_fader[3]) * scale;
915 rect[2] = vg_ui.click_fader[2];
916 ui_rect_center( vg_ui.click_fader, rect );
917
918 ui_fill( rect, colour );
919 }
920 ui_flush( k_ui_shader_colour );
921
922 if( !vg_ui.focused_control_hit ){
923 ui_defocus_all();
924 }
925 }
926
927 static int ui_textbox( ui_rect rect, char *buf, u32 len )
928 {
929 int clickup= ui_click_up(),
930 click = ui_clicking() | clickup,
931 target = ui_inside_rect( rect, vg_ui.mouse_click ) && click,
932 hover = ui_inside_rect( rect, vg_ui.mouse );
933
934 u32 col_base = vg_ui.scheme[ k_ui_bg ],
935 col_highlight = vg_ui.scheme[ k_ui_fg ];
936
937 ui_px border = -1;
938
939 if( vg_ui.focused_control_id == buf ){
940 ui_fill( rect, col_base );
941 ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ] );
942 vg_ui.focused_control_hit = 1;
943 return 0;
944 }
945
946 if( click ){
947 if( target ){
948 if( hover ){
949 if( clickup ){
950 ui_fill( rect, col_highlight );
951 vg_ui.ignore_input_frames = 2;
952 rect_copy( rect, vg_ui.click_fader );
953 vg_ui.click_fade_opacity = 1.0f;
954 vg_ui.focused_control_id = buf;
955 vg_ui.focused_control_hit = 1;
956 vg_ui.focused_control_type = k_ui_control_textbox;
957 SDL_StartTextInput();
958 }
959 else{
960 ui_fill( rect, col_highlight );
961 }
962 }
963 else{
964 ui_fill( rect, col_base );
965 ui_outline( rect, -1, col_highlight );
966 }
967 }
968 else{
969 ui_fill( rect, col_base );
970 }
971 }
972 else{
973 ui_fill( rect, col_base );
974
975 if( hover ){
976 ui_outline( rect, -1, col_highlight );
977 }
978 }
979
980 return 0;
981 }
982
983 #endif /* VG_IMGUI_H */