25104044dfa25b6604009d95112b5c1089ba7084
[vg.git] / vg_imgui.c
1 /* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */
2
3 /*
4 * Principles:
5 *
6 * 1. layout is defined by subdividing
7 * 2. a parent node should never be resized by the content after creation
8 * 3. when the ui is in an interactive state, no controls should ever move
9 * 4. controls directly reference a memory location and use that as their
10 * unique id
11 * 5. a maximum of ONE control per memory location can be drawn at any given
12 * point.
13 */
14
15 #pragma once
16
17 #include "vg_imgui.h"
18 #include "vg_engine.h"
19 #include "vg_tex.h"
20 #include "vg_shader.h"
21 #include <string.h>
22
23 ui_px k_ui_widget_height = 28,
24 k_ui_scale = 1,
25 k_ui_padding = 8;
26
27 ui_font vg_ui_font_small = {
28 .glyph_width = 8,
29 .glyph_height = 14,
30 .glyph_baseline = 4,
31 .line_height = 14,
32 .sheet_size = 256,
33 .spacing = 8,
34 .ascii_start = ' ',
35 .offset_y = 0
36 },
37 vg_ui_font_big = {
38 .glyph_width = 12,
39 .glyph_height = 21,
40 .glyph_baseline = 6,
41 .line_height = 21,
42 .sheet_size = 256,
43 .spacing = 10,
44 .ascii_start = ' ',
45 .offset_y = 84
46 };
47
48 struct vg_imgui vg_ui = {
49 .scheme = {
50 [ k_ui_bg+0 ] = UI_RGB( 0x1d2021 ),
51 [ k_ui_bg+1 ] = UI_RGB( 0x282828 ),
52 [ k_ui_bg+2 ] = UI_RGB( 0x3c3836 ),
53 [ k_ui_bg+3 ] = UI_RGB( 0x504945 ),
54 [ k_ui_bg+4 ] = UI_RGB( 0x665c54 ),
55 [ k_ui_bg+5 ] = UI_RGB( 0x7c6f64 ),
56 [ k_ui_bg+6 ] = UI_RGB( 0x928374 ),
57 [ k_ui_bg+7 ] = UI_RGB( 0xa89984 ),
58
59 [ k_ui_fg+0 ] = UI_RGB( 0xebdbb2 ),
60 [ k_ui_fg+1 ] = UI_RGB( 0xfbf1c7 ),
61 [ k_ui_fg+2 ] = UI_RGB( 0xd5c4a1 ),
62 [ k_ui_fg+3 ] = UI_RGB( 0xbdae93 ),
63 [ k_ui_fg+4 ] = UI_RGB( 0xa89984 ),
64 [ k_ui_fg+5 ] = UI_RGB( 0x000000 ),
65 [ k_ui_fg+6 ] = UI_RGB( 0x000000 ),
66 [ k_ui_fg+7 ] = UI_RGB( 0x000000 ),
67
68 [ k_ui_red ] = UI_RGB( 0xcc241d ),
69 [ k_ui_orange ] = UI_RGB( 0xd65d0e ),
70 [ k_ui_yellow ] = UI_RGB( 0xd79921 ),
71 [ k_ui_green ] = UI_RGB( 0x98971a ),
72 [ k_ui_aqua ] = UI_RGB( 0x689d6a ),
73 [ k_ui_blue ] = UI_RGB( 0x458588 ),
74 [ k_ui_purple ] = UI_RGB( 0xb16286 ),
75 [ k_ui_gray ] = UI_RGB( 0x928374 ),
76 [ k_ui_red + k_ui_brighter ] = UI_RGB( 0xfb4934 ),
77 [ k_ui_orange + k_ui_brighter ] = UI_RGB( 0xfe8019 ),
78 [ k_ui_yellow + k_ui_brighter ] = UI_RGB( 0xfabd2f ),
79 [ k_ui_green + k_ui_brighter ] = UI_RGB( 0xb8bb26 ),
80 [ k_ui_aqua + k_ui_brighter ] = UI_RGB( 0x8ec07c ),
81 [ k_ui_blue + k_ui_brighter ] = UI_RGB( 0x83a598 ),
82 [ k_ui_purple + k_ui_brighter ] = UI_RGB( 0xd3869b ),
83 [ k_ui_gray + k_ui_brighter ] = UI_RGB( 0xa89984 ),
84 },
85 .font = &vg_ui_font_small,
86 .colour = {1.0f,1.0f,1.0f,1.0f},
87 .bg_inverse_ratio = {1,1}
88 };
89
90 static struct vg_shader _shader_ui ={
91 .name = "[vg] ui - transparent",
92 .link = NULL,
93 .vs = {
94 .orig_file = NULL,
95 .static_src =
96 "layout (location=0) in vec2 a_co;"
97 "layout (location=1) in vec2 a_uv;"
98 "layout (location=2) in vec4 a_colour;"
99 "uniform mat3 uPv;"
100 "uniform vec2 uBGInverseRatio;"
101 ""
102 "out vec4 aTexCoords;"
103 "out vec4 aColour;"
104 ""
105 "void main(){"
106 "vec4 proj_pos = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
107 "gl_Position = proj_pos;"
108 "aTexCoords = vec4( a_uv * 0.00390625, "
109 " (proj_pos.xy*0.5+0.5) * uBGInverseRatio );"
110 "aColour = a_colour;"
111 "}",
112 },
113 .fs = {
114 .orig_file = NULL,
115 .static_src =
116 "uniform sampler2D uTexGlyphs;"
117 "uniform sampler2D uTexBG;"
118 "uniform vec4 uColour;"
119 "uniform float uSpread;"
120 "out vec4 FragColor;"
121 ""
122 "in vec4 aTexCoords;"
123 "in vec4 aColour;"
124 ""
125 "vec2 rand_hash22( vec2 p ){"
126 "vec3 p3 = fract(vec3(p.xyx) * 213.8976123);"
127 "p3 += dot(p3, p3.yzx+19.19);"
128 "return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));"
129 "}"
130 ""
131 "void main(){"
132 "vec4 diffuse = aColour;"
133
134 "vec4 avg = vec4(0.0);"
135
136 "if( aColour.a == 0.0 ){"
137 "avg = aColour;"
138 "avg.a = texture( uTexGlyphs, aTexCoords.xy ).r;"
139 "}"
140 "else{"
141 "if( uSpread > 0.0001 ){"
142 "for( int i=0; i<4; i ++ ){"
143 "vec2 spread = rand_hash22(aTexCoords.zw+vec2(float(i)));"
144 "avg += texture( uTexBG, aTexCoords.zw + (spread-0.5)*uSpread );"
145 "}"
146 "avg *= 0.25;"
147 "avg.a = 1.0;"
148 "avg.rgb = mix( avg.rgb, aColour.rgb, aColour.a );"
149 "}"
150 "else{"
151 "avg = aColour;"
152 "}"
153 "}"
154
155 "FragColor = avg * uColour;"
156 "}"
157 }
158 };
159
160 static struct vg_shader _shader_ui_image = {
161 .name = "[vg] ui_image",
162 .link = NULL,
163 .vs =
164 {
165 .orig_file = NULL,
166 .static_src =
167 "layout (location=0) in vec2 a_co;"
168 "layout (location=1) in vec2 a_uv;"
169 "layout (location=2) in vec4 a_colour;"
170 "uniform mat3 uPv;"
171
172 "out vec2 aTexCoords;"
173 "out vec4 aColour;"
174 "out vec2 aWsp;"
175
176 "void main()"
177 "{"
178 "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
179 "aTexCoords = a_uv * 0.00390625;"
180 "aColour = a_colour;"
181
182 "aWsp = a_co;"
183 "}",
184 },
185 .fs =
186 {
187 .orig_file = NULL,
188 .static_src =
189 "uniform sampler2D uTexImage;"
190 "uniform vec4 uColour;"
191 "out vec4 FragColor;"
192
193 "in vec2 aTexCoords;"
194 "in vec4 aColour;"
195 "in vec2 aWsp;"
196
197 "void main()"
198 "{"
199 "vec4 colour = texture( uTexImage, aTexCoords );"
200 "FragColor = colour * uColour;"
201 "}"
202 }
203 };
204
205 static struct vg_shader _shader_ui_hsv = {
206 .name = "[vg] ui_hsv",
207 .link = NULL,
208 .vs = {
209 .orig_file = NULL,
210 .static_src =
211 "layout (location=0) in vec2 a_co;"
212 "layout (location=1) in vec2 a_uv;"
213 "layout (location=2) in vec4 a_colour;"
214 "uniform mat3 uPv;"
215
216 "out vec2 aTexCoords;"
217 "out vec4 aColour;"
218 "out vec2 aWsp;"
219
220 "void main()"
221 "{"
222 "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
223 "aTexCoords = a_uv * 0.00390625;"
224 "aColour = a_colour;"
225
226 "aWsp = a_co;"
227 "}",
228 },
229 .fs =
230 {
231 .orig_file = NULL,
232 .static_src =
233 "uniform float uHue;"
234 "out vec4 FragColor;"
235
236 "in vec2 aTexCoords;"
237 "in vec4 aColour;"
238 "in vec2 aWsp;"
239
240 "void main()"
241 "{"
242 "vec3 c = vec3( uHue, aTexCoords );"
243 "vec4 K = vec4(1.0,2.0/3.0,1.0/3.0,3.0);"
244 "vec3 p = abs(fract(c.xxx+K.xyz)*6.0 - K.www);"
245 "vec3 colour = c.z*mix(K.xxx,clamp(p-K.xxx,0.0,1.0),c.y);"
246 "FragColor = vec4( colour, 1.0 );"
247 "}"
248 }
249 };
250
251 void vg_ui_init(void)
252 {
253 if( !vg_shader_compile( &_shader_ui ) ||
254 !vg_shader_compile( &_shader_ui_image ) ||
255 !vg_shader_compile( &_shader_ui_hsv ) ){
256 vg_fatal_error( "Failed to compile ui shader" );
257 }
258
259 /*
260 * Vertex buffer
261 * ----------------------------------------
262 */
263
264 vg_ui.max_indices = 20000;
265 vg_ui.max_verts = 30000;
266
267 /* Generate the buffer we are gonna be drawing to */
268 glGenVertexArrays( 1, &vg_ui.vao );
269 glGenBuffers( 1, &vg_ui.vbo );
270 glGenBuffers( 1, &vg_ui.ebo );
271
272 glBindVertexArray( vg_ui.vao );
273 glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo );
274
275 glBufferData( GL_ARRAY_BUFFER,
276 vg_ui.max_verts * sizeof( struct ui_vert ),
277 NULL, GL_DYNAMIC_DRAW );
278 glBindVertexArray( vg_ui.vao );
279
280 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo );
281 glBufferData( GL_ELEMENT_ARRAY_BUFFER,
282 vg_ui.max_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW );
283
284 VG_CHECK_GL_ERR();
285
286 /* Set pointers */
287 u32 const stride = sizeof( struct ui_vert );
288
289 /* XY */
290 glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride,
291 (void *)offsetof( struct ui_vert, co ) );
292 glEnableVertexAttribArray( 0 );
293
294 /* UV */
295 glVertexAttribPointer( 1, 2, GL_UNSIGNED_SHORT, GL_FALSE, stride,
296 (void *)offsetof( struct ui_vert, uv ) );
297 glEnableVertexAttribArray( 1 );
298
299 /* COLOUR */
300 glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride,
301 (void *)offsetof( struct ui_vert, colour ) );
302 glEnableVertexAttribArray( 2 );
303
304 VG_CHECK_GL_ERR();
305
306 /* Alloc RAM default context */
307 u32 vert_size = vg_ui.max_verts*sizeof(struct ui_vert),
308 inds_size = vg_align8( vg_ui.max_indices*sizeof(u16) );
309
310 vg_ui.vertex_buffer = vg_linear_alloc( vg_mem.rtmemory, vert_size );
311 vg_ui.indice_buffer = vg_linear_alloc( vg_mem.rtmemory, inds_size );
312
313 /* font
314 * -----------------------------------------------------
315 */
316
317 /* Load default font */
318 u32 compressed[] = {
319 #include "vg/vg_pxfont_thin.h"
320 };
321
322 u32 pixels = 0, total = 256*256, data = 0;
323 u8 image[256*256];
324
325 while( pixels < total ){
326 for( int b = 31; b >= 0; b-- ){
327 image[ pixels ++ ] = (compressed[data] & (0x1u << b))? 0xffu: 0x00u;
328
329 if( pixels >= total ){
330 total = 0;
331 break;
332 }
333 }
334 data++;
335 }
336
337 glGenTextures( 1, &vg_ui.tex_glyphs );
338 glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs );
339 glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, 256, 256, 0,
340 GL_RED, GL_UNSIGNED_BYTE, image );
341
342 VG_CHECK_GL_ERR();
343 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
344 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
345 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
346 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
347
348 /*
349 * Cursors
350 * ---------------------------------------------------------------
351 */
352
353 vg_ui.cursor_map[ k_ui_cursor_default ] =
354 SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_ARROW );
355 vg_ui.cursor_map[ k_ui_cursor_hand ] =
356 SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_HAND );
357 vg_ui.cursor_map[ k_ui_cursor_ibeam ] =
358 SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_IBEAM );
359 }
360
361 void rect_copy( ui_rect a, ui_rect b ){
362 for( int i=0; i<4; i++ )
363 b[i] = a[i];
364 }
365
366 void ui_flush( enum ui_shader shader, f32 w, f32 h ){
367 u32 vertex_offset = vg_ui.vert_start*sizeof(ui_vert),
368 vertex_count = vg_ui.cur_vert-vg_ui.vert_start,
369 vertex_size = vertex_count*sizeof(ui_vert),
370
371 indice_offset = vg_ui.indice_start*sizeof(u16),
372 indice_count = vg_ui.cur_indice-vg_ui.indice_start,
373 indice_size = indice_count * sizeof(u16);
374
375 if( !vertex_size || !indice_size )
376 return;
377
378 glBindVertexArray( vg_ui.vao );
379 glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo );
380 glBufferSubData( GL_ARRAY_BUFFER, vertex_offset, vertex_size,
381 vg_ui.vertex_buffer+vg_ui.vert_start );
382
383 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo );
384 glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, indice_offset, indice_size,
385 vg_ui.indice_buffer+vg_ui.indice_start );
386
387 glDisable( GL_DEPTH_TEST );
388 glEnable( GL_BLEND );
389 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
390 glBlendEquation( GL_FUNC_ADD );
391 glDisable( GL_CULL_FACE );
392
393 m3x3f view = M3X3_IDENTITY;
394 m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
395 m3x3_scale( view, (v3f){ 1.0f/(w*0.5f),
396 -1.0f/(h*0.5f), 1.0f } );
397
398 if( shader == k_ui_shader_colour ){
399 glUseProgram( _shader_ui.id );
400
401 glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1,
402 GL_FALSE, (float *)view );
403 glUniform4fv( glGetUniformLocation( _shader_ui.id, "uColour" ), 1,
404 vg_ui.colour );
405
406 glActiveTexture( GL_TEXTURE0 );
407 glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs );
408 glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 );
409
410 glActiveTexture( GL_TEXTURE1 );
411 glBindTexture( GL_TEXTURE_2D, vg_ui.tex_bg );
412 glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexBG" ), 1 );
413 glUniform1f( glGetUniformLocation( _shader_ui.id, "uSpread" ),
414 vg_ui.frosting );
415 glUniform2fv( glGetUniformLocation( _shader_ui.id, "uBGInverseRatio" ),
416 1, vg_ui.bg_inverse_ratio );
417 }
418 else if( shader == k_ui_shader_image ){
419 glUseProgram( _shader_ui_image.id );
420 glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1,
421 GL_FALSE, (float *)view );
422 glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 );
423 glUniform4fv( glGetUniformLocation( _shader_ui_image.id, "uColour" ), 1,
424 vg_ui.colour );
425 }
426 else if( shader == k_ui_shader_hsv ){
427 glUseProgram( _shader_ui_hsv.id );
428 glUniformMatrix3fv( glGetUniformLocation( _shader_ui_hsv.id, "uPv" ), 1,
429 GL_FALSE, (float *)view );
430 glUniform1f( glGetUniformLocation(_shader_ui_hsv.id,"uHue"), vg_ui.hue );
431 }
432 else
433 vg_fatal_error( "Invalid UI shader (%d)\n", shader );
434
435 glDrawElements( GL_TRIANGLES, indice_count, GL_UNSIGNED_SHORT,
436 (void *)(vg_ui.indice_start*sizeof(u16)) );
437
438 glDisable( GL_BLEND );
439
440 vg_ui.indice_start = vg_ui.cur_indice;
441 vg_ui.vert_start = vg_ui.cur_vert;
442 }
443
444 void ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] )
445 {
446 /* this if far from ideal but stops us from crashing */
447 if( (vg_ui.cur_vert + 4 > vg_ui.max_verts) ||
448 (vg_ui.cur_indice + 6 > vg_ui.max_indices))
449 return;
450
451 struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ];
452 u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ];
453
454 for( int i=0; i<4; i++ ){
455 vertices[i].colour = colour;
456 }
457
458 vertices[0].co[0] = rect[0];
459 vertices[0].co[1] = rect[1];
460 vertices[0].uv[0] = uv[0];
461 vertices[0].uv[1] = uv[1];
462 vertices[1].co[0] = rect[0]+rect[2];
463 vertices[1].co[1] = rect[1];
464 vertices[1].uv[0] = uv[2];
465 vertices[1].uv[1] = uv[1];
466 vertices[2].co[0] = rect[0]+rect[2];
467 vertices[2].co[1] = rect[1]+rect[3];
468 vertices[2].uv[0] = uv[2];
469 vertices[2].uv[1] = uv[3];
470 vertices[3].co[0] = rect[0];
471 vertices[3].co[1] = rect[1]+rect[3];
472 vertices[3].uv[0] = uv[0];
473 vertices[3].uv[1] = uv[3];
474 u16 ind_start = vg_ui.cur_vert;
475
476 u16 start = vg_ui.cur_vert;
477 u32 mesh[] = { 0,2,1, 0,3,2 };
478
479 for( u32 i=0; i<vg_list_size(mesh); i++ ){
480 indices[i] = start+mesh[i];
481 }
482
483 vg_ui.cur_indice += 6;
484 vg_ui.cur_vert += 4;
485 }
486
487 void ui_fill( ui_rect rect, u32 colour )
488 {
489 ui_fill_rect( rect, colour, (ui_px[4]){ 4,4,4,4 } );
490 }
491
492 void ui_outline( ui_rect rect, ui_px thickness, u32 colour, u32 mask )
493 {
494 /* this if far from ideal but stops us from crashing */
495 if( (vg_ui.cur_vert + 8 > vg_ui.max_verts) ||
496 (vg_ui.cur_indice + 24 > vg_ui.max_indices))
497 return;
498
499 struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ];
500 u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ];
501
502 for( int i=0; i<8; i++ ){
503 vertices[i].uv[0] = 4;
504 vertices[i].uv[1] = 4;
505 vertices[i].colour = colour;
506 }
507
508 vertices[0].co[0] = rect[0];
509 vertices[0].co[1] = rect[1];
510 vertices[1].co[0] = rect[0]+rect[2];
511 vertices[1].co[1] = rect[1];
512 vertices[2].co[0] = rect[0]+rect[2];
513 vertices[2].co[1] = rect[1]+rect[3];
514 vertices[3].co[0] = rect[0];
515 vertices[3].co[1] = rect[1]+rect[3];
516 vertices[4].co[0] = vertices[0].co[0]-thickness;
517 vertices[4].co[1] = vertices[0].co[1]-thickness;
518 vertices[5].co[0] = vertices[1].co[0]+thickness;
519 vertices[5].co[1] = vertices[1].co[1]-thickness;
520 vertices[6].co[0] = vertices[2].co[0]+thickness;
521 vertices[6].co[1] = vertices[2].co[1]+thickness;
522 vertices[7].co[0] = vertices[3].co[0]-thickness;
523 vertices[7].co[1] = vertices[3].co[1]+thickness;
524
525 u16 start = vg_ui.cur_vert;
526 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 };
527
528 if( !mask )
529 mask = UI_TOP|UI_LEFT|UI_BOTTOM|UI_RIGHT;
530
531 u32 c = 0;
532 for( u32 i=0; i<vg_list_size(mesh)/6; i++ ){
533 if( (0x1<<i) & mask ){
534 for( u32 j=0; j<6; j++ )
535 indices[c ++] = start+mesh[i*6+j];
536 }
537 }
538
539 vg_ui.cur_indice += c;
540 vg_ui.cur_vert += 8;
541 }
542
543 void ui_split( ui_rect rect, enum ui_axis other, ui_px width, ui_px gap,
544 ui_rect l, ui_rect r ){
545 enum ui_axis dir = other ^ 0x1;
546
547 if( width < 0 ) width = rect[ 2+dir ] + width;
548
549 ui_rect temp;
550 rect_copy( rect, temp );
551
552 l[ dir ] = temp[ dir ];
553 r[ dir ] = temp[ dir ] + width + (gap/2);
554 l[ other ] = temp[ other ];
555 r[ other ] = temp[ other ];
556 l[ 2+dir ] = width - (gap/2);
557 r[ 2+dir ] = temp[ 2+dir ] - width - (gap/2);
558 l[ 2+other ] = temp[ 2+other ];
559 r[ 2+other ] = temp[ 2+other ];
560 }
561
562 void ui_rect_center( ui_rect parent, ui_rect rect )
563 {
564 rect[0] = parent[0] + (parent[2]-rect[2])/2;
565 rect[1] = parent[1] + (parent[3]-rect[3])/2;
566 }
567
568 void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d )
569 {
570 i32 rp = (i32)rect[2] * (i32)size[1],
571 rc = (i32)size[0] * (i32)rect[3];
572
573 enum ui_axis dir, other;
574 if( rc > rp ) dir = k_ui_axis_h;
575 else dir = k_ui_axis_v;
576 other = dir ^ 0x1;
577
578 d[2+dir] = rect[2+dir];
579 d[2+other] = (rect[2+dir] * size[other]) / size[dir];
580
581 ui_rect_center( rect, d );
582 }
583
584 void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio,
585 ui_px gap, ui_rect l, ui_rect r )
586 {
587 ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio;
588 ui_split( rect, dir, width, gap, l, r );
589 }
590
591 void ui_rect_pad( ui_rect rect, ui_px pad[2] )
592 {
593 rect[0] += pad[0];
594 rect[1] += pad[1];
595 rect[2] -= pad[0]*2;
596 rect[3] -= pad[1]*2;
597 }
598
599 ui_px ui_text_line_width( const char *str )
600 {
601 int length = 0;
602 const char *_c = str;
603 u8 c;
604
605 while( (c = *(_c ++)) ){
606 if( c >= 32 ) length ++;
607 else if( c == '\n' ) break;
608 }
609
610 return length * vg_ui.font->spacing;
611 }
612
613 ui_px ui_text_string_height( const char *str )
614 {
615 int height = 1;
616 const char *_c = str;
617 u8 c;
618
619 while( (c = *(_c ++)) ){
620 if( c == '\n' ) height ++;
621 }
622
623 return height * 14;
624 }
625
626 ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale,
627 enum ui_align align )
628 {
629 enum ui_align lwr = k_ui_align_lwr & align;
630 if( lwr == k_ui_align_left ){
631 return rect[0];
632 }
633 else{
634 ui_px width = ui_text_line_width( str ) * scale;
635
636 if( lwr == k_ui_align_right )
637 return rect[0] + rect[2]-width;
638 else
639 return rect[0] + (rect[2]-width)/2;
640 }
641 }
642
643 static ui_px ui_min( ui_px a, ui_px b ){ return a<b?a:b; }
644 static ui_px ui_max( ui_px a, ui_px b ){ return a>b?a:b; }
645 static ui_px ui_clamp( ui_px a, ui_px min, ui_px max ){
646 return ui_min( max, ui_max( a, min ) );
647 }
648
649 int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped )
650 {
651 ui_px parent_max[2], child_max[2];
652 parent_max[0] = parent[0]+parent[2];
653 parent_max[1] = parent[1]+parent[3];
654 child_max[0] = child[0]+child[2];
655 child_max[1] = child[1]+child[3];
656
657 clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] );
658 clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] );
659 clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] );
660 clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] );
661
662 if( clipped[0] == clipped[2] ||
663 clipped[1] == clipped[3] )
664 return 0;
665
666 clipped[2] -= clipped[0];
667 clipped[3] -= clipped[1];
668
669 return 1;
670 }
671
672 int ui_inside_rect( ui_rect rect, ui_px co[2] )
673 {
674 if( co[0] >= rect[0] &&
675 co[1] >= rect[1] &&
676 co[0] < rect[0]+rect[2] &&
677 co[1] < rect[1]+rect[3] ){
678 return 1;
679 }
680 else
681 return 0;
682 }
683
684 int ui_click_down( u32 mask )
685 {
686 if( vg_ui.ignore_input_frames ) return 0;
687 if( (vg_ui.mouse_state[0] & mask) &&
688 !(vg_ui.mouse_state[1] & mask) )
689 return 1;
690 else
691 return 0;
692 }
693
694 int ui_clicking( u32 mask )
695 {
696 if( vg_ui.ignore_input_frames ) return 0;
697 return vg_ui.mouse_state[0] & mask;
698 }
699
700 int ui_click_up( u32 mask )
701 {
702 if( vg_ui.ignore_input_frames ) return 0;
703 if( (vg_ui.mouse_state[1] & mask) &&
704 !(vg_ui.mouse_state[0] & mask) )
705 return 1;
706 else
707 return 0;
708 }
709
710 void ui_prerender(void)
711 {
712 int x, y;
713 vg_ui.mouse_state[1] = vg_ui.mouse_state[0];
714 vg_ui.mouse_state[0] = SDL_GetMouseState( &x, &y );
715 vg_ui.mouse_delta[0] = x-vg_ui.mouse[0];
716 vg_ui.mouse_delta[1] = y-vg_ui.mouse[1];
717 vg_ui.mouse[0] = x;
718 vg_ui.mouse[1] = y;
719
720 vg_ui.cur_vert = 0;
721 vg_ui.cur_indice = 0;
722 vg_ui.vert_start = 0;
723 vg_ui.indice_start = 0;
724 vg_ui.focused_control_hit = 0;
725 vg_ui.cursor = k_ui_cursor_default;
726 vg_ui.wants_mouse = 0;
727
728 if( vg_ui.ignore_input_frames ){
729 vg_ui.ignore_input_frames --;
730 return;
731 }
732
733 if( ui_click_down(UI_MOUSE_LEFT)||ui_click_down(UI_MOUSE_MIDDLE) ){
734 vg_ui.mouse_click[0] = vg_ui.mouse[0];
735 vg_ui.mouse_click[1] = vg_ui.mouse[1];
736 }
737 }
738
739 u32 ui_colour( enum ui_scheme_colour id )
740 {
741 return vg_ui.scheme[ id ];
742 }
743
744 /* get an appropriately contrasting colour given the base */
745 u32 ui_colourcont( enum ui_scheme_colour id )
746 {
747 if ( id < k_ui_bg+6 ) return ui_colour( k_ui_fg );
748 else if( id < k_ui_fg ) return ui_colour( k_ui_bg+1 );
749 else if( id < k_ui_hue ) return ui_colour( k_ui_bg+3 );
750 else if( id < k_ui_red+k_ui_brighter ) return ui_colour( k_ui_fg );
751 else return ui_colour( k_ui_fg+1 );
752 }
753
754 void ui_hex_to_norm( u32 hex, v4f norm )
755 {
756 norm[0] = ((hex ) & 0xff);
757 norm[1] = ((hex>>8 ) & 0xff);
758 norm[2] = ((hex>>16) & 0xff);
759 norm[3] = ((hex>>24) & 0xff);
760 v4_muls( norm, 1.0f/255.0f, norm );
761 }
762
763 u32 v4f_u32_colour( v4f colour )
764 {
765 u32 r = colour[0] * 255.0f,
766 g = colour[1] * 255.0f,
767 b = colour[2] * 255.0f,
768 a = colour[3] * 255.0f;
769
770 return r | (g<<8) | (b<<16) | (a<<24);
771 }
772
773 static void ui_text_glyph( const struct ui_font *font, ui_px scale,
774 u8 glyph, ui_rect out_texcoords ){
775 glyph -= font->ascii_start;
776
777 ui_px per_row = font->sheet_size / font->glyph_width,
778 column = (ui_px)glyph % per_row,
779 row = (glyph - column) / per_row;
780
781 out_texcoords[0] = column * font->glyph_width;
782 out_texcoords[1] = row * font->glyph_height + font->offset_y;
783 out_texcoords[2] = out_texcoords[0] + font->glyph_width;
784 out_texcoords[3] = out_texcoords[1] + font->glyph_height;
785 }
786
787 u32 ui_opacity( u32 colour, f32 opacity )
788 {
789 u32 alpha = opacity * 255.0f;
790 return (colour & 0x00ffffff) | (alpha << 24);
791 }
792
793 u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale,
794 enum ui_align align, u32 colour )
795 {
796 ui_rect text_cursor;
797 if( colour == 0 ) colour = ui_colour( k_ui_fg );
798
799 colour &= 0x00ffffff;
800
801 const char *_c = str;
802 u8 c;
803
804 text_cursor[0] = ui_text_aligned_x( str, rect, scale, align );
805 text_cursor[1] = rect[1];
806 text_cursor[2] = vg_ui.font->glyph_width*scale;
807 text_cursor[3] = vg_ui.font->glyph_height*scale;
808
809 u32 printed_chars = 0;
810
811 if( align & (k_ui_align_middle|k_ui_align_bottom) ){
812 ui_px height = ui_text_string_height( str ) * scale;
813
814 if( align & k_ui_align_bottom )
815 text_cursor[1] += rect[3]-height;
816 else
817 text_cursor[1] += (rect[3]-height)/2;
818 }
819
820 while( (c = *(_c ++)) ){
821 if( printed_chars >= len ){
822 printed_chars = 0;
823 text_cursor[1] += vg_ui.font->line_height*scale;
824 text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align );
825 text_cursor[0] -= vg_ui.font->spacing*scale;
826
827 ui_rect glyph;
828 ui_text_glyph( vg_ui.font, scale, '\xb6' /*FIXME*/, glyph );
829 ui_fill_rect( text_cursor, 0x00ffffff, glyph );
830 text_cursor[0] += vg_ui.font->spacing*scale;
831 }
832
833 if( c == '\n' ){
834 text_cursor[1] += vg_ui.font->line_height*scale;
835 text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align );
836 printed_chars = 0;
837 continue;
838 }
839 else if( c >= 33 ){
840 ui_rect glyph;
841 ui_text_glyph( vg_ui.font, scale, c, glyph );
842
843 ui_rect cursor_clipped;
844 if( ui_clip( rect, text_cursor, cursor_clipped ) ){
845 ui_fill_rect( cursor_clipped, colour, glyph );
846 }
847 }
848 else if( c == '\x1B' ){
849 /* vt codes */
850 _c ++;
851 u16 colour_id = 0;
852 for( int i=0; i<3; i ++ ){
853 if( _c[i] ){
854 if( _c[i] == 'm' ){
855 _c = _c + i + 1;
856
857 switch( colour_id ){
858 case '0': colour = ui_colour( k_ui_fg ); break;
859 case '3'|'0'<<8: colour = ui_colour( k_ui_bg ); break;
860 case '3'|'1'<<8: colour = ui_colour( k_ui_red ); break;
861 case '3'|'2'<<8: colour = ui_colour( k_ui_green ); break;
862 case '3'|'3'<<8: colour = ui_colour( k_ui_yellow ); break;
863 case '3'|'4'<<8: colour = ui_colour( k_ui_blue ); break;
864 case '3'|'5'<<8: colour = ui_colour( k_ui_purple ); break;
865 case '3'|'6'<<8: colour = ui_colour( k_ui_aqua ); break;
866 case '3'|'7'<<8: colour = 0xffffffff; break;
867 }
868
869 colour &= 0x00ffffff;
870 break;
871 }
872
873 colour_id |= _c[i] << (i*8);
874 }
875 else{
876 _c = _c +i;
877 break;
878 }
879 }
880
881 continue;
882 }
883 else if( c == '\t' ){
884 text_cursor[0] += vg_ui.font->spacing*scale*4;
885 printed_chars += 4;
886 continue;
887 }
888
889 text_cursor[0] += vg_ui.font->spacing*scale;
890 printed_chars ++;
891 }
892
893 return printed_chars;
894 }
895
896 void ui_text( ui_rect rect, const char *str, ui_px scale,
897 enum ui_align align, u32 colour )
898 {
899 ui_ntext( rect, str, 1024, scale, align, colour );
900 }
901
902 /*
903 * Standard layout stuff
904 * -----------------------------------------------------------------------------
905 */
906
907 void ui_panel( ui_rect in_rect, ui_rect out_panel )
908 {
909 ui_fill( in_rect, ui_colour( k_ui_bg+1 ) );
910 ui_outline( in_rect, 1, ui_colour( k_ui_bg+7 ), 0 );
911 rect_copy( in_rect, out_panel );
912 ui_rect_pad( out_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } );
913 }
914
915 void ui_label( ui_rect rect, const char *text, ui_px size,
916 ui_px gap, ui_rect r )
917 {
918 ui_rect l;
919 ui_px width = (ui_text_line_width(text)+vg_ui.font->spacing) * size;
920 ui_split( rect, k_ui_axis_v, width, gap, l, r );
921 ui_text( l, text, 1, k_ui_align_middle_left, 0 );
922 }
923
924 void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, ui_px count )
925 {
926 ui_px height = (count * vg_ui.font->glyph_height + 18) * k_ui_scale;
927 ui_split( inout_panel, k_ui_axis_h, height, k_ui_padding,
928 out_rect, inout_panel );
929 }
930
931 void ui_info( ui_rect inout_panel, const char *text )
932 {
933 ui_rect box;
934 ui_standard_widget( inout_panel, box, 1 );
935 ui_text( box, text, 1, k_ui_align_middle_left, 0 );
936 }
937
938 void ui_image( ui_rect rect, GLuint image )
939 {
940 ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y );
941 glActiveTexture( GL_TEXTURE0 );
942 glBindTexture( GL_TEXTURE_2D, image );
943 ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,256,256,0 } );
944 ui_flush( k_ui_shader_image, vg.window_x, vg.window_y );
945 }
946
947 void ui_defocus_all(void)
948 {
949 if( vg_ui.focused_control_type == k_ui_control_textbox ){
950 SDL_StopTextInput();
951 if( vg_ui.textbox.callbacks.escape )
952 vg_ui.textbox.callbacks.escape();
953 }
954
955 vg_ui.focused_control_id = NULL;
956 vg_ui.focused_control_hit = 0;
957 vg_ui.focused_control_type = k_ui_control_none;
958 }
959
960 /* TODO: split this out into a formatless button and one that auto fills */
961 enum ui_button_state ui_colourbutton( ui_rect rect,
962 enum ui_scheme_colour colour,
963 enum ui_scheme_colour hover_colour,
964 enum ui_scheme_colour hi_colour,
965 bool const fill )
966 {
967 int clickup= ui_click_up(UI_MOUSE_LEFT),
968 click = ui_clicking(UI_MOUSE_LEFT) | clickup,
969 target = ui_inside_rect( rect, vg_ui.mouse_click ) && click,
970 hover = ui_inside_rect( rect, vg_ui.mouse );
971
972 u32 col_base = vg_ui.scheme[ colour ],
973 col_highlight = vg_ui.scheme[ hi_colour? hi_colour: k_ui_fg ],
974 col_hover = vg_ui.scheme[ hover_colour? hover_colour:
975 colour + k_ui_brighter ];
976
977 if( vg_ui.focused_control_type != k_ui_control_none ){
978 clickup = 0;
979 click = 0;
980 target = 0;
981 hover = 0;
982 }
983
984 if( hover ){
985 vg_ui.cursor = k_ui_cursor_hand;
986 }
987
988 if( click ){
989 if( target ){
990 if( hover ){
991 if( clickup ){
992 vg_ui.ignore_input_frames = 2;
993 ui_defocus_all();
994
995 if( fill ) {
996 ui_fill( rect, col_highlight );
997 rect_copy( rect, vg_ui.click_fader );
998 rect_copy( rect, vg_ui.click_fader_end );
999 vg_ui.click_fader_end[3] = 0;
1000 ui_rect_center( rect, vg_ui.click_fader_end );
1001 vg_ui.click_fade_opacity = 1.0f;
1002 }
1003
1004 return k_ui_button_click;
1005 }
1006 else{
1007 if( fill ) ui_fill( rect, col_highlight );
1008 return k_ui_button_holding_inside;
1009 }
1010 }
1011 else{
1012 if( fill ) ui_fill( rect, col_base );
1013 ui_outline( rect, 1, col_highlight, 0 );
1014 return k_ui_button_holding_outside;
1015 }
1016 }
1017 else{
1018 if( fill ) ui_fill( rect, col_base );
1019 return k_ui_button_none;
1020 }
1021 }
1022 else{
1023 if( hover ){
1024 if( fill ) ui_fill( rect, col_hover );
1025 return k_ui_button_hover;
1026 }
1027 else{
1028 if( fill ) ui_fill( rect, col_base );
1029 return k_ui_button_none;
1030 }
1031 }
1032 }
1033
1034 enum ui_button_state ui_colourbutton_text(
1035 ui_rect rect, const char *string, ui_px scale,
1036 enum ui_scheme_colour colour ){
1037 enum ui_button_state state = ui_colourbutton( rect, colour, 0, 0, 1 );
1038 ui_rect t = { 0,0, ui_text_line_width( string )*scale, 14*scale };
1039 ui_rect_center( rect, t );
1040
1041 u32 text_colour = ui_colourcont(colour);
1042 if( state == k_ui_button_holding_inside )
1043 text_colour = colour;
1044
1045 ui_text( t, string, scale, k_ui_align_left, text_colour );
1046 return state;
1047 }
1048
1049 enum ui_button_state ui_button_text( ui_rect rect,
1050 const char *string, ui_px scale )
1051 {
1052 return ui_colourbutton_text( rect, string, scale, k_ui_bg+4 );
1053 }
1054
1055 enum ui_button_state ui_button( ui_rect inout_panel, const char *string )
1056 {
1057 ui_rect rect;
1058 ui_standard_widget( inout_panel, rect, 1 );
1059 return ui_colourbutton_text( rect, string, 1, k_ui_bg+4 );
1060 }
1061
1062 static void ui_enum_post(void);
1063 void ui_postrender(void)
1064 {
1065 if( vg_ui.click_fade_opacity > 0.0f ){
1066 float scale = vg_ui.click_fade_opacity;
1067 scale = vg_maxf( 1.0f/255.0f, scale*scale );
1068
1069 vg_ui.click_fade_opacity -= vg.time_frame_delta * 3.8f;
1070 u32 colour = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000;
1071
1072 v4f begin, end, dest;
1073 for( int i=0; i<4; i++ ){
1074 begin[i] = vg_ui.click_fader[i];
1075 end[i] = vg_ui.click_fader_end[i]+1;
1076 }
1077
1078 v4_lerp( end, begin, scale, dest );
1079
1080 ui_rect rect;
1081 for( int i=0; i<4; i++ ){
1082 rect[i] = dest[i];
1083 }
1084
1085 ui_fill( rect, colour );
1086 }
1087
1088 if( vg_ui.focused_control_type == k_ui_control_enum ){
1089 ui_enum_post();
1090 }
1091 else if( vg_ui.focused_control_type == k_ui_control_modal ){
1092 ui_rect screen = {0,0,vg.window_x,vg.window_y};
1093 ui_fill( screen, 0xa0000000 );
1094 ui_rect box = {0,0,400,200};
1095
1096 u32 colour = ui_colour(k_ui_fg),
1097 type = vg_ui.modal.options & UI_MODAL_TYPE_BITS;
1098 if ( type == 1 ) colour = ui_colour(k_ui_green);
1099 else if( type == 2 ) colour = ui_colour(k_ui_red);
1100 else if( type == 3 ) colour = ui_colour(k_ui_yellow);
1101
1102 ui_rect_center( screen, box );
1103 ui_fill( box, ui_colour(k_ui_bg) );
1104 ui_outline( box, -1, colour, 0 );
1105
1106 ui_rect message;
1107 rect_copy( box, message );
1108 message[3] = 100;
1109 ui_rect_center( box, message );
1110
1111 ui_rect row0, row1, btn;
1112 ui_split_ratio( message, k_ui_axis_h, 0.5f, 0, row0, row1 );
1113 row0[0] += vg_ui.font->spacing;
1114 ui_ntext( row0, vg_ui.modal.message, (box[2]/vg_ui.font->spacing)-2, 1,
1115 k_ui_align_left, colour );
1116
1117 rect_copy( row1, btn );
1118 btn[2] = 86;
1119 btn[3] = 28;
1120 ui_rect_center( row1, btn );
1121
1122 vg_ui.focused_control_type = k_ui_control_none; /* HACK */
1123 if( ui_button_text( btn, "OK", 1 ) != 1 )
1124 vg_ui.focused_control_hit = 1;
1125 vg_ui.focused_control_type = k_ui_control_modal; /* HACK */
1126 vg_ui.wants_mouse = 1;
1127 }
1128
1129 ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y );
1130
1131 if( !vg_ui.focused_control_hit ){
1132 ui_defocus_all();
1133 }
1134
1135 if( vg_ui.wants_mouse ){
1136 SDL_SetWindowGrab( vg.window, SDL_FALSE );
1137 SDL_SetRelativeMouseMode( SDL_FALSE );
1138 }
1139 else{
1140 SDL_SetWindowGrab( vg.window, SDL_TRUE );
1141 SDL_SetRelativeMouseMode( SDL_TRUE );
1142 }
1143
1144 SDL_SetCursor( vg_ui.cursor_map[ vg_ui.cursor ] );
1145 SDL_ShowCursor(1);
1146 }
1147
1148 /*
1149 * checkbox
1150 * -----------------------------------------------------------------------------
1151 */
1152
1153 int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data )
1154 {
1155 ui_rect rect, label, box;
1156 ui_standard_widget( inout_panel, rect, 1 );
1157
1158 ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box );
1159 ui_text( label, str_label, k_ui_scale, k_ui_align_middle_left, 0 );
1160
1161 int changed = ui_colourbutton( box, k_ui_bg, 0, 0, 1 )==1;
1162 if( changed )
1163 *data = (*data) ^ 0x1;
1164
1165 if( *data ){
1166 ui_rect_pad( box, (ui_px[2]){4,4} );
1167 ui_fill( box, ui_colour( k_ui_orange ) );
1168 }
1169
1170 return changed;
1171 }
1172
1173 /*
1174 * Dropdown / Enum
1175 * -----------------------------------------------------------------------------
1176 */
1177
1178 /*
1179 * unfortunately no return value since we only find out that event in the
1180 * postrender step.
1181 */
1182 void ui_enum( ui_rect inout_panel, const char *str_label,
1183 struct ui_enum_opt *options, u32 len, i32 *value )
1184 {
1185 ui_rect rect, label, box;
1186 ui_standard_widget( inout_panel, rect, 1 );
1187 ui_label( rect, str_label, k_ui_scale, 0, box );
1188
1189 const char *display = "OUT OF RANGE";
1190 int valid = 0;
1191 for( u32 i=0; i<len; i ++ ){
1192 if( *value == options[i].value ){
1193 display = options[i].alias;
1194 valid = 1;
1195 break;
1196 }
1197 }
1198
1199 if( ui_button_text( box, display, k_ui_scale ) == 1 ){
1200 vg_ui.focused_control_type = k_ui_control_enum;
1201 vg_ui.ptr_enum = value;
1202 vg_ui._enum.option_count = len;
1203 vg_ui._enum.options = options;
1204 rect_copy( box, vg_ui._enum.rect );
1205 }
1206
1207 if( !valid )
1208 ui_outline( box, 1, ui_colour(k_ui_red), 0 );
1209 }
1210
1211 static void ui_enum_post(void){
1212 ui_rect drawer;
1213 rect_copy( vg_ui._enum.rect, drawer );
1214 drawer[3] *= vg_ui._enum.option_count;
1215
1216 int close = 0;
1217 int clickany= ui_click_up(UI_MOUSE_LEFT|UI_MOUSE_RIGHT|UI_MOUSE_MIDDLE),
1218 hover = ui_inside_rect( drawer, vg_ui.mouse );
1219
1220 if( clickany && !hover ){
1221 return;
1222 }
1223
1224 /* HACK */
1225 vg_ui.focused_control_type = k_ui_control_none;
1226 i32 *value = vg_ui.ptr_enum;
1227
1228 for( u32 i=0; i<vg_ui._enum.option_count; i++ ){
1229 ui_rect button;
1230 ui_split( drawer, k_ui_axis_h, vg_ui._enum.rect[3], 0, button,drawer );
1231
1232 enum ui_scheme_colour colour = k_ui_bg+3;
1233 if( vg_ui._enum.options[i].value == *value )
1234 colour = k_ui_orange;
1235
1236 if( ui_colourbutton_text( button, vg_ui._enum.options[i].alias,
1237 k_ui_scale, colour ) == 1 ){
1238 *value = vg_ui._enum.options[i].value;
1239 close = 1;
1240 }
1241 }
1242
1243 /* HACK */
1244 vg_ui.focused_control_type = k_ui_control_enum;
1245
1246 if( !close )
1247 vg_ui.focused_control_hit = 1;
1248 }
1249
1250 /*
1251 * Slider
1252 * -----------------------------------------------------------------------------
1253 */
1254
1255 static enum ui_button_state _ui_slider(
1256 ui_rect box, f32 min, f32 max, f32 *value, const char *format )
1257 {
1258 f32 t;
1259
1260 enum ui_button_state
1261 mask_using =
1262 k_ui_button_holding_inside |
1263 k_ui_button_holding_outside |
1264 k_ui_button_click,
1265 mask_brighter =
1266 mask_using | k_ui_button_hover,
1267 state = ui_colourbutton( box, k_ui_bg, k_ui_bg+2, k_ui_bg+3, 1 );
1268
1269 if( state & mask_using ){
1270 t = vg_clampf( (f32)(vg_ui.mouse[0] - box[0]) / (f32)( box[2] ),
1271 0.0f, 1.0f );
1272 *value = vg_lerpf( min, max, t );
1273 }
1274 else
1275 t = vg_clampf( (*value - min) / (max-min), 0.0f, 1.0f );
1276
1277 ui_rect line = { box[0], box[1], t * (f32)box[2], box[3] };
1278 ui_fill( line, ui_colour(state&mask_brighter? k_ui_bg+4: k_ui_bg+2) );
1279
1280 ui_outline( box, 1, ui_colour(state? k_ui_fg+3: k_ui_bg+3), 0 );
1281
1282 /* TODO: replace this one day with our own function */
1283 char buf[32];
1284 snprintf( buf, sizeof(buf), format? format: "%.2f", *value );
1285 ui_text( box, buf, 1, k_ui_align_middle_center, 0 );
1286
1287 return state;
1288 }
1289
1290 void ui_slider( ui_rect inout_panel, const char *str_label,
1291 f32 min, f32 max, f32 *value, const char *format )
1292 {
1293 ui_rect rect, label, box;
1294 ui_standard_widget( inout_panel, rect, 1 );
1295 ui_label( rect, str_label, k_ui_scale, 0, box );
1296 _ui_slider( box, min, max, value, format );
1297 }
1298
1299 /*
1300 * Colour picker
1301 * -----------------------------------------------------------------------------
1302 */
1303
1304 void ui_colourpicker( ui_rect inout_panel, const char *str_label, v4f value )
1305 {
1306 ui_rect widget, left, right;
1307 ui_standard_widget( inout_panel, widget, 8 );
1308 ui_split_ratio( widget, k_ui_axis_v, 0.5f, 8, left, right );
1309
1310 ui_rect sliders[4];
1311 ui_split_ratio( right, k_ui_axis_h, 0.5f, 2, sliders[0], sliders[2] );
1312 ui_split_ratio( sliders[0], k_ui_axis_h, 0.5f, 2, sliders[0], sliders[1] );
1313 ui_split_ratio( sliders[2], k_ui_axis_h, 0.5f, 2, sliders[2], sliders[3] );
1314
1315 v4f hsv;
1316 vg_rgb_hsv( value, hsv );
1317 hsv[3] = value[3];
1318
1319 enum ui_button_state modified = 0x00;
1320
1321 for( u32 i=0; i<4; i ++ ){
1322 modified |= _ui_slider( sliders[i], 0.0f, 1.0f, hsv+i,
1323 (const char *[]){ "hue %.2f",
1324 "sat %.2f",
1325 "lum %.2f",
1326 "alpha %.2f" }[i] );
1327 }
1328
1329 ui_rect preview, square;
1330 ui_split_ratio( left, k_ui_axis_v, 0.8f, 8, square, preview );
1331
1332 u32 state = ui_colourbutton( square, 0, 0, 0, 0 );
1333 modified |= state;
1334
1335 enum ui_button_state
1336 mask_using =
1337 k_ui_button_holding_inside |
1338 k_ui_button_holding_outside |
1339 k_ui_button_click;
1340
1341 if( state & mask_using ){
1342 for( u32 i=0; i<2; i ++ ){
1343 hsv[1+i] = vg_clampf(
1344 (f32)(vg_ui.mouse[i] - square[i]) / (f32)(square[2+i]),
1345 0.0f, 1.0f );
1346 }
1347
1348 hsv[2] = 1.0f-hsv[2];
1349 }
1350
1351 if( modified & (k_ui_button_click|k_ui_button_holding_inside|
1352 k_ui_button_holding_outside) ){
1353 vg_hsv_rgb( hsv, value );
1354 value[3] = hsv[3];
1355 }
1356
1357 ui_outline( square, 1, ui_colour( state? k_ui_fg+3: k_ui_bg+3 ), 0 );
1358
1359 /* preview colour */
1360 v4f colour;
1361 vg_hsv_rgb( hsv, colour );
1362 colour[3] = 1.0f;
1363 ui_fill( preview, v4f_u32_colour( colour ) );
1364
1365 /* Draw hsv shader thingy */
1366 ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y );
1367 ui_fill_rect( square, 0xffffffff, (ui_px[4]){ 0,256,256,0 } );
1368 vg_ui.hue = hsv[0];
1369 ui_flush( k_ui_shader_hsv, vg.window_x, vg.window_y );
1370
1371 ui_rect marker = { square[0] + hsv[1] * (f32)square[2] - 4,
1372 square[1] + (1.0f-hsv[2]) * (f32)square[3] - 4,
1373 8, 8 },
1374 lx = { square[0],
1375 square[1] + (1.0f-hsv[2]) * (f32)square[3],
1376 square[2], 1 },
1377 ly = { square[0] + hsv[1] * (f32)square[2],
1378 square[1],
1379 1, square[3] };
1380
1381 ui_fill( marker, ui_colour( k_ui_fg ) );
1382 ui_fill( lx, ui_colour( k_ui_fg ) );
1383 ui_fill( ly, ui_colour( k_ui_fg ) );
1384 }
1385
1386 /*
1387 * Textbox chaos
1388 * -----------------------------------------------------------------------------
1389 */
1390
1391 static void _ui_textbox_make_selection( int *start, int *end ){
1392 *start = VG_MIN( vg_ui.textbox.cursor_pos, vg_ui.textbox.cursor_user );
1393 *end = VG_MAX( vg_ui.textbox.cursor_pos, vg_ui.textbox.cursor_user );
1394 }
1395
1396 void _ui_textbox_move_cursor( int *cursor0, int *cursor1,
1397 int dir, int snap_together )
1398 {
1399 *cursor0 = VG_MAX( 0, vg_ui.textbox.cursor_user + dir );
1400 *cursor0 =
1401 VG_MIN(
1402 VG_MIN( vg_ui.textbox.len-1, strlen( vg_ui.textbuf )),
1403 *cursor0 );
1404
1405 if( snap_together )
1406 *cursor1 = *cursor0;
1407 }
1408
1409 static int _ui_textbox_makeroom( int datastart, int length ){
1410 int move_to = VG_MIN( datastart+length, vg_ui.textbox.len-1 );
1411 int move_amount = strlen( vg_ui.textbuf )-datastart;
1412 int move_end = VG_MIN( move_to+move_amount, vg_ui.textbox.len-1 );
1413 move_amount = move_end-move_to;
1414
1415 if( move_amount )
1416 memmove( &vg_ui.textbuf[ move_to ],
1417 &vg_ui.textbuf[ datastart ],
1418 move_end-move_to );
1419
1420 vg_ui.textbuf[ move_end ] = '\0';
1421
1422 return VG_MIN( length, vg_ui.textbox.len-datastart-1 );
1423 }
1424
1425 int _ui_textbox_delete_char( int direction )
1426 {
1427 int start, end;
1428 _ui_textbox_make_selection( &start, &end );
1429
1430 /* There is no selection */
1431 if( !(end-start) ){
1432 if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_ui.textbuf ) );
1433 else if( direction == -1 ) start = VG_MAX( start-1, 0 );
1434 }
1435
1436 /* Still no selction, no need to do anything */
1437 if( !(end-start) )
1438 return start;
1439
1440 /* Copy the end->terminator to start */
1441 int remaining_length = strlen( vg_ui.textbuf )+1-end;
1442 memmove( &vg_ui.textbuf[ start ],
1443 &vg_ui.textbuf[ end ],
1444 remaining_length );
1445 return start;
1446 }
1447
1448 static void _ui_textbox_to_clipboard(void){
1449 int start, end;
1450 _ui_textbox_make_selection( &start, &end );
1451 char buffer[512];
1452
1453 if( end-start ){
1454 memcpy( buffer, &vg_ui.textbuf[ start ], end-start );
1455 buffer[ end-start ] = 0x00;
1456 SDL_SetClipboardText( buffer );
1457 }
1458 }
1459
1460 static void _ui_textbox_change_callback(void){
1461 if( vg_ui.textbox.callbacks.change ){
1462 vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len );
1463
1464 /* we gave permission to modify the buffer in this callback so.. */
1465 int len = strlen( vg_ui.textbuf );
1466 vg_ui.textbox.cursor_user = VG_MIN( vg_ui.textbox.cursor_user, len );
1467 vg_ui.textbox.cursor_pos = VG_MIN( vg_ui.textbox.cursor_pos, len );
1468 }
1469 }
1470
1471 void ui_start_modal( const char *message, u32 options );
1472 static void _ui_textbox_clipboard_paste(void){
1473 if( !SDL_HasClipboardText() )
1474 return;
1475
1476 char *text = SDL_GetClipboardText();
1477
1478 if( !text )
1479 return;
1480
1481 int datastart = _ui_textbox_delete_char( 0 );
1482 int length = strlen( text );
1483
1484 if( (vg_ui.textbox.len - strlen(vg_ui.textbuf)) < length ){
1485 ui_start_modal( "Clipboard content exceeds buffer size.", UI_MODAL_BAD );
1486 return;
1487 }
1488
1489 int cpylength = _ui_textbox_makeroom( datastart, length );
1490
1491 memcpy( vg_ui.textbuf + datastart, text, cpylength);
1492 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
1493 &vg_ui.textbox.cursor_pos, cpylength, 1 );
1494 SDL_free( text );
1495 _ui_textbox_change_callback();
1496 }
1497
1498 void _ui_textbox_put_char( char c )
1499 {
1500 vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0);
1501 if( (vg_ui.textbox.len - strlen(vg_ui.textbuf)) <= 1 ) return;
1502
1503 if( _ui_textbox_makeroom( vg_ui.textbox.cursor_user, 1 ) )
1504 vg_ui.textbuf[ vg_ui.textbox.cursor_user ] = c;
1505
1506 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
1507 &vg_ui.textbox.cursor_pos, 1, 1 );
1508 }
1509
1510 /* Receed secondary cursor */
1511 void _ui_textbox_left_select(void)
1512 {
1513 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, -1, 0 );
1514 }
1515
1516 /* Match and receed both cursors */
1517 void _ui_textbox_left(void)
1518 {
1519 int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1;
1520
1521 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
1522 &vg_ui.textbox.cursor_pos, -cursor_diff, 1 );
1523 }
1524
1525 void _ui_textbox_up(void)
1526 {
1527 if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){
1528 int line_begin = vg_ui.textbox.cursor_user;
1529
1530 while( line_begin ){
1531 if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){
1532 break;
1533 }
1534
1535 line_begin --;
1536 }
1537
1538 if( line_begin ){
1539 int line_above_begin = line_begin-1;
1540
1541 while( line_above_begin ){
1542 if( vg_ui.textbuf[ line_above_begin-1 ] == '\n' ){
1543 break;
1544 }
1545
1546 line_above_begin --;
1547 }
1548
1549 int offset = vg_ui.textbox.cursor_user - line_begin,
1550 line_length_above = line_begin - line_above_begin -1;
1551
1552 offset = VG_MIN( line_length_above, offset );
1553
1554 vg_ui.textbox.cursor_user = line_above_begin+offset;
1555 vg_ui.textbox.cursor_pos = line_above_begin+offset;
1556 }
1557 else{
1558 vg_ui.textbox.cursor_user = line_begin;
1559 vg_ui.textbox.cursor_pos = line_begin;
1560 }
1561 }
1562 else{
1563 if( vg_ui.textbox.callbacks.up ){
1564 vg_ui.textbox.callbacks.up( vg_ui.textbuf, vg_ui.textbox.len );
1565 }
1566 }
1567 }
1568
1569 void _ui_textbox_down(void)
1570 {
1571 if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){
1572 int line_begin = vg_ui.textbox.cursor_user;
1573
1574 while( line_begin ){
1575 if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){
1576 break;
1577 }
1578
1579 line_begin --;
1580 }
1581
1582 int line_below_begin = vg_ui.textbox.cursor_user;
1583
1584 while(1){
1585 if( vg_ui.textbuf[ line_below_begin ] == '\0' ){
1586 vg_ui.textbox.cursor_user = line_below_begin;
1587 vg_ui.textbox.cursor_pos = line_below_begin;
1588 return;
1589 }
1590
1591 if( vg_ui.textbuf[ line_below_begin ] == '\n' ){
1592 line_below_begin ++;
1593 break;
1594 }
1595
1596 line_below_begin ++;
1597 }
1598
1599 int line_below_end = line_below_begin;
1600 while(1){
1601 if( vg_ui.textbuf[ line_below_end ] == '\0' ||
1602 vg_ui.textbuf[ line_below_end ] == '\n' ){
1603 line_below_end ++;
1604 break;
1605 }
1606 line_below_end ++;
1607 }
1608
1609 int offset = vg_ui.textbox.cursor_user - line_begin,
1610 line_length_below = line_below_end - line_below_begin -1;
1611
1612 offset = VG_MIN( line_length_below, offset );
1613
1614 vg_ui.textbox.cursor_user = line_below_begin+offset;
1615 vg_ui.textbox.cursor_pos = line_below_begin+offset;
1616 }
1617 else{
1618 if( vg_ui.textbox.callbacks.down ){
1619 vg_ui.textbox.callbacks.down( vg_ui.textbuf, vg_ui.textbox.len );
1620 }
1621 }
1622 }
1623
1624 void _ui_textbox_right_select(void)
1625 {
1626 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 1, 0 );
1627 }
1628
1629 void _ui_textbox_right(void)
1630 {
1631 int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1;
1632
1633 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user,
1634 &vg_ui.textbox.cursor_pos, +cursor_diff, 1 );
1635 }
1636
1637 void _ui_textbox_backspace(void)
1638 {
1639 if( vg_ui.focused_control_type == k_ui_control_textbox ){
1640 vg_ui.textbox.cursor_user = _ui_textbox_delete_char( -1 );
1641 vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user;
1642 _ui_textbox_change_callback();
1643 }
1644 }
1645
1646 void _ui_textbox_delete(void)
1647 {
1648 if( vg_ui.focused_control_type == k_ui_control_textbox ){
1649 vg_ui.textbox.cursor_user = _ui_textbox_delete_char( 1 );
1650 vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user;
1651 _ui_textbox_change_callback();
1652 }
1653 }
1654
1655 void _ui_textbox_home_select(void)
1656 {
1657 i32 start = vg_ui.textbox.cursor_user;
1658
1659 if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){
1660 while( start ){
1661 if( vg_ui.textbuf[start-1] == '\n' )
1662 break;
1663 else
1664 start --;
1665 }
1666 }
1667 else
1668 start = 0;
1669
1670 vg_ui.textbox.cursor_user = start;
1671 }
1672
1673 void _ui_textbox_home(void)
1674 {
1675 _ui_textbox_home_select();
1676 vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user;
1677 }
1678
1679 void _ui_textbox_end_select(void)
1680 {
1681 i32 end = vg_ui.textbox.cursor_user;
1682
1683 if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){
1684 while( vg_ui.textbuf[end] ){
1685 if( vg_ui.textbuf[end] == '\n' )
1686 break;
1687 else
1688 end ++;
1689 }
1690 }
1691 else
1692 end = VG_MIN( vg_ui.textbox.len-1, strlen(vg_ui.textbuf) );
1693
1694 vg_ui.textbox.cursor_user = end;
1695 }
1696
1697 void _ui_textbox_end(void)
1698 {
1699 _ui_textbox_end_select();
1700 vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user;
1701 }
1702
1703 void _ui_textbox_select_all(void)
1704 {
1705 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 10000, 0);
1706 _ui_textbox_move_cursor( &vg_ui.textbox.cursor_pos, NULL, -10000, 0);
1707 }
1708
1709 void _ui_textbox_cut(void)
1710 {
1711 _ui_textbox_to_clipboard();
1712 vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0);
1713 vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user;
1714 _ui_textbox_change_callback();
1715 }
1716
1717 void _ui_textbox_enter(void)
1718 {
1719 if( vg_ui.focused_control_type == k_ui_control_textbox ){
1720 vg_ui.ignore_input_frames = 2;
1721
1722 if( vg_ui.textbox.callbacks.enter )
1723 vg_ui.textbox.callbacks.enter( vg_ui.textbuf, vg_ui.textbox.len );
1724
1725 if( vg_ui.focused_control_type != k_ui_control_textbox ) return;
1726
1727 if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){
1728 _ui_textbox_put_char( '\n' );
1729 _ui_textbox_change_callback();
1730 }
1731 else{
1732 if( !(vg_ui.textbox.flags & UI_TEXTBOX_AUTOFOCUS ) )
1733 ui_defocus_all();
1734 }
1735 }
1736 }
1737
1738 /*
1739 * based on a visual character coordinate relative to the anchor of the textbox,
1740 * this works out the linear place in the buffer that coordinate maps to
1741 *
1742 * input coordinates go in co[0], co[1], and the result index is in co[2]
1743 */
1744 static void _ui_textbox_calc_index_from_grid( int co[3], int wrap_length ){
1745 int i[3] = {0,0,0};
1746
1747 char c;
1748 while( (c = vg_ui.textbuf[i[2]]) ){
1749 if( i[1]==co[1] && i[0]>=co[0] ) break;
1750
1751 if( i[0] >= wrap_length ){
1752 i[1] ++;
1753 i[0] = 0;
1754 }
1755
1756 if( c >= 32 && c <= 126 ){
1757 i[0] ++;
1758 i[2] ++;
1759 }
1760 else if( c == '\n' ){
1761 i[1] ++;
1762
1763 if( i[1] > co[1] ) break;
1764
1765 i[2] ++;
1766 i[0] = 0;
1767 }
1768 else i[2] ++;
1769 }
1770
1771 co[0] = i[0];
1772 co[1] = i[1];
1773 co[2] = i[2];
1774 }
1775
1776 /*
1777 * based on the index specied in co[2], work out the visual character
1778 * coordinates and store them in co[0], co[1]
1779 */
1780 static void _ui_textbox_index_calc_coords( int co[3], int wrap_length ){
1781 co[0] = 0;
1782 co[1] = 0;
1783
1784 char c;
1785 int i=0;
1786
1787 while( (c = vg_ui.textbuf[i ++]) ){
1788 if( i > co[2] ) break;
1789 if( co[0] >= wrap_length ){
1790 co[1] ++;
1791 co[0] = 0;
1792 }
1793 if( c >= 32 && c <= 126 ) co[0] ++;
1794 else if( c == '\n' ){
1795 co[1] ++;
1796 co[0] = 0;
1797 }
1798 }
1799 }
1800
1801 /*
1802 * calculate the number of characters remaining until either:
1803 * - the wrap_length limit is hit
1804 * - end of the line/string
1805 *
1806 * index must be fully populated with visual X/Y, and linear index
1807 */
1808 static int _ui_textbox_run_remaining( int index[3], int wrap_length ){
1809 int i=0, printed_chars=0;
1810 char c;
1811 while( (c = vg_ui.textbuf[index[2] + (i ++)]) ){
1812 if( index[0]+i >= wrap_length ) break;
1813 if( c >= 32 && c <= 126 ) printed_chars ++;
1814 else if( c == '\n' ) break;
1815 }
1816
1817 return printed_chars+1;
1818 }
1819
1820 int ui_textbox( ui_rect inout_panel, const char *label,
1821 char *buf, u32 len, u32 lines, u32 flags,
1822 struct ui_textbox_callbacks *callbacks )
1823 {
1824 if( lines > 1 ) flags |= UI_TEXTBOX_MULTILINE;
1825
1826 ui_rect rect;
1827 ui_standard_widget( inout_panel, rect, lines );
1828
1829 if( label )
1830 ui_label( rect, label, 1, 0, rect );
1831
1832 int clickup= ui_click_up(UI_MOUSE_LEFT),
1833 clickdown = ui_click_down(UI_MOUSE_LEFT),
1834 click = ui_clicking(UI_MOUSE_LEFT) | clickup,
1835 target = ui_inside_rect( rect, vg_ui.mouse_click ) && click,
1836 hover = ui_inside_rect( rect, vg_ui.mouse );
1837
1838 /* allow instant transitions from textbox->textbox */
1839 if( (vg_ui.focused_control_type != k_ui_control_none) &&
1840 (vg_ui.focused_control_type != k_ui_control_textbox) ){
1841 clickup = 0;
1842 clickdown = 0;
1843 click = 0;
1844 target = 0;
1845 hover = 0;
1846 flags &= ~UI_TEXTBOX_AUTOFOCUS;
1847 }
1848
1849 u32 col_base = ui_colour( k_ui_bg ),
1850 col_highlight = ui_colour( k_ui_fg ),
1851 col_cursor = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000;
1852
1853 ui_px border = -1;
1854
1855 ui_rect text_rect;
1856 rect_copy( rect, text_rect );
1857
1858 if( flags & UI_TEXTBOX_MULTILINE ) text_rect[3] = rect[3]-16;
1859 else text_rect[3] = vg_ui.font->line_height;
1860
1861 text_rect[2] -= 16;
1862 ui_rect_center( rect, text_rect );
1863
1864 ui_px wrap_length = 1024;
1865
1866 if( flags & UI_TEXTBOX_WRAP )
1867 wrap_length = text_rect[2] / vg_ui.font->spacing;
1868
1869 if( hover ){
1870 vg_ui.cursor = k_ui_cursor_ibeam;
1871 }
1872
1873 if( vg_ui.focused_control_id == buf ){
1874 ui_fill( rect, col_base );
1875 ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 );
1876
1877 if( !(flags & UI_TEXTBOX_AUTOFOCUS) && ((clickup||clickdown) && !target)){
1878 ui_defocus_all();
1879 }
1880 else{
1881 vg_ui.focused_control_hit = 1;
1882 if( click && target ){
1883 int p0[3] ={
1884 (vg_ui.mouse_click[0] - text_rect[0]) / vg_ui.font->spacing,
1885 (vg_ui.mouse_click[1] - text_rect[1]) / vg_ui.font->line_height,
1886 -1
1887 },
1888 p1[3] = {
1889 (vg_ui.mouse[0] - text_rect[0]) / vg_ui.font->spacing,
1890 (vg_ui.mouse[1] - text_rect[1]) / vg_ui.font->line_height,
1891 -1
1892 };
1893
1894 if( flags & UI_TEXTBOX_MULTILINE ){
1895 _ui_textbox_calc_index_from_grid( p0, wrap_length );
1896 _ui_textbox_calc_index_from_grid( p1, wrap_length );
1897
1898 vg_ui.textbox.cursor_pos = p0[2];
1899 vg_ui.textbox.cursor_user = p1[2];
1900 }
1901 else{
1902 int max = strlen( buf );
1903 vg_ui.textbox.cursor_pos = VG_MAX( 0, VG_MIN( max, p0[0] )),
1904 vg_ui.textbox.cursor_user = VG_MAX( 0, VG_MIN( max, p1[0] ));
1905 }
1906 }
1907
1908 ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ], 0 );
1909
1910 ui_rect cursor;
1911
1912 int c0 = vg_ui.textbox.cursor_pos,
1913 c1 = vg_ui.textbox.cursor_user,
1914 start = VG_MIN( c0, c1 ),
1915 end = VG_MAX( c0, c1 ),
1916 chars = end-start;
1917
1918 if( flags & (UI_TEXTBOX_WRAP|UI_TEXTBOX_MULTILINE) ){
1919 int pos[3], remaining = chars;
1920
1921 pos[2] = start;
1922 _ui_textbox_index_calc_coords( pos, wrap_length );
1923
1924 if( start==end ){
1925 cursor[0] = text_rect[0] + pos[0]*vg_ui.font->spacing-1;
1926 cursor[1] = text_rect[1] + pos[1]*14;
1927 cursor[2] = 2;
1928 cursor[3] = 13;
1929 ui_fill( cursor, col_cursor );
1930 rect_copy( cursor, vg_ui.click_fader_end );
1931 }
1932 else{
1933 while( remaining ){
1934 int run = _ui_textbox_run_remaining( pos, wrap_length );
1935 run = VG_MIN( run, remaining );
1936
1937 cursor[0] = text_rect[0] + pos[0]*vg_ui.font->spacing-1;
1938 cursor[1] = text_rect[1] + pos[1]*14;
1939 cursor[2] = (float)(run)*(float)vg_ui.font->spacing;
1940 cursor[3] = 13;
1941
1942 ui_fill( cursor, col_cursor );
1943
1944 remaining -= run;
1945 pos[0] = 0;
1946 pos[1] ++;
1947 pos[2] += run;
1948 }
1949 rect_copy( cursor, vg_ui.click_fader_end );
1950 }
1951 }
1952 else{
1953 cursor[0] = text_rect[0] + start*vg_ui.font->spacing-1;
1954 cursor[1] = text_rect[1];
1955 cursor[3] = 13;
1956
1957 if( start==end ){
1958 cursor[2] = 2;
1959 }
1960 else{
1961 cursor[2] = (float)(chars)*(float)vg_ui.font->spacing;
1962 }
1963
1964 if( (vg_ui.click_fade_opacity<=0.0f) &&
1965 ui_clip( rect, cursor, cursor ) ){
1966 ui_fill( cursor, col_cursor );
1967 }
1968
1969 rect_copy( cursor, vg_ui.click_fader_end );
1970 }
1971 }
1972
1973 return 0;
1974 }
1975
1976 if( click || (flags & UI_TEXTBOX_AUTOFOCUS) ){
1977 if( (target && hover) || (flags & UI_TEXTBOX_AUTOFOCUS) ){
1978 ui_defocus_all();
1979
1980 ui_fill( rect, col_highlight );
1981 vg_ui.ignore_input_frames = 2;
1982 rect_copy( rect, vg_ui.click_fader );
1983 rect_copy( rect, vg_ui.click_fader_end );
1984
1985 vg_ui.click_fade_opacity = 1.0f;
1986 vg_ui.textbuf = buf;
1987 vg_ui.focused_control_hit = 1;
1988 vg_ui.focused_control_type = k_ui_control_textbox;
1989 vg_ui.textbox.len = len;
1990 vg_ui.textbox.flags = flags;
1991 vg_ui.textbox.cursor_pos = 0;
1992 vg_ui.textbox.cursor_user = 0;
1993
1994 if( callbacks ){
1995 vg_ui.textbox.callbacks = *callbacks;
1996 }
1997 else{
1998 vg_ui.textbox.callbacks.change = NULL;
1999 vg_ui.textbox.callbacks.down = NULL;
2000 vg_ui.textbox.callbacks.up = NULL;
2001 vg_ui.textbox.callbacks.enter = NULL;
2002 }
2003
2004 SDL_StartTextInput();
2005 }
2006 }
2007
2008 ui_fill( rect, col_base );
2009
2010 if( hover ){
2011 ui_outline( rect, -1, col_highlight, 0 );
2012 }
2013
2014 ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 );
2015 return 0;
2016 }
2017
2018 /*
2019 * Tabs
2020 * -----------------------------------------------------------------------------
2021 */
2022
2023 void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel,
2024 const char **titles, u32 count, i32 *page )
2025 {
2026 ui_rect bar;
2027 ui_standard_widget( inout_panel, bar, 1 );
2028
2029 i32 cur_page = *page;
2030
2031 f32 width = (f32)inout_panel[2] / (f32)count;
2032
2033 ui_px h = (inout_panel[1] + inout_panel[3]) - (bar[1]+bar[3]);
2034 inout_panel[1] = bar[1]+bar[3];
2035 inout_panel[3] = h;
2036
2037 ui_fill( inout_panel, ui_colour( k_ui_bg+2 ) );
2038 ui_outline( inout_panel, 1, ui_colour( k_ui_bg+5 ), 0 );
2039
2040 rect_copy( inout_panel, out_content_panel );
2041 ui_rect_pad( out_content_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } );
2042
2043 /* place buttons */
2044 for( i32 i=0; i<count; i++ ){
2045 ui_rect button = {
2046 bar[0] + ((f32)i*width),
2047 bar[1],
2048 width,
2049 bar[3]-1
2050 };
2051
2052 enum ui_scheme_colour colour = k_ui_bg+4;
2053 if( i == cur_page ){
2054 colour = k_ui_bg+2;
2055 ui_outline( button, 1, ui_colour( k_ui_bg+5 ),
2056 UI_TOP|UI_LEFT|UI_RIGHT );
2057 button[3] ++;
2058 }
2059
2060 if( ui_colourbutton_text( button, titles[i], 1, colour ) == 1 )
2061 *page = i;
2062 }
2063 }
2064
2065 /*
2066 * Modal UI
2067 * -----------------------------------------------------------------------------
2068 */
2069 void ui_start_modal( const char *message, u32 options )
2070 {
2071 vg_ui.focused_control_type = k_ui_control_modal;
2072 vg_ui.modal.message = message;
2073 vg_ui.modal.callbacks.close = NULL;
2074 vg_ui.modal.options = options;
2075
2076 u32 type = options & UI_MODAL_TYPE_BITS;
2077 if( type == UI_MODAL_OK ) vg_info( message );
2078 else if( type == UI_MODAL_WARN ) vg_warn( message );
2079 else if( type == UI_MODAL_GOOD ) vg_success( message );
2080 else if( type == UI_MODAL_BAD ) vg_error( message );
2081 }
2082
2083 /*
2084 * Input handling
2085 * -----------------------------------------------------------------------------
2086 */
2087
2088 /*
2089 * Handles binds
2090 */
2091 void ui_proc_key( SDL_Keysym ev )
2092 {
2093 if( vg_ui.focused_control_type != k_ui_control_textbox ){
2094 return;
2095 }
2096
2097 struct textbox_mapping{
2098 u16 mod;
2099 SDL_Keycode key;
2100
2101 void (*handler)(void);
2102 }
2103 mappings[] =
2104 {
2105 { 0, SDLK_LEFT, _ui_textbox_left },
2106 { KMOD_SHIFT, SDLK_LEFT, _ui_textbox_left_select },
2107 { 0, SDLK_RIGHT, _ui_textbox_right },
2108 { KMOD_SHIFT, SDLK_RIGHT, _ui_textbox_right_select },
2109 { 0, SDLK_DOWN, _ui_textbox_down },
2110 { 0, SDLK_UP, _ui_textbox_up },
2111 { 0, SDLK_BACKSPACE, _ui_textbox_backspace },
2112 { KMOD_SHIFT, SDLK_BACKSPACE, _ui_textbox_backspace },
2113 { KMOD_CTRL, SDLK_BACKSPACE, _ui_textbox_backspace },
2114 { 0, SDLK_DELETE, _ui_textbox_delete },
2115 { 0, SDLK_HOME, _ui_textbox_home },
2116 { KMOD_SHIFT, SDLK_HOME, _ui_textbox_home_select },
2117 { 0, SDLK_END, _ui_textbox_end },
2118 { KMOD_SHIFT, SDLK_END, _ui_textbox_end_select },
2119 { KMOD_CTRL, SDLK_a, _ui_textbox_select_all },
2120 { KMOD_CTRL, SDLK_c, _ui_textbox_to_clipboard },
2121 { KMOD_CTRL, SDLK_x, _ui_textbox_cut },
2122 { KMOD_CTRL, SDLK_v, _ui_textbox_clipboard_paste },
2123 { 0, SDLK_RETURN, _ui_textbox_enter },
2124 { 0, SDLK_ESCAPE, ui_defocus_all },
2125 };
2126
2127 SDL_Keymod mod = 0;
2128
2129 if( ev.mod & KMOD_SHIFT )
2130 mod |= KMOD_SHIFT;
2131
2132 if( ev.mod & KMOD_CTRL )
2133 mod |= KMOD_CTRL;
2134
2135 if( ev.mod & KMOD_ALT )
2136 mod |= KMOD_ALT;
2137
2138 for( int i=0; i<vg_list_size( mappings ); i++ ){
2139 struct textbox_mapping *mapping = &mappings[i];
2140
2141 if( mapping->key == ev.sym ){
2142 if( mapping->mod == 0 ){
2143 if( mod == 0 ){
2144 mapping->handler();
2145 return;
2146 }
2147 }
2148 else if( (mod & mapping->mod) == mapping->mod ){
2149 mapping->handler();
2150 return;
2151 }
2152 }
2153 }
2154 }
2155
2156 /*
2157 * Callback for text entry mode
2158 */
2159 void ui_proc_utf8( const char *text )
2160 {
2161 if( vg_ui.focused_control_type == k_ui_control_textbox ){
2162 const char *ptr = text;
2163
2164 while( *ptr ){
2165 if( *ptr != '`' ) _ui_textbox_put_char( *ptr );
2166 ptr ++;
2167 }
2168
2169 _ui_textbox_change_callback();
2170 }
2171 }
2172
2173 /*
2174 * Development utils
2175 * -----------------------------------------------------------------------------
2176 */
2177
2178 void ui_dev_colourview(void)
2179 {
2180 ui_rect window = {vg.window_x-256,0,256,vg.window_y}, swatch;
2181
2182 const char *names[vg_list_size(vg_ui.scheme)] = {
2183 [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3",
2184 "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7",
2185
2186 [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3",
2187 "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7",
2188
2189 [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green",
2190 "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray",
2191 "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8",
2192 "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" };
2193
2194 ui_rect col[2];
2195 ui_split_ratio( window, k_ui_axis_v, 0.5f, 0, col[0], col[1] );
2196
2197 for( int i=0; i<vg_list_size(vg_ui.scheme); i++ ){
2198 int which = (i/8)%2;
2199
2200 ui_split( col[which], k_ui_axis_h, 24, 0, swatch, col[which] );
2201 ui_fill( swatch, ui_colour(i) );
2202
2203 if( names[i] )
2204 ui_text(swatch, names[i], 1, k_ui_align_middle_left, ui_colourcont(i));
2205 }
2206 }