minor ui tweaks
[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_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_fill_y( ui_ctx *ctx )
308 {
309 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
310 ctx->cursor[3] = node->rect[3] - (ctx->cursor[1]-node->rect[1]);
311 }
312
313 static void ui_align_right( ui_ctx *ctx )
314 {
315 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
316 ctx->cursor[0] = node->rect[0] + node->rect[2] - ctx->cursor[2];
317 }
318
319 static void ui_align_top( ui_ctx *ctx )
320 {
321 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
322 ctx->cursor[1] = node->rect[1] + node->rect[3] - ctx->cursor[3];
323 }
324
325 static void ui_clamp_rect( ui_rect parent, ui_rect dest )
326 {
327 dest[0] = vg_min( parent[0] + parent[2] - dest[2], dest[0] );
328 dest[1] = vg_min( parent[1] + parent[3] - dest[3], dest[1] );
329 dest[0] = vg_max( parent[0], dest[0] );
330 dest[1] = vg_max( parent[1], dest[1] );
331 }
332
333 static u32 ui_group_id( ui_ctx *ctx, u32 lesser_unique )
334 {
335 return ctx->id_base | lesser_unique;
336 }
337
338 static void ui_capture_mouse( ui_ctx *ctx, u32 id )
339 {
340 u32 group_uid = ui_group_id(ctx,id);
341
342 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
343 node->capture_id = group_uid;
344
345 if( !ctx->capture_lock && node->mouse_over )
346 {
347 ctx->capture_mouse_id = group_uid;
348 }
349 }
350
351 static struct ui_vert *ui_fill_rect_uv( ui_ctx *ctx, ui_rect rect, u32 colour, ui_px uv[4] )
352 {
353 struct ui_vert *vertices = &ctx->verts[ ctx->num_verts ];
354 vertices[0].co[0] = rect[0];
355 vertices[0].co[1] = rect[1];
356 vertices[0].uv[0] = uv[0];
357 vertices[0].uv[1] = uv[1];
358 vertices[0].colour = colour;
359 vertices[1].co[0] = rect[0]+rect[2];
360 vertices[1].co[1] = rect[1];
361 vertices[1].uv[0] = uv[2];
362 vertices[1].uv[1] = uv[1];
363 vertices[1].colour = colour;
364 vertices[2].co[0] = rect[0]+rect[2];
365 vertices[2].co[1] = rect[1]+rect[3];
366 vertices[2].uv[0] = uv[2];
367 vertices[2].uv[1] = uv[3];
368 vertices[2].colour = colour;
369 vertices[3].co[0] = rect[0];
370 vertices[3].co[1] = rect[1]+rect[3];
371 vertices[3].uv[0] = uv[0];
372 vertices[3].uv[1] = uv[3];
373 vertices[3].colour = colour;
374 u16 ind_start = ctx->num_verts;
375 u16 *indices = &ctx->indices[ ctx->num_indices ];
376
377 indices[0] = ind_start+0;
378 indices[1] = ind_start+2;
379 indices[2] = ind_start+1;
380
381 indices[3] = ind_start+0;
382 indices[4] = ind_start+3;
383 indices[5] = ind_start+2;
384
385 ctx->num_indices += 6;
386 ctx->num_verts += 4;
387
388 return vertices;
389 }
390
391 static struct ui_vert *ui_fill_rect( ui_ctx *ctx, ui_rect rect, u32 colour )
392 {
393 return ui_fill_rect_uv( ctx, rect, colour, (ui_px[4]){ 66, 66, 66, 66 } );
394 }
395
396 static void ui_text( ui_ctx *ctx, const char *str, ui_px scale, int alignment )
397 {
398 ui_rect text_cursor;
399
400 text_cursor[0] = ctx->cursor[0];
401 text_cursor[1] = ctx->cursor[1];
402 text_cursor[2] = 7*scale;
403 text_cursor[3] = 7*scale;
404
405 const char *_c = str;
406 char c;
407 while( (c = *(_c ++)) )
408 {
409 if( c == '\n' )
410 {
411 text_cursor[1] += 10*scale;
412 text_cursor[0] = ctx->cursor[0];
413 continue;
414 }
415 else if( c >= 33 && c <= 126 )
416 {
417 u8 glyph_base[2];
418 u8 glyph_index = c - 32;
419 glyph_base[0] = glyph_index%10;
420 glyph_base[1] = (glyph_index-glyph_base[0])/10;
421
422 glyph_base[0] *= 7;
423 glyph_base[1] *= 7;
424
425 ui_fill_rect_uv( ctx, text_cursor, 0xffffffff, (ui_px[4]){glyph_base[0],glyph_base[1],glyph_base[0]+7,glyph_base[1]+7} );
426 }
427
428 text_cursor[0] += 6*scale;
429 }
430 }
431
432 // API control
433 // ====================================================================
434
435 static void ui_begin( ui_ctx *ctx, ui_px res_x, ui_px res_y )
436 {
437 ctx->cursor[0] = 0;
438 ctx->cursor[1] = 0;
439 ctx->cursor[2] = res_x;
440 ctx->cursor[3] = res_y;
441
442 ui_rect_copy( ctx->cursor, ctx->stack[0].rect );
443 ctx->stack[0].mouse_over = 1;
444
445 ctx->stack_count = 1;
446
447 ctx->num_verts = 0;
448 ctx->num_indices = 0;
449 }
450
451 static void ui_resolve( ui_ctx *ctx )
452 {
453 if( ctx->stack_count-1 )
454 vg_exiterr( "[UI] Mismatched node create/drestroy!" );
455
456 if( ctx->click_state == 3 || ctx->click_state == 0 )
457 {
458 ctx->capture_lock = 0;
459 }
460 }
461
462 // User Input piping
463 // ====================================================================
464
465 static void ui_set_mouse( ui_ctx *ctx, int x, int y, int click_state )
466 {
467 ctx->mouse[0] = x;
468 ctx->mouse[1] = y;
469
470 ctx->click_state = click_state;
471 }
472
473 // High level controls
474 // ====================================================================
475
476 struct ui_window
477 {
478 const char *title;
479 ui_rect transform;
480
481 int drag;
482 ui_px drag_offset[2];
483 };
484
485 static int ui_button( ui_ctx *ctx, u32 id )
486 {
487 ui_new_node( ctx );
488 {
489 ui_capture_mouse( ctx, id );
490
491 if( ui_hasmouse(ctx) )
492 {
493 ui_fill_rect( ctx, ctx->cursor, 0xffcccccc );
494
495 if( ctx->click_state == 1 )
496 ctx->capture_lock = 1;
497 else if( ctx->capture_lock && ctx->click_state == 3 )
498 {
499 return 1;
500 }
501 }
502 else
503 ui_fill_rect( ctx, ctx->cursor, 0xff999999 );
504 }
505
506 return 0;
507 }
508
509 static int ui_window( ui_ctx *ctx, struct ui_window *window, u32 control_group )
510 {
511 ctx->id_base = control_group << 16;
512
513 if( window->drag )
514 {
515 window->transform[0] = ctx->mouse[0]+window->drag_offset[0];
516 window->transform[1] = ctx->mouse[1]+window->drag_offset[1];
517
518 ui_clamp_rect( ctx->stack[0].rect, window->transform );
519
520 if( ctx->click_state == 0 || ctx->click_state == 3 )
521 {
522 window->drag = 0;
523 }
524 }
525
526 ui_rect_copy( window->transform, ctx->cursor );
527
528 ui_new_node( ctx );
529 {
530 ui_capture_mouse( ctx, __COUNTER__ );
531
532 //ui_fill_rect( ctx, ctx->cursor, 0xff333333 );
533
534 // Drag bar
535 ctx->cursor[3] = 25;
536 ui_new_node( ctx );
537 {
538 ui_capture_mouse( ctx, __COUNTER__ );
539
540 struct ui_vert *drag_bar = ui_fill_rect( ctx, ctx->cursor, 0xff555555 );
541
542 // title..
543 ctx->cursor[0] += 2;
544 ctx->cursor[1] += 2;
545 ui_text( ctx, window->title, 2, 0 );
546
547 // Close button
548 ctx->cursor[3] = 25;
549 ctx->cursor[2] = 25;
550 ui_align_right( ctx );
551 ui_align_top( ctx );
552 ui_rect_pad( ctx->cursor, 4 );
553
554 if( ui_button( ctx, __COUNTER__ ) )
555 {
556 vg_info( "Click clacked\n" );
557 }
558 ctx->cursor[0] += 2;
559 ui_text( ctx, "x", 2, 0 );
560 ui_end( ctx );
561
562 if( ui_hasmouse( ctx ) )
563 {
564 drag_bar[0].colour = 0xff777777;
565 drag_bar[1].colour = 0xff777777;
566 drag_bar[2].colour = 0xff777777;
567 drag_bar[3].colour = 0xff777777;
568
569 // start drag
570 if( ctx->click_state == 1 )
571 {
572 window->drag = 1;
573 window->drag_offset[0] = window->transform[0]-ctx->mouse[0];
574 window->drag_offset[1] = window->transform[1]-ctx->mouse[1];
575 }
576 }
577 }
578 ui_end_down( ctx );
579 }
580
581 return 1;
582 }
583
584 ui_ctx test_ctx = { .padding = 8 };
585
586 static void ui_test_init(void)
587 {
588 test_ctx.verts = (struct ui_vert *)malloc( UI_BUFFER_SIZE * sizeof(struct ui_vert) );
589 test_ctx.indices = (u16*)malloc( UI_INDEX_SIZE * sizeof(u16) );
590 }
591
592 static void ui_test_free(void)
593 {
594 free( test_ctx.verts );
595 free( test_ctx.indices );
596 }
597
598 static void ui_test(void)
599 {
600 /*
601 +------------------------------------------------------+
602 | Central Market [x]|
603 +------+--------------+-+------------------------------+
604 | Buy | Balance |#| [filters] [favorites] |
605 | <>_ | () 2,356 |#|----------------------------+-+
606 |------|--------------|#| [] potion of madness 4 |#|
607 | Sell | \ Main sword |#|----------------------------|#|
608 | _*^ |--------------|#| [] Balance of time 23 | |
609 |------| * Side arm |#|----------------------------| |
610 | 235 |--------------| | [] Strength 5,300 | |
611 | | () Sheild | |----------------------------| |
612 | |--------------| | [] Bewilder 2,126 | |
613 | [ & Spells ] |----------------------------| |
614 | |--------------| | [] Eternal flames 6 | |
615 +------+--------------+-+----------------------------+-+
616 */
617
618 ui_begin( &test_ctx, vg_window_x, vg_window_y );
619
620 // TODO: Find a more elegent form for this
621 int mouse_state = 0;
622 if( vg_get_button( "primary" ) ) mouse_state = 2;
623 if( vg_get_button_down( "primary" ) ) mouse_state = 1;
624 if( vg_get_button_up( "primary" ) ) mouse_state = 3;
625
626 ui_set_mouse( &test_ctx, vg_mouse[0], vg_mouse[1], mouse_state );
627
628 static struct ui_window window =
629 {
630 .transform = { 20, 20, 500, 350 },
631 .title = "Central Market"
632 };
633
634 if( ui_window( &test_ctx, &window, __COUNTER__ ) )
635 {
636 // Contents
637 //ui_text( &test_ctx, "A slice of heaven. O for awesome, this chocka \nfull cuzzie is as rip-off as a cracker.\nMeanwhile, in behind the bicycle shed, Hercules Morse,\nas big as a horse and Mrs Falani were up to no \ngood with a bunch of crook pikelets. Meanwhile, \nat the black singlet woolshed party, not even au,\nsort your drinking out.", 1, 0 );
638 test_ctx.cursor[2] = 75;
639 ui_fill_y( &test_ctx );
640
641 ui_new_node( &test_ctx );
642 {
643 test_ctx.cursor[3] = 75;
644
645 if( ui_button( &test_ctx, __COUNTER__ ) )
646 vg_info( "Buy\n" );
647 {
648 ui_rect_pad( test_ctx.cursor, 4 );
649 ui_text( &test_ctx, "Buy", 2, 0 );
650 }
651 ui_end_down( &test_ctx );
652
653 if( ui_button( &test_ctx, __COUNTER__ ) )
654 vg_info( "Sell\n" );
655 {
656 ui_rect_pad( test_ctx.cursor, 4 );
657 ui_text( &test_ctx, "Sell", 2, 0 );
658 }
659 ui_end_down( &test_ctx );
660 }
661 ui_end_right( &test_ctx );
662
663 test_ctx.cursor[2] = 200;
664 ui_new_node( &test_ctx );
665 {
666
667 }
668 ui_end_right( &test_ctx );
669 }
670 ui_end( &test_ctx );
671
672 ui_resolve( &test_ctx );
673
674 m3x3f view = M3X3_IDENTITY;
675 m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
676 m3x3_scale( view, (v3f){ 1.0f/((float)vg_window_x*0.5f), -1.0f/((float)vg_window_y*0.5f), 1.0f } );
677 vg_lines_drawall( (float*)view );
678
679 ui_draw( &test_ctx );
680 }