X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=vg_imgui.h;h=62ec1697b0cd34db8b7e633dcf2bfe823508a625;hb=fac9f6fff674421f27fa4f4c1d2844998f0003d6;hp=2f84885e64b1a89f2ab7ee5103ad95c4cc78b202;hpb=2026f6fe648696888762c5b10210191748a6c9de;p=vg.git diff --git a/vg_imgui.h b/vg_imgui.h index 2f84885..62ec169 100644 --- a/vg_imgui.h +++ b/vg_imgui.h @@ -6,6 +6,10 @@ * 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 + * 4. controls directly reference a memory location and use that as their + * unique id + * 5. a maximum of ONE control per memory location can be drawn at any given + * point. */ #ifndef VG_IMGUI_H @@ -18,6 +22,7 @@ typedef i16 ui_px; typedef ui_px ui_rect[4]; +typedef ui_px ui_point[2]; typedef struct ui_vert ui_vert; enum ui_axis { @@ -28,6 +33,7 @@ enum ui_axis { /* Relative to cursor p0 */ enum ui_align { /* DC BA */ + k_ui_align_lwr = 0xff, k_ui_align_left = 0x0000| 0x00, k_ui_align_right = 0x0000| 0x01, k_ui_align_center = 0x0000| 0x02, @@ -47,7 +53,7 @@ enum ui_align struct ui_vert { ui_px co[2]; - u8 uv[2]; + u16 uv[2]; u32 colour; }; #pragma pack(pop) @@ -67,8 +73,46 @@ enum ui_scheme_colour{ k_ui_brighter = 8 }; +static ui_px k_ui_widget_height = 28, + k_ui_scale = 1, + k_ui_padding = 8; + typedef u32 ui_scheme[8*4]; +struct ui_font { + ui_px glyph_width, + glyph_height, + glyph_baseline, + line_height, + sheet_size, + spacing, + offset_y; + + u8 ascii_start; +}; +typedef struct ui_font ui_font; + +static const ui_font vg_ui_font_small = { + .glyph_width = 8, + .glyph_height = 14, + .glyph_baseline = 4, + .line_height = 14, + .sheet_size = 256, + .spacing = 8, + .ascii_start = ' ', + .offset_y = 0 +}, +vg_ui_font_big = { + .glyph_width = 12, + .glyph_height = 21, + .glyph_baseline = 6, + .line_height = 21, + .sheet_size = 256, + .spacing = 10, + .ascii_start = ' ', + .offset_y = 84 +}; + #define UI_RGB( STDHEX ) 0xff000000 |\ ((STDHEX&0x000000ff)<<16) |\ ((STDHEX&0x0000ff00) ) |\ @@ -76,6 +120,22 @@ typedef u32 ui_scheme[8*4]; #define UI_TEXTBOX_MULTILINE 0x1 #define UI_TEXTBOX_WRAP 0x2 +#define UI_TEXTBOX_AUTOFOCUS 0x4 + +#define UI_MODAL_OK 0x0 +#define UI_MODAL_GOOD 0x1 +#define UI_MODAL_BAD 0x2 +#define UI_MODAL_WARN 0x3 +#define UI_MODAL_TYPE_BITS 0x3 + +#define UI_MOUSE_LEFT (SDL_BUTTON(SDL_BUTTON_LEFT)) +#define UI_MOUSE_RIGHT (SDL_BUTTON(SDL_BUTTON_RIGHT)) +#define UI_MOUSE_MIDDLE (SDL_BUTTON(SDL_BUTTON_MIDDLE)) + +#define UI_TOP 0x1 +#define UI_LEFT 0x2 +#define UI_BOTTOM 0x4 +#define UI_RIGHT 0x8 struct{ struct ui_vert *vertex_buffer; @@ -88,32 +148,72 @@ struct{ void *focused_control_id; /* uses the memory location of various locking controls as an id */ char *textbuf; + i32 *ptr_enum; }; u32 focused_control_hit; enum ui_control_type{ k_ui_control_none, k_ui_control_textbox, + k_ui_control_enum, + k_ui_control_modal } focused_control_type; - struct ui_textbuf{ - int cursor_user, cursor_pos; - u32 len; - u32 flags; - } - textbox; + union{ /* controls that can be focused */ + struct ui_textbuf{ + int cursor_user, cursor_pos; + u32 len; + u32 flags; + + struct ui_textbox_callbacks{ + void (*enter)( char *, u32 ), + (*up)( char *, u32 ), + (*down)( char *, u32 ), + (*change)( char *, u32 ), + (*escape)( void ); + } + callbacks; + } + textbox; + + struct ui_enum{ + struct ui_enum_opt{ + i32 value; + const char *alias; + } + *options; + u32 option_count; + ui_rect rect; + } + _enum; + }; + + struct ui_modal{ + const char *message; + u32 options; + + struct ui_modal_callbacks{ + void (*close)(u32); + } + callbacks; + } + modal; - GLuint tex_glyphs, vao, vbo, ebo; + GLuint tex_glyphs, vao, vbo, ebo, tex_bg; + v2f bg_inverse_ratio; ui_px mouse[2], mouse_click[2]; u32 mouse_state[2]; u32 ignore_input_frames; + int wants_mouse; ui_rect click_fader, click_fader_end; float click_fade_opacity; + f32 frosting; ui_scheme scheme; + const ui_font *font; enum ui_cursor{ k_ui_cursor_default, @@ -124,6 +224,7 @@ struct{ cursor; SDL_Cursor *cursor_map[ k_ui_cursor_max ]; + v4f colour; } static vg_ui = { .scheme = { @@ -161,70 +262,83 @@ static vg_ui = { [ k_ui_blue + k_ui_brighter ] = UI_RGB( 0x83a598 ), [ k_ui_purple + k_ui_brighter ] = UI_RGB( 0xd3869b ), [ k_ui_gray + k_ui_brighter ] = UI_RGB( 0xa89984 ), - } + }, + .font = &vg_ui_font_small, + .colour = {1.0f,1.0f,1.0f,1.0f}, + .bg_inverse_ratio = {1,1} }; -static struct vg_shader _shader_ui = -{ - .name = "[vg] ui", +static struct vg_shader _shader_ui ={ + .name = "[vg] ui - transparent", .link = NULL, - .vs = - { + .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;" + "uniform vec2 uBGInverseRatio;" "" - "out vec2 aTexCoords;" + "out vec4 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;" + "void main(){" + "vec4 proj_pos = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "gl_Position = proj_pos;" + "aTexCoords = vec4( a_uv * 0.00390625, " + " (proj_pos.xy*0.5+0.5) * uBGInverseRatio );" "aColour = a_colour;" - - "aWsp = a_co;" "}", }, - .fs = - { + .fs = { .orig_file = NULL, .static_src = - "uniform sampler2D uTexGlyphs;" + "uniform sampler2D uTexGlyphs;" + "uniform sampler2D uTexBG;" + "uniform vec4 uColour;" + "uniform float uSpread;" "out vec4 FragColor;" "" - "in vec2 aTexCoords;" + "in vec4 aTexCoords;" "in vec4 aColour;" "" - "in vec2 aWsp;" - - "vec2 rand_hash22( vec2 p )" - "{" + "vec2 rand_hash22( vec2 p ){" "vec3 p3 = fract(vec3(p.xyx) * 213.8976123);" "p3 += dot(p3, p3.yzx+19.19);" "return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));" "}" "" - "void main()" - "{" + "void main(){" "vec4 diffuse = aColour;" - "if( diffuse.a == 0.0 )" - "{" - "diffuse.a = texture( uTexGlyphs, aTexCoords ).r;" + "vec4 avg = vec4(0.0);" + + "if( aColour.a == 0.0 ){" + "avg = aColour;" + "avg.a = texture( uTexGlyphs, aTexCoords.xy ).r;" "}" - - "FragColor = diffuse;" + "else{" + "if( uSpread > 0.0001 ){" + "for( int i=0; i<4; i ++ ){" + "vec2 spread = rand_hash22(aTexCoords.zw+vec2(float(i)));" + "avg += texture( uTexBG, aTexCoords.zw + (spread-0.5)*uSpread );" + "}" + "avg *= 0.25;" + "avg.a = 1.0;" + "avg.rgb = mix( avg.rgb, aColour.rgb, aColour.a );" + "}" + "else{" + "avg = aColour;" + "}" + "}" + + "FragColor = avg * uColour;" "}" } }; -static struct vg_shader _shader_ui_image = -{ +static struct vg_shader _shader_ui_image = { .name = "[vg] ui_image", .link = NULL, .vs = @@ -243,7 +357,7 @@ static struct vg_shader _shader_ui_image = "void main()" "{" "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" - "aTexCoords = a_uv * 0.0078125;" + "aTexCoords = a_uv * 0.00390625;" "aColour = a_colour;" "aWsp = a_co;" @@ -254,6 +368,7 @@ static struct vg_shader _shader_ui_image = .orig_file = NULL, .static_src = "uniform sampler2D uTexImage;" + "uniform vec4 uColour;" "out vec4 FragColor;" "in vec2 aTexCoords;" @@ -263,27 +378,16 @@ static struct vg_shader _shader_ui_image = "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;" + "FragColor = colour * uColour;" "}" } }; -#define UI_GLYPH_SPACING_X 8 -VG_STATIC void _vg_ui_init(void) -{ +static void _vg_ui_init(void){ if( !vg_shader_compile( &_shader_ui ) || - !vg_shader_compile( &_shader_ui_image ) ) + !vg_shader_compile( &_shader_ui_image ) ){ vg_fatal_error( "Failed to compile ui shader" ); + } /* * Vertex buffer @@ -321,7 +425,7 @@ VG_STATIC void _vg_ui_init(void) glEnableVertexAttribArray( 0 ); /* UV */ - glVertexAttribPointer( 1, 2, GL_UNSIGNED_BYTE, GL_FALSE, stride, + glVertexAttribPointer( 1, 2, GL_UNSIGNED_SHORT, GL_FALSE, stride, (void *)offsetof( struct ui_vert, uv ) ); glEnableVertexAttribArray( 1 ); @@ -392,14 +496,12 @@ enum ui_shader { k_ui_shader_image }; -static void rect_copy( ui_rect a, ui_rect b ) -{ +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 ) -{ +static void ui_flush( enum ui_shader shader, f32 w, f32 h ){ u32 vertex_offset = vg_ui.vert_start*sizeof(ui_vert), vertex_count = vg_ui.cur_vert-vg_ui.vert_start, vertex_size = vertex_count*sizeof(ui_vert), @@ -420,30 +522,44 @@ VG_STATIC void ui_flush( enum ui_shader shader ) glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, indice_offset, indice_size, vg_ui.indice_buffer+vg_ui.indice_start ); + glDisable( GL_DEPTH_TEST ); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glBlendEquation( GL_FUNC_ADD ); + glDisable( GL_CULL_FACE ); 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 } ); + m3x3_scale( view, (v3f){ 1.0f/(w*0.5f), + -1.0f/(h*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 ); + glUniform4fv( glGetUniformLocation( _shader_ui.id, "uColour" ), 1, + vg_ui.colour ); glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 ); + + glActiveTexture( GL_TEXTURE1 ); + glBindTexture( GL_TEXTURE_2D, vg_ui.tex_bg ); + glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexBG" ), 1 ); + glUniform1f( glGetUniformLocation( _shader_ui.id, "uSpread" ), + vg_ui.frosting ); + glUniform2fv( glGetUniformLocation( _shader_ui.id, "uBGInverseRatio" ), + 1, vg_ui.bg_inverse_ratio ); } 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 ); + glUniform4fv( glGetUniformLocation( _shader_ui_image.id, "uColour" ), 1, + vg_ui.colour ); } else vg_fatal_error( "Invalid UI shader (%d)\n", shader ); @@ -457,8 +573,7 @@ VG_STATIC void ui_flush( enum ui_shader shader ) vg_ui.vert_start = vg_ui.cur_vert; } -static void ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ) -{ +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_ui.cur_vert + 4 > vg_ui.max_verts) || (vg_ui.cur_indice + 6 > vg_ui.max_indices)) @@ -500,13 +615,11 @@ static void ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ) vg_ui.cur_vert += 4; } -static void ui_fill( ui_rect rect, u32 colour ) -{ +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 ) -{ +static void ui_outline( ui_rect rect, ui_px thickness, u32 colour, u32 mask ){ /* this if far from ideal but stops us from crashing */ if( (vg_ui.cur_vert + 8 > vg_ui.max_verts) || (vg_ui.cur_indice + 24 > vg_ui.max_indices)) @@ -541,41 +654,49 @@ static void ui_outline( ui_rect rect, ui_px thickness, u32 colour ) u16 start = vg_ui.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= 32 && c <= 126 ) - length ++; - else if( c == '\n' ) - break; + if( c >= 32 ) length ++; + else if( c == '\n' ) break; } - return length * 8; + return length * vg_ui.font->spacing; } -static ui_px ui_text_string_height( const char *str ) -{ +static ui_px ui_text_string_height( const char *str ){ int height = 1; const char *_c = str; - char c; + u8 c; while( (c = *(_c ++)) ){ if( c == '\n' ) height ++; @@ -635,15 +750,15 @@ static ui_px ui_text_string_height( const char *str ) } 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 ){ + enum ui_align align ){ + enum ui_align lwr = k_ui_align_lwr & align; + if( lwr == k_ui_align_left ){ return rect[0]; } else{ ui_px width = ui_text_line_width( str ) * scale; - if( align == k_ui_align_right ) + if( lwr == k_ui_align_right ) return rect[0] + rect[2]-width; else return rect[0] + (rect[2]-width)/2; @@ -652,13 +767,11 @@ static ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale, static ui_px ui_min( ui_px a, ui_px b ){ return ab?a:b; } -static ui_px ui_clamp( ui_px a, ui_px min, ui_px max ) -{ +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 ) -{ +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]; @@ -680,48 +793,41 @@ static int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ) return 1; } -static int ui_inside_rect( ui_rect rect, ui_px co[2] ) -{ +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] ) - { + co[0] < rect[0]+rect[2] && + co[1] < rect[1]+rect[3] ){ return 1; } else return 0; } -static int ui_click_down(void) -{ +static int ui_click_down( u32 mask ){ if( vg_ui.ignore_input_frames ) return 0; - - if( (vg_ui.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT)) && - !(vg_ui.mouse_state[1] & SDL_BUTTON(SDL_BUTTON_LEFT)) ) + if( (vg_ui.mouse_state[0] & mask) && + !(vg_ui.mouse_state[1] & mask) ) return 1; else return 0; } -static int ui_clicking(void) -{ +static int ui_clicking( u32 mask ){ if( vg_ui.ignore_input_frames ) return 0; - return vg_ui.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT); + return vg_ui.mouse_state[0] & mask; } -static int ui_click_up(void) -{ +static int ui_click_up( u32 mask ){ if( vg_ui.ignore_input_frames ) return 0; - if( (vg_ui.mouse_state[1] & SDL_BUTTON(SDL_BUTTON_LEFT)) && - !(vg_ui.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT)) ) + if( (vg_ui.mouse_state[1] & mask) && + !(vg_ui.mouse_state[0] & mask) ) return 1; else return 0; } -static void ui_prerender(void) -{ +static void ui_prerender(void){ int x, y; vg_ui.mouse_state[1] = vg_ui.mouse_state[0]; vg_ui.mouse_state[0] = SDL_GetMouseState( &x, &y ); @@ -734,26 +840,25 @@ static void ui_prerender(void) vg_ui.indice_start = 0; vg_ui.focused_control_hit = 0; vg_ui.cursor = k_ui_cursor_default; + vg_ui.wants_mouse = 0; if( vg_ui.ignore_input_frames ){ vg_ui.ignore_input_frames --; return; } - if( ui_click_down() ){ + if( ui_click_down(UI_MOUSE_LEFT)||ui_click_down(UI_MOUSE_MIDDLE) ){ vg_ui.mouse_click[0] = vg_ui.mouse[0]; vg_ui.mouse_click[1] = vg_ui.mouse[1]; } } -static u32 ui_colour( enum ui_scheme_colour id ) -{ +static u32 ui_colour( enum ui_scheme_colour id ){ return vg_ui.scheme[ id ]; } /* get an appropriately contrasting colour given the base */ -static u32 ui_colourcont( enum ui_scheme_colour id ) -{ +static u32 ui_colourcont( enum ui_scheme_colour id ){ if ( id < k_ui_bg+6 ) return ui_colour( k_ui_fg ); else if( id < k_ui_fg ) return ui_colour( k_ui_bg+1 ); else if( id < k_ui_hue ) return ui_colour( k_ui_bg+3 ); @@ -761,9 +866,35 @@ static u32 ui_colourcont( enum ui_scheme_colour id ) else return ui_colour( k_ui_fg+1 ); } +static void ui_hex_to_norm( u32 hex, v4f norm ){ + norm[0] = ((hex ) & 0xff); + norm[1] = ((hex>>8 ) & 0xff); + norm[2] = ((hex>>16) & 0xff); + norm[3] = ((hex>>24) & 0xff); + v4_muls( norm, 1.0f/255.0f, norm ); +} + +static void ui_text_glyph( const struct ui_font *font, ui_px scale, + u8 glyph, ui_rect out_texcoords ){ + glyph -= font->ascii_start; + + ui_px per_row = font->sheet_size / font->glyph_width, + column = (ui_px)glyph % per_row, + row = (glyph - column) / per_row; + + out_texcoords[0] = column * font->glyph_width; + out_texcoords[1] = row * font->glyph_height + font->offset_y; + out_texcoords[2] = out_texcoords[0] + font->glyph_width; + out_texcoords[3] = out_texcoords[1] + font->glyph_height; +} + +static u32 ui_opacity( u32 colour, f32 opacity ){ + u32 alpha = opacity * 255.0f; + return (colour & 0x00ffffff) | (alpha << 24); +} + static u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, - enum ui_align align, u32 colour ) -{ + enum ui_align align, u32 colour ){ ui_rect text_cursor; if( colour == 0 ) colour = ui_colour( k_ui_fg ); @@ -774,8 +905,8 @@ static u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, 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; + text_cursor[2] = vg_ui.font->glyph_width*scale; + text_cursor[3] = vg_ui.font->glyph_height*scale; u32 printed_chars = 0; @@ -791,53 +922,29 @@ static u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, while( (c = *(_c ++)) ){ if( printed_chars >= len ){ printed_chars = 0; - text_cursor[1] += 14*scale; + text_cursor[1] += vg_ui.font->line_height*scale; text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); - text_cursor[0] -= UI_GLYPH_SPACING_X*scale; - - u8 glyph_base[2]; - u8 glyph_index = '\x90'; - glyph_base[0] = glyph_index & 0xf; - glyph_base[1] = (glyph_index-glyph_base[0])>>4; - glyph_base[0] *= 8; - glyph_base[1] *= 8; - - ui_fill_rect( text_cursor, 0x00ffffff, (ui_px[4]) - { - glyph_base[0]+2, - glyph_base[1]+1, - glyph_base[0]+6, - glyph_base[1]+8 - }); - - text_cursor[0] += UI_GLYPH_SPACING_X*scale; + text_cursor[0] -= vg_ui.font->spacing*scale; + + ui_rect glyph; + ui_text_glyph( vg_ui.font, scale, '\xb6' /*FIXME*/, glyph ); + ui_fill_rect( text_cursor, 0x00ffffff, glyph ); + text_cursor[0] += vg_ui.font->spacing*scale; } if( c == '\n' ){ - text_cursor[1] += 14*scale; + text_cursor[1] += vg_ui.font->line_height*scale; text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); printed_chars = 0; 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, colour, (ui_px[4]) - { - glyph_base[0]+2, - glyph_base[1]+1, - glyph_base[0]+6, - glyph_base[1]+8 - }); + else if( c >= 33 ){ + ui_rect glyph; + ui_text_glyph( vg_ui.font, scale, c, glyph ); + + ui_rect cursor_clipped; + if( ui_clip( rect, text_cursor, cursor_clipped ) ){ + ui_fill_rect( cursor_clipped, colour, glyph ); } } else if( c == '\x1B' ){ @@ -858,7 +965,7 @@ static u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, case '3'|'4'<<8: colour = ui_colour( k_ui_blue ); break; case '3'|'5'<<8: colour = ui_colour( k_ui_purple ); break; case '3'|'6'<<8: colour = ui_colour( k_ui_aqua ); break; - case '3'|'7'<<8: colour = ui_colour( 0xffffffff ); break; + case '3'|'7'<<8: colour = 0xffffffff; break; } colour &= 0x00ffffff; @@ -876,12 +983,12 @@ static u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, continue; } else if( c == '\t' ){ - text_cursor[0] += UI_GLYPH_SPACING_X*scale*4; + text_cursor[0] += vg_ui.font->spacing*scale*4; printed_chars += 4; continue; } - text_cursor[0] += UI_GLYPH_SPACING_X*scale; + text_cursor[0] += vg_ui.font->spacing*scale; printed_chars ++; } @@ -889,23 +996,52 @@ static u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, } static void ui_text( ui_rect rect, const char *str, ui_px scale, - enum ui_align align, u32 colour ) -{ + enum ui_align align, u32 colour ){ ui_ntext( rect, str, 1024, scale, align, colour ); } -static void ui_image( ui_rect rect, GLuint image ) -{ - ui_flush( k_ui_shader_colour ); +/* + * Standard layout stuff + * ----------------------------------------------------------------------------- + */ + +static void ui_panel( ui_rect in_rect, ui_rect out_panel ){ + ui_fill( in_rect, ui_colour( k_ui_bg+1 ) ); + ui_outline( in_rect, 1, ui_colour( k_ui_bg+7 ), 0 ); + rect_copy( in_rect, out_panel ); + ui_rect_pad( out_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } ); +} +static void ui_label( ui_rect rect, const char *text, ui_px size, + ui_px gap, ui_rect r ){ + ui_rect l; + ui_px width = (ui_text_line_width(text)+vg_ui.font->spacing) * size; + ui_split( rect, k_ui_axis_v, width, gap, l, r ); + ui_text( l, text, 1, k_ui_align_middle_left, 0 ); +} + +static void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, + ui_px count ){ + ui_px height = (count * vg_ui.font->glyph_height + 18) * k_ui_scale; + ui_split( inout_panel, k_ui_axis_h, height, k_ui_padding, + out_rect, inout_panel ); +} + +static void ui_info( ui_rect inout_panel, const char *text ){ + ui_rect box; + ui_standard_widget( inout_panel, box, 1 ); + ui_text( box, text, 1, k_ui_align_middle_left, 0 ); +} + +static void ui_image( ui_rect rect, GLuint image ){ + ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y ); 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 ); + ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,256,256,0 } ); + ui_flush( k_ui_shader_image, vg.window_x, vg.window_y ); } -static u32 v4f_u32_colour( v4f colour ) -{ +static u32 v4f_u32_colour( v4f colour ){ u32 r = colour[0] * 255.0f, g = colour[1] * 255.0f, b = colour[2] * 255.0f, @@ -914,11 +1050,11 @@ static u32 v4f_u32_colour( v4f colour ) return r | (g<<8) | (b<<16) | (a<<24); } -static void ui_defocus_all(void) -{ +static void ui_defocus_all(void){ if( vg_ui.focused_control_type == k_ui_control_textbox ){ - vg_info( "SDL_StopTextInput()\n" ); SDL_StopTextInput(); + if( vg_ui.textbox.callbacks.escape ) + vg_ui.textbox.callbacks.escape(); } vg_ui.focused_control_id = NULL; @@ -926,10 +1062,18 @@ static void ui_defocus_all(void) vg_ui.focused_control_type = k_ui_control_none; } -static int ui_button( ui_rect rect, enum ui_scheme_colour colour ) -{ - int clickup= ui_click_up(), - click = ui_clicking() | clickup, +enum ui_button_state { + k_ui_button_none = 0, + k_ui_button_click = 1, + k_ui_button_holding_inside = 2, + k_ui_button_holding_outside = 3, + k_ui_button_hover = 4 +}; + +static enum ui_button_state ui_colourbutton( ui_rect rect, + enum ui_scheme_colour colour ){ + int clickup= ui_click_up(UI_MOUSE_LEFT), + click = ui_clicking(UI_MOUSE_LEFT) | clickup, target = ui_inside_rect( rect, vg_ui.mouse_click ) && click, hover = ui_inside_rect( rect, vg_ui.mouse ); @@ -937,6 +1081,13 @@ static int ui_button( ui_rect rect, enum ui_scheme_colour colour ) col_highlight = vg_ui.scheme[ k_ui_fg ], col_hover = vg_ui.scheme[ colour + k_ui_brighter ]; + if( vg_ui.focused_control_type != k_ui_control_none ){ + clickup = 0; + click = 0; + target = 0; + hover = 0; + } + if( hover ){ vg_ui.cursor = k_ui_cursor_hand; } @@ -955,50 +1106,66 @@ static int ui_button( ui_rect rect, enum ui_scheme_colour colour ) vg_ui.click_fade_opacity = 1.0f; ui_defocus_all(); - return 1; + return k_ui_button_click; } else{ ui_fill( rect, col_highlight ); - return 0; + return k_ui_button_holding_inside; } } else{ ui_fill( rect, col_base ); - ui_outline( rect, 1, col_highlight ); - return 0; + ui_outline( rect, 1, col_highlight, 0 ); + return k_ui_button_holding_outside; } } else{ ui_fill( rect, col_base ); - return 0; + return k_ui_button_none; } } else{ if( hover ){ ui_fill( rect, col_hover ); - return 0; + return k_ui_button_hover; } else{ ui_fill( rect, col_base ); - return 0; + return k_ui_button_none; } } } -static int ui_button_text( ui_rect rect, const char *string, ui_px scale, - enum ui_scheme_colour colour ) -{ - int result = ui_button( rect, colour ); +static enum ui_button_state ui_colourbutton_text( + ui_rect rect, const char *string, ui_px scale, + enum ui_scheme_colour colour ){ + enum ui_button_state state = ui_colourbutton( 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, ui_colourcont(colour) ); - return result; + + u32 text_colour = ui_colourcont(colour); + if( state == k_ui_button_holding_inside ) + text_colour = colour; + + ui_text( t, string, scale, k_ui_align_left, text_colour ); + return state; } -static void ui_postrender(void) -{ - if( vg_ui.click_fade_opacity > 0.0f ){ +static enum ui_button_state ui_button_text( ui_rect rect, + const char *string, ui_px scale ){ + return ui_colourbutton_text( rect, string, scale, k_ui_bg+4 ); +} +static enum ui_button_state ui_button( ui_rect inout_panel, + const char *string ){ + ui_rect rect; + ui_standard_widget( inout_panel, rect, 1 ); + return ui_colourbutton_text( rect, string, 1, k_ui_bg+4 ); +} + +static void ui_enum_post(void); +static void ui_postrender(void){ + if( vg_ui.click_fade_opacity > 0.0f ){ float scale = vg_ui.click_fade_opacity; scale = vg_maxf( 1.0f/255.0f, scale*scale ); @@ -1020,64 +1187,179 @@ static void ui_postrender(void) ui_fill( rect, colour ); } - ui_flush( k_ui_shader_colour ); + + if( vg_ui.focused_control_type == k_ui_control_enum ){ + ui_enum_post(); + } + else if( vg_ui.focused_control_type == k_ui_control_modal ){ + ui_rect screen = {0,0,vg.window_x,vg.window_y}; + ui_fill( screen, 0xa0000000 ); + ui_rect box = {0,0,400,200}; + + u32 colour = ui_colour(k_ui_fg), + type = vg_ui.modal.options & UI_MODAL_TYPE_BITS; + if ( type == 1 ) colour = ui_colour(k_ui_green); + else if( type == 2 ) colour = ui_colour(k_ui_red); + else if( type == 3 ) colour = ui_colour(k_ui_yellow); + + ui_rect_center( screen, box ); + ui_fill( box, ui_colour(k_ui_bg) ); + ui_outline( box, -1, colour, 0 ); + + ui_rect message; + rect_copy( box, message ); + message[3] = 100; + ui_rect_center( box, message ); + + ui_rect row0, row1, btn; + ui_split_ratio( message, k_ui_axis_h, 0.5f, 0, row0, row1 ); + row0[0] += vg_ui.font->spacing; + ui_ntext( row0, vg_ui.modal.message, (box[2]/vg_ui.font->spacing)-2, 1, + k_ui_align_left, colour ); + + rect_copy( row1, btn ); + btn[2] = 86; + btn[3] = 28; + ui_rect_center( row1, btn ); + + vg_ui.focused_control_type = k_ui_control_none; /* HACK */ + if( ui_button_text( btn, "OK", 1 ) != 1 ) + vg_ui.focused_control_hit = 1; + vg_ui.focused_control_type = k_ui_control_modal; /* HACK */ + vg_ui.wants_mouse = 1; + } + + ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y ); if( !vg_ui.focused_control_hit ){ ui_defocus_all(); } + if( vg_ui.wants_mouse ){ + SDL_SetWindowGrab( vg.window, SDL_FALSE ); + SDL_SetRelativeMouseMode( SDL_FALSE ); + } + else{ + SDL_SetWindowGrab( vg.window, SDL_TRUE ); + SDL_SetRelativeMouseMode( SDL_TRUE ); + } + SDL_SetCursor( vg_ui.cursor_map[ vg_ui.cursor ] ); SDL_ShowCursor(1); } +/* + * checkbox + * ----------------------------------------------------------------------------- + */ +static int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ){ + ui_rect rect, label, box; + ui_standard_widget( inout_panel, rect, 1 ); -static void ui_dev_colourview(void) -{ - ui_rect window = {vg.window_x-256,0,256,vg.window_y}, swatch; - - const char *names[vg_list_size(vg_ui.scheme)] = { - [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3", - "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7", + ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box ); + ui_text( label, str_label, k_ui_scale, k_ui_align_middle_left, 0 ); - [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3", - "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7", + int changed = ui_colourbutton( box, k_ui_bg )==1; + if( changed ) + *data = (*data) ^ 0x1; - [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green", - "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray", - "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8", - "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" }; + if( *data ){ + ui_rect_pad( box, (ui_px[2]){4,4} ); + ui_fill( box, ui_colour( k_ui_orange ) ); + } - ui_rect col[2]; - ui_split_ratio( window, k_ui_axis_v, 0.5f, 0.0f, col[0], col[1] ); + return changed; +} - for( int i=0; ikey == ev.sym ){ - if( mapping->mod == 0 ){ - if( mod == 0 ){ - mapping->handler(); - return; - } - } - else if( (mod & mapping->mod) == mapping->mod ){ - mapping->handler(); - return; - } - } - } -} - -/* - * Callback for text entry mode - */ -VG_STATIC void ui_proc_utf8( const char *text ) -{ - const char *ptr = text; - - while( *ptr ){ - if( *ptr != '`' ) _ui_textbox_put_char( *ptr ); - ptr ++; - } -} - - - - - - - - - - - - - - - - - - - - - - -static void _ui_textbox_cursor_rect( ui_rect text_rect, ui_rect cursor ) -{ - int start = VG_MIN( vg_ui.textbox.cursor_pos, vg_ui.textbox.cursor_user ), - end = VG_MAX( vg_ui.textbox.cursor_pos, vg_ui.textbox.cursor_user ); - - cursor[0] = text_rect[0] + start*UI_GLYPH_SPACING_X-1; - cursor[1] = text_rect[1]; - cursor[2] = start == end? 2: (float)(end-start)*(float)UI_GLYPH_SPACING_X; - cursor[3] = 14; -} - -static void _ui_textbox_calc_index_from_grid( int co[3], int char_width ) -{ +static void _ui_textbox_calc_index_from_grid( int co[3], int wrap_length ){ int i[3] = {0,0,0}; char c; while( (c = vg_ui.textbuf[i[2]]) ){ if( i[1]==co[1] && i[0]>=co[0] ) break; - if( i[0] >= char_width ){ + if( i[0] >= wrap_length ){ i[1] ++; i[0] = 0; } @@ -1506,9 +1720,11 @@ static void _ui_textbox_calc_index_from_grid( int co[3], int char_width ) co[2] = i[2]; } -static -void _ui_textbox_index_calc_coords( int co[3], int char_width ) -{ +/* + * based on the index specied in co[2], work out the visual character + * coordinates and store them in co[0], co[1] + */ +static void _ui_textbox_index_calc_coords( int co[3], int wrap_length ){ co[0] = 0; co[1] = 0; @@ -1517,7 +1733,7 @@ void _ui_textbox_index_calc_coords( int co[3], int char_width ) while( (c = vg_ui.textbuf[i ++]) ){ if( i > co[2] ) break; - if( co[0] >= char_width ){ + if( co[0] >= wrap_length ){ co[1] ++; co[0] = 0; } @@ -1529,12 +1745,18 @@ void _ui_textbox_index_calc_coords( int co[3], int char_width ) } } -static int _ui_textbox_run_remaining( int index[3], int char_width ) -{ +/* + * calculate the number of characters remaining until either: + * - the wrap_length limit is hit + * - end of the line/string + * + * index must be fully populated with visual X/Y, and linear index + */ +static int _ui_textbox_run_remaining( int index[3], int wrap_length ){ int i=0, printed_chars=0; char c; while( (c = vg_ui.textbuf[index[2] + (i ++)]) ){ - if( index[0]+i >= char_width ) break; + if( index[0]+i >= wrap_length ) break; if( c >= 32 && c <= 126 ) printed_chars ++; else if( c == '\n' ) break; } @@ -1542,13 +1764,37 @@ static int _ui_textbox_run_remaining( int index[3], int char_width ) return printed_chars+1; } -static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) -{ - int clickup= ui_click_up(), - click = ui_clicking() | clickup, +static int ui_textbox( ui_rect inout_panel, const char *label, + char *buf, u32 len, u32 lines, u32 flags, + struct ui_textbox_callbacks *callbacks ){ + assert( lines > 0 ); + assert( buf ); + + if( lines > 1 ) flags |= UI_TEXTBOX_MULTILINE; + + ui_rect rect; + ui_standard_widget( inout_panel, rect, lines ); + + if( label ) + ui_label( rect, label, 1, 0, rect ); + + int clickup= ui_click_up(UI_MOUSE_LEFT), + clickdown = ui_click_down(UI_MOUSE_LEFT), + click = ui_clicking(UI_MOUSE_LEFT) | clickup, target = ui_inside_rect( rect, vg_ui.mouse_click ) && click, hover = ui_inside_rect( rect, vg_ui.mouse ); + /* allow instant transitions from textbox->textbox */ + if( (vg_ui.focused_control_type != k_ui_control_none) && + (vg_ui.focused_control_type != k_ui_control_textbox) ){ + clickup = 0; + clickdown = 0; + click = 0; + target = 0; + hover = 0; + flags &= ~UI_TEXTBOX_AUTOFOCUS; + } + u32 col_base = ui_colour( k_ui_bg ), col_highlight = ui_colour( k_ui_fg ), col_cursor = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; @@ -1559,15 +1805,15 @@ static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) rect_copy( rect, text_rect ); if( flags & UI_TEXTBOX_MULTILINE ) text_rect[3] = rect[3]-16; - else text_rect[3] = 14; + else text_rect[3] = vg_ui.font->line_height; text_rect[2] -= 16; ui_rect_center( rect, text_rect ); - ui_px char_width = 1024; + ui_px wrap_length = 1024; if( flags & UI_TEXTBOX_WRAP ) - char_width = text_rect[2] / UI_GLYPH_SPACING_X; + wrap_length = text_rect[2] / vg_ui.font->spacing; if( hover ){ vg_ui.cursor = k_ui_cursor_ibeam; @@ -1575,28 +1821,28 @@ static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) if( vg_ui.focused_control_id == buf ){ ui_fill( rect, col_base ); - ui_ntext( text_rect, buf, char_width, 1, k_ui_align_left, 0 ); + ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); - if( (clickup||ui_click_down()) && !target ){ + if( !(flags & UI_TEXTBOX_AUTOFOCUS) && ((clickup||clickdown) && !target)){ ui_defocus_all(); } else{ vg_ui.focused_control_hit = 1; if( click && target ){ int p0[3] ={ - (vg_ui.mouse_click[0] - text_rect[0]) / UI_GLYPH_SPACING_X, - (vg_ui.mouse_click[1] - text_rect[1]) / 14, + (vg_ui.mouse_click[0] - text_rect[0]) / vg_ui.font->spacing, + (vg_ui.mouse_click[1] - text_rect[1]) / vg_ui.font->line_height, -1 }, p1[3] = { - (vg_ui.mouse[0] - text_rect[0]) / UI_GLYPH_SPACING_X, - (vg_ui.mouse[1] - text_rect[1]) / 14, + (vg_ui.mouse[0] - text_rect[0]) / vg_ui.font->spacing, + (vg_ui.mouse[1] - text_rect[1]) / vg_ui.font->line_height, -1 }; if( flags & UI_TEXTBOX_MULTILINE ){ - _ui_textbox_calc_index_from_grid( p0, char_width ); - _ui_textbox_calc_index_from_grid( p1, char_width ); + _ui_textbox_calc_index_from_grid( p0, wrap_length ); + _ui_textbox_calc_index_from_grid( p1, wrap_length ); vg_ui.textbox.cursor_pos = p0[2]; vg_ui.textbox.cursor_user = p1[2]; @@ -1608,7 +1854,7 @@ static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) } } - ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ] ); + ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ], 0 ); ui_rect cursor; @@ -1618,15 +1864,14 @@ static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) end = VG_MAX( c0, c1 ), chars = end-start; - if( flags & (UI_TEXTBOX_MULTILINE|UI_TEXTBOX_WRAP) ){ - + if( flags & (UI_TEXTBOX_WRAP|UI_TEXTBOX_MULTILINE) ){ int pos[3], remaining = chars; pos[2] = start; - _ui_textbox_index_calc_coords( pos, char_width ); + _ui_textbox_index_calc_coords( pos, wrap_length ); if( start==end ){ - cursor[0] = text_rect[0] + pos[0]*UI_GLYPH_SPACING_X-1; + cursor[0] = text_rect[0] + pos[0]*vg_ui.font->spacing-1; cursor[1] = text_rect[1] + pos[1]*14; cursor[2] = 2; cursor[3] = 13; @@ -1635,15 +1880,12 @@ static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) } else{ while( remaining ){ - /* TODO: scan for newlines and stuff - * eol or char_width can have line breaks! */ - - int run = _ui_textbox_run_remaining( pos, char_width ); + int run = _ui_textbox_run_remaining( pos, wrap_length ); run = VG_MIN( run, remaining ); - cursor[0] = text_rect[0] + pos[0]*UI_GLYPH_SPACING_X-1; + cursor[0] = text_rect[0] + pos[0]*vg_ui.font->spacing-1; cursor[1] = text_rect[1] + pos[1]*14; - cursor[2] = (float)(run)*(float)UI_GLYPH_SPACING_X; + cursor[2] = (float)(run)*(float)vg_ui.font->spacing; cursor[3] = 13; ui_fill( cursor, col_cursor ); @@ -1657,7 +1899,7 @@ static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) } } else{ - cursor[0] = text_rect[0] + start*UI_GLYPH_SPACING_X-1; + cursor[0] = text_rect[0] + start*vg_ui.font->spacing-1; cursor[1] = text_rect[1]; cursor[3] = 13; @@ -1665,7 +1907,7 @@ static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) cursor[2] = 2; } else{ - cursor[2] = (float)(chars)*(float)UI_GLYPH_SPACING_X; + cursor[2] = (float)(chars)*(float)vg_ui.font->spacing; } if( (vg_ui.click_fade_opacity<=0.0f) && @@ -1680,8 +1922,8 @@ static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) return 0; } - if( click ){ - if( target && hover ){ + if( click || (flags & UI_TEXTBOX_AUTOFOCUS) ){ + if( (target && hover) || (flags & UI_TEXTBOX_AUTOFOCUS) ){ ui_defocus_all(); ui_fill( rect, col_highlight ); @@ -1698,7 +1940,16 @@ static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) vg_ui.textbox.cursor_pos = 0; vg_ui.textbox.cursor_user = 0; - vg_info( "SDL_StartTextInput()\n" ); + if( callbacks ){ + vg_ui.textbox.callbacks = *callbacks; + } + else{ + vg_ui.textbox.callbacks.change = NULL; + vg_ui.textbox.callbacks.down = NULL; + vg_ui.textbox.callbacks.up = NULL; + vg_ui.textbox.callbacks.enter = NULL; + } + SDL_StartTextInput(); } } @@ -1706,11 +1957,196 @@ static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags ) ui_fill( rect, col_base ); if( hover ){ - ui_outline( rect, -1, col_highlight ); + ui_outline( rect, -1, col_highlight, 0 ); } - ui_ntext( text_rect, buf, char_width, 1, k_ui_align_left, 0 ); + ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); return 0; } +/* + * Tabs + * ----------------------------------------------------------------------------- + */ + +static void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel, + const char **titles, u32 count, i32 *page ){ + ui_rect bar; + ui_standard_widget( inout_panel, bar, 1 ); + + i32 cur_page = *page; + + f32 width = (f32)inout_panel[2] / (f32)count; + + ui_px h = (inout_panel[1] + inout_panel[3]) - (bar[1]+bar[3]); + inout_panel[1] = bar[1]+bar[3]; + inout_panel[3] = h; + + ui_fill( inout_panel, ui_colour( k_ui_bg+2 ) ); + ui_outline( inout_panel, 1, ui_colour( k_ui_bg+5 ), 0 ); + + rect_copy( inout_panel, out_content_panel ); + ui_rect_pad( out_content_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } ); + + /* place buttons */ + for( i32 i=0; ikey == ev.sym ){ + if( mapping->mod == 0 ){ + if( mod == 0 ){ + mapping->handler(); + return; + } + } + else if( (mod & mapping->mod) == mapping->mod ){ + mapping->handler(); + return; + } + } + } +} + +/* + * Callback for text entry mode + */ +static void ui_proc_utf8( const char *text ){ + if( vg_ui.focused_control_type == k_ui_control_textbox ){ + const char *ptr = text; + + while( *ptr ){ + if( *ptr != '`' ) _ui_textbox_put_char( *ptr ); + ptr ++; + } + + _ui_textbox_change_callback(); + } +} + +/* + * Development utils + * ----------------------------------------------------------------------------- + */ + +static void ui_dev_colourview(void){ + ui_rect window = {vg.window_x-256,0,256,vg.window_y}, swatch; + + const char *names[vg_list_size(vg_ui.scheme)] = { + [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3", + "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7", + + [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3", + "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7", + + [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green", + "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray", + "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8", + "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" }; + + ui_rect col[2]; + ui_split_ratio( window, k_ui_axis_v, 0.5f, 0, col[0], col[1] ); + + for( int i=0; i