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