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