intermediate representation HSV
[vg.git] / vg_imgui.h
index 53d8472302f8f3f5e6acb07a81e1860d654a5084..5c957ed2ba1b053b3002e4e64299b8a7d8b7c2d4 100644 (file)
@@ -22,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 {
@@ -169,7 +170,8 @@ struct{
             void (*enter)( char *, u32 ),  
                  (*up)( char *, u32 ), 
                  (*down)( char *, u32 ),
-                 (*change)( char *, u32 );
+                 (*change)( char *, u32 ),
+                 (*escape)( void );
          }
          callbacks;
       }
@@ -198,7 +200,8 @@ struct{
        }
    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];
@@ -207,6 +210,7 @@ struct{
 
    ui_rect click_fader, click_fader_end;
    float click_fade_opacity;
+   f32 frosting;
 
    ui_scheme scheme;
    const ui_font *font;
@@ -220,8 +224,9 @@ struct{
    cursor;
 
    SDL_Cursor *cursor_map[ k_ui_cursor_max ];
-
    v4f colour;
+
+   f32 hue; /* lol this sucks */
 } 
 static vg_ui = {
    .scheme = {
@@ -261,59 +266,76 @@ static vg_ui = {
       [ k_ui_gray   + k_ui_brighter ] = UI_RGB( 0xa89984 ),
    },
    .font = &vg_ui_font_small,
-   .colour = {1.0f,1.0f,1.0f,1.0f}
+   .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.00390625;" /* TODO: Uniform */
+       "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 ){"
+      "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 * uColour;"
+      "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;"
        "}"
    }
 };
@@ -363,10 +385,58 @@ static struct vg_shader _shader_ui_image = {
    }
 };
 
+static struct vg_shader _shader_ui_hsv = {
+   .name = "[vg] ui_hsv",
+   .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.00390625;"
+               "aColour = a_colour;"
+               
+               "aWsp = a_co;"
+       "}",
+   },
+   .fs = 
+   {
+      .orig_file = NULL,
+      .static_src = 
+   "uniform float uHue;"
+       "out vec4 FragColor;"
+
+       "in vec2 aTexCoords;"
+       "in vec4 aColour;"
+       "in vec2 aWsp;"
+
+       "void main()"
+       "{"
+      "vec3 c = vec3( uHue, aTexCoords );"
+      "vec4 K = vec4(1.0,2.0/3.0,1.0/3.0,3.0);"
+      "vec3 p = abs(fract(c.xxx+K.xyz)*6.0 - K.www);"
+      "vec3 colour = c.z*mix(K.xxx,clamp(p-K.xxx,0.0,1.0),c.y);"
+               "FragColor = vec4( colour, 1.0 );"
+       "}"
+   }
+};
+
 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_shader_compile( &_shader_ui_hsv ) ){
       vg_fatal_error( "Failed to compile ui shader" );
+   }
 
    /*
     * Vertex buffer
@@ -472,7 +542,8 @@ static void _vg_ui_init(void){
 
 enum ui_shader {
    k_ui_shader_colour,
-   k_ui_shader_image
+   k_ui_shader_image,
+   k_ui_shader_hsv,
 };
 
 static void rect_copy( ui_rect a, ui_rect b ){
@@ -480,7 +551,7 @@ static void rect_copy( ui_rect a, ui_rect b ){
       b[i] = a[i];
 }
 
-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),
@@ -509,8 +580,8 @@ static void ui_flush( enum ui_shader shader ){
        
        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 );
@@ -523,6 +594,14 @@ static void ui_flush( enum ui_shader shader ){
       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 );
@@ -532,6 +611,12 @@ static void ui_flush( enum ui_shader shader ){
       glUniform4fv( glGetUniformLocation( _shader_ui_image.id, "uColour" ), 1,
                      vg_ui.colour );
    }
+   else if( shader == k_ui_shader_hsv ){
+      glUseProgram( _shader_ui_hsv.id );
+      glUniformMatrix3fv( glGetUniformLocation( _shader_ui_hsv.id, "uPv" ), 1, 
+                          GL_FALSE, (float *)view );
+      glUniform1f( glGetUniformLocation(_shader_ui_hsv.id,"uHue"), vg_ui.hue );
+   }
    else
       vg_fatal_error( "Invalid UI shader (%d)\n", shader );
        
@@ -1005,11 +1090,11 @@ static void ui_info( ui_rect inout_panel, const char *text ){
 }
 
 static void ui_image( ui_rect rect, GLuint image ){
-   ui_flush( k_ui_shader_colour );
+   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,256,256,0 } );
-   ui_flush( k_ui_shader_image );
+   ui_flush( k_ui_shader_image, vg.window_x, vg.window_y );
 }
 
 static u32 v4f_u32_colour( v4f colour ){
@@ -1024,6 +1109,8 @@ static u32 v4f_u32_colour( v4f colour ){
 static void ui_defocus_all(void){
    if( vg_ui.focused_control_type == k_ui_control_textbox ){
       SDL_StopTextInput();
+      if( vg_ui.textbox.callbacks.escape )
+         vg_ui.textbox.callbacks.escape();
    }
 
    vg_ui.focused_control_id = NULL;
@@ -1032,23 +1119,28 @@ static void ui_defocus_all(void){
 }
 
 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
+   k_ui_button_none            = 0x0,
+   k_ui_button_click           = 0x1,
+   k_ui_button_holding_inside  = 0x2,
+   k_ui_button_holding_outside = 0x4,
+   k_ui_button_hover           = 0x8
 };
 
+/* TODO: split this out into a formatless button and one that auto fills */
 static enum ui_button_state ui_colourbutton( ui_rect rect, 
-                                             enum ui_scheme_colour colour ){
+                                             enum ui_scheme_colour colour,
+                                             enum ui_scheme_colour hover_colour,
+                                             enum ui_scheme_colour hi_colour,
+                                             bool const fill ){
    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 );
 
    u32 col_base      = vg_ui.scheme[ colour ],
