medium sized dollop
[vg.git] / src / vg / vg_ui.h
1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
2
3 /*
4 * TODO: Get rid of many context design
5 */
6
7 #ifndef VG_UI_H
8 #define VG_UI_H
9
10 #include "vg/vg.h"
11 #include "vg/vg_tex.h"
12 #include "vg/vg_shader.h"
13
14 static struct vg_shader _shader_ui =
15 {
16 .name = "[vg] ui",
17 .link = NULL,
18 .vs =
19 {
20 .orig_file = NULL,
21 .static_src =
22 "layout (location=0) in vec2 a_co;"
23 "layout (location=1) in vec2 a_uv;"
24 "layout (location=2) in vec4 a_colour;"
25 "layout (location=3) in vec4 a_clip;"
26 "uniform mat3 uPv;"
27 ""
28 "out vec2 aTexCoords;"
29 "out vec4 aColour;"
30 "out vec2 aWsp;"
31 "out vec4 aClip;"
32 ""
33 "void main()"
34 "{"
35 "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
36 "aTexCoords = a_uv * 0.0078125;"
37 "aColour = a_colour;"
38
39 "aWsp = a_co;"
40 "aClip = a_clip;"
41 "}",
42 },
43 .fs =
44 {
45 .orig_file = NULL,
46 .static_src =
47 "uniform sampler2D uTexGlyphs;"
48 "out vec4 FragColor;"
49 ""
50 "in vec2 aTexCoords;"
51 "in vec4 aColour;"
52 ""
53 "in vec2 aWsp;"
54 "in vec4 aClip;"
55 ""
56 "void main()"
57 "{"
58 "float clip_blend = step( aWsp.x, aClip.z ) *"
59 "step( aWsp.y, aClip.w ) *"
60 "step( aClip.x, aWsp.x ) *"
61 "step( aClip.y, aWsp.y ); "
62
63 "vec4 glyph = vec4(1.0,1.0,1.0,1.0);"
64
65 "if( aColour.a == 0.0 )"
66 "{"
67 "glyph = texture( uTexGlyphs, aTexCoords );"
68 "glyph.a = smoothstep( 0.47, 0.53, glyph.r );"
69 "}"
70 "else"
71 "{"
72 "glyph.a = aColour.a;"
73 "}"
74
75 "FragColor = vec4( aColour.rgb, glyph.a*clip_blend );"
76 "}"
77 }
78 };
79
80 typedef i16 ui_px;
81 typedef u32 ui_colour;
82 typedef ui_px ui_rect[4];
83 typedef struct ui_ctx ui_ctx;
84 typedef struct ui_colourset ui_colourset;
85
86 /* Relative to cursor p0 */
87 enum ui_text_align
88 {
89 k_text_align_left = 0,
90 k_text_align_right = 1,
91 k_text_align_center = 2
92 };
93
94 struct ui_colourset
95 {
96 union
97 {
98 struct
99 {
100 ui_colour main;
101 ui_colour hover;
102 ui_colour active;
103 };
104 struct
105 {
106 ui_colour background;
107 ui_colour bar;
108 ui_colour bar_hover;
109 };
110 };
111 };
112
113 struct ui_ctx
114 {
115 struct ui_qnode
116 {
117 ui_rect rect;
118 ui_colour colour;
119 int mouse_over;
120 int capture_id;
121 }
122 stack[ 32 ];
123
124 #pragma pack(push,1)
125 struct ui_vert
126 {
127 ui_px co[2];
128 u8 uv[2];
129 u32 colour;
130 ui_rect clip;
131 }
132 *verts;
133 #pragma pack(pop)
134
135 u32 control_id;
136
137 u32 num_verts;
138 u16 *indices;
139 u32 num_indices;
140
141 ui_rect clipping;
142 ui_rect cursor;
143 u32 stack_count;
144 u32 capture_mouse_id;
145 int capture_lock;
146
147
148 /* User input */
149 ui_px mouse[2];
150 int click_state; /* 0: released, 1: on down, 2: pressed, 3: on release */
151
152 ui_colourset *colours;
153
154 GLuint vao;
155 GLuint vbo;
156 GLuint ebo;
157
158 struct ui_image
159 {
160 ui_rect rc;
161 GLuint image;
162 }
163 images[16];
164 int image_count;
165 };
166
167 #define UI_GLYPH_SPACING_X 9
168
169 static GLuint ui_glyph_texture = 0;
170 static ui_colourset ui_default_colours = {
171 .main = 0xff807373,
172 .hover = 0xff918484,
173 .active = 0xffad9f9e
174 };
175
176 static ui_ctx ui_global_ctx;
177
178 static void ui_context_free( ui_ctx *ctx )
179 {
180 glDeleteVertexArrays( 1, &ctx->vao );
181 glDeleteBuffers( 1, &ctx->vbo );
182 glDeleteBuffers( 1, &ctx->ebo );
183
184 free( ctx->verts );
185 free( ctx->indices );
186 }
187
188 static int ui_init_context( ui_ctx *ctx, int index_buffer_size )
189 {
190 u32 vertex_buffer_size = (index_buffer_size+(index_buffer_size/2));
191
192 /* Generate the buffer we are gonna be drawing to */
193 glGenVertexArrays( 1, &ctx->vao );
194 glGenBuffers( 1, &ctx->vbo );
195 glGenBuffers( 1, &ctx->ebo );
196
197 glBindVertexArray( ctx->vao );
198 glBindBuffer( GL_ARRAY_BUFFER, ctx->vbo );
199
200 glBufferData( GL_ARRAY_BUFFER,
201 vertex_buffer_size * sizeof( struct ui_vert ),
202 NULL, GL_DYNAMIC_DRAW );
203 glBindVertexArray( ctx->vao );
204
205 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ctx->ebo );
206 glBufferData( GL_ELEMENT_ARRAY_BUFFER,
207 index_buffer_size * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW );
208
209 if( VG_CHECK_GL_ERR() )
210 goto il_fail_alloc_gpu;
211
212 /* Set pointers */
213 u32 const stride = sizeof( struct ui_vert );
214
215 /* XY */
216 glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE,
217 stride, (void *)offsetof( struct ui_vert, co ) );
218 glEnableVertexAttribArray( 0 );
219
220 /* UV */
221 glVertexAttribPointer( 1, 2, GL_UNSIGNED_BYTE, GL_FALSE,
222 stride, (void *)offsetof( struct ui_vert, uv ) );
223 glEnableVertexAttribArray( 1 );
224
225 /* COLOUR */
226 glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride,
227 (void *)offsetof( struct ui_vert, colour ) );
228 glEnableVertexAttribArray( 2 );
229
230 /* CLIPPING */
231 glVertexAttribPointer( 3, 4, GL_SHORT, GL_FALSE, stride,
232 (void *)offsetof( struct ui_vert, clip ) );
233 glEnableVertexAttribArray( 3 );
234
235 if( VG_CHECK_GL_ERR() )
236 goto il_fail_attributes;
237
238
239 /* Alloc RAM default context */
240 ctx->verts = (struct ui_vert *)malloc(
241 vertex_buffer_size * sizeof(struct ui_vert) );
242 if( !ctx->verts )
243 goto il_fail_alloc_verts;
244
245 ctx->indices = (u16*)malloc( index_buffer_size * sizeof(u16) );
246 if( !ctx->indices )
247 goto il_fail_alloc_indices;
248
249 if( !ctx->colours )
250 ctx->colours = &ui_default_colours;
251
252 return 1;
253
254 free( ctx->indices );
255 ctx->indices = NULL;
256 il_fail_alloc_indices:
257 free( ctx->verts );
258 ctx->verts = NULL;
259 il_fail_alloc_verts:
260 il_fail_attributes:
261 il_fail_alloc_gpu:
262 glDeleteBuffers( 1, &ctx->ebo );
263 glDeleteBuffers( 1, &ctx->vbo );
264 glDeleteVertexArrays( 1, &ctx->vao );
265 VG_CHECK_GL_ERR();
266 return 0;
267 }
268
269 static int ui_default_init(void)
270 {
271 /* Load default font */
272 u32 compressed[] = {
273 #include "vg/vg_pxfont.h"
274 };
275
276 u32 pixels = 0, total = 256*256, data = 0;
277 u8 *image = malloc( total );
278 if( !image ) goto il_fail_alloc_image;
279
280 while( pixels < total )
281 {
282 for( int b = 31; b >= 0; b-- )
283 {
284 image[ pixels ++ ] = (compressed[data] & (0x1 << b))? 0xff: 0x00;
285
286 if( pixels >= total )
287 {
288 total = 0;
289 break;
290 }
291 }
292 data++;
293 }
294
295 glGenTextures( 1, &ui_glyph_texture );
296 if( !ui_glyph_texture ) goto il_fail_gen_texture;
297
298 glBindTexture( GL_TEXTURE_2D, ui_glyph_texture );
299 glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, 256, 256, 0,
300 GL_RED, GL_UNSIGNED_BYTE, image );
301
302 if( VG_CHECK_GL_ERR() ) goto il_fail_alloc_gpu;
303
304 vg_tex2d_clamp();
305 vg_tex2d_nearest();
306
307 free( image );
308
309 if( !ui_init_context( &ui_global_ctx, 20000 ) ) goto il_generic_fail;
310 if( !vg_shader_compile( &_shader_ui ) ) goto il_shader_fail;
311
312 return 1;
313
314 il_shader_fail:
315 il_fail_alloc_gpu:
316 il_generic_fail:
317 glDeleteTextures( 1, &ui_glyph_texture );
318
319 il_fail_gen_texture:
320 free( image );
321
322 il_fail_alloc_image:
323 return 0;
324 }
325
326 static void ui_default_free(void)
327 {
328 vg_free_shader( &_shader_ui );
329 glDeleteTextures( 1, &ui_glyph_texture );
330 ui_context_free( &ui_global_ctx );
331 }
332
333 static struct ui_vert *ui_fill_rect_uv( ui_ctx *ctx, ui_rect rect,
334 u32 colour, ui_px uv[4] );
335
336 static void ui_draw( ui_ctx *ctx, m3x3f view_override )
337 {
338 u32 num_indices_normal = ctx->num_indices;
339
340 /* Append images to back of buffer */
341 for( int i = 0; i < ctx->image_count; i ++ )
342 {
343 ui_fill_rect_uv( ctx, ctx->images[i].rc,
344 0xffffffff, (ui_px[4]){0,0,128,128} );
345 }
346
347 glBindVertexArray( ctx->vao );
348
349 glBindBuffer( GL_ARRAY_BUFFER, ctx->vbo );
350 glBufferSubData( GL_ARRAY_BUFFER, 0,
351 ctx->num_verts * sizeof( struct ui_vert ), ctx->verts );
352
353 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ctx->ebo );
354 glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, 0,
355 ctx->num_indices * sizeof( u16 ), ctx->indices );
356
357 glEnable(GL_BLEND);
358 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
359 glBlendEquation(GL_FUNC_ADD);
360
361 glUseProgram( _shader_ui.id );
362
363 m3x3f view = M3X3_IDENTITY;
364
365 if( !view_override )
366 {
367 view_override = view;
368
369 m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
370 m3x3_scale( view, (v3f){ 1.0f/((float)vg_window_x*0.5f),
371 -1.0f/((float)vg_window_y*0.5f), 1.0f } );
372 }
373
374 /* TODO? */
375 glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1,
376 GL_FALSE, (float *)view_override );
377
378 glActiveTexture( GL_TEXTURE0 );
379 glBindTexture( GL_TEXTURE_2D, ui_glyph_texture );
380 glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 );
381
382 glDrawElements( GL_TRIANGLES, num_indices_normal,
383 GL_UNSIGNED_SHORT, (void*)(0) );
384
385 /* Draw image elements */
386 for( int i = 0; i < ctx->image_count; i ++ )
387 {
388 struct ui_image *img = &ctx->images[i];
389
390 glBindTexture( GL_TEXTURE_2D, img->image );
391 glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT,
392 (void*)( (num_indices_normal + 6*i)*sizeof(u16) ) );
393 }
394
395 glDisable(GL_BLEND);
396 }
397
398 /*
399 * Rect controls
400 */
401 static void ui_rect_copy( ui_rect src, ui_rect dst )
402 {
403 dst[0] = src[0];
404 dst[1] = src[1];
405 dst[2] = src[2];
406 dst[3] = src[3];
407 }
408
409 static void ui_rect_pad( ui_rect rect, ui_px pad )
410 {
411 rect[0] += pad;
412 rect[1] += pad;
413 rect[2] -= pad*2;
414 rect[3] -= pad*2;
415 }
416
417 /*
418 * Stack control
419 */
420 static struct ui_qnode *ui_current( ui_ctx *ctx )
421 {
422 return &ctx->stack[ ctx->stack_count-1 ];
423 }
424
425 static void ui_new_node( ui_ctx *ctx )
426 {
427 if( ctx->stack_count == vg_list_size( ctx->stack ) )
428 {
429 vg_error( "[UI] Stack overflow while creating box!" );
430 return;
431 }
432
433 struct ui_qnode *parent = &ctx->stack[ ctx->stack_count-1 ];
434 struct ui_qnode *node = &ctx->stack[ ctx->stack_count++ ];
435 ui_rect_copy( ctx->cursor, node->rect );
436
437 if( parent->mouse_over )
438 {
439 if( ctx->mouse[0] >= node->rect[0] &&
440 ctx->mouse[0] < node->rect[0]+node->rect[2] &&
441 ctx->mouse[1] >= node->rect[1] &&
442 ctx->mouse[1] < node->rect[1]+node->rect[3] )
443 node->mouse_over = 1;
444 else
445 node->mouse_over = 0;
446 }
447 else
448 {
449 node->mouse_over = 0;
450 }
451 }
452
453 static int ui_hasmouse( ui_ctx *ctx )
454 {
455 struct ui_qnode *node = ui_current( ctx );
456 return (node->mouse_over && (node->capture_id == ctx->capture_mouse_id));
457 }
458
459 static void ui_end( ui_ctx *ctx )
460 {
461 struct ui_qnode *node = &ctx->stack[ --ctx->stack_count ];
462 ui_rect_copy( node->rect, ctx->cursor );
463 }
464
465 static void ui_end_down( ui_ctx *ctx )
466 {
467 ui_px height = ui_current( ctx )->rect[3];
468 ui_end( ctx );
469 ctx->cursor[1] += height;
470 }
471
472 static void ui_end_right( ui_ctx *ctx )
473 {
474 ui_px width = ui_current( ctx )->rect[2];
475 ui_end( ctx );
476 ctx->cursor[0] += width;
477 }
478
479 static void ui_fill_y( ui_ctx *ctx )
480 {
481 struct ui_qnode *node = ui_current( ctx );
482 ctx->cursor[3] = node->rect[3] - (ctx->cursor[1]-node->rect[1]);
483 }
484
485 static void ui_fill_x( ui_ctx *ctx )
486 {
487 struct ui_qnode *node = ui_current( ctx );
488 ctx->cursor[2] = node->rect[2] - (ctx->cursor[0]-node->rect[0]);
489 }
490
491 static void ui_align_bottom( ui_ctx *ctx )
492 {
493 struct ui_qnode *node = ui_current( ctx );
494 ctx->cursor[1] = node->rect[1] + node->rect[3] - ctx->cursor[3];
495 }
496
497 static void ui_align_right( ui_ctx *ctx )
498 {
499 struct ui_qnode *node = ui_current( ctx );
500 ctx->cursor[0] = node->rect[0] + node->rect[2] - ctx->cursor[2];
501 }
502
503 static void ui_align_top( ui_ctx *ctx )
504 {
505 ctx->cursor[1] = ui_current( ctx )->rect[1];
506 }
507
508 static void ui_align_left( ui_ctx *ctx )
509 {
510 ctx->cursor[0] = ui_current( ctx )->rect[0];
511 }
512
513 static void ui_clamp_rect( ui_rect parent, ui_rect dest )
514 {
515 dest[0] = vg_min( parent[0] + parent[2] - dest[2], dest[0] );
516 dest[1] = vg_min( parent[1] + parent[3] - dest[3], dest[1] );
517 dest[0] = vg_max( parent[0], dest[0] );
518 dest[1] = vg_max( parent[1], dest[1] );
519 }
520
521 static void ui_capture_mouse( ui_ctx *ctx, u32 id )
522 {
523 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
524 node->capture_id = id;
525
526 if( !ctx->capture_lock && node->mouse_over )
527 {
528 ctx->capture_mouse_id = id;
529 }
530 }
531
532 static int ui_want_mouse( ui_ctx *ctx )
533 {
534 return ctx->capture_mouse_id == 0? 0: 1;
535 }
536
537 static void ui_set_clip( ui_ctx *ctx, ui_rect clip )
538 {
539 ctx->clipping[0] = clip[0];
540 ctx->clipping[1] = clip[1];
541 ctx->clipping[2] = clip[0] + clip[2];
542 ctx->clipping[3] = clip[1] + clip[3];
543 }
544
545 static void ui_release_clip( ui_ctx *ctx )
546 {
547 ctx->clipping[0] = -32000;
548 ctx->clipping[1] = -32000;
549 ctx->clipping[2] = 32000;
550 ctx->clipping[3] = 32000;
551 }
552
553 static struct ui_vert *ui_fill_rect_uv( ui_ctx *ctx, ui_rect rect,
554 u32 colour, ui_px uv[4] )
555 {
556 struct ui_vert *vertices = &ctx->verts[ ctx->num_verts ];
557 vertices[0].co[0] = rect[0];
558 vertices[0].co[1] = rect[1];
559 vertices[0].uv[0] = uv[0];
560 vertices[0].uv[1] = uv[1];
561 vertices[0].colour = colour;
562 vertices[1].co[0] = rect[0]+rect[2];
563 vertices[1].co[1] = rect[1];
564 vertices[1].uv[0] = uv[2];
565 vertices[1].uv[1] = uv[1];
566 vertices[1].colour = colour;
567 vertices[2].co[0] = rect[0]+rect[2];
568 vertices[2].co[1] = rect[1]+rect[3];
569 vertices[2].uv[0] = uv[2];
570 vertices[2].uv[1] = uv[3];
571 vertices[2].colour = colour;
572 vertices[3].co[0] = rect[0];
573 vertices[3].co[1] = rect[1]+rect[3];
574 vertices[3].uv[0] = uv[0];
575 vertices[3].uv[1] = uv[3];
576 vertices[3].colour = colour;
577 u16 ind_start = ctx->num_verts;
578 u16 *indices = &ctx->indices[ ctx->num_indices ];
579
580 ui_rect_copy( ctx->clipping, vertices[0].clip );
581 ui_rect_copy( ctx->clipping, vertices[1].clip );
582 ui_rect_copy( ctx->clipping, vertices[2].clip );
583 ui_rect_copy( ctx->clipping, vertices[3].clip );
584
585 indices[0] = ind_start+0;
586 indices[1] = ind_start+2;
587 indices[2] = ind_start+1;
588
589 indices[3] = ind_start+0;
590 indices[4] = ind_start+3;
591 indices[5] = ind_start+2;
592
593 ctx->num_indices += 6;
594 ctx->num_verts += 4;
595
596 return vertices;
597 }
598
599 static struct ui_vert *ui_fill_rect( ui_ctx *ctx, ui_rect rect, u32 colour )
600 {
601 return ui_fill_rect_uv( ctx, rect, colour, (ui_px[4]){ 4,4, 4,4 } );
602 }
603
604 static ui_px ui_text_line_offset( const char *str, ui_px scale,
605 enum ui_text_align align )
606 {
607 if( align == k_text_align_left )
608 return 0;
609
610 int length = 0;
611 const char *_c = str;
612 char c;
613
614 while( (c = *(_c ++)) )
615 if( c >= 32 && c <= 126 )
616 length ++;
617 else if( c == '\n' )
618 break;
619
620 if( align == k_text_align_right )
621 return -length * scale*8;
622 else
623 return (-length * scale*8) / 2;
624 }
625
626 static void ui_text( ui_ctx *ctx, ui_px pos[2],
627 const char *str, ui_px scale, enum ui_text_align align )
628 {
629 ui_rect text_cursor;
630 u32 current_colour = 0x00ffffff;
631
632 const char *_c = str;
633 u8 c;
634
635 text_cursor[0] = pos[0] + ui_text_line_offset( str, scale, align );
636 text_cursor[1] = pos[1];
637 text_cursor[2] = 8*scale;
638 text_cursor[3] = 14*scale;
639
640 while( (c = *(_c ++)) )
641 {
642 if( c == '\n' )
643 {
644 text_cursor[1] += 14*scale;
645 text_cursor[0] = pos[0] + ui_text_line_offset( _c, scale, align );
646 continue;
647 }
648 else if( c >= 33 )
649 {
650 u8 glyph_base[2];
651 u8 glyph_index = c;
652 glyph_base[0] = glyph_index & 0xf;
653 glyph_base[1] = (glyph_index-glyph_base[0])>>4;
654
655 glyph_base[0] *= 8;
656 glyph_base[1] *= 8;
657
658 ui_fill_rect_uv( ctx, text_cursor, current_colour, (ui_px[4])
659 {
660 glyph_base[0]+2,
661 glyph_base[1]+1,
662 glyph_base[0]+6,
663 glyph_base[1]+8
664 });
665 }
666 else if( c == '\x1B' )
667 {
668 _c ++;
669 u16 colour_id = 0;
670 for( int i = 0; i < 3; i ++ )
671 {
672 if( _c[i] )
673 {
674 if( _c[i] == 'm' )
675 {
676 _c = _c + i + 1;
677
678 switch( colour_id )
679 {
680 case '0': current_colour = 0x00ffffff; break;
681 case '3'|'1'<<8: current_colour = 0x00201fee; break;
682 case '3'|'2'<<8: current_colour = 0x0037e420; break;
683 case '3'|'3'<<8: current_colour = 0x000ed8e2; break;
684 case '3'|'4'<<8: current_colour = 0x00f15010; break;
685 case '3'|'5'<<8: current_colour = 0x00ee20ee; break;
686 case '3'|'6'<<8: current_colour = 0x00eeee20; break;
687 case '3'|'7'<<8: current_colour = 0x00ffffff; break;
688 }
689
690 break;
691 }
692
693 colour_id |= _c[i] << (i*8);
694 }
695 else
696 {
697 _c = _c +i;
698 break;
699 }
700 }
701
702 continue;
703 }
704 else if( c == '\t' )
705 {
706 text_cursor[0] += UI_GLYPH_SPACING_X*scale*4;
707 continue;
708 }
709
710 text_cursor[0] += UI_GLYPH_SPACING_X*scale;
711 }
712 }
713
714 /*
715 * API Control
716 */
717 static void ui_begin( ui_ctx *ctx, ui_px res_x, ui_px res_y )
718 {
719 ctx->cursor[0] = 0;
720 ctx->cursor[1] = 0;
721 ctx->cursor[2] = res_x;
722 ctx->cursor[3] = res_y;
723
724 ui_rect_copy( ctx->cursor, ctx->stack[0].rect );
725 ctx->stack[0].mouse_over = 1;
726
727 ctx->stack_count = 1;
728
729 ctx->num_verts = 0;
730 ctx->num_indices = 0;
731
732 ui_release_clip( ctx );
733
734 if( ctx->click_state == 0 )
735 ctx->capture_mouse_id = 0;
736
737 ctx->image_count = 0;
738 }
739
740 static void ui_resolve( ui_ctx *ctx )
741 {
742 if( ctx->stack_count-1 )
743 {
744 vg_error( "[UI] Mismatched node create/drestroy!" );
745 return;
746 }
747
748 if( ctx->click_state == 3 || ctx->click_state == 0 )
749 {
750 ctx->capture_lock = 0;
751 }
752 }
753
754 static void ui_set_mouse( ui_ctx *ctx, int x, int y, int click_state )
755 {
756 ctx->mouse[0] = x;
757 ctx->mouse[1] = y;
758
759 ctx->click_state = click_state;
760 }
761
762 /*
763 * High level controls
764 */
765 struct ui_window
766 {
767 const char *title;
768 ui_rect transform;
769
770 int drag;
771 ui_px drag_offset[2];
772 };
773
774 enum button_state
775 {
776 k_button_released = 0,
777 k_button_start_click,
778 k_button_click,
779 k_button_hold
780 };
781
782 static int ui_button( ui_ctx *ctx )
783 {
784 u32 uid = ctx->control_id ++;
785
786 ui_new_node( ctx );
787 {
788 ui_capture_mouse( ctx, uid );
789
790 if( ui_hasmouse(ctx) )
791 {
792 ui_fill_rect( ctx, ctx->cursor, ctx->colours->hover );
793
794 if( ctx->click_state == 1 )
795 {
796 ctx->capture_lock = 1;
797 return k_button_start_click;
798 }
799 else if( ctx->capture_lock && ctx->click_state == 3 )
800 return k_button_click;
801 else if( ctx->capture_lock && ctx->click_state == 2 )
802 return k_button_hold;
803
804 return k_button_click;
805 }
806 else
807 ui_fill_rect( ctx, ctx->cursor, ctx->colours->main );
808 }
809
810 return k_button_released;
811 }
812
813 static int ui_window( ui_ctx *ctx, struct ui_window *window, u32 control_group )
814 {
815 if( window->drag )
816 {
817 window->transform[0] = ctx->mouse[0]+window->drag_offset[0];
818 window->transform[1] = ctx->mouse[1]+window->drag_offset[1];
819
820 ui_clamp_rect( ctx->stack[0].rect, window->transform );
821
822 if( ctx->click_state == 0 || ctx->click_state == 3 )
823 {
824 window->drag = 0;
825 }
826 }
827
828 ui_rect_copy( window->transform, ctx->cursor );
829
830 ui_new_node( ctx );
831 {
832 ui_capture_mouse( ctx, ctx->control_id ++ );
833
834 /* Drag bar */
835 ctx->cursor[3] = 25;
836 ui_new_node( ctx );
837 {
838 ui_capture_mouse( ctx, ctx->control_id ++ );
839
840 struct ui_vert *drag_bar = ui_fill_rect( ctx, ctx->cursor, 0xff555555 );
841
842 /* title.. */
843 ctx->cursor[0] += 2;
844 ctx->cursor[1] += 2;
845 ui_text( ctx, ctx->cursor, window->title, 2, 0 );
846
847 /* Close button */
848 ctx->cursor[3] = 25;
849 ctx->cursor[2] = 25;
850 ui_align_right( ctx );
851 ui_align_top( ctx );
852 ui_rect_pad( ctx->cursor, 4 );
853
854 if( ui_button( ctx ) )
855 {
856 vg_info( "Click clacked\n" );
857 }
858 ctx->cursor[0] += 2;
859 ui_text( ctx, ctx->cursor, "x", 2, 0 );
860 ui_end( ctx );
861
862 if( ui_hasmouse( ctx ) )
863 {
864 drag_bar[0].colour = 0xff777777;
865 drag_bar[1].colour = 0xff777777;
866 drag_bar[2].colour = 0xff777777;
867 drag_bar[3].colour = 0xff777777;
868
869 /* start drag */
870 if( ctx->click_state == 1 )
871 {
872 window->drag = 1;
873 window->drag_offset[0] = window->transform[0]-ctx->mouse[0];
874 window->drag_offset[1] = window->transform[1]-ctx->mouse[1];
875 }
876 }
877 }
878 ui_end_down( ctx );
879 }
880
881 return 1;
882 }
883
884 static void ui_push_image( ui_ctx *ctx, ui_rect rc, GLuint image )
885 {
886 struct ui_image *img = &ctx->images[ ctx->image_count ++ ];
887 ui_rect_copy( rc, img->rc );
888 img->image = image;
889 }
890
891 struct ui_slider
892 {
893 float *data;
894 float min, max;
895 };
896
897 struct ui_slider_vector
898 {
899 float *data, min, max;
900 struct ui_slider sub[4];
901 u32 len;
902 };
903
904 struct ui_checkbox
905 {
906 int *data;
907 };
908
909 static void ui_slider( ui_ctx *ctx, struct ui_slider *slider )
910 {
911 ui_new_node( ctx );
912
913 ui_px slider_start = ctx->cursor[0];
914
915 float const ftotal = ctx->cursor[2],
916 fwidth = ftotal*0.25f,
917 fmove = ftotal - fwidth,
918 fstart = fwidth*0.5f,
919 frange = slider->max-slider->min,
920 fpos = (*slider->data - slider->min) / frange;
921
922 ui_fill_rect( ctx, ctx->cursor, 0xff111111 );
923 ctx->cursor[2] = fwidth;
924 ctx->cursor[0] = slider_start + fpos * fmove;
925
926 u32 uid = ctx->control_id ++;
927 int status = ui_button( ctx );
928 if( ctx->capture_lock && (ctx->capture_mouse_id == uid))
929 {
930 float ui_new = ctx->mouse[0],
931 local = ui_new - (slider_start + fstart),
932 zo = vg_clampf(local / fmove,0.0f,1.0f);
933
934 *slider->data = vg_lerpf( slider->min, slider->max, zo );
935 }
936
937 ctx->cursor[0] += 4;
938 ctx->cursor[1] += 4;
939
940 char buf[12];
941 snprintf( buf, 12, "%.2f", *slider->data );
942 ui_text( ctx, ctx->cursor, buf, 1, 0 );
943 ui_end_down( ctx );
944 ui_end_down( ctx );
945 }
946
947 static void ui_slider_vector( ui_ctx *ctx, struct ui_slider_vector *slider )
948 {
949 for( int i=0; i<slider->len; i++ )
950 {
951 slider->sub[i].data = &slider->data[i];
952 slider->sub[i].min = slider->min;
953 slider->sub[i].max = slider->max;
954 ui_slider( ctx, &slider->sub[i] );
955 }
956 }
957
958 static void ui_checkbox( ui_ctx *ctx, struct ui_checkbox *cb )
959 {
960 if( ui_button(ctx) == k_button_click )
961 *cb->data ^= 0x1;
962
963 ui_new_node(ctx);
964 ui_rect_pad( ctx->cursor, 4 );
965 if( *cb->data )
966 ui_fill_rect( ctx, ctx->cursor, 0xff00e052 );
967 else
968 ui_fill_rect( ctx, ctx->cursor, 0xff0052e0 );
969
970 ui_end(ctx);
971 ui_end_down(ctx);
972 }
973
974 /* Shortnames */
975 #define gui_draw(...) ui_draw( &ui_global_ctx, __VA_ARGS__)
976 #define gui_current(...) ui_current( &ui_global_ctx, __VA_ARGS__)
977 #define gui_new_node() ui_new_node( &ui_global_ctx )
978 #define gui_hasmouse(...) ui_hasmouse( &ui_global_ctx, __VA_ARGS__)
979 #define gui_end() ui_end( &ui_global_ctx )
980 #define gui_end_down() ui_end_down( &ui_global_ctx )
981 #define gui_end_right() ui_end_right( &ui_global_ctx )
982 #define gui_fill_y() ui_fill_y( &ui_global_ctx)
983 #define gui_fill_x() ui_fill_x( &ui_global_ctx)
984 #define gui_align_bottom() ui_align_bottom( &ui_global_ctx )
985 #define gui_align_right() ui_align_right( &ui_global_ctx )
986 #define gui_align_top() ui_align_top( &ui_global_ctx )
987 #define gui_align_left() ui_align_left( &ui_global_ctx )
988 #define gui_clamp_rect(...) ui_clamp_rect( &ui_global_ctx, __VA_ARGS__)
989 #define gui_capture_mouse(...) ui_capture_mouse( &ui_global_ctx, __VA_ARGS__)
990 #define gui_set_clip(...) ui_set_clip( &ui_global_ctx, __VA_ARGS__)
991 #define gui_release_clip() ui_release_clip( &ui_global_ctx )
992 #define gui_fill_rect_uv(...) ui_fill_rect_uv( &ui_global_ctx, __VA_ARGS__)
993 #define gui_fill_rect(...) ui_fill_rect( &ui_global_ctx, __VA_ARGS__)
994 #define gui_text(...) ui_text( &ui_global_ctx, __VA_ARGS__)
995 #define gui_begin(...) ui_begin( &ui_global_ctx, __VA_ARGS__)
996 #define gui_resolve(...) ui_resolve( &ui_global_ctx, __VA_ARGS__)
997 #define gui_set_mouse(...) ui_set_mouse( &ui_global_ctx, __VA_ARGS__)
998 #define gui_button(...) ui_button( &ui_global_ctx, __VA_ARGS__)
999 #define gui_window(...) ui_window( &ui_global_ctx, __VA_ARGS__)
1000 #define gui_want_mouse() ui_want_mouse( &ui_global_ctx )
1001 #define gui_push_image(...) ui_push_image( &ui_global_ctx, __VA_ARGS__ )
1002 #define gui_reset_colours(...) ui_reset_colours( &ui_global_ctx )
1003
1004 #endif /* VG_UI_H */