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 {
void (*enter)( char *, u32 ),
(*up)( char *, u32 ),
(*down)( char *, u32 ),
- (*change)( char *, u32 );
+ (*change)( char *, u32 ),
+ (*escape)( void );
}
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];
ui_rect click_fader, click_fader_end;
float click_fade_opacity;
+ f32 frosting;
ui_scheme scheme;
const ui_font *font;
cursor;
SDL_Cursor *cursor_map[ k_ui_cursor_max ];
-
v4f colour;
+
+ f32 hue; /* lol this sucks */
}
static vg_ui = {
.scheme = {
[ 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;"
"}"
}
};
}
};
+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
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 ){
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),
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 );
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 );
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 );
}
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 ){
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;
}
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;
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;
}
}
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 );
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();
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;
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
* -----------------------------------------------------------------------------
}
}
+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() )
_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 ){
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();
}
}
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();
}
}
_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();
ptr ++;
}
- if( vg_ui.textbox.callbacks.change ){
- vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len );
- }
+ _ui_textbox_change_callback();
}
}