-       col_highlight = vg_ui.scheme[ k_ui_fg ],
-       col_hover     = vg_ui.scheme[ colour + k_ui_brighter ];
+       col_highlight = vg_ui.scheme[ hi_colour? hi_colour: k_ui_fg ],
+       col_hover     = vg_ui.scheme[ hover_colour? hover_colour: 
+                                     colour + k_ui_brighter ];
 
    if( vg_ui.focused_control_type != k_ui_control_none ){
       clickup = 0;
@@ -1065,41 +1157,43 @@ static enum ui_button_state ui_colourbutton( ui_rect rect,
       if( target ){
          if( hover ){
             if( clickup ){
-               ui_fill( rect, col_highlight );
                vg_ui.ignore_input_frames = 2;
-               rect_copy( rect, vg_ui.click_fader );
+               ui_defocus_all();
 
-               rect_copy( rect, vg_ui.click_fader_end );
-               vg_ui.click_fader_end[3] = 0;
-               ui_rect_center( rect, vg_ui.click_fader_end );
+               if( fill ) {
+                  ui_fill( rect, col_highlight );
+                  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;
+               }
 
-               vg_ui.click_fade_opacity = 1.0f;
-               ui_defocus_all();
                return k_ui_button_click;
             }
             else{
-               ui_fill( rect, col_highlight );
+               if( fill ) ui_fill( rect, col_highlight );
                return k_ui_button_holding_inside;
             }
          }
          else{
-            ui_fill( rect, col_base );
+            if( fill ) ui_fill( rect, col_base );
             ui_outline( rect, 1, col_highlight, 0 );
             return k_ui_button_holding_outside;
          }
       }
       else{
-         ui_fill( rect, col_base );
+         if( fill ) ui_fill( rect, col_base );
          return k_ui_button_none;
       }
    }
    else{
       if( hover ){
-         ui_fill( rect, col_hover );
+         if( fill ) ui_fill( rect, col_hover );
          return k_ui_button_hover;
       }
       else{
-         ui_fill( rect, col_base );
+         if( fill ) ui_fill( rect, col_base );
          return k_ui_button_none;
       }
    }
@@ -1108,7 +1202,7 @@ static enum ui_button_state ui_colourbutton( ui_rect rect,
 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 );
+   enum ui_button_state state = ui_colourbutton( rect, colour, 0, 0, 1 );
    ui_rect t = { 0,0, ui_text_line_width( string )*scale, 14*scale };
    ui_rect_center( rect, t );
 
@@ -1198,7 +1292,7 @@ static void ui_postrender(void){
       vg_ui.wants_mouse = 1;
    }
 
-   ui_flush( k_ui_shader_colour );
+   ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y );
 
    if( !vg_ui.focused_control_hit ){
       ui_defocus_all();
@@ -1229,7 +1323,7 @@ static int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ){
    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 );
 
