textbox
[vg.git] / vg_imgui.h
1 /* Copyright (C) 2021-2023 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 */
10
11 #ifndef VG_IMGUI_H
12 #define VG_IMGUI_H
13
14 #define VG_GAME
15 #include "vg/vg.h"
16 #include "vg/vg_tex.h"
17 #include "vg/vg_shader.h"
18
19 typedef i16 ui_px;
20 typedef ui_px ui_rect[4];
21 typedef struct ui_vert ui_vert;
22
23 enum ui_axis {
24 k_ui_axis_h = 0x0u,
25 k_ui_axis_v = 0x1u,
26 };
27
28 /* Relative to cursor p0 */
29 enum ui_align
30 { /* DC BA */
31 k_ui_align_left = 0x0000| 0x00,
32 k_ui_align_right = 0x0000| 0x01,
33 k_ui_align_center = 0x0000| 0x02,
34
35 k_ui_align_middle = 0x0100,
36 k_ui_align_middle_left = 0x0100| 0x00,
37 k_ui_align_middle_right = 0x0100| 0x01,
38 k_ui_align_middle_center = 0x0100| 0x02,
39
40 k_ui_align_bottom = 0x0200,
41 k_ui_align_bottom_left = 0x0200| 0x00,
42 k_ui_align_bottom_right = 0x0200| 0x01,
43 k_ui_align_bottom_center = 0x0200| 0x02,
44 };
45
46 #pragma pack(push,1)
47 struct ui_vert
48 {
49 ui_px co[2];
50 u8 uv[2];
51 u32 colour;
52 };
53 #pragma pack(pop)
54
55 enum ui_scheme_colour{
56 k_ui_bg = 0,
57 k_ui_fg = 8,
58 k_ui_hue = 16,
59 k_ui_red = 16,
60 k_ui_orange,
61 k_ui_yellow,
62 k_ui_green,
63 k_ui_aqua,
64 k_ui_blue,
65 k_ui_purple,
66 k_ui_gray,
67 k_ui_brighter = 8
68 };
69
70 typedef u32 ui_scheme[8*4];
71
72 #define UI_RGB( STDHEX ) 0xff000000 |\
73 ((STDHEX&0x000000ff)<<16) |\
74 ((STDHEX&0x0000ff00) ) |\
75 ((STDHEX&0x00ff0000)>>16)
76
77 struct{
78 struct ui_vert *vertex_buffer;
79 u16 *indice_buffer;
80 u32 max_verts, max_indices,
81 cur_vert, cur_indice,
82 vert_start, indice_start;
83
84 union {
85 void *focused_control_id; /* uses the memory location of various locking
86 controls as an id */
87 char *textbuf;
88 };
89
90 u32 focused_control_hit;
91 enum ui_control_type{
92 k_ui_control_none,
93 k_ui_control_textbox,
94 }
95 focused_control_type;
96 int cursor_user, cursor_pos, string_length;
97 u32 textbuf_len;
98
99 GLuint tex_glyphs, vao, vbo, ebo;
100
101 ui_px mouse[2], mouse_click[2];
102 u32 mouse_state[2];
103 u32 ignore_input_frames;
104
105 ui_rect click_fader, click_fader_end;
106 float click_fade_opacity;
107
108 ui_scheme scheme;
109
110 enum ui_cursor{
111 k_ui_cursor_default,
112 k_ui_cursor_ibeam,
113 k_ui_cursor_hand,
114 k_ui_cursor_max
115 }
116 cursor;
117
118 SDL_Cursor *cursor_map[ k_ui_cursor_max ];
119 }
120 static vg_ui = {
121 .scheme = {
122 [ k_ui_bg+0 ] = UI_RGB( 0x1d2021 ),
123 [ k_ui_bg+1 ] = UI_RGB( 0x282828 ),
124 [ k_ui_bg+2 ] = UI_RGB( 0x3c3836 ),
125 [ k_ui_bg+3 ] = UI_RGB( 0x504945 ),
126 [ k_ui_bg+4 ] = UI_RGB( 0x665c54 ),
127 [ k_ui_bg+5 ] = UI_RGB( 0x7c6f64 ),
128 [ k_ui_bg+6 ] = UI_RGB( 0x928374 ),
129 [ k_ui_bg+7 ] = UI_RGB( 0xa89984 ),
130
131 [ k_ui_fg+0 ] = UI_RGB( 0xebdbb2 ),
132 [ k_ui_fg+1 ] = UI_RGB( 0xfbf1c7 ),
133 [ k_ui_fg+2 ] = UI_RGB( 0xd5c4a1 ),
134 [ k_ui_fg+3 ] = UI_RGB( 0xbdae93 ),
135 [ k_ui_fg+4 ] = UI_RGB( 0xa89984 ),
136 [ k_ui_fg+5 ] = UI_RGB( 0x000000 ),
137 [ k_ui_fg+6 ] = UI_RGB( 0x000000 ),
138 [ k_ui_fg+7 ] = UI_RGB( 0x000000 ),
139
140 [ k_ui_red ] = UI_RGB( 0xcc241d ),
141 [ k_ui_orange ] = UI_RGB( 0xd65d0e ),
142 [ k_ui_yellow ] = UI_RGB( 0xd79921 ),
143 [ k_ui_green ] = UI_RGB( 0x98971a ),
144 [ k_ui_aqua ] = UI_RGB( 0x689d6a ),
145 [ k_ui_blue ] = UI_RGB( 0x458588 ),
146 [ k_ui_purple ] = UI_RGB( 0xb16286 ),
147 [ k_ui_gray ] = UI_RGB( 0x928374 ),
148 [ k_ui_red + k_ui_brighter ] = UI_RGB( 0xfb4934 ),
149 [ k_ui_orange + k_ui_brighter ] = UI_RGB( 0xfe8019 ),
150 [ k_ui_yellow + k_ui_brighter ] = UI_RGB( 0xfabd2f ),
151 [ k_ui_green + k_ui_brighter ] = UI_RGB( 0xb8bb26 ),
152 [ k_ui_aqua + k_ui_brighter ] = UI_RGB( 0x8ec07c ),
153 [ k_ui_blue + k_ui_brighter ] = UI_RGB( 0x83a598 ),
154 [ k_ui_purple + k_ui_brighter ] = UI_RGB( 0xd3869b ),
155 [ k_ui_gray + k_ui_brighter ] = UI_RGB( 0xa89984 ),
156 }
157 };
158
159 static struct vg_shader _shader_ui =
160 {
161 .name = "[vg] ui",
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.0078125;"
180 "aColour = a_colour;"
181
182 "aWsp = a_co;"
183 "}",
184 },
185 .fs =
186 {
187 .orig_file = NULL,
188 .static_src =
189 "uniform sampler2D uTexGlyphs;"
190 "out vec4 FragColor;"
191 ""
192 "in vec2 aTexCoords;"
193 "in vec4 aColour;"
194 ""
195 "in vec2 aWsp;"
196
197 "vec2 rand_hash22( vec2 p )"
198 "{"
199 "vec3 p3 = fract(vec3(p.xyx) * 213.8976123);"
200 "p3 += dot(p3, p3.yzx+19.19);"
201 "return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));"
202 "}"
203 ""
204 "void main()"
205 "{"
206 "vec4 diffuse = aColour;"
207
208 "if( diffuse.a == 0.0 )"
209 "{"
210 "diffuse.a = texture( uTexGlyphs, aTexCoords ).r;"
211 "}"
212
213 "FragColor = diffuse;"
214 "}"
215 }
216 };
217
218 static struct vg_shader _shader_ui_image =
219 {
220 .name = "[vg] ui_image",
221 .link = NULL,
222 .vs =
223 {
224 .orig_file = NULL,
225 .static_src =
226 "layout (location=0) in vec2 a_co;"
227 "layout (location=1) in vec2 a_uv;"
228 "layout (location=2) in vec4 a_colour;"
229 "uniform mat3 uPv;"
230
231 "out vec2 aTexCoords;"
232 "out vec4 aColour;"
233 "out vec2 aWsp;"
234
235 "void main()"
236 "{"
237 "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );"
238 "aTexCoords = a_uv * 0.0078125;"
239 "aColour = a_colour;"
240
241 "aWsp = a_co;"
242 "}",
243 },
244 .fs =
245 {
246 .orig_file = NULL,
247 .static_src =
248 "uniform sampler2D uTexImage;"
249 "out vec4 FragColor;"
250
251 "in vec2 aTexCoords;"
252 "in vec4 aColour;"
253 "in vec2 aWsp;"
254
255 "void main()"
256 "{"
257 "vec4 colour = texture( uTexImage, aTexCoords );"
258
259 /* wtf is this?? */
260 #if 0
261 "float value = dot(vec4(1.0),colour)*0.25;"
262
263 "vec3 col = vec3(pow(cos(value*3.14159265*2.0)*0.5+0.5,0.5))"
264 "* vec3(step(value,0.5),0.3,step(1.0-value,0.5));"
265 "FragColor = vec4( col*4.0, 1.0 );"
266 #endif
267
268 "FragColor = colour;"
269 "}"
270 }
271 };
272 #define UI_GLYPH_SPACING_X 8
273
274 VG_STATIC void _vg_ui_init(void)
275 {
276 if( !vg_shader_compile( &_shader_ui ) ||
277 !vg_shader_compile( &_shader_ui_image ) )
278 vg_fatal_error( "Failed to compile ui shader" );
279
280 /*
281 * Vertex buffer
282 * ----------------------------------------
283 */
284
285 vg_ui.max_indices = 20000;
286 vg_ui.max_verts = 30000;
287
288 /* Generate the buffer we are gonna be drawing to */
289 glGenVertexArrays( 1, &vg_ui.vao );
290 glGenBuffers( 1, &vg_ui.vbo );
291 glGenBuffers( 1, &vg_ui.ebo );
292
293 glBindVertexArray( vg_ui.vao );
294 glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo );
295
296 glBufferData( GL_ARRAY_BUFFER,
297 vg_ui.max_verts * sizeof( struct ui_vert ),
298 NULL, GL_DYNAMIC_DRAW );
299 glBindVertexArray( vg_ui.vao );
300
301 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo );
302 glBufferData( GL_ELEMENT_ARRAY_BUFFER,
303 vg_ui.max_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW );
304
305 VG_CHECK_GL_ERR();
306
307 /* Set pointers */
308 u32 const stride = sizeof( struct ui_vert );
309
310 /* XY */
311 glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride,
312 (void *)offsetof( struct ui_vert, co ) );
313 glEnableVertexAttribArray( 0 );
314
315 /* UV */
316 glVertexAttribPointer( 1, 2, GL_UNSIGNED_BYTE, GL_FALSE, stride,
317 (void *)offsetof( struct ui_vert, uv ) );
318 glEnableVertexAttribArray( 1 );
319
320 /* COLOUR */
321 glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride,
322 (void *)offsetof( struct ui_vert, colour ) );
323 glEnableVertexAttribArray( 2 );
324
325 VG_CHECK_GL_ERR();
326
327 /* Alloc RAM default context */
328 u32 vert_size = vg_ui.max_verts*sizeof(struct ui_vert),
329 inds_size = vg_align8( vg_ui.max_indices*sizeof(u16) );
330
331 vg_ui.vertex_buffer = vg_linear_alloc( vg_mem.rtmemory, vert_size );
332 vg_ui.indice_buffer = vg_linear_alloc( vg_mem.rtmemory, inds_size );
333
334 /* font
335 * -----------------------------------------------------
336 */
337
338 /* Load default font */
339 u32 compressed[] = {
340 #include "vg/vg_pxfont_thin.h"
341 };
342
343 u32 pixels = 0, total = 256*256, data = 0;
344 u8 image[256*256];
345
346 while( pixels < total ){
347 for( int b = 31; b >= 0; b-- ){
348 image[ pixels ++ ] = (compressed[data] & (0x1u << b))? 0xffu: 0x00u;
349
350 if( pixels >= total ){
351 total = 0;
352 break;
353 }
354 }
355 data++;
356 }
357
358 glGenTextures( 1, &vg_ui.tex_glyphs );
359 glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs );
360 glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, 256, 256, 0,
361 GL_RED, GL_UNSIGNED_BYTE, image );
362
363 VG_CHECK_GL_ERR();
364 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
365 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
366 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
367 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
368
369 /*
370 * Cursors
371 * ---------------------------------------------------------------
372 */
373
374 vg_ui.cursor_map[ k_ui_cursor_default ] =
375 SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_ARROW );
376 vg_ui.cursor_map[ k_ui_cursor_hand ] =
377 SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_HAND );
378 vg_ui.cursor_map[ k_ui_cursor_ibeam ] =
379 SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_IBEAM );
380 }
381
382 enum ui_shader {
383 k_ui_shader_colour,
384 k_ui_shader_image
385 };
386
387 static void rect_copy( ui_rect a, ui_rect b )
388 {
389 for( int i=0; i<4; i++ )
390 b[i] = a[i];
391 }
392
393 VG_STATIC void ui_flush( enum ui_shader shader )
394 {
395 u32 vertex_offset = vg_ui.vert_start*sizeof(ui_vert),
396 vertex_count = vg_ui.cur_vert-vg_ui.vert_start,
397 vertex_size = vertex_count*sizeof(ui_vert),
398
399 indice_offset = vg_ui.indice_start*sizeof(u16),
400 indice_count = vg_ui.cur_indice-vg_ui.indice_start,
401 indice_size = indice_count * sizeof(u16);
402
403 if( !vertex_size || !indice_size )
404 return;
405
406 glBindVertexArray( vg_ui.vao );
407 glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo );
408 glBufferSubData( GL_ARRAY_BUFFER, vertex_offset, vertex_size,
409 vg_ui.vertex_buffer+vg_ui.vert_start );
410
411 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo );
412 glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, indice_offset, indice_size,
413 vg_ui.indice_buffer+vg_ui.indice_start );
414
415 glEnable( GL_BLEND );
416 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
417 glBlendEquation( GL_FUNC_ADD );
418
419 m3x3f view = M3X3_IDENTITY;
420 m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
421 m3x3_scale( view, (v3f){ 1.0f/((float)vg.window_x*0.5f),
422 -1.0f/((float)vg.window_y*0.5f), 1.0f } );
423
424 if( shader == k_ui_shader_colour ){
425 glUseProgram( _shader_ui.id );
426
427 glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1,
428 GL_FALSE, (float *)view );
429
430 glActiveTexture( GL_TEXTURE0 );
431 glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs );
432 glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 );
433 }
434 else if( shader == k_ui_shader_image ){
435 glUseProgram( _shader_ui_image.id );
436 glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1,
437 GL_FALSE, (float *)view );
438 glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 );
439 }
440 else
441 vg_fatal_error( "Invalid UI shader (%d)\n", shader );
442
443 glDrawElements( GL_TRIANGLES, indice_count, GL_UNSIGNED_SHORT,
444 (void *)(vg_ui.indice_start*sizeof(u16)) );
445
446 glDisable( GL_BLEND );
447
448 vg_ui.indice_start = vg_ui.cur_indice;
449 vg_ui.vert_start = vg_ui.cur_vert;
450 }
451
452 static void ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] )
453 {
454 /* this if far from ideal but stops us from crashing */
455 if( (vg_ui.cur_vert + 4 > vg_ui.max_verts) ||
456 (vg_ui.cur_indice + 6 > vg_ui.max_indices))
457 return;
458
459 struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ];
460 u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ];
461
462 for( int i=0; i<4; i++ ){
463 vertices[i].colour = colour;
464 }
465
466 vertices[0].co[0] = rect[0];
467 vertices[0].co[1] = rect[1];
468 vertices[0].uv[0] = uv[0];
469 vertices[0].uv[1] = uv[1];
470 vertices[1].co[0] = rect[0]+rect[2];
471 vertices[1].co[1] = rect[1];
472 vertices[1].uv[0] = uv[2];
473 vertices[1].uv[1] = uv[1];
474 vertices[2].co[0] = rect[0]+rect[2];
475 vertices[2].co[1] = rect[1]+rect[3];
476 vertices[2].uv[0] = uv[2];
477 vertices[2].uv[1] = uv[3];
478 vertices[3].co[0] = rect[0];
479 vertices[3].co[1] = rect[1]+rect[3];
480 vertices[3].uv[0] = uv[0];
481 vertices[3].uv[1] = uv[3];
482 u16 ind_start = vg_ui.cur_vert;
483
484 u16 start = vg_ui.cur_vert;
485 u32 mesh[] = { 0,2,1, 0,3,2 };
486
487 for( u32 i=0; i<vg_list_size(mesh); i++ ){
488 indices[i] = start+mesh[i];
489 }
490
491 vg_ui.cur_indice += 6;
492 vg_ui.cur_vert += 4;
493 }
494
495 static void ui_fill( ui_rect rect, u32 colour )
496 {
497 ui_fill_rect( rect, colour, (ui_px[4]){ 4,4,4,4 } );
498 }
499
500 static void ui_outline( ui_rect rect, ui_px thickness, u32 colour )
501 {
502 /* this if far from ideal but stops us from crashing */
503 if( (vg_ui.cur_vert + 8 > vg_ui.max_verts) ||
504 (vg_ui.cur_indice + 24 > vg_ui.max_indices))
505 return;
506
507 struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ];
508 u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ];
509
510 for( int i=0; i<8; i++ ){
511 vertices[i].uv[0] = 4;
512 vertices[i].uv[1] = 4;
513 vertices[i].colour = colour;
514 }
515
516 vertices[0].co[0] = rect[0];
517 vertices[0].co[1] = rect[1];
518 vertices[1].co[0] = rect[0]+rect[2];
519 vertices[1].co[1] = rect[1];
520 vertices[2].co[0] = rect[0]+rect[2];
521 vertices[2].co[1] = rect[1]+rect[3];
522 vertices[3].co[0] = rect[0];
523 vertices[3].co[1] = rect[1]+rect[3];
524 vertices[4].co[0] = vertices[0].co[0]-thickness;
525 vertices[4].co[1] = vertices[0].co[1]-thickness;
526 vertices[5].co[0] = vertices[1].co[0]+thickness;
527 vertices[5].co[1] = vertices[1].co[1]-thickness;
528 vertices[6].co[0] = vertices[2].co[0]+thickness;
529 vertices[6].co[1] = vertices[2].co[1]+thickness;
530 vertices[7].co[0] = vertices[3].co[0]-thickness;
531 vertices[7].co[1] = vertices[3].co[1]+thickness;
532
533 u16 start = vg_ui.cur_vert;
534 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 };
535
536 for( u32 i=0; i<vg_list_size(mesh); i++ ){
537 indices[i] = start+mesh[i];
538 }
539
540 vg_ui.cur_indice += 24;
541 vg_ui.cur_vert += 8;
542 }
543
544 static void ui_split_px( ui_rect rect,
545 enum ui_axis other, ui_px width, ui_px pad,
546 ui_rect l, ui_rect r )
547 {
548 enum ui_axis dir = other ^ 0x1;
549
550 ui_rect temp;
551 rect_copy( rect, temp );
552
553 l[ dir ] = temp[ dir ] + pad;
554 r[ dir ] = temp[ dir ] + width + (pad/2);
555 l[ other ] = temp[ other ] + pad;
556 r[ other ] = temp[ other ] + pad;
557 l[ 2+dir ] = width - ((3*pad)/2);
558 r[ 2+dir ] = temp[ 2+dir ] - width - ((3*pad)/2);
559 l[ 2+other ] = temp[ 2+other ] - pad*2;
560 r[ 2+other ] = temp[ 2+other ] - pad*2;
561 }
562
563 static void ui_rect_center( ui_rect parent, ui_rect rect )
564 {
565 rect[0] = parent[0] + (parent[2]-rect[2])/2;
566 rect[1] = parent[1] + (parent[3]-rect[3])/2;
567 }
568
569 static void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d )
570 {
571 i32 rp = (i32)rect[2] * (i32)size[1],
572 rc = (i32)size[0] * (i32)rect[3];
573
574 enum ui_axis dir, other;
575 if( rc > rp ) dir = k_ui_axis_h;
576 else dir = k_ui_axis_v;
577 other = dir ^ 0x1;
578
579 d[2+dir] = rect[2+dir];
580 d[2+other] = (rect[2+dir] * size[other]) / size[dir];
581
582 ui_rect_center( rect, d );
583 }
584
585 static void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio,
586 ui_px pad, ui_rect l, ui_rect r )
587 {
588 ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio;
589 ui_split_px( rect, dir, width, pad, l, r );
590 }
591
592 static void ui_rect_pad( ui_rect rect, ui_px pad )
593 {
594 rect[0] += pad;
595 rect[1] += pad;
596 rect[2] -= pad*2;
597 rect[3] -= pad*2;
598 }
599
600 static ui_px ui_text_line_width( const char *str )
601 {
602 int length = 0;
603 const char *_c = str;
604 char c;
605
606 while( (c = *(_c ++)) ){
607 if( c >= 32 && c <= 126 )
608 length ++;
609 else if( c == '\n' )
610 break;
611 }
612
613 return length * 8;
614 }
615
616 static ui_px ui_text_string_height( const char *str )
617 {
618 int height = 1;
619 const char *_c = str;
620 char c;
621
622 while( (c = *(_c ++)) ){
623 if( c == '\n' ) height ++;
624 }
625
626 return height * 14;
627 }
628
629 static ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale,
630 enum ui_align align )
631 {
632 if( align == k_ui_align_left ){
633 return rect[0];
634 }
635 else{
636 ui_px width = ui_text_line_width( str ) * scale;
637
638 if( align == k_ui_align_right )
639 return rect[0] + rect[2]-width;
640 else
641 return rect[0] + (rect[2]-width)/2;
642 }
643 }
644
645 static ui_px ui_min( ui_px a, ui_px b ){ return a<b?a:b; }
646 static ui_px ui_max( ui_px a, ui_px b ){ return a>b?a:b; }
647 static ui_px ui_clamp( ui_px a, ui_px min, ui_px max )
648 {
649 return ui_min( max, ui_max( a, min ) );
650 }
651
652 static int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped )
653 {
654 ui_px parent_max[2], child_max[2];
655 parent_max[0] = parent[0]+parent[2];
656 parent_max[1] = parent[1]+parent[3];
657 child_max[0] = child[0]+child[2];
658 child_max[1] = child[1]+child[3];
659
660 clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] );
661 clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] );
662 clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] );
663 clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] );
664
665 if( clipped[0] == clipped[2] ||
666 clipped[1] == clipped[3] )
667 return 0;
668
669 clipped[2] -= clipped[0];
670 clipped[3] -= clipped[1];
671
672 return 1;
673 }
674
675 static int ui_inside_rect( ui_rect rect, ui_px co[2] )
676 {
677 if( co[0] >= rect[0] &&
678 co[1] >= rect[1] &&
679 co[0] <= rect[0]+rect[2] &&
680 co[1] <= rect[1]+rect[3] )
681 {
682 return 1;
683 }
684 else
685 return 0;
686 }
687
688 static int ui_click_down(void)
689 {
690 if( vg_ui.ignore_input_frames ) return 0;
691
692 if( (vg_ui.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT)) &&
693 !(vg_ui.mouse_state[1] & SDL_BUTTON(SDL_BUTTON_LEFT)) )
694 return 1;
695 else
696 return 0;
697 }
698
699 static int ui_clicking(void)
700 {
701 if( vg_ui.ignore_input_frames ) return 0;
702 return vg_ui.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT);
703 }
704
705 static int ui_click_up(void)
706 {
707 if( vg_ui.ignore_input_frames ) return 0;
708 if( (vg_ui.mouse_state[1] & SDL_BUTTON(SDL_BUTTON_LEFT)) &&
709 !(vg_ui.mouse_state[0] & SDL_BUTTON(SDL_BUTTON_LEFT)) )
710 return 1;
711 else
712 return 0;
713 }
714
715 static void ui_prerender(void)
716 {
717 int x, y;
718 vg_ui.mouse_state[1] = vg_ui.mouse_state[0];
719 vg_ui.mouse_state[0] = SDL_GetMouseState( &x, &y );
720 vg_ui.mouse[0] = x;
721 vg_ui.mouse[1] = y;
722
723 vg_ui.cur_vert = 0;
724 vg_ui.cur_indice = 0;
725 vg_ui.vert_start = 0;
726 vg_ui.indice_start = 0;
727 vg_ui.focused_control_hit = 0;
728 vg_ui.cursor = k_ui_cursor_default;
729
730 if( vg_ui.ignore_input_frames ){
731 vg_ui.ignore_input_frames --;
732 return;
733 }
734
735 if( ui_click_down() ){
736 vg_ui.mouse_click[0] = vg_ui.mouse[0];
737 vg_ui.mouse_click[1] = vg_ui.mouse[1];
738 }
739 }
740
741 static u32 ui_colour( enum ui_scheme_colour id )
742 {
743 return vg_ui.scheme[ id ];
744 }
745
746 /* get an appropriately contrasting colour given the base */
747 static u32 ui_colourcont( enum ui_scheme_colour id )
748 {
749 if ( id < k_ui_bg+6 ) return ui_colour( k_ui_fg );
750 else if( id < k_ui_fg ) return ui_colour( k_ui_bg+1 );
751 else if( id < k_ui_hue ) return ui_colour( k_ui_bg+3 );
752 else if( id < k_ui_red+k_ui_brighter ) return ui_colour( k_ui_fg );
753 else return ui_colour( k_ui_fg+1 );
754 }
755
756 static void ui_text( ui_rect rect, const char *str, ui_px scale,
757 enum ui_align align, u32 colour )
758 {
759 ui_rect text_cursor;
760 if( colour == 0 ) colour = ui_colour( k_ui_fg );
761
762 colour &= 0x00ffffff;
763
764 const char *_c = str;
765 u8 c;
766
767 text_cursor[0] = ui_text_aligned_x( str, rect, scale, align );
768 text_cursor[1] = rect[1];
769 text_cursor[2] = 8*scale;
770 text_cursor[3] = 14*scale;
771
772 if( align & (k_ui_align_middle|k_ui_align_bottom) ){
773 ui_px height = ui_text_string_height( str ) * scale;
774
775 if( align & k_ui_align_bottom )
776 text_cursor[1] += rect[3]-height;
777 else
778 text_cursor[1] += (rect[3]-height)/2;
779 }
780
781 while( (c = *(_c ++)) ){
782 if( c == '\n' ){
783 text_cursor[1] += 14*scale;
784 text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align );
785 continue;
786 }
787 else if( c >= 33 ){
788 u8 glyph_base[2];
789 u8 glyph_index = c;
790 glyph_base[0] = glyph_index & 0xf;
791 glyph_base[1] = (glyph_index-glyph_base[0])>>4;
792
793 glyph_base[0] *= 8;
794 glyph_base[1] *= 8;
795
796 ui_rect rect_char;
797
798 if( ui_clip( rect, text_cursor, rect_char ) ){
799 ui_fill_rect( rect_char, colour, (ui_px[4])
800 {
801 glyph_base[0]+2,
802 glyph_base[1]+1,
803 glyph_base[0]+6,
804 glyph_base[1]+8
805 });
806 }
807 }
808 else if( c == '\x1B' ){
809 /* vt codes */
810 _c ++;
811 u16 colour_id = 0;
812 for( int i=0; i<3; i ++ ){
813 if( _c[i] ){
814 if( _c[i] == 'm' ){
815 _c = _c + i + 1;
816
817 switch( colour_id ){
818 case '0': colour = ui_colour( k_ui_fg ); break;
819 case '3'|'0'<<8: colour = ui_colour( k_ui_bg ); break;
820 case '3'|'1'<<8: colour = ui_colour( k_ui_red ); break;
821 case '3'|'2'<<8: colour = ui_colour( k_ui_green ); break;
822 case '3'|'3'<<8: colour = ui_colour( k_ui_yellow ); break;
823 case '3'|'4'<<8: colour = ui_colour( k_ui_blue ); break;
824 case '3'|'5'<<8: colour = ui_colour( k_ui_purple ); break;
825 case '3'|'6'<<8: colour = ui_colour( k_ui_aqua ); break;
826 case '3'|'7'<<8: colour = ui_colour( 0xffffffff ); break;
827 }
828
829 colour &= 0x00ffffff;
830 break;
831 }
832
833 colour_id |= _c[i] << (i*8);
834 }
835 else{
836 _c = _c +i;
837 break;
838 }
839 }
840
841 continue;
842 }
843 else if( c == '\t' ){
844 text_cursor[0] += UI_GLYPH_SPACING_X*scale*4;
845 continue;
846 }
847
848 text_cursor[0] += UI_GLYPH_SPACING_X*scale;
849 }
850 }
851
852 static void ui_image( ui_rect rect, GLuint image )
853 {
854 ui_flush( k_ui_shader_colour );
855
856 glActiveTexture( GL_TEXTURE0 );
857 glBindTexture( GL_TEXTURE_2D, image );
858 ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,0, 255,255 } );
859 ui_flush( k_ui_shader_image );
860 }
861
862 static u32 v4f_u32_colour( v4f colour )
863 {
864 u32 r = colour[0] * 255.0f,
865 g = colour[1] * 255.0f,
866 b = colour[2] * 255.0f,
867 a = colour[3] * 255.0f;
868
869 return r | (g<<8) | (b<<16) | (a<<24);
870 }
871
872 static void ui_defocus_all(void)
873 {
874 if( vg_ui.focused_control_type == k_ui_control_textbox ){
875 vg_info( "SDL_StopTextInput()\n" );
876 SDL_StopTextInput();
877 }
878
879 vg_ui.focused_control_id = NULL;
880 vg_ui.focused_control_hit = 0;
881 vg_ui.focused_control_type = k_ui_control_none;
882 }
883
884 static int ui_button( ui_rect rect, enum ui_scheme_colour colour )
885 {
886 int clickup= ui_click_up(),
887 click = ui_clicking() | clickup,
888 target = ui_inside_rect( rect, vg_ui.mouse_click ) && click,
889 hover = ui_inside_rect( rect, vg_ui.mouse );
890
891 u32 col_base = vg_ui.scheme[ colour ],
892 col_highlight = vg_ui.scheme[ k_ui_fg ],
893 col_hover = vg_ui.scheme[ colour + k_ui_brighter ];
894
895 if( hover ){
896 vg_ui.cursor = k_ui_cursor_hand;
897 }
898
899 if( click ){
900 if( target ){
901 if( hover ){
902 if( clickup ){
903 ui_fill( rect, col_highlight );
904 vg_ui.ignore_input_frames = 2;
905 rect_copy( rect, vg_ui.click_fader );
906
907 rect_copy( rect, vg_ui.click_fader_end );
908 vg_ui.click_fader_end[3] = 0;
909 ui_rect_center( rect, vg_ui.click_fader_end );
910
911 vg_ui.click_fade_opacity = 1.0f;
912 ui_defocus_all();
913 return 1;
914 }
915 else{
916 ui_fill( rect, col_highlight );
917 return 0;
918 }
919 }
920 else{
921 ui_fill( rect, col_base );
922 ui_outline( rect, 1, col_highlight );
923 return 0;
924 }
925 }
926 else{
927 ui_fill( rect, col_base );
928 return 0;
929 }
930 }
931 else{
932 if( hover ){
933 ui_fill( rect, col_hover );
934 return 0;
935 }
936 else{
937 ui_fill( rect, col_base );
938 return 0;
939 }
940 }
941 }
942
943 static int ui_button_text( ui_rect rect, const char *string, ui_px scale,
944 enum ui_scheme_colour colour )
945 {
946 int result = ui_button( rect, colour );
947 ui_rect t = { 0,0, ui_text_line_width( string )*scale, 14*scale };
948 ui_rect_center( rect, t );
949 ui_text( t, string, scale, k_ui_align_left, ui_colourcont(colour) );
950 return result;
951 }
952
953 static void ui_postrender(void)
954 {
955 if( vg_ui.click_fade_opacity > 0.0f ){
956
957 float scale = vg_ui.click_fade_opacity;
958 scale = vg_maxf( 1.0f/255.0f, scale*scale );
959
960 vg_ui.click_fade_opacity -= vg.time_frame_delta * 3.8f;
961 u32 colour = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000;
962
963 v4f begin, end, dest;
964 for( int i=0; i<4; i++ ){
965 begin[i] = vg_ui.click_fader[i];
966 end[i] = vg_ui.click_fader_end[i]+1;
967 }
968
969 v4_lerp( end, begin, scale, dest );
970
971 ui_rect rect;
972 for( int i=0; i<4; i++ ){
973 rect[i] = dest[i];
974 }
975
976 ui_fill( rect, colour );
977 }
978 ui_flush( k_ui_shader_colour );
979
980 if( !vg_ui.focused_control_hit ){
981 ui_defocus_all();
982 }
983
984 SDL_SetCursor( vg_ui.cursor_map[ vg_ui.cursor ] );
985 SDL_ShowCursor(1);
986 }
987
988
989
990 static void ui_dev_colourview(void)
991 {
992 ui_rect window = {vg.window_x-256,0,256,vg.window_y}, swatch;
993
994 const char *names[vg_list_size(vg_ui.scheme)] = {
995 [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3",
996 "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7",
997
998 [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3",
999 "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7",
1000
1001 [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green",
1002 "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray",
1003 "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8",
1004 "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" };
1005
1006 ui_rect col[2];
1007 ui_split_ratio( window, k_ui_axis_v, 0.5f, 0.0f, col[0], col[1] );
1008
1009 for( int i=0; i<vg_list_size(vg_ui.scheme); i++ ){
1010 int which = (i/8)%2;
1011
1012 ui_split_px( col[which], k_ui_axis_h, 24, 0, swatch, col[which] );
1013 ui_fill( swatch, ui_colour(i) );
1014
1015 if( names[i] )
1016 ui_text(swatch, names[i], 1, k_ui_align_middle_left, ui_colourcont(i));
1017 }
1018 }
1019
1020
1021
1022
1023
1024 /*
1025 * text box interface
1026 */
1027 static void _ui_textbox_make_selection( int *start, int *end )
1028 {
1029 *start = VG_MIN( vg_ui.cursor_pos, vg_ui.cursor_user );
1030 *end = VG_MAX( vg_ui.cursor_pos, vg_ui.cursor_user );
1031 }
1032
1033 static void _ui_textbox_move_cursor( int *cursor0, int *cursor1,
1034 int dir, int snap_together )
1035 {
1036 *cursor0 = VG_MAX( 0, vg_ui.cursor_user + dir );
1037 *cursor0 =
1038 VG_MIN(
1039 VG_MIN( vg_ui.textbuf_len-1, strlen( vg_ui.textbuf )),
1040 *cursor0 );
1041
1042 if( snap_together )
1043 *cursor1 = *cursor0;
1044 }
1045
1046 static int _ui_textbox_makeroom( int datastart, int length )
1047 {
1048 int move_to = VG_MIN( datastart+length, vg_ui.textbuf_len-1 );
1049 int move_amount = strlen( vg_ui.textbuf )-datastart;
1050 int move_end =
1051 VG_MIN( move_to+move_amount, vg_ui.textbuf_len-1 );
1052 move_amount = move_end-move_to;
1053
1054 if( move_amount )
1055 memmove( &vg_ui.textbuf[ move_to ],
1056 &vg_ui.textbuf[ datastart ],
1057 move_end-move_to );
1058
1059 vg_ui.textbuf[ move_end ] = '\0';
1060
1061 return VG_MIN( length, vg_ui.textbuf_len-datastart-1 );
1062 }
1063
1064 static int _ui_textbox_delete_char( int direction )
1065 {
1066 int start, end;
1067 _ui_textbox_make_selection( &start, &end );
1068
1069 /* There is no selection */
1070 if( !(end-start) ){
1071 if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_ui.textbuf ) );
1072 else if( direction == -1 ) start = VG_MAX( start-1, 0 );
1073 }
1074
1075 /* Still no selction, no need to do anything */
1076 if( !(end-start) )
1077 return start;
1078
1079 /* Copy the end->terminator to start */
1080 int remaining_length = strlen( vg_ui.textbuf )+1-end;
1081 memmove( &vg_ui.textbuf[ start ],
1082 &vg_ui.textbuf[ end ],
1083 remaining_length );
1084 return start;
1085 }
1086
1087 static void _ui_textbox_to_clipboard(void)
1088 {
1089 int start, end;
1090 _ui_textbox_make_selection( &start, &end );
1091 char buffer[512];
1092
1093 if( end-start ){
1094 memcpy( buffer, &vg_ui.textbuf[ start ], end-start );
1095 buffer[ end-start ] = 0x00;
1096 SDL_SetClipboardText( buffer );
1097 }
1098 }
1099
1100 static void _ui_textbox_clipboard_paste(void)
1101 {
1102 if( !SDL_HasClipboardText() )
1103 return;
1104
1105 char *text = SDL_GetClipboardText();
1106
1107 if( !text )
1108 return;
1109
1110 int datastart = _ui_textbox_delete_char( 0 );
1111 int length = strlen( text );
1112 int cpylength = _ui_textbox_makeroom( datastart, length );
1113
1114 memcpy( vg_ui.textbuf + datastart, text, cpylength);
1115 _ui_textbox_move_cursor( &vg_ui.cursor_user,
1116 &vg_ui.cursor_pos, cpylength, 1 );
1117 SDL_free( text );
1118 }
1119
1120 static void _ui_textbox_put_char( char c )
1121 {
1122 vg_ui.cursor_user = _ui_textbox_delete_char(0);
1123
1124 if( _ui_textbox_makeroom( vg_ui.cursor_user, 1 ) )
1125 vg_ui.textbuf[ vg_ui.cursor_user ] = c;
1126
1127 _ui_textbox_move_cursor( &vg_ui.cursor_user, &vg_ui.cursor_pos, 1, 1 );
1128 }
1129
1130 /* Receed secondary cursor */
1131 static void _ui_textbox_left_select(void)
1132 {
1133 _ui_textbox_move_cursor( &vg_ui.cursor_user, NULL, -1, 0 );
1134 }
1135
1136 /* Match and receed both cursors */
1137 static void _ui_textbox_left(void)
1138 {
1139 int cursor_diff = vg_ui.cursor_pos - vg_ui.cursor_user? 0: 1;
1140
1141 _ui_textbox_move_cursor( &vg_ui.cursor_user,
1142 &vg_ui.cursor_pos, -cursor_diff, 1 );
1143 }
1144
1145 static void _ui_textbox_right_select(void)
1146 {
1147 _ui_textbox_move_cursor( &vg_ui.cursor_user, NULL, 1, 0 );
1148 }
1149
1150 static void _ui_textbox_right(void)
1151 {
1152 int cursor_diff = vg_ui.cursor_pos - vg_ui.cursor_user? 0: 1;
1153
1154 _ui_textbox_move_cursor( &vg_ui.cursor_user,
1155 &vg_ui.cursor_pos, +cursor_diff, 1 );
1156 }
1157
1158 static void _ui_textbox_backspace(void)
1159 {
1160 vg_ui.cursor_user = _ui_textbox_delete_char( -1 );
1161 vg_ui.cursor_pos = vg_ui.cursor_user;
1162 }
1163
1164 static void _ui_textbox_delete(void)
1165 {
1166 vg_ui.cursor_user = _ui_textbox_delete_char( 1 );
1167 vg_ui.cursor_pos = vg_ui.cursor_user;
1168 }
1169
1170 static void _ui_textbox_home_select(void)
1171 {
1172 _ui_textbox_move_cursor( &vg_ui.cursor_user, NULL, -10000, 0 );
1173 }
1174
1175 static void _ui_textbox_home(void)
1176 {
1177 _ui_textbox_move_cursor( &vg_ui.cursor_user,
1178 &vg_ui.cursor_pos, -10000, 1 );
1179 }
1180
1181 static void _ui_textbox_end_select(void)
1182 {
1183 _ui_textbox_move_cursor( &vg_ui.cursor_user, NULL, 10000, 0 );
1184 }
1185
1186 static void _ui_textbox_end(void)
1187 {
1188 _ui_textbox_move_cursor( &vg_ui.cursor_user,
1189 &vg_ui.cursor_pos,
1190 vg_ui.textbuf_len-1, 1 );
1191 }
1192
1193 static void _ui_textbox_select_all(void)
1194 {
1195 _ui_textbox_move_cursor( &vg_ui.cursor_user, NULL, 10000, 0);
1196 _ui_textbox_move_cursor( &vg_ui.cursor_pos, NULL, -10000, 0);
1197 }
1198
1199 static void _ui_textbox_cut(void)
1200 {
1201 _ui_textbox_to_clipboard();
1202 vg_ui.cursor_user = _ui_textbox_delete_char(0);
1203 vg_ui.cursor_pos = vg_ui.cursor_user;
1204 }
1205
1206 static void _ui_textbox_enter(void)
1207 {
1208 if( !strlen( vg_ui.textbuf ) )
1209 return;
1210 }
1211
1212 /*
1213 * Handles binds
1214 */
1215 static void _ui_proc_key( SDL_Keysym ev )
1216 {
1217 struct textbox_mapping
1218 {
1219 u16 mod;
1220 SDL_Keycode key;
1221
1222 void (*handler)(void);
1223 }
1224 mappings[] =
1225 {
1226 { 0, SDLK_LEFT, _ui_textbox_left },
1227 { KMOD_SHIFT, SDLK_LEFT, _ui_textbox_left_select },
1228 { 0, SDLK_RIGHT, _ui_textbox_right },
1229 { KMOD_SHIFT, SDLK_RIGHT, _ui_textbox_right_select },
1230 #if 0
1231 { 0, SDLK_DOWN, _ui_textbox_down },
1232 { 0, SDLK_UP, _ui_textbox_up },
1233 #endif
1234 { 0, SDLK_BACKSPACE, _ui_textbox_backspace },
1235 { 0, SDLK_DELETE, _ui_textbox_delete },
1236 { 0, SDLK_HOME, _ui_textbox_home },
1237 { KMOD_SHIFT, SDLK_HOME, _ui_textbox_home_select },
1238 { 0, SDLK_END, _ui_textbox_end },
1239 { KMOD_SHIFT, SDLK_END, _ui_textbox_end_select },
1240 { KMOD_CTRL, SDLK_a, _ui_textbox_select_all },
1241 { KMOD_CTRL, SDLK_c, _ui_textbox_to_clipboard },
1242 { KMOD_CTRL, SDLK_x, _ui_textbox_cut },
1243 { KMOD_CTRL, SDLK_v, _ui_textbox_clipboard_paste },
1244 { 0, SDLK_RETURN, _ui_textbox_enter },
1245 #if 0
1246 { KMOD_CTRL, SDLK_n, _ui_textbox_suggest_next },
1247 { KMOD_CTRL, SDLK_p, _ui_textbox_suggest_prev }
1248 #endif
1249 { 0, SDLK_ESCAPE, ui_defocus_all }
1250 };
1251
1252 SDL_Keymod mod = 0;
1253
1254 if( ev.mod & KMOD_SHIFT )
1255 mod |= KMOD_SHIFT;
1256
1257 if( ev.mod & KMOD_CTRL )
1258 mod |= KMOD_CTRL;
1259
1260 if( ev.mod & KMOD_ALT )
1261 mod |= KMOD_ALT;
1262
1263 for( int i=0; i<vg_list_size( mappings ); i++ ){
1264 struct textbox_mapping *mapping = &mappings[i];
1265
1266 if( mapping->key == ev.sym ){
1267 if( mapping->mod == 0 ){
1268 if( mod == 0 ){
1269 mapping->handler();
1270 return;
1271 }
1272 }
1273 else if( (mod & mapping->mod) == mapping->mod ){
1274 mapping->handler();
1275 return;
1276 }
1277 }
1278 }
1279 }
1280
1281 /*
1282 * Callback for text entry mode
1283 */
1284 VG_STATIC void ui_proc_utf8( const char *text )
1285 {
1286 const char *ptr = text;
1287
1288 while( *ptr ){
1289 if( *ptr != '`' ) _ui_textbox_put_char( *ptr );
1290 ptr ++;
1291 }
1292 }
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316 static void _ui_textbox_cursor_rect( ui_rect text_rect, ui_rect cursor )
1317 {
1318 int start = VG_MIN( vg_ui.cursor_pos, vg_ui.cursor_user ),
1319 end = VG_MAX( vg_ui.cursor_pos, vg_ui.cursor_user );
1320
1321 cursor[0] = text_rect[0] + start*UI_GLYPH_SPACING_X-1;
1322 cursor[1] = text_rect[1];
1323 cursor[2] = start == end? 2: (float)(end-start)*(float)UI_GLYPH_SPACING_X;
1324 cursor[3] = text_rect[3];
1325
1326 }
1327
1328
1329
1330 static int ui_textbox( ui_rect rect, char *buf, u32 len )
1331 {
1332 int clickup= ui_click_up(),
1333 click = ui_clicking() | clickup,
1334 target = ui_inside_rect( rect, vg_ui.mouse_click ) && click,
1335 hover = ui_inside_rect( rect, vg_ui.mouse );
1336
1337 u32 col_base = ui_colour( k_ui_bg ),
1338 col_highlight = ui_colour( k_ui_fg ),
1339 col_cursor = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000;
1340
1341 ui_px border = -1;
1342
1343 ui_rect text_rect;
1344 rect_copy( rect, text_rect );
1345 text_rect[3] = 14;
1346 text_rect[2] -= 16;
1347 ui_rect_center( rect, text_rect );
1348
1349 if( hover ){
1350 vg_ui.cursor = k_ui_cursor_ibeam;
1351 }
1352
1353 if( vg_ui.focused_control_id == buf ){
1354 ui_fill( rect, col_base );
1355 ui_text( text_rect, buf, 1, k_ui_align_left, 0 );
1356
1357 if( (clickup||ui_click_down()) && !target ){
1358 ui_defocus_all();
1359 }
1360 else{
1361 vg_ui.focused_control_hit = 1;
1362
1363 if( click && target ){
1364 ui_px d0 = vg_ui.mouse_click[0] - text_rect[0],
1365 d1 = vg_ui.mouse[0] - text_rect[0];
1366
1367 int max = strlen( vg_ui.textbuf );
1368
1369 vg_ui.cursor_pos = VG_MAX( 0, VG_MIN( max, d0/UI_GLYPH_SPACING_X )),
1370 vg_ui.cursor_user= VG_MAX( 0, VG_MIN( max, d1/UI_GLYPH_SPACING_X ));
1371 }
1372
1373 ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ] );
1374
1375 ui_rect cursor;
1376 _ui_textbox_cursor_rect( text_rect, cursor );
1377 rect_copy( cursor, vg_ui.click_fader_end );
1378
1379 if( (vg_ui.click_fade_opacity<=0.0f) &&
1380 ui_clip( rect, cursor, cursor ) ){
1381 ui_fill( cursor, col_cursor );
1382 }
1383 }
1384
1385 return 0;
1386 }
1387
1388 if( click ){
1389 if( target && hover ){
1390 ui_fill( rect, col_highlight );
1391 vg_ui.ignore_input_frames = 2;
1392 rect_copy( rect, vg_ui.click_fader );
1393 _ui_textbox_cursor_rect( text_rect, vg_ui.click_fader_end );
1394
1395 vg_ui.click_fade_opacity = 1.0f;
1396 vg_ui.textbuf = buf;
1397 vg_ui.focused_control_hit = 1;
1398 vg_ui.focused_control_type = k_ui_control_textbox;
1399 vg_ui.textbuf_len = len;
1400 vg_info( "SDL_StartTextInput()\n" );
1401 SDL_StartTextInput();
1402 }
1403 }
1404
1405 ui_fill( rect, col_base );
1406
1407 if( hover ){
1408 ui_outline( rect, -1, col_highlight );
1409 }
1410
1411 ui_text( text_rect, buf, 1, k_ui_align_left, 0 );
1412 return 0;
1413 }
1414
1415 #endif /* VG_IMGUI_H */