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