-   int changed = ui_colourbutton( box, k_ui_bg )==1;
+   int changed = ui_colourbutton( box, k_ui_bg, 0, 0, 1 )==1;
    if( changed )
       *data = (*data) ^ 0x1;
 
@@ -1317,6 +1411,138 @@ static void ui_enum_post(void){
       vg_ui.focused_control_hit = 1;
 }
 
+/*
+ * Slider
+ * -----------------------------------------------------------------------------
+ */
+
+static enum ui_button_state _ui_slider( ui_rect box, 
+                        f32 min, f32 max, f32 *value, const char *format ){
+   f32 t;
+
+   enum ui_button_state 
+      mask_using = 
+         k_ui_button_holding_inside |
+         k_ui_button_holding_outside |
+         k_ui_button_click,
+      mask_brighter =
+         mask_using | k_ui_button_hover,
+      state = ui_colourbutton( box, k_ui_bg, k_ui_bg+2, k_ui_bg+3, 1 );
+
+   if( state & mask_using ){
+      t = vg_clampf( (f32)(vg_ui.mouse[0] - box[0]) / (f32)( box[2] ),
+                     0.0f, 1.0f );
+      *value = vg_lerpf( min, max, t );
+   }
+   else
+      t = vg_clampf( (*value - min) / (max-min), 0.0f, 1.0f );
+   
+   ui_rect line     = { box[0], box[1], t * (f32)box[2], box[3] };
+   ui_fill( line, ui_colour(state&mask_brighter? k_ui_bg+4: k_ui_bg+2) );
+
+   ui_outline( box, 1, ui_colour(state? k_ui_fg+3: k_ui_bg+3), 0 );
+
+   /* TODO: replace this one day with our own function */
+   char buf[32];
+   snprintf( buf, sizeof(buf), format? format: "%.2f", *value );
+   ui_text( box, buf, 1, k_ui_align_middle_center, 0 );
+
+   return state;
+}
+
+static void ui_slider( ui_rect inout_panel, const char *str_label, 
+                       f32 min, f32 max, f32 *value, const char *format ){
+   ui_rect rect, label, box;
+   ui_standard_widget( inout_panel, rect, 1 );
+   ui_label( rect, str_label, k_ui_scale, 0, box );
+   _ui_slider( box, min, max, value, format );
+}
+
+/*
+ * Colour picker
+ * -----------------------------------------------------------------------------
+ */
+
+static void ui_colourpicker( ui_rect inout_panel, const char *str_label,
+                             v4f value ){
+   ui_rect widget, left, right;
+   ui_standard_widget( inout_panel, widget, 8 );
+   ui_split_ratio( widget, k_ui_axis_v, 0.5f, 8, left, right );
+
+   ui_rect sliders[4];
+   ui_split_ratio( right, k_ui_axis_h, 0.5f, 2, sliders[0], sliders[2] );
+   ui_split_ratio( sliders[0], k_ui_axis_h, 0.5f, 2, sliders[0], sliders[1] );
+   ui_split_ratio( sliders[2], k_ui_axis_h, 0.5f, 2, sliders[2], sliders[3] );
+
+   v4f hsv;
+   vg_rgb_hsv( value, hsv );
+
+   enum ui_button_state modified = 0x00;
+
+   for( u32 i=0; i<4; i ++ ){
+      modified |= _ui_slider( sliders[i], 0.0f, 1.0f, hsv+i, 
+                     (const char *[]){ "hue %.2f",
+                                       "sat %.2f",
+                                       "lum %.2f",
+                                       "alpha %.2f" }[i] );
+   }
+
+   ui_rect preview, square;
+   ui_split_ratio( left, k_ui_axis_v, 0.8f, 8, square, preview );
+
+   u32 state = ui_colourbutton( square, 0, 0, 0, 0 );
+   modified |= state;
+
+   enum ui_button_state 
+      mask_using = 
+         k_ui_button_holding_inside |
+         k_ui_button_holding_outside |
+         k_ui_button_click;
+   
+   if( state & mask_using ){
+      for( u32 i=0; i<2; i ++ ){
+         hsv[1+i] = vg_clampf( 
+                        (f32)(vg_ui.mouse[i] - square[i]) / (f32)(square[2+i]),
+                        0.0f, 1.0f );
+      }
+
+      hsv[2] = 1.0f-hsv[2];
+   }
+
+   if( modified & (k_ui_button_click|k_ui_button_holding_inside|
+                   k_ui_button_holding_outside) ){
+      vg_hsv_rgb( hsv, value );
+   }
+
+   ui_outline( square, 1, ui_colour( state? k_ui_fg+3: k_ui_bg+3 ), 0 );
+
+   /* preview colour */
+   v4f colour;
+   vg_hsv_rgb( hsv, colour );
+   colour[3] = 1.0f;
+   ui_fill( preview, v4f_u32_colour( colour ) );
+
+   /* Draw hsv shader thingy */
+   ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y );
+   ui_fill_rect( square, 0xffffffff, (ui_px[4]){ 0,256,256,0 } );
+   vg_ui.hue = hsv[0];
+   ui_flush( k_ui_shader_hsv, vg.window_x, vg.window_y );
+
+   ui_rect marker = { square[0] + hsv[1] * (f32)square[2] - 4, 
+                      square[1] + (1.0f-hsv[2]) * (f32)square[3] - 4,
+                      8, 8 },
+           lx     = { square[0],
+                      square[1] + (1.0f-hsv[2]) * (f32)square[3],
+                      square[2], 1 },
+           ly     = { square[0] + hsv[1] * (f32)square[2],
+                      square[1],
+                      1, square[3] };
+
+   ui_fill( marker, ui_colour( k_ui_fg ) );
+   ui_fill( lx, ui_colour( k_ui_fg ) );
+   ui_fill( ly, ui_colour( k_ui_fg ) );
+}
+
 /*
  * Textbox chaos
  * -----------------------------------------------------------------------------
@@ -1389,6 +1615,17 @@ static void _ui_textbox_to_clipboard(void){
        }
 }
 
+static void _ui_textbox_change_callback(void){
+   if( vg_ui.textbox.callbacks.change ){
+      vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len );
+
+      /* we gave permission to modify the buffer in this callback so.. */
+      int len = strlen( vg_ui.textbuf );
+      vg_ui.textbox.cursor_user = VG_MIN( vg_ui.textbox.cursor_user, len );
+      vg_ui.textbox.cursor_pos  = VG_MIN( vg_ui.textbox.cursor_pos,  len );
+   }
+}
+
 static void ui_start_modal( const char *message, u32 options );
 static void _ui_textbox_clipboard_paste(void){
    if( !SDL_HasClipboardText() )
@@ -1413,6 +1650,7 @@ static void _ui_textbox_clipboard_paste(void){
        _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, 
                             &vg_ui.textbox.cursor_pos, cpylength, 1 );
    SDL_free( text );
+   _ui_textbox_change_callback();
 }
 
 static void _ui_textbox_put_char( char c ){
@@ -1551,10 +1789,7 @@ 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 );
-      }
+      _ui_textbox_change_callback();
    }
 }
 
@@ -1562,10 +1797,7 @@ 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 );
-      }
+      _ui_textbox_change_callback();
    }
 }
 
@@ -1622,17 +1854,22 @@ 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;
+   _ui_textbox_change_callback();
 }
 
 static void _ui_textbox_enter(void){
    if( vg_ui.focused_control_type == k_ui_control_textbox ){
+      vg_ui.ignore_input_frames = 2;
+
       if( vg_ui.textbox.callbacks.enter )
          vg_ui.textbox.callbacks.enter( vg_ui.textbuf, vg_ui.textbox.len );
 
       if( vg_ui.focused_control_type != k_ui_control_textbox ) return;
 
-      if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE )
+      if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){
          _ui_textbox_put_char( '\n' );
+         _ui_textbox_change_callback();
+      }
       else{
          if( !(vg_ui.textbox.flags & UI_TEXTBOX_AUTOFOCUS ) )
             ui_defocus_all();
@@ -2069,9 +2306,7 @@ static void ui_proc_utf8( const char *text ){
          ptr ++;
       }
 
-      if( vg_ui.textbox.callbacks.change ){
-         vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len );
-      }
+      _ui_textbox_change_callback();
    }
 }