X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=vg_imgui.h;h=1b5aceac38bfc931a723ac4bf24213ada60381c7;hb=13737a7a9faa5b31696c711f153b7de4201c404e;hp=deda2fce05ca1b2e955984c0516835c39632bc87;hpb=0247832f4a677987dda7edff6f306cf82a18b01e;p=vg.git diff --git a/vg_imgui.h b/vg_imgui.h index deda2fc..1b5acea 100644 --- a/vg_imgui.h +++ b/vg_imgui.h @@ -6,9 +6,12 @@ * 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 #define VG_IMGUI_H @@ -29,6 +32,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, @@ -75,6 +79,13 @@ typedef u32 ui_scheme[8*4]; ((STDHEX&0x0000ff00) ) |\ ((STDHEX&0x00ff0000)>>16) +#define UI_TEXTBOX_MULTILINE 0x1 +#define UI_TEXTBOX_WRAP 0x2 +#define UI_TEXTBOX_AUTOFOCUS 0x4 +#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)) + struct{ struct ui_vert *vertex_buffer; u16 *indice_buffer; @@ -82,25 +93,73 @@ struct{ cur_vert, cur_indice, vert_start, indice_start; + union { void *focused_control_id; /* uses the memory location of various locking controls as an id */ + char *textbuf; + struct ui_dropdown_value{ + i32 index, value; + } + *ptr_dropdown; + }; + u32 focused_control_hit; enum ui_control_type{ k_ui_control_none, k_ui_control_textbox, + k_ui_control_dropdown } focused_control_type; + + union{ + 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 ); + } + callbacks; + } + textbox; + + struct ui_dropdown{ + struct ui_dropdown_opt{ + const char *alias; + i32 value; + } + *options; + u32 option_count; + ui_rect rect; + } + dropdown; + }; GLuint tex_glyphs, vao, vbo, ebo; ui_px mouse[2], mouse_click[2]; u32 mouse_state[2]; u32 ignore_input_frames; + int wants_mouse; - ui_rect click_fader; + ui_rect click_fader, click_fader_end; float click_fade_opacity; ui_scheme scheme; + + enum ui_cursor{ + k_ui_cursor_default, + k_ui_cursor_ibeam, + k_ui_cursor_hand, + k_ui_cursor_max + } + cursor; + + SDL_Cursor *cursor_map[ k_ui_cursor_max ]; } static vg_ui = { .scheme = { @@ -240,16 +299,6 @@ 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;" "}" } @@ -350,6 +399,18 @@ VG_STATIC void _vg_ui_init(void) 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 ); + + /* + * Cursors + * --------------------------------------------------------------- + */ + + vg_ui.cursor_map[ k_ui_cursor_default ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_ARROW ); + vg_ui.cursor_map[ k_ui_cursor_hand ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_HAND ); + vg_ui.cursor_map[ k_ui_cursor_ibeam ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_IBEAM ); } enum ui_shader { @@ -385,9 +446,11 @@ 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 } ); @@ -514,25 +577,29 @@ static void ui_outline( ui_rect rect, ui_px thickness, u32 colour ) vg_ui.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 ) +static void ui_split( ui_rect rect, + enum ui_axis other, ui_px width, ui_px gap, + ui_rect l, ui_rect r ) { enum ui_axis dir = other ^ 0x1; + if( width < 0 ) width = rect[ 2+dir ] + width; + 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; + l[ dir ] = temp[ dir ]; + r[ dir ] = temp[ dir ] + width + (gap/2); + l[ other ] = temp[ other ]; + r[ other ] = temp[ other ]; + l[ 2+dir ] = width - (gap/2); + r[ 2+dir ] = temp[ 2+dir ] - width - (gap/2); + l[ 2+other ] = temp[ 2+other ]; + r[ 2+other ] = temp[ 2+other ]; } +static ui_px ui_text_line_width( const char *str ); + static void ui_rect_center( ui_rect parent, ui_rect rect ) { rect[0] = parent[0] + (parent[2]-rect[2])/2; @@ -556,31 +623,29 @@ static void ui_fit_item( ui_rect rect, ui_px size[2], ui_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 gap, ui_rect l, ui_rect r ) { ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio; - ui_split_px( rect, dir, width, pad, l, r ); + ui_split( rect, dir, width, gap, l, r ); } -static void ui_rect_pad( ui_rect rect, ui_px pad ) +static void ui_rect_pad( ui_rect rect, ui_px pad[2] ) { - rect[0] += pad; - rect[1] += pad; - rect[2] -= pad*2; - rect[3] -= pad*2; + rect[0] += pad[0]; + rect[1] += pad[1]; + rect[2] -= pad[0]*2; + rect[3] -= pad[1]*2; } static ui_px ui_text_line_width( const char *str ) { int length = 0; const char *_c = str; - char c; + u8 c; while( (c = *(_c ++)) ){ - if( c >= 32 && c <= 126 ) - length ++; - else if( c == '\n' ) - break; + if( c >= 32 ) length ++; + else if( c == '\n' ) break; } return length * 8; @@ -590,7 +655,7 @@ 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 ++; @@ -602,13 +667,14 @@ 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 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; @@ -649,8 +715,8 @@ 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; } @@ -658,28 +724,27 @@ static int ui_inside_rect( ui_rect rect, ui_px co[2] ) 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; @@ -698,13 +763,15 @@ static void ui_prerender(void) vg_ui.vert_start = 0; 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]; } @@ -715,11 +782,32 @@ static u32 ui_colour( enum ui_scheme_colour id ) return vg_ui.scheme[ id ]; } -static void ui_text( ui_rect rect, const char *str, ui_px scale, - enum ui_align align ) +/* get an appropriately contrasting colour given the base */ +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 ); + else if( id < k_ui_red+k_ui_brighter ) return ui_colour( k_ui_fg ); + 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 u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, + enum ui_align align, u32 colour ) { ui_rect text_cursor; - u32 colour = ui_colour( k_ui_fg ) & 0x00ffffff; + if( colour == 0 ) colour = ui_colour( k_ui_fg ); + + colour &= 0x00ffffff; const char *_c = str; u8 c; @@ -729,6 +817,8 @@ static void ui_text( ui_rect rect, const char *str, ui_px scale, text_cursor[2] = 8*scale; text_cursor[3] = 14*scale; + u32 printed_chars = 0; + if( align & (k_ui_align_middle|k_ui_align_bottom) ){ ui_px height = ui_text_string_height( str ) * scale; @@ -739,9 +829,34 @@ static void ui_text( ui_rect rect, const char *str, ui_px scale, } while( (c = *(_c ++)) ){ + if( printed_chars >= len ){ + printed_chars = 0; + text_cursor[1] += 14*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; + } + if( c == '\n' ){ text_cursor[1] += 14*scale; text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); + printed_chars = 0; continue; } else if( c >= 33 ){ @@ -749,7 +864,6 @@ static void ui_text( ui_rect rect, const char *str, ui_px scale, 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; @@ -783,7 +897,7 @@ static void ui_text( ui_rect rect, const char *str, 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; @@ -802,20 +916,29 @@ static void ui_text( ui_rect rect, const char *str, ui_px scale, } else if( c == '\t' ){ text_cursor[0] += UI_GLYPH_SPACING_X*scale*4; + printed_chars += 4; continue; } text_cursor[0] += UI_GLYPH_SPACING_X*scale; + printed_chars ++; } + + return printed_chars; +} + +static void ui_text( ui_rect rect, const char *str, ui_px scale, + 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 ); - glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, image ); - ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,0, 255,255 } ); + ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,128, 128, 0 } ); ui_flush( k_ui_shader_image ); } @@ -840,10 +963,10 @@ 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 ) +static int ui_colourbutton( ui_rect rect, enum ui_scheme_colour colour ) { - int clickup= ui_click_up(), - click = ui_clicking() | clickup, + 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 ); @@ -851,6 +974,17 @@ 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; + } + if( click ){ if( target ){ if( hover ){ @@ -858,6 +992,11 @@ static int ui_button( ui_rect rect, enum ui_scheme_colour colour ) ui_fill( rect, col_highlight ); vg_ui.ignore_input_frames = 2; rect_copy( rect, vg_ui.click_fader ); + + rect_copy( rect, vg_ui.click_fader_end ); + vg_ui.click_fader_end[3] = 0; + ui_rect_center( rect, vg_ui.click_fader_end ); + vg_ui.click_fade_opacity = 1.0f; ui_defocus_all(); return 1; @@ -890,94 +1029,876 @@ static int ui_button( ui_rect rect, enum ui_scheme_colour colour ) } } -static int ui_button_text( ui_rect rect, const char *string, ui_px scale, - enum ui_scheme_colour colour ) +static int ui_colourbutton_text( ui_rect rect, const char *string, ui_px scale, + enum ui_scheme_colour colour ) { - int result = ui_button( rect, colour ); + int result = 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_text( t, string, scale, k_ui_align_left, ui_colourcont(colour) ); return result; } +static int ui_button_text( ui_rect rect, const char *string, ui_px scale ) +{ + return ui_colourbutton_text( rect, string, scale, 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 ); - vg_ui.click_fade_opacity -= vg.time_frame_delta * 1.8f; - u32 colour = v4f_u32_colour( (v4f){ 1.0f,1.0f,1.0f, scale } ); + vg_ui.click_fade_opacity -= vg.time_frame_delta * 3.8f; + u32 colour = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; + + v4f begin, end, dest; + for( int i=0; i<4; i++ ){ + begin[i] = vg_ui.click_fader[i]; + end[i] = vg_ui.click_fader_end[i]+1; + } + + v4_lerp( end, begin, scale, dest ); ui_rect rect; - rect[3] = (float)(vg_ui.click_fader[3]) * scale; - rect[2] = vg_ui.click_fader[2]; - ui_rect_center( vg_ui.click_fader, rect ); + for( int i=0; i<4; i++ ){ + rect[i] = dest[i]; + } ui_fill( rect, colour ); } + + if( vg_ui.focused_control_type == k_ui_control_dropdown ){ + ui_enum_post(); + } + ui_flush( k_ui_shader_colour ); 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); +} + +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; iindex ].alias, 1 ) ){ + vg_ui.focused_control_type = k_ui_control_dropdown; + vg_ui.ptr_dropdown = value; + vg_ui.dropdown.option_count = len; + vg_ui.dropdown.options = options; + rect_copy( box, vg_ui.dropdown.rect ); + } +} + +static void ui_enum_post(void) +{ + ui_rect drawer; + rect_copy( vg_ui.dropdown.rect, drawer ); + drawer[3] *= vg_ui.dropdown.option_count; + + int close = 0; + int clickany= ui_click_up(UI_MOUSE_LEFT|UI_MOUSE_RIGHT|UI_MOUSE_MIDDLE), + hover = ui_inside_rect( drawer, vg_ui.mouse ); + + if( clickany && !hover ){ + return; + } + + /* HACK */ + vg_ui.focused_control_type = k_ui_control_none; + struct ui_dropdown_value *value = vg_ui.ptr_dropdown; + + for( u32 i=0; iindex ) colour = k_ui_orange; + + if( ui_colourbutton_text( button, vg_ui.dropdown.options[i].alias, 1, + colour ) ){ + value->index = i; + value->value = vg_ui.dropdown.options[i].value; + close = 1; + } + } + + /* HACK */ + vg_ui.focused_control_type = k_ui_control_dropdown; + + if( !close ) + vg_ui.focused_control_hit = 1; } -static int ui_textbox( ui_rect rect, char *buf, u32 len ) +/* + * Textbox chaos + * ----------------------------------------------------------------------------- + */ + +static void _ui_textbox_make_selection( int *start, int *end ) { - int clickup= ui_click_up(), - click = ui_clicking() | clickup, + *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 ); +} + +static void _ui_textbox_move_cursor( int *cursor0, int *cursor1, + int dir, int snap_together ) +{ + *cursor0 = VG_MAX( 0, vg_ui.textbox.cursor_user + dir ); + *cursor0 = + VG_MIN( + VG_MIN( vg_ui.textbox.len-1, strlen( vg_ui.textbuf )), + *cursor0 ); + + if( snap_together ) + *cursor1 = *cursor0; +} + +static int _ui_textbox_makeroom( int datastart, int length ) +{ + int move_to = VG_MIN( datastart+length, vg_ui.textbox.len-1 ); + int move_amount = strlen( vg_ui.textbuf )-datastart; + int move_end = VG_MIN( move_to+move_amount, vg_ui.textbox.len-1 ); + move_amount = move_end-move_to; + + if( move_amount ) + memmove( &vg_ui.textbuf[ move_to ], + &vg_ui.textbuf[ datastart ], + move_end-move_to ); + + vg_ui.textbuf[ move_end ] = '\0'; + + return VG_MIN( length, vg_ui.textbox.len-datastart-1 ); +} + +static int _ui_textbox_delete_char( int direction ) +{ + int start, end; + _ui_textbox_make_selection( &start, &end ); + + /* There is no selection */ + if( !(end-start) ){ + if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_ui.textbuf ) ); + else if( direction == -1 ) start = VG_MAX( start-1, 0 ); + } + + /* Still no selction, no need to do anything */ + if( !(end-start) ) + return start; + + /* Copy the end->terminator to start */ + int remaining_length = strlen( vg_ui.textbuf )+1-end; + memmove( &vg_ui.textbuf[ start ], + &vg_ui.textbuf[ end ], + remaining_length ); + return start; +} + +static void _ui_textbox_to_clipboard(void) +{ + int start, end; + _ui_textbox_make_selection( &start, &end ); + char buffer[512]; + + if( end-start ){ + memcpy( buffer, &vg_ui.textbuf[ start ], end-start ); + buffer[ end-start ] = 0x00; + SDL_SetClipboardText( buffer ); + } +} + +static void _ui_textbox_clipboard_paste(void) +{ + if( !SDL_HasClipboardText() ) + return; + + char *text = SDL_GetClipboardText(); + + if( !text ) + return; + + int datastart = _ui_textbox_delete_char( 0 ); + int length = strlen( text ); + int cpylength = _ui_textbox_makeroom( datastart, length ); + + memcpy( vg_ui.textbuf + datastart, text, cpylength); + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, cpylength, 1 ); + SDL_free( text ); +} + +static void _ui_textbox_put_char( char c ) +{ + vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0); + + if( _ui_textbox_makeroom( vg_ui.textbox.cursor_user, 1 ) ) + vg_ui.textbuf[ vg_ui.textbox.cursor_user ] = c; + + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, 1, 1 ); +} + +/* Receed secondary cursor */ +static void _ui_textbox_left_select(void) +{ + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, -1, 0 ); +} + +/* Match and receed both cursors */ +static void _ui_textbox_left(void) +{ + int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1; + + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, -cursor_diff, 1 ); +} + +static void _ui_textbox_up(void) +{ + if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ + int line_begin = vg_ui.textbox.cursor_user; + + while( line_begin ){ + if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){ + break; + } + + line_begin --; + } + + if( line_begin ){ + int line_above_begin = line_begin-1; + + while( line_above_begin ){ + if( vg_ui.textbuf[ line_above_begin-1 ] == '\n' ){ + break; + } + + line_above_begin --; + } + + int offset = vg_ui.textbox.cursor_user - line_begin, + line_length_above = line_begin - line_above_begin -1; + + offset = VG_MIN( line_length_above, offset ); + + vg_ui.textbox.cursor_user = line_above_begin+offset; + vg_ui.textbox.cursor_pos = line_above_begin+offset; + } + else{ + vg_ui.textbox.cursor_user = line_begin; + vg_ui.textbox.cursor_pos = line_begin; + } + } + else{ + if( vg_ui.textbox.callbacks.up ){ + vg_ui.textbox.callbacks.up( vg_ui.textbuf, vg_ui.textbox.len ); + } + } +} + +static void _ui_textbox_down(void) +{ + if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ + int line_begin = vg_ui.textbox.cursor_user; + + while( line_begin ){ + if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){ + break; + } + + line_begin --; + } + + int line_below_begin = vg_ui.textbox.cursor_user; + + while(1){ + if( vg_ui.textbuf[ line_below_begin ] == '\0' ){ + vg_ui.textbox.cursor_user = line_below_begin; + vg_ui.textbox.cursor_pos = line_below_begin; + return; + } + + if( vg_ui.textbuf[ line_below_begin ] == '\n' ){ + line_below_begin ++; + break; + } + + line_below_begin ++; + } + + int line_below_end = line_below_begin; + while(1){ + if( vg_ui.textbuf[ line_below_end ] == '\0' || + vg_ui.textbuf[ line_below_end ] == '\n' ){ + line_below_end ++; + break; + } + line_below_end ++; + } + + int offset = vg_ui.textbox.cursor_user - line_begin, + line_length_below = line_below_end - line_below_begin -1; + + offset = VG_MIN( line_length_below, offset ); + + vg_ui.textbox.cursor_user = line_below_begin+offset; + vg_ui.textbox.cursor_pos = line_below_begin+offset; + } + else{ + if( vg_ui.textbox.callbacks.down ){ + vg_ui.textbox.callbacks.down( vg_ui.textbuf, vg_ui.textbox.len ); + } + } +} + +static void _ui_textbox_right_select(void) +{ + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 1, 0 ); +} + +static void _ui_textbox_right(void) +{ + int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1; + + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, +cursor_diff, 1 ); +} + +static void _ui_textbox_backspace(void) +{ + if( vg_ui.focused_control_type == k_ui_control_textbox ){ + vg_ui.textbox.cursor_user = _ui_textbox_delete_char( -1 ); + vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; + + if( vg_ui.textbox.callbacks.change ){ + vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len ); + } + } +} + +static void _ui_textbox_delete(void) +{ + if( vg_ui.focused_control_type == k_ui_control_textbox ){ + vg_ui.textbox.cursor_user = _ui_textbox_delete_char( 1 ); + vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; + + if( vg_ui.textbox.callbacks.change ){ + vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len ); + } + } +} + +static void _ui_textbox_home_select(void) +{ + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, -10000, 0 ); +} + +static void _ui_textbox_home(void) +{ + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, -10000, 1 ); +} + +static void _ui_textbox_end_select(void) +{ + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 10000, 0 ); +} + +static void _ui_textbox_end(void) +{ + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, + &vg_ui.textbox.cursor_pos, + vg_ui.textbox.len-1, 1 ); +} + +static void _ui_textbox_select_all(void) +{ + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 10000, 0); + _ui_textbox_move_cursor( &vg_ui.textbox.cursor_pos, NULL, -10000, 0); +} + +static void _ui_textbox_cut(void) +{ + _ui_textbox_to_clipboard(); + vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0); + vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; +} + +static void _ui_textbox_enter(void) +{ + if( vg_ui.focused_control_type == k_ui_control_textbox ){ + if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ + _ui_textbox_put_char( '\n' ); + } + else{ + if( !(vg_ui.textbox.flags & UI_TEXTBOX_AUTOFOCUS ) ) + ui_defocus_all(); + } + + if( vg_ui.textbox.callbacks.enter ){ + vg_ui.textbox.callbacks.enter( vg_ui.textbuf, vg_ui.textbox.len ); + } + } +} + +/* + * based on a visual character coordinate relative to the anchor of the textbox, + * this works out the linear place in the buffer that coordinate maps to + * + * input coordinates go in co[0], co[1], and the result index is in co[2] + */ +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] >= wrap_length ){ + i[1] ++; + i[0] = 0; + } + + if( c >= 32 && c <= 126 ){ + i[0] ++; + i[2] ++; + } + else if( c == '\n' ){ + i[1] ++; + + if( i[1] > co[1] ) break; + + i[2] ++; + i[0] = 0; + } + else i[2] ++; + } + + co[0] = i[0]; + co[1] = i[1]; + co[2] = i[2]; +} + +/* + * 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; + + char c; + int i=0; + + while( (c = vg_ui.textbuf[i ++]) ){ + if( i > co[2] ) break; + if( co[0] >= wrap_length ){ + co[1] ++; + co[0] = 0; + } + if( c >= 32 && c <= 126 ) co[0] ++; + else if( c == '\n' ){ + co[1] ++; + co[0] = 0; + } + } +} + +/* + * 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 >= wrap_length ) break; + if( c >= 32 && c <= 126 ) printed_chars ++; + else if( c == '\n' ) break; + } + + return printed_chars+1; +} + +static int ui_textbox( ui_rect rect, char *buf, u32 len, u32 flags, + struct ui_textbox_callbacks *callbacks ) +{ + 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 ); - u32 col_base = vg_ui.scheme[ k_ui_bg ], - col_highlight = vg_ui.scheme[ k_ui_fg ]; + /* 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; + } + + u32 col_base = ui_colour( k_ui_bg ), + col_highlight = ui_colour( k_ui_fg ), + col_cursor = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; ui_px border = -1; + ui_rect text_rect; + rect_copy( rect, text_rect ); + + if( flags & UI_TEXTBOX_MULTILINE ) text_rect[3] = rect[3]-16; + else text_rect[3] = 14; + + text_rect[2] -= 16; + ui_rect_center( rect, text_rect ); + + ui_px wrap_length = 1024; + + if( flags & UI_TEXTBOX_WRAP ) + wrap_length = text_rect[2] / UI_GLYPH_SPACING_X; + + if( hover ){ + vg_ui.cursor = k_ui_cursor_ibeam; + } + if( vg_ui.focused_control_id == buf ){ ui_fill( rect, col_base ); - ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ] ); - vg_ui.focused_control_hit = 1; - return 0; - } + ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); - if( click ){ - if( target ){ - if( hover ){ - if( clickup ){ - ui_fill( rect, col_highlight ); - vg_ui.ignore_input_frames = 2; - rect_copy( rect, vg_ui.click_fader ); - vg_ui.click_fade_opacity = 1.0f; - vg_ui.focused_control_id = buf; - vg_ui.focused_control_hit = 1; - vg_ui.focused_control_type = k_ui_control_textbox; - SDL_StartTextInput(); + 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, + -1 + }, + p1[3] = { + (vg_ui.mouse[0] - text_rect[0]) / UI_GLYPH_SPACING_X, + (vg_ui.mouse[1] - text_rect[1]) / 14, + -1 + }; + + if( flags & UI_TEXTBOX_MULTILINE ){ + _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]; } else{ - ui_fill( rect, col_highlight ); + int max = strlen( buf ); + vg_ui.textbox.cursor_pos = VG_MAX( 0, VG_MIN( max, p0[0] )), + vg_ui.textbox.cursor_user = VG_MAX( 0, VG_MIN( max, p1[0] )); + } + } + + ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ] ); + + ui_rect cursor; + + int c0 = vg_ui.textbox.cursor_pos, + c1 = vg_ui.textbox.cursor_user, + start = VG_MIN( c0, c1 ), + end = VG_MAX( c0, c1 ), + chars = end-start; + + if( flags & (UI_TEXTBOX_MULTILINE|UI_TEXTBOX_WRAP) ){ + + int pos[3], remaining = chars; + + pos[2] = start; + _ui_textbox_index_calc_coords( pos, wrap_length ); + + if( start==end ){ + cursor[0] = text_rect[0] + pos[0]*UI_GLYPH_SPACING_X-1; + cursor[1] = text_rect[1] + pos[1]*14; + cursor[2] = 2; + cursor[3] = 13; + ui_fill( cursor, col_cursor ); + rect_copy( cursor, vg_ui.click_fader_end ); + } + else{ + while( remaining ){ + 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[1] = text_rect[1] + pos[1]*14; + cursor[2] = (float)(run)*(float)UI_GLYPH_SPACING_X; + cursor[3] = 13; + + ui_fill( cursor, col_cursor ); + + remaining -= run; + pos[0] = 0; + pos[1] ++; + pos[2] += run; + } + rect_copy( cursor, vg_ui.click_fader_end ); } } else{ - ui_fill( rect, col_base ); - ui_outline( rect, -1, col_highlight ); + cursor[0] = text_rect[0] + start*UI_GLYPH_SPACING_X-1; + cursor[1] = text_rect[1]; + cursor[3] = 13; + + if( start==end ){ + cursor[2] = 2; + } + else{ + cursor[2] = (float)(chars)*(float)UI_GLYPH_SPACING_X; + } + + if( (vg_ui.click_fade_opacity<=0.0f) && + ui_clip( rect, cursor, cursor ) ){ + ui_fill( cursor, col_cursor ); + } + + rect_copy( cursor, vg_ui.click_fader_end ); } } - else{ - ui_fill( rect, col_base ); - } + + return 0; } - else{ - ui_fill( rect, col_base ); - if( hover ){ - ui_outline( rect, -1, col_highlight ); + if( click || (flags & UI_TEXTBOX_AUTOFOCUS) ){ + if( (target && hover) || (flags & UI_TEXTBOX_AUTOFOCUS) ){ + ui_defocus_all(); + + ui_fill( rect, col_highlight ); + vg_ui.ignore_input_frames = 2; + rect_copy( rect, vg_ui.click_fader ); + rect_copy( rect, vg_ui.click_fader_end ); + + vg_ui.click_fade_opacity = 1.0f; + vg_ui.textbuf = buf; + vg_ui.focused_control_hit = 1; + vg_ui.focused_control_type = k_ui_control_textbox; + vg_ui.textbox.len = len; + vg_ui.textbox.flags = flags; + vg_ui.textbox.cursor_pos = 0; + vg_ui.textbox.cursor_user = 0; + + 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(); } } + ui_fill( rect, col_base ); + + if( hover ){ + ui_outline( rect, -1, col_highlight ); + } + + ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); return 0; } +/* + * Input handling + * ----------------------------------------------------------------------------- + */ + +/* + * Handles binds + */ +static void _ui_proc_key( SDL_Keysym ev ) +{ + if( vg_ui.focused_control_type != k_ui_control_textbox ){ + return; + } + + struct textbox_mapping + { + u16 mod; + SDL_Keycode key; + + void (*handler)(void); + } + mappings[] = + { + { 0, SDLK_LEFT, _ui_textbox_left }, + { KMOD_SHIFT, SDLK_LEFT, _ui_textbox_left_select }, + { 0, SDLK_RIGHT, _ui_textbox_right }, + { KMOD_SHIFT, SDLK_RIGHT, _ui_textbox_right_select }, + { 0, SDLK_DOWN, _ui_textbox_down }, + { 0, SDLK_UP, _ui_textbox_up }, + { 0, SDLK_BACKSPACE, _ui_textbox_backspace }, + { KMOD_SHIFT, SDLK_BACKSPACE, _ui_textbox_backspace }, + { KMOD_CTRL, SDLK_BACKSPACE, _ui_textbox_backspace }, + { 0, SDLK_DELETE, _ui_textbox_delete }, + { 0, SDLK_HOME, _ui_textbox_home }, + { KMOD_SHIFT, SDLK_HOME, _ui_textbox_home_select }, + { 0, SDLK_END, _ui_textbox_end }, + { KMOD_SHIFT, SDLK_END, _ui_textbox_end_select }, + { KMOD_CTRL, SDLK_a, _ui_textbox_select_all }, + { KMOD_CTRL, SDLK_c, _ui_textbox_to_clipboard }, + { KMOD_CTRL, SDLK_x, _ui_textbox_cut }, + { KMOD_CTRL, SDLK_v, _ui_textbox_clipboard_paste }, + { 0, SDLK_RETURN, _ui_textbox_enter }, + { 0, SDLK_ESCAPE, ui_defocus_all }, + }; + + SDL_Keymod mod = 0; + + if( ev.mod & KMOD_SHIFT ) + mod |= KMOD_SHIFT; + + if( ev.mod & KMOD_CTRL ) + mod |= KMOD_CTRL; + + if( ev.mod & KMOD_ALT ) + mod |= KMOD_ALT; + + 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 ) +{ + if( vg_ui.focused_control_type == k_ui_control_textbox ){ + const char *ptr = text; + + while( *ptr ){ + if( *ptr != '`' ) _ui_textbox_put_char( *ptr ); + ptr ++; + } + + if( vg_ui.textbox.callbacks.change ){ + vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len ); + } + } +} + +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)+UI_GLYPH_SPACING_X) * size; + ui_split( rect, k_ui_axis_v, width, gap, l, r ); + ui_text( l, text, 1, k_ui_align_middle_left, 0 ); +} + #endif /* VG_IMGUI_H */