create font/ui text system
[fishladder.git] / vg / vg_ui.h
1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
2 /*
3
4 Concurrent UI buffer system
5 Coordinate space:
6
7 0,0 --- +(res:x)
8 |
9 |
10 |
11 +(res:y)
12
13 */
14
15 SHADER_DEFINE( shader_ui,
16
17 // VERTEX
18 "layout (location=0) in vec2 a_co;" // i16, i16, .. ?
19 "layout (location=1) in vec2 a_uv;" // i8, i8
20 "layout (location=2) in vec4 a_colour;" // u32
21 "uniform mat3 uPv;"
22 ""
23 "out vec2 aTexCoords;"
24 "out vec4 aColour;"
25 ""
26 "void main()"
27 "{"
28 "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
29 "aTexCoords = a_uv * 0.01388888888;"
30 "aColour = a_colour;"
31 "}",
32
33 // FRAGMENT
34 "uniform sampler2D uTexGlyphs;"
35 "out vec4 FragColor;"
36 ""
37 "in vec2 aTexCoords;"
38 "in vec4 aColour;"
39 ""
40 "void main()"
41 "{"
42 "vec4 glyph = texture( uTexGlyphs, aTexCoords );"
43 "FragColor = aColour * vec4( 1.0, 1.0, 1.0, glyph.r );"
44 "}"
45 ,
46 UNIFORMS({ "uPv", "uTexGlyphs" })
47 )
48
49 #define UI_AUTO_FILL 0
50
51 // Types
52 // ================================================================
53 typedef i16 ui_px;
54 typedef u32 ui_colour;
55 typedef ui_px ui_rect[4];
56 typedef struct ui_ctx ui_ctx;
57
58 #pragma pack(push,1)
59 struct ui_vert
60 {
61 ui_px co[2];
62 u8 uv[2];
63 u32 colour;
64 };
65 #pragma pack(pop)
66
67 struct ui_ctx
68 {
69 ui_px padding;
70
71 struct ui_qnode
72 {
73 ui_rect rect;
74 ui_colour colour;
75 int mouse_over;
76 int capture_id;
77 }
78 stack[ 32 ];
79
80 struct ui_vert *verts;
81 u32 num_verts;
82 u16 *indices;
83 u32 num_indices;
84
85 ui_rect cursor;
86 u32 stack_count;
87 u32 capture_mouse_id;
88 int capture_lock;
89 u32 id_base;
90
91 // User input
92 ui_px mouse[2];
93 int click_state; // 0: released, 1: on down, 2: pressed, 3: on release
94 };
95
96 // Opengl
97 GLuint ui_glyph_texture;
98 GLuint ui_vao;
99 GLuint ui_vbo;
100 GLuint ui_ebo;
101
102
103
104 #define UI_BUFFER_SIZE 30000
105 #define UI_INDEX_SIZE 20000
106
107 static void ui_glyphs_load( u32 *compressed )
108 {
109 u32 pixels = 0, total = 72*72, data = 0;
110 u8 *image = malloc( total );
111
112 while( pixels < total )
113 {
114 for( int b = 31; b >= 0; b-- )
115 {
116 image[ pixels ++ ] = (compressed[data] & (0x1 << b))? 0xff: 0x00;
117
118 if( pixels >= total )
119 {
120 total = 0;
121 break;
122 }
123 }
124 data++;
125 }
126
127 for( int j = 0; j < 70; j ++ )
128 {
129 for( int i = 0; i < 70; i ++ )
130 printf( "%s",image[j*72+i]?"#":" " );
131 printf( "\n" );
132 }
133
134 glGenTextures( 1, &ui_glyph_texture );
135 glBindTexture( GL_TEXTURE_2D, ui_glyph_texture );
136
137 glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, 72, 72, 0, GL_RED, GL_UNSIGNED_BYTE, image );
138
139 vg_tex2d_clamp();
140 vg_tex2d_nearest();
141
142 free( image );
143 }
144
145 static void ui_default_init(void)
146 {
147 ui_glyphs_load( (u32[]){
148 #include "fonts/weiholmir.h"
149 });
150
151 SHADER_INIT( shader_ui );
152
153 // Generate the buffer we are gonna be drawing to
154 glGenVertexArrays(1, &ui_vao);
155 glGenBuffers( 1, &ui_vbo );
156 glGenBuffers( 1, &ui_ebo );
157 glBindVertexArray( ui_vao );
158
159 glBindBuffer( GL_ARRAY_BUFFER, ui_vbo );
160
161 glBufferData( GL_ARRAY_BUFFER, UI_BUFFER_SIZE * sizeof( struct ui_vert ), NULL, GL_DYNAMIC_DRAW );
162 glBindVertexArray( ui_vao );
163
164 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ui_ebo );
165 glBufferData( GL_ELEMENT_ARRAY_BUFFER, UI_INDEX_SIZE * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW );
166
167 // XY
168 glVertexAttribPointer( 0, 2, GL_UNSIGNED_SHORT, GL_FALSE, sizeof( struct ui_vert ), (void *)offsetof( struct ui_vert, co ) );
169 glEnableVertexAttribArray( 0 );
170
171 // UV
172 glVertexAttribPointer( 1, 2, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( struct ui_vert ), (void *)offsetof( struct ui_vert, uv ) );
173 glEnableVertexAttribArray( 1 );
174
175 // COLOUR
176 glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( struct ui_vert ), (void *)offsetof( struct ui_vert, colour ) );
177 glEnableVertexAttribArray( 2 );
178 }
179
180 static void ui_default_free(void)
181 {
182 glDeleteTextures( 1, &ui_glyph_texture );
183
184 glDeleteVertexArrays( 1, &ui_vao );
185 glDeleteBuffers( 1, &ui_vbo );
186 glDeleteBuffers( 1, &ui_ebo );
187 }
188
189 static void ui_draw( ui_ctx *ctx )
190 {
191 glBindVertexArray( ui_vao );
192
193 glBindBuffer( GL_ARRAY_BUFFER, ui_vbo );
194 glBufferSubData( GL_ARRAY_BUFFER, 0, ctx->num_verts * sizeof( struct ui_vert ), ctx->verts );
195
196 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ui_ebo );
197 glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, 0, ctx->num_indices * sizeof( u16 ), ctx->indices );
198
199 glEnable(GL_BLEND);
200 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
201 glBlendEquation(GL_FUNC_ADD);
202
203 SHADER_USE( shader_ui );
204
205 m3x3f view = M3X3_IDENTITY;
206 m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
207 m3x3_scale( view, (v3f){ 1.0f/((float)vg_window_x*0.5f), -1.0f/((float)vg_window_y*0.5f), 1.0f } );
208 glUniformMatrix3fv( SHADER_UNIFORM( shader_ui, "uPv" ), 1, GL_FALSE, (float *)view );
209
210 glActiveTexture( GL_TEXTURE0 );
211 glBindTexture( GL_TEXTURE_2D, ui_glyph_texture );
212 glUniform1i( SHADER_UNIFORM( shader_ui, "uTexGlyphs" ), 0 );
213
214 glDrawElements( GL_TRIANGLES, ctx->num_indices, GL_UNSIGNED_SHORT, (void*)(0) );
215
216 //vg_info( "Verts: %u, Indices: %u\n", ctx->num_verts, ctx->num_indices );
217
218 glDisable(GL_BLEND);
219 }
220
221 // Rect controls
222 // ==========================================================
223
224 static void ui_rect_copy( ui_rect src, ui_rect dst )
225 {
226 dst[0] = src[0];
227 dst[1] = src[1];
228 dst[2] = src[2];
229 dst[3] = src[3];
230 }
231
232 static void ui_rect_pad( ui_ctx *ctx, ui_rect rect, ui_px pad )
233 {
234 rect[0] += pad;
235 rect[1] += pad;
236 rect[2] -= pad*2;
237 rect[3] -= pad*2;
238 }
239
240 static void ui_vis_rect( ui_rect rect, u32 colour )
241 {
242 v2f p0;
243 v2f p1;
244
245 p0[0] = rect[0];
246 p0[1] = rect[1];
247 p1[0] = rect[0]+rect[2];
248 p1[1] = rect[1]+rect[3];
249
250 vg_line( p0, (v2f){p1[0],p0[1]}, colour );
251 vg_line( (v2f){p1[0],p0[1]}, p1, colour );
252 vg_line( p1, (v2f){p0[0],p1[1]}, colour );
253 vg_line( (v2f){p0[0],p1[1]}, p0, colour );
254 }
255
256 static void ui_new_node( ui_ctx *ctx )
257 {
258 if( ctx->stack_count == vg_list_size( ctx->stack ) )
259 vg_exiterr( "[UI] Stack overflow while creating box!" );
260
261 struct ui_qnode *parent = &ctx->stack[ ctx->stack_count-1 ];
262 struct ui_qnode *node = &ctx->stack[ ctx->stack_count++ ];
263 ui_rect_copy( ctx->cursor, node->rect );
264
265 if( parent->mouse_over )
266 {
267 if( ctx->mouse[0] >= node->rect[0] && ctx->mouse[0] <= node->rect[0]+node->rect[2] &&
268 ctx->mouse[1] >= node->rect[1] && ctx->mouse[1] <= node->rect[1]+node->rect[3] )
269 node->mouse_over = 1;
270 else
271 node->mouse_over = 0;
272 }
273 else
274 {
275 node->mouse_over = 0;
276 }
277 }
278
279 static int ui_hasmouse( ui_ctx *ctx )
280 {
281 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
282 return (node->mouse_over && (node->capture_id == ctx->capture_mouse_id));
283 }
284
285 static void ui_end( ui_ctx *ctx )
286 {
287 struct ui_qnode *node = &ctx->stack[ --ctx->stack_count ];
288
289 ui_rect_copy( node->rect, ctx->cursor );
290 ui_vis_rect( ctx->cursor, (node->mouse_over && (node->capture_id == ctx->capture_mouse_id))? 0xffff0000: 0xff0000ff );
291 }
292
293 static void ui_end_down( ui_ctx *ctx )
294 {
295 ui_px height = ctx->stack[ ctx->stack_count-1 ].rect[3];
296 ui_end( ctx );
297 ctx->cursor[1] += height;
298 }
299
300 static void ui_end_right( ui_ctx *ctx )
301 {
302 ui_px width = ctx->stack[ ctx->stack_count-1 ].rect[2];
303 ui_end( ctx );
304 ctx->cursor[0] += width;
305 }
306
307 static void ui_align_right( ui_ctx *ctx )
308 {
309 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
310 ctx->cursor[0] = node->rect[0] + node->rect[2] - ctx->cursor[2];
311 }
312
313 static void ui_align_top( ui_ctx *ctx )
314 {
315 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
316 ctx->cursor[1] = node->rect[1] + node->rect[3] - ctx->cursor[3];
317 }
318
319 static void ui_clamp_rect( ui_rect parent, ui_rect dest )
320 {
321 dest[0] = vg_min( parent[0] + parent[2] - dest[2], dest[0] );
322 dest[1] = vg_min( parent[1] + parent[3] - dest[3], dest[1] );
323 dest[0] = vg_max( parent[0], dest[0] );
324 dest[1] = vg_max( parent[1], dest[1] );
325 }
326
327 static u32 ui_group_id( ui_ctx *ctx, u32 lesser_unique )
328 {
329 return ctx->id_base | lesser_unique;
330 }
331
332 static void ui_capture_mouse( ui_ctx *ctx, u32 id )
333 {
334 u32 group_uid = ui_group_id(ctx,id);
335
336 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
337 node->capture_id = group_uid;
338
339 if( !ctx->capture_lock && node->mouse_over )
340 {
341 ctx->capture_mouse_id = group_uid;
342 }
343 }
344
345 static void ui_fill_rect( ui_ctx *ctx, ui_rect rect, u32 colour )
346 {
347 ui_px uv[2];
348 uv[0] = 66;
349 uv[1] = 66;
350
351 struct ui_vert *vertices = &ctx->verts[ ctx->num_verts ];
352 vertices[0].co[0] = rect[0];
353 vertices[0].co[1] = rect[1];
354 vertices[0].uv[0] = uv[0];
355 vertices[0].uv[1] = uv[1];
356 vertices[0].colour = colour;
357 vertices[1].co[0] = rect[0]+rect[2];
358 vertices[1].co[1] = rect[1];
359 vertices[1].uv[0] = uv[0];
360 vertices[1].uv[1] = uv[1];
361 vertices[1].colour = colour;
362 vertices[2].co[0] = rect[0]+rect[2];
363 vertices[2].co[1] = rect[1]+rect[3];
364 vertices[2].uv[0] = uv[0];
365 vertices[2].uv[1] = uv[1];
366 vertices[2].colour = colour;
367 vertices[3].co[0] = rect[0];
368 vertices[3].co[1] = rect[1]+rect[3];
369 vertices[3].uv[0] = uv[0];
370 vertices[3].uv[1] = uv[1];
371 vertices[3].colour = colour;
372 u16 ind_start = ctx->num_verts;
373 u16 *indices = &ctx->indices[ ctx->num_indices ];
374
375 indices[0] = ind_start+0;
376 indices[1] = ind_start+2;
377 indices[2] = ind_start+1;
378
379 indices[3] = ind_start+0;
380 indices[4] = ind_start+3;
381 indices[5] = ind_start+2;
382
383 ctx->num_indices += 6;
384 ctx->num_verts += 4;
385 }
386
387 static void ui_text( ui_ctx *ctx, const char *str, ui_px scale, int alignment )
388 {
389 ui_rect text_cursor;
390
391 text_cursor[0] = ctx->cursor[0];
392 text_cursor[1] = ctx->cursor[1];
393 text_cursor[2] = 7*scale;
394 text_cursor[3] = 7*scale;
395
396 const char *_c = str;
397 char c;
398 while( (c = *(_c ++)) )
399 {
400 if( c == '\n' )
401 {
402 text_cursor[1] += 10*scale;
403 text_cursor[0] = ctx->cursor[0];
404 continue;
405 }
406 else if( c >= 33 && c <= 126 )
407 {
408 u8 glyph_base[2];
409 u8 glyph_index = c - 32;
410 glyph_base[0] = glyph_index%10;
411 glyph_base[1] = (glyph_index-glyph_base[0])/10;
412
413 glyph_base[0] *= 7;
414 glyph_base[1] *= 7;
415
416
417 struct ui_vert *vertices = &ctx->verts[ctx->num_verts];
418 vertices[0].co[0] = text_cursor[0];
419 vertices[0].co[1] = text_cursor[1];
420 vertices[0].uv[0] = glyph_base[0];
421 vertices[0].uv[1] = glyph_base[1];
422 vertices[0].colour = 0xffffffff;
423
424 vertices[1].co[0] = text_cursor[0]+text_cursor[2];
425 vertices[1].co[1] = text_cursor[1];
426 vertices[1].uv[0] = glyph_base[0]+7;
427 vertices[1].uv[1] = glyph_base[1];
428 vertices[1].colour = 0xffffffff;
429
430 vertices[2].co[0] = text_cursor[0]+text_cursor[2];
431 vertices[2].co[1] = text_cursor[1]+text_cursor[3];
432 vertices[2].uv[0] = glyph_base[0]+7;
433 vertices[2].uv[1] = glyph_base[1]+7;
434 vertices[2].colour = 0xffffffff;
435
436 vertices[3].co[0] = text_cursor[0];
437 vertices[3].co[1] = text_cursor[1]+text_cursor[3];
438 vertices[3].uv[0] = glyph_base[0];
439 vertices[3].uv[1] = glyph_base[1]+7;
440 vertices[3].colour = 0xffffffff;
441
442 u16 ind_start = ctx->num_verts;
443 u16 *indices = &ctx->indices[ ctx->num_indices ];
444
445 indices[0] = ind_start+0;
446 indices[1] = ind_start+2;
447 indices[2] = ind_start+1;
448
449 indices[3] = ind_start+0;
450 indices[4] = ind_start+3;
451 indices[5] = ind_start+2;
452
453 ctx->num_indices += 6;
454 ctx->num_verts += 4;
455 }
456
457 text_cursor[0] += 6*scale;
458 }
459 }
460
461 // API control
462 // ====================================================================
463
464 static void ui_begin( ui_ctx *ctx, ui_px res_x, ui_px res_y )
465 {
466 ctx->cursor[0] = 0;
467 ctx->cursor[1] = 0;
468 ctx->cursor[2] = res_x;
469 ctx->cursor[3] = res_y;
470
471 ui_rect_copy( ctx->cursor, ctx->stack[0].rect );
472 ctx->stack[0].mouse_over = 1;
473
474 ctx->stack_count = 1;
475
476 ctx->num_verts = 0;
477 ctx->num_indices = 0;
478 }
479
480 static void ui_resolve( ui_ctx *ctx )
481 {
482 if( ctx->stack_count-1 )
483 vg_exiterr( "[UI] Mismatched node create/drestroy!" );
484
485 if( ctx->click_state == 3 || ctx->click_state == 0 )
486 {
487 ctx->capture_lock = 0;
488 }
489 }
490
491 // User Input piping
492 // ====================================================================
493
494 static void ui_set_mouse( ui_ctx *ctx, int x, int y, int click_state )
495 {
496 ctx->mouse[0] = x;
497 ctx->mouse[1] = y;
498
499 ctx->click_state = click_state;
500 }
501
502 // High level controls
503 // ====================================================================
504
505 struct ui_window
506 {
507 const char *title;
508 ui_rect transform;
509
510 int drag;
511 ui_px drag_offset[2];
512 };
513
514 static int ui_button( ui_ctx *ctx, u32 id )
515 {
516 ui_new_node( ctx );
517 {
518 ui_capture_mouse( ctx, id );
519
520 if( ui_hasmouse(ctx) )
521 {
522 if( ctx->click_state == 1 )
523 ctx->capture_lock = 1;
524 else if( ctx->capture_lock && ctx->click_state == 3 )
525 {
526 return 1;
527 }
528 }
529 }
530
531 return 0;
532 }
533
534 static int ui_window( ui_ctx *ctx, struct ui_window *window, u32 control_group )
535 {
536 ctx->id_base = control_group << 16;
537
538 if( window->drag )
539 {
540 window->transform[0] = ctx->mouse[0]+window->drag_offset[0];
541 window->transform[1] = ctx->mouse[1]+window->drag_offset[1];
542
543 ui_clamp_rect( ctx->stack[0].rect, window->transform );
544
545 if( ctx->click_state == 0 || ctx->click_state == 3 )
546 {
547 window->drag = 0;
548 }
549 }
550
551 ui_rect_copy( window->transform, ctx->cursor );
552
553 ui_new_node( ctx );
554 {
555 ui_capture_mouse( ctx, __COUNTER__ );
556
557 ui_fill_rect( ctx, ctx->cursor, 0xff333333 );
558
559 // Drag bar
560 ctx->cursor[3] = 25;
561 ui_new_node( ctx );
562 {
563 ui_capture_mouse( ctx, __COUNTER__ );
564
565 ui_fill_rect( ctx, ctx->cursor, 0xff555555 );
566
567 // title..
568 ctx->cursor[0] += 2;
569 ctx->cursor[1] += 2;
570 ui_text( ctx, window->title, 2, 0 );
571
572 // Close button
573 ctx->cursor[3] = 25;
574 ctx->cursor[2] = 25;
575 ui_align_right( ctx );
576 ui_align_top( ctx );
577 ui_rect_pad( ctx, ctx->cursor, 4 );
578
579 if( ui_button( ctx, __COUNTER__ ) )
580 {
581 vg_info( "Click clacked\n" );
582 }
583 ui_end( ctx );
584
585 if( ui_hasmouse( ctx ) )
586 {
587 // start drag
588 if( ctx->click_state == 1 )
589 {
590 window->drag = 1;
591 window->drag_offset[0] = window->transform[0]-ctx->mouse[0];
592 window->drag_offset[1] = window->transform[1]-ctx->mouse[1];
593 }
594 }
595 }
596 ui_end_down( ctx );
597 }
598
599 return 1;
600 }
601
602 ui_ctx test_ctx = { .padding = 8 };
603
604 static void ui_test_init(void)
605 {
606 test_ctx.verts = (struct ui_vert *)malloc( UI_BUFFER_SIZE * sizeof(struct ui_vert) );
607 test_ctx.indices = (u16*)malloc( UI_INDEX_SIZE * sizeof(u16) );
608 }
609
610 static void ui_test_free(void)
611 {
612 free( test_ctx.verts );
613 free( test_ctx.indices );
614 }
615
616 static void ui_test(void)
617 {
618 /*
619 +------------------------------------------------------+
620 | Central Market [x]|
621 +------+--------------+-+------------------------------+
622 | Buy | Balance |#| [filters] [favorites] |
623 | <>_ | () 2,356 |#|----------------------------+-+
624 |------|--------------|#| [] potion of madness 4 |#|
625 | Sell | \ Main sword |#|----------------------------|#|
626 | _*^ |--------------|#| [] Balance of time 23 | |
627 |------| * Side arm |#|----------------------------| |
628 | 235 |--------------| | [] Strength 5,300 | |
629 | | () Sheild | |----------------------------| |
630 | |--------------| | [] Bewilder 2,126 | |
631 | [ & Spells ] |----------------------------| |
632 | |--------------| | [] Eternal flames 6 | |
633 +------+--------------+-+----------------------------+-+
634 */
635
636 ui_begin( &test_ctx, vg_window_x, vg_window_y );
637
638 // TODO: Find a more elegent form for this
639 int mouse_state = 0;
640 if( vg_get_button( "primary" ) ) mouse_state = 2;
641 if( vg_get_button_down( "primary" ) ) mouse_state = 1;
642 if( vg_get_button_up( "primary" ) ) mouse_state = 3;
643
644 ui_set_mouse( &test_ctx, vg_mouse[0], vg_mouse[1], mouse_state );
645
646 static struct ui_window window =
647 {
648 .transform = { 20, 20, 500, 350 },
649 .title = "Central Market"
650 };
651
652 if( ui_window( &test_ctx, &window, __COUNTER__ ) )
653 {
654 // Contents
655 ui_text( &test_ctx, "Just say anything, George, say what ever's natural,\nthe first thing that comes to your mind.\nTake that you mutated son-of-a-bitch.", 1, 0 );
656 }
657 ui_end( &test_ctx );
658
659 ui_resolve( &test_ctx );
660
661 m3x3f view = M3X3_IDENTITY;
662 m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
663 m3x3_scale( view, (v3f){ 1.0f/((float)vg_window_x*0.5f), -1.0f/((float)vg_window_y*0.5f), 1.0f } );
664 vg_lines_drawall( (float*)view );
665
666 ui_draw( &test_ctx );
667 }