--- /dev/null
+/* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved */
+
+/*
+ * Principles:
+ *
+ * 1. layout is defined by subdividing
+ * 2. a parent node should never be resized by the content after creation
+ * 3. when the ui is in an interactive state, no controls should ever move
+ */
+
+
+#ifndef VG_IMGUI_H
+#define VG_IMGUI_H
+
+#define VG_GAME
+#include "vg/vg.h"
+#include "vg/vg_tex.h"
+#include "vg/vg_shader.h"
+
+typedef i16 ui_px;
+typedef u32 ui_colour;
+typedef ui_px ui_rect[4];
+typedef struct ui_vert ui_vert;
+
+enum ui_axis {
+ k_ui_axis_h = 0x0u,
+ k_ui_axis_v = 0x1u,
+};
+
+/* Relative to cursor p0 */
+enum ui_align
+{ /* DC BA */
+ k_ui_align_left = 0x0000| 0x00,
+ k_ui_align_right = 0x0000| 0x01,
+ k_ui_align_center = 0x0000| 0x02,
+
+ k_ui_align_middle = 0x0100,
+ k_ui_align_middle_left = 0x0100| 0x00,
+ k_ui_align_middle_right = 0x0100| 0x01,
+ k_ui_align_middle_center = 0x0100| 0x02,
+
+ k_ui_align_bottom = 0x0200,
+ k_ui_align_bottom_left = 0x0200| 0x00,
+ k_ui_align_bottom_right = 0x0200| 0x01,
+ k_ui_align_bottom_center = 0x0200| 0x02,
+};
+
+#pragma pack(push,1)
+struct ui_vert
+{
+ ui_px co[2];
+ u8 uv[2];
+ u32 colour;
+};
+#pragma pack(pop)
+
+struct
+{
+ struct ui_vert *vertex_buffer;
+ u16 *indice_buffer;
+ u32 max_verts, max_indices,
+ cur_vert, cur_indice,
+ vert_start, indice_start;
+
+ GLuint tex_glyphs, vao, vbo, ebo;
+
+ ui_px mouse[2], mouse_click[2];
+ u32 mouse_state[2];
+ u32 ignore_input_frames;
+
+ ui_rect click_fader;
+ float click_fade_opacity;
+}
+
+static vg_uictx;
+static struct vg_shader _shader_ui =
+{
+ .name = "[vg] ui",
+ .link = NULL,
+ .vs =
+ {
+ .orig_file = NULL,
+ .static_src =
+ "layout (location=0) in vec2 a_co;"
+ "layout (location=1) in vec2 a_uv;"
+ "layout (location=2) in vec4 a_colour;"
+ "uniform mat3 uPv;"
+ ""
+ "out vec2 aTexCoords;"
+ "out vec4 aColour;"
+ "out vec2 aWsp;"
+ ""
+ "void main()"
+ "{"
+ "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
+ "aTexCoords = a_uv * 0.0078125;"
+ "aColour = a_colour;"
+
+ "aWsp = a_co;"
+ "}",
+ },
+ .fs =
+ {
+ .orig_file = NULL,
+ .static_src =
+ "uniform sampler2D uTexGlyphs;"
+ "out vec4 FragColor;"
+ ""
+ "in vec2 aTexCoords;"
+ "in vec4 aColour;"
+ ""
+ "in vec2 aWsp;"
+ ""
+ "void main()"
+ "{"
+
+ "vec4 glyph = vec4(1.0,1.0,1.0,1.0);"
+
+ "if( aColour.a == 0.0 )"
+ "{"
+ "glyph = texture( uTexGlyphs, aTexCoords );"
+ "glyph.a = smoothstep( 0.47, 0.53, glyph.r );"
+ "}"
+ "else"
+ "{"
+ "glyph.a = aColour.a;"
+ "}"
+
+ "FragColor = vec4( aColour.rgb, glyph.a );"
+ "}"
+ }
+};
+
+static struct vg_shader _shader_ui_image =
+{
+ .name = "[vg] ui_image",
+ .link = NULL,
+ .vs =
+ {
+ .orig_file = NULL,
+ .static_src =
+ "layout (location=0) in vec2 a_co;"
+ "layout (location=1) in vec2 a_uv;"
+ "layout (location=2) in vec4 a_colour;"
+ "uniform mat3 uPv;"
+
+ "out vec2 aTexCoords;"
+ "out vec4 aColour;"
+ "out vec2 aWsp;"
+
+ "void main()"
+ "{"
+ "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
+ "aTexCoords = a_uv * 0.0078125;"
+ "aColour = a_colour;"
+
+ "aWsp = a_co;"
+ "}",
+ },
+ .fs =
+ {
+ .orig_file = NULL,
+ .static_src =
+ "uniform sampler2D uTexImage;"
+ "out vec4 FragColor;"
+
+ "in vec2 aTexCoords;"
+ "in vec4 aColour;"
+ "in vec2 aWsp;"
+
+ "void main()"
+ "{"
+ "vec4 colour = texture( uTexImage, aTexCoords );"
+
+ /* wtf is this?? */
+#if 0
+ "float value = dot(vec4(1.0),colour)*0.25;"
+
+ "vec3 col = vec3(pow(cos(value*3.14159265*2.0)*0.5+0.5,0.5))"
+ "* vec3(step(value,0.5),0.3,step(1.0-value,0.5));"
+ "FragColor = vec4( col*4.0, 1.0 );"
+#endif
+
+ "FragColor = colour;"
+ "}"
+ }
+};
+#define UI_GLYPH_SPACING_X 8
+
+VG_STATIC void _vg_ui_init(void)
+{
+ if( !vg_shader_compile( &_shader_ui ) ||
+ !vg_shader_compile( &_shader_ui_image ) )
+ vg_fatal_error( "Failed to compile ui shader" );
+
+ /*
+ * Vertex buffer
+ * ----------------------------------------
+ */
+
+ vg_uictx.max_indices = 20000;
+ vg_uictx.max_verts = 30000;
+
+ /* Generate the buffer we are gonna be drawing to */
+ glGenVertexArrays( 1, &vg_uictx.vao );
+ glGenBuffers( 1, &vg_uictx.vbo );
+ glGenBuffers( 1, &vg_uictx.ebo );
+
+ glBindVertexArray( vg_uictx.vao );
+ glBindBuffer( GL_ARRAY_BUFFER, vg_uictx.vbo );
+
+ glBufferData( GL_ARRAY_BUFFER,
+ vg_uictx.max_verts * sizeof( struct ui_vert ),
+ NULL, GL_DYNAMIC_DRAW );
+ glBindVertexArray( vg_uictx.vao );
+
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_uictx.ebo );
+ glBufferData( GL_ELEMENT_ARRAY_BUFFER,
+ vg_uictx.max_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW );
+
+ VG_CHECK_GL_ERR();
+
+ /* Set pointers */
+ u32 const stride = sizeof( struct ui_vert );
+
+ /* XY */
+ glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride,
+ (void *)offsetof( struct ui_vert, co ) );
+ glEnableVertexAttribArray( 0 );
+
+ /* UV */
+ glVertexAttribPointer( 1, 2, GL_UNSIGNED_BYTE, GL_FALSE, stride,
+ (void *)offsetof( struct ui_vert, uv ) );
+ glEnableVertexAttribArray( 1 );
+
+ /* COLOUR */
+ glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride,
+ (void *)offsetof( struct ui_vert, colour ) );
+ glEnableVertexAttribArray( 2 );
+
+ VG_CHECK_GL_ERR();
+
+ /* Alloc RAM default context */
+ u32 vert_size = vg_uictx.max_verts*sizeof(struct ui_vert),
+ inds_size = vg_align8( vg_uictx.max_indices*sizeof(u16) );
+
+ vg_uictx.vertex_buffer = vg_linear_alloc( vg_mem.rtmemory, vert_size );
+ vg_uictx.indice_buffer = vg_linear_alloc( vg_mem.rtmemory, inds_size );
+
+ /* font
+ * -----------------------------------------------------
+ */
+
+ /* Load default font */
+ u32 compressed[] = {
+ #include "vg/vg_pxfont_thin.h"
+ };
+
+ u32 pixels = 0, total = 256*256, data = 0;
+ u8 image[256*256];
+
+ while( pixels < total ){
+ for( int b = 31; b >= 0; b-- ){
+ image[ pixels ++ ] = (compressed[data] & (0x1u << b))? 0xffu: 0x00u;
+
+ if( pixels >= total ){
+ total = 0;
+ break;
+ }
+ }
+ data++;
+ }
+
+ glGenTextures( 1, &vg_uictx.tex_glyphs );
+ glBindTexture( GL_TEXTURE_2D, vg_uictx.tex_glyphs );
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, 256, 256, 0,
+ GL_RED, GL_UNSIGNED_BYTE, image );
+
+ VG_CHECK_GL_ERR();
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+}
+
+enum ui_shader {
+ k_ui_shader_colour,
+ k_ui_shader_image
+};
+
+static void rect_copy( ui_rect a, ui_rect b )
+{
+ for( int i=0; i<4; i++ )
+ b[i] = a[i];
+}
+
+VG_STATIC void ui_flush( enum ui_shader shader )
+{
+ u32 vertex_offset = vg_uictx.vert_start*sizeof(ui_vert),
+ vertex_count = vg_uictx.cur_vert-vg_uictx.vert_start,
+ vertex_size = vertex_count*sizeof(ui_vert),
+
+ indice_offset = vg_uictx.indice_start*sizeof(u16),
+ indice_count = vg_uictx.cur_indice-vg_uictx.indice_start,
+ indice_size = indice_count * sizeof(u16);
+
+ if( !vertex_size || !indice_size )
+ return;
+
+ glBindVertexArray( vg_uictx.vao );
+ glBindBuffer( GL_ARRAY_BUFFER, vg_uictx.vbo );
+ glBufferSubData( GL_ARRAY_BUFFER, vertex_offset, vertex_size,
+ vg_uictx.vertex_buffer+vg_uictx.vert_start );
+
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_uictx.ebo );
+ glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, indice_offset, indice_size,
+ vg_uictx.indice_buffer+vg_uictx.indice_start );
+
+ glEnable( GL_BLEND );
+ glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+ glBlendEquation( GL_FUNC_ADD );
+
+ m3x3f view = M3X3_IDENTITY;
+ m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
+ m3x3_scale( view, (v3f){ 1.0f/((float)vg.window_x*0.5f),
+ -1.0f/((float)vg.window_y*0.5f), 1.0f } );
+
+ if( shader == k_ui_shader_colour ){
+ glUseProgram( _shader_ui.id );
+
+ glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1,
+ GL_FALSE, (float *)view );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, vg_uictx.tex_glyphs );
+ glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 );
+ }
+ else if( shader == k_ui_shader_image ){
+ glUseProgram( _shader_ui_image.id );
+ glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1,
+ GL_FALSE, (float *)view );
+ glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 );
+ }
+ else
+ vg_fatal_error( "Invalid UI shader (%d)\n", shader );
+
+ glDrawElements( GL_TRIANGLES, indice_count, GL_UNSIGNED_SHORT,
+ (void *)(vg_uictx.indice_start*sizeof(u16)) );
+
+ glDisable( GL_BLEND );
+
+ vg_uictx.indice_start = vg_uictx.cur_indice;
+ vg_uictx.vert_start = vg_uictx.cur_vert;
+}
+
+static void ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] )
+{
+ /* this if far from ideal but stops us from crashing */
+ if( (vg_uictx.cur_vert + 4 > vg_uictx.max_verts) ||
+ (vg_uictx.cur_indice + 6 > vg_uictx.max_indices))
+ return;
+
+ struct ui_vert *vertices = &vg_uictx.vertex_buffer[ vg_uictx.cur_vert ];
+ u16 *indices = &vg_uictx.indice_buffer[ vg_uictx.cur_indice ];
+
+ for( int i=0; i<4; i++ ){
+ vertices[i].colour = colour;
+ }
+
+ vertices[0].co[0] = rect[0];
+ vertices[0].co[1] = rect[1];
+ vertices[0].uv[0] = uv[0];
+ vertices[0].uv[1] = uv[1];
+ vertices[1].co[0] = rect[0]+rect[2];
+ vertices[1].co[1] = rect[1];
+ vertices[1].uv[0] = uv[2];
+ vertices[1].uv[1] = uv[1];
+ vertices[2].co[0] = rect[0]+rect[2];
+ vertices[2].co[1] = rect[1]+rect[3];
+ vertices[2].uv[0] = uv[2];
+ vertices[2].uv[1] = uv[3];
+ vertices[3].co[0] = rect[0];
+ vertices[3].co[1] = rect[1]+rect[3];
+ vertices[3].uv[0] = uv[0];
+ vertices[3].uv[1] = uv[3];
+ u16 ind_start = vg_uictx.cur_vert;
+
+ u16 start = vg_uictx.cur_vert;
+ u32 mesh[] = { 0,2,1, 0,3,2 };
+
+ for( u32 i=0; i<vg_list_size(mesh); i++ ){
+ indices[i] = start+mesh[i];
+ }
+
+ vg_uictx.cur_indice += 6;
+ vg_uictx.cur_vert += 4;
+}
+
+static void ui_fill( ui_rect rect, u32 colour )
+{
+ ui_fill_rect( rect, colour, (ui_px[4]){ 4,4,4,4 } );
+}
+
+static void ui_outline( ui_rect rect, ui_px thickness, u32 colour )
+{
+ /* this if far from ideal but stops us from crashing */
+ if( (vg_uictx.cur_vert + 8 > vg_uictx.max_verts) ||
+ (vg_uictx.cur_indice + 24 > vg_uictx.max_indices))
+ return;
+
+ struct ui_vert *vertices = &vg_uictx.vertex_buffer[ vg_uictx.cur_vert ];
+ u16 *indices = &vg_uictx.indice_buffer[ vg_uictx.cur_indice ];
+
+ for( int i=0; i<8; i++ ){
+ vertices[i].uv[0] = 4;
+ vertices[i].uv[1] = 4;
+ vertices[i].colour = colour;
+ }
+
+ vertices[0].co[0] = rect[0];
+ vertices[0].co[1] = rect[1];
+ vertices[1].co[0] = rect[0]+rect[2];
+ vertices[1].co[1] = rect[1];
+ vertices[2].co[0] = rect[0]+rect[2];
+ vertices[2].co[1] = rect[1]+rect[3];
+ vertices[3].co[0] = rect[0];
+ vertices[3].co[1] = rect[1]+rect[3];
+ vertices[4].co[0] = vertices[0].co[0]-thickness;
+ vertices[4].co[1] = vertices[0].co[1]-thickness;
+ vertices[5].co[0] = vertices[1].co[0]+thickness;
+ vertices[5].co[1] = vertices[1].co[1]-thickness;
+ vertices[6].co[0] = vertices[2].co[0]+thickness;
+ vertices[6].co[1] = vertices[2].co[1]+thickness;
+ vertices[7].co[0] = vertices[3].co[0]-thickness;
+ vertices[7].co[1] = vertices[3].co[1]+thickness;
+
+ u16 start = vg_uictx.cur_vert;
+ 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 };
+
+ for( u32 i=0; i<vg_list_size(mesh); i++ ){
+ indices[i] = start+mesh[i];
+ }
+
+ vg_uictx.cur_indice += 24;
+ vg_uictx.cur_vert += 8;
+}
+
+static void ui_split_px( ui_rect rect,
+ enum ui_axis other, ui_px width, ui_px pad,
+ ui_rect l, ui_rect r )
+{
+ enum ui_axis dir = other ^ 0x1;
+
+ ui_rect temp;
+ rect_copy( rect, temp );
+
+ l[ dir ] = temp[ dir ] + pad;
+ r[ dir ] = temp[ dir ] + width + (pad/2);
+ l[ other ] = temp[ other ] + pad;
+ r[ other ] = temp[ other ] + pad;
+ l[ 2+dir ] = width - ((3*pad)/2);
+ r[ 2+dir ] = temp[ 2+dir ] - width - ((3*pad)/2);
+ l[ 2+other ] = temp[ 2+other ] - pad*2;
+ r[ 2+other ] = temp[ 2+other ] - pad*2;
+}
+
+static void ui_rect_center( ui_rect parent, ui_rect rect )
+{
+ rect[0] = parent[0] + (parent[2]-rect[2])/2;
+ rect[1] = parent[1] + (parent[3]-rect[3])/2;
+}
+
+static void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d )
+{
+ i32 rp = (i32)rect[2] * (i32)size[1],
+ rc = (i32)size[0] * (i32)rect[3];
+
+ enum ui_axis dir, other;
+ if( rc > rp ) dir = k_ui_axis_h;
+ else dir = k_ui_axis_v;
+ other = dir ^ 0x1;
+
+ d[2+dir] = rect[2+dir];
+ d[2+other] = (rect[2+dir] * size[other]) / size[dir];
+
+ ui_rect_center( rect, d );
+}
+
+static void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio,
+ ui_px pad, ui_rect l, ui_rect r )
+{
+ ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio;
+ ui_split_px( rect, dir, width, pad, l, r );
+}
+
+static void ui_rect_pad( ui_rect rect, ui_px pad )
+{
+ rect[0] += pad;
+ rect[1] += pad;
+ rect[2] -= pad*2;
+ rect[3] -= pad*2;
+}
+
+static ui_px ui_text_line_width( const char *str )
+{
+ int length = 0;
+ const char *_c = str;
+ char c;
+
+ while( (c = *(_c ++)) ){
+ if( c >= 32 && c <= 126 )
+ length ++;
+ else if( c == '\n' )
+ break;
+ }
+
+ return length * 8;
+}
+
+static ui_px ui_text_string_height( const char *str )
+{
+ int height = 1;
+ const char *_c = str;
+ char c;
+
+ while( (c = *(_c ++)) ){
+ if( c == '\n' ) height ++;
+ }
+
+ return height * 14;
+}
+
+static ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale,
+ enum ui_align align )
+{
+ if( align == k_ui_align_left ){
+ return rect[0];
+ }
+ else{
+ ui_px width = ui_text_line_width( str ) * scale;
+
+ if( align == k_ui_align_right )
+ return rect[0] + rect[2]-width;
+ else
+ return rect[0] + (rect[2]-width)/2;
+ }
+}
+
+static ui_px ui_min( ui_px a, ui_px b ){ return a<b?a:b; }
+static ui_px ui_max( ui_px a, ui_px b ){ return a>b?a:b; }
+static ui_px ui_clamp( ui_px a, ui_px min, ui_px max )
+{
+ return ui_min( max, ui_max( a, min ) );
+}
+
+static int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped )
+{
+ ui_px parent_max[2], child_max[2];
+ parent_max[0] = parent[0]+parent[2];
+ parent_max[1] = parent[1]+parent[3];
+ child_max[0] = child[0]+child[2];
+ child_max[1] = child[1]+child[3];
+
+ clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] );
+ clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] );
+ clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] );
+ clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] );
+
+ if( clipped[0] == clipped[2] ||
+ clipped[1] == clipped[3] )
+ return 0;
+
+ clipped[2] -= clipped[0];
+ clipped[3] -= clipped[1];
+
+ return 1;
+}
+
+static int ui_inside_rect( ui_rect rect, ui_px co[2] )
+{
+ if( co[0] >= rect[0] &&
+ co[1] >= rect[1] &&
+ co[0] <= rect[0]+rect[2] &&
+ co[1] <= rect[1]+rect[3] )
+ {
+ return 1;
+ }
+ else
+ return 0;
+}
+
+static int ui_click_down(void)
+{
+ if( vg_uictx.ignore_input_frames ) return 0;
+
+ if( (vg_uictx.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT)) &&
+ !(vg_uictx.mouse_state[1] & SDL_BUTTON(SDL_BUTTON_LEFT)) )
+ return 1;
+ else
+ return 0;
+}
+
+static int ui_clicking(void)
+{
+ if( vg_uictx.ignore_input_frames ) return 0;
+ return vg_uictx.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT);
+}
+
+static int ui_click_up(void)
+{
+ if( vg_uictx.ignore_input_frames ) return 0;
+ if( (vg_uictx.mouse_state[1] & SDL_BUTTON(SDL_BUTTON_LEFT)) &&
+ !(vg_uictx.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT)) )
+ return 1;
+ else
+ return 0;
+}
+
+static void ui_prerender(void)
+{
+ int x, y;
+ vg_uictx.mouse_state[1] = vg_uictx.mouse_state[0];
+ vg_uictx.mouse_state[0] = SDL_GetMouseState( &x, &y );
+ vg_uictx.mouse[0] = x;
+ vg_uictx.mouse[1] = y;
+
+ vg_uictx.cur_vert = 0;
+ vg_uictx.cur_indice = 0;
+ vg_uictx.vert_start = 0;
+ vg_uictx.indice_start = 0;
+
+ if( vg_uictx.ignore_input_frames ){
+ vg_uictx.ignore_input_frames --;
+ return;
+ }
+
+ if( ui_click_down() ){
+ vg_uictx.mouse_click[0] = vg_uictx.mouse[0];
+ vg_uictx.mouse_click[1] = vg_uictx.mouse[1];
+ }
+}
+
+static void ui_text( ui_rect rect, const char *str, ui_px scale,
+ enum ui_align align )
+{
+ ui_rect text_cursor;
+ u32 current_colour = 0x00ffffff;
+
+ const char *_c = str;
+ u8 c;
+
+ text_cursor[0] = ui_text_aligned_x( str, rect, scale, align );
+ text_cursor[1] = rect[1];
+ text_cursor[2] = 8*scale;
+ text_cursor[3] = 14*scale;
+
+ if( align & (k_ui_align_middle|k_ui_align_bottom) ){
+ ui_px height = ui_text_string_height( str ) * scale;
+
+ if( align & k_ui_align_bottom )
+ text_cursor[1] += rect[3]-height;
+ else
+ text_cursor[1] += (rect[3]-height)/2;
+ }
+
+ while( (c = *(_c ++)) ){
+ if( c == '\n' ){
+ text_cursor[1] += 14*scale;
+ text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align );
+ continue;
+ }
+ else if( c >= 33 ){
+ u8 glyph_base[2];
+ u8 glyph_index = c;
+ glyph_base[0] = glyph_index & 0xf;
+ glyph_base[1] = (glyph_index-glyph_base[0])>>4;
+
+ glyph_base[0] *= 8;
+ glyph_base[1] *= 8;
+
+ ui_rect rect_char;
+
+ if( ui_clip( rect, text_cursor, rect_char ) ){
+ ui_fill_rect( rect_char, current_colour, (ui_px[4])
+ {
+ glyph_base[0]+2,
+ glyph_base[1]+1,
+ glyph_base[0]+6,
+ glyph_base[1]+8
+ });
+ }
+ }
+ else if( c == '\x1B' ){
+ /* vt codes */
+ _c ++;
+ u16 colour_id = 0;
+ for( int i=0; i<3; i ++ ){
+ if( _c[i] ){
+ if( _c[i] == 'm' ){
+ _c = _c + i + 1;
+
+ switch( colour_id ){
+ case '0': current_colour = 0x00ffffff; break;
+ case '3'|'1'<<8: current_colour = 0x00201fee; break;
+ case '3'|'2'<<8: current_colour = 0x0037e420; break;
+ case '3'|'3'<<8: current_colour = 0x000ed8e2; break;
+ case '3'|'4'<<8: current_colour = 0x00f15010; break;
+ case '3'|'5'<<8: current_colour = 0x00ee20ee; break;
+ case '3'|'6'<<8: current_colour = 0x00eeee20; break;
+ case '3'|'7'<<8: current_colour = 0x00ffffff; break;
+ }
+
+ break;
+ }
+
+ colour_id |= _c[i] << (i*8);
+ }
+ else{
+ _c = _c +i;
+ break;
+ }
+ }
+
+ continue;
+ }
+ else if( c == '\t' ){
+ text_cursor[0] += UI_GLYPH_SPACING_X*scale*4;
+ continue;
+ }
+
+ text_cursor[0] += UI_GLYPH_SPACING_X*scale;
+ }
+}
+
+static void ui_image( ui_rect rect, GLuint image )
+{
+ ui_flush( k_ui_shader_colour );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, image );
+ ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,0, 255,255 } );
+ ui_flush( k_ui_shader_image );
+}
+
+static u32 v4f_u32_colour( v4f colour )
+{
+ u32 r = colour[0] * 255.0f,
+ g = colour[1] * 255.0f,
+ b = colour[2] * 255.0f,
+ a = colour[3] * 255.0f;
+
+ return r | (g<<8) | (b<<16) | (a<<24);
+}
+
+static int ui_button( ui_rect rect, v4f colour )
+{
+ int clickup= ui_click_up(),
+ click = ui_clicking() | clickup,
+ target = ui_inside_rect( rect, vg_uictx.mouse_click ) && click,
+ hover = ui_inside_rect( rect, vg_uictx.mouse );
+
+ u32 basecolour = v4f_u32_colour( colour );
+
+ if( click ){
+ if( target ){
+ if( hover ){
+ if( clickup ){
+ ui_fill( rect, 0xffffffff );
+ vg_uictx.ignore_input_frames = 2;
+ rect_copy( rect, vg_uictx.click_fader );
+ vg_uictx.click_fade_opacity = 1.0f;
+ return 1;
+ }
+ else{
+ ui_fill( rect, 0xffcccccc );
+ return 0;
+ }
+ }
+ else{
+ ui_fill( rect, 0xff505050 );
+ ui_outline( rect, 1, 0xffffffff );
+ return 0;
+ }
+ }
+ else{
+ ui_fill( rect, 0xff505050 );
+ return 0;
+ }
+ }
+ else{
+ if( hover ){
+ ui_fill( rect, 0xffa0a0a0 );
+ return 0;
+ }
+ else{
+ ui_fill( rect, 0xff505050 );
+ return 0;
+ }
+ }
+}
+
+static int ui_button_text( ui_rect rect, const char *string, ui_px scale,
+ v4f colour )
+{
+ int result = ui_button( rect, colour );
+ ui_rect t = { 0,0, ui_text_line_width( string )*scale, 14*scale };
+ ui_rect_center( rect, t );
+ ui_text( t, string, scale, k_ui_align_left );
+ return result;
+}
+
+static void ui_postrender(void)
+{
+ if( vg_uictx.click_fade_opacity > 0.0f ){
+
+ float scale = vg_uictx.click_fade_opacity;
+ scale = vg_maxf( 1.0f/255.0f, scale*scale );
+
+ vg_uictx.click_fade_opacity -= vg.time_frame_delta * 1.8f;
+ u32 colour = v4f_u32_colour( (v4f){ 1.0f,1.0f,1.0f, scale } );
+
+ ui_rect rect;
+ rect[3] = (float)(vg_uictx.click_fader[3]) * scale;
+ rect[2] = vg_uictx.click_fader[2];
+ ui_rect_center( vg_uictx.click_fader, rect );
+
+ ui_fill( rect, colour );
+ }
+ ui_flush( k_ui_shader_colour );
+}
+
+#endif /* VG_IMGUI_H */