1 /*
2 * Copyright (C) 2003 Robert Kooima
3 *
4 * NEVERBALL is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published
6 * by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 */
14
15 #include <stdlib.h>
16 #include <string.h>
17 #include <stdio.h>
18 #include <limits.h>
19
20 #include "config.h"
21 #include "video.h"
22 #include "glext.h"
23 #include "image.h"
24 #include "vec3.h"
25 #include "gui.h"
26 #include "common.h"
27 #include "font.h"
28 #include "theme.h"
29
30 #include "fs.h"
31 #include "fs_rwops.h"
32
33 /*---------------------------------------------------------------------------*/
34
35 /* Very pure colors for the GUI. I was watching BANZAI when I designed this. */
36
37 const GLubyte gui_wht[4] = { 0xFF, 0xFF, 0xFF, 0xFF }; /* White */
38 const GLubyte gui_yel[4] = { 0xFF, 0xFF, 0x00, 0xFF }; /* Yellow */
39 const GLubyte gui_red[4] = { 0xFF, 0x00, 0x00, 0xFF }; /* Red */
40 const GLubyte gui_grn[4] = { 0x00, 0xFF, 0x00, 0xFF }; /* Green */
41 const GLubyte gui_blu[4] = { 0x00, 0x00, 0xFF, 0xFF }; /* Blue */
42 const GLubyte gui_blk[4] = { 0x00, 0x00, 0x00, 0xFF }; /* Black */
43 const GLubyte gui_gry[4] = { 0x55, 0x55, 0x55, 0xFF }; /* Gray */
44 const GLubyte gui_shd[4] = { 0x00, 0x00, 0x00, 0x80 }; /* Shadow */
45
46 /*---------------------------------------------------------------------------*/
47
48 #define WIDGET_MAX 256
49
50 #define GUI_FREE 0
51 #define GUI_HARRAY 1
52 #define GUI_VARRAY 2
53 #define GUI_HSTACK 3
54 #define GUI_VSTACK 4
55 #define GUI_FILLER 5
56 #define GUI_IMAGE 6
57 #define GUI_LABEL 7
58 #define GUI_COUNT 8
59 #define GUI_CLOCK 9
60 #define GUI_SPACE 10
61 #define GUI_BUTTON 11
62
63 #define GUI_STATE 1
64 #define GUI_FILL 2
65 #define GUI_HILITE 4
66 #define GUI_RECT 8
67
68 #define GUI_LINES 8
69
70 /*---------------------------------------------------------------------------*/
71
72 struct widget
73 {
74 int type;
75 int flags;
76 int token;
77 int value;
78 int font;
79 int size;
80 int rect;
81
82 const GLubyte *color0;
83 const GLubyte *color1;
84
85 int x, y;
86 int w, h;
87 int car;
88 int cdr;
89
90 GLuint image;
91 GLfloat scale;
92
93 int text_w;
94 int text_h;
95
96 enum trunc trunc;
97 };
98
99 /*---------------------------------------------------------------------------*/
100
101 /* GUI widget state */
102
103 static struct widget widget[WIDGET_MAX];
104 static int active;
105 static int hovered;
106 static int clicked;
107 static int padding;
108 static int borders[4];
109
110 /* Digit widgets for the HUD. */
111
112 static int digit_id[3][11];
113
114 /* Cursor image. */
115
116 static int cursor_id = 0;
117 static int cursor_st = 0;
118
119 /* GUI theme. */
120
121 static struct theme curr_theme;
122
123 /*---------------------------------------------------------------------------*/
124
gui_hot(int id)125 static int gui_hot(int id)
126 {
127 return (widget[id].flags & GUI_STATE);
128 }
129
gui_size(void)130 static int gui_size(void)
131 {
132 const int w = video.device_w;
133 const int h = video.device_h;
134
135 return MIN(w, h);
136 }
137
138 /*---------------------------------------------------------------------------*/
139
140 /* Vertex buffer definitions for widget rendering. */
141
142 /* Vertex count */
143
144 #define RECT_VERT 16
145 #define TEXT_VERT 8
146 #define IMAGE_VERT 4
147
148 #define WIDGET_VERT (RECT_VERT + MAX(TEXT_VERT, IMAGE_VERT))
149
150 /* Element count */
151
152 #define RECT_ELEM 28
153
154 #define WIDGET_ELEM (RECT_ELEM)
155
156 struct vert
157 {
158 GLubyte c[4];
159 GLfloat u[2];
160 GLshort p[2];
161 };
162
163 static struct vert vert_buf[WIDGET_MAX * WIDGET_VERT];
164 static GLuint vert_vbo = 0;
165 static GLuint vert_ebo = 0;
166
167 /*---------------------------------------------------------------------------*/
168
set_vert(struct vert * v,int x,int y,GLfloat s,GLfloat t,const GLubyte * c)169 static void set_vert(struct vert *v, int x, int y,
170 GLfloat s, GLfloat t, const GLubyte *c)
171 {
172 v->c[0] = c[0];
173 v->c[1] = c[1];
174 v->c[2] = c[2];
175 v->c[3] = c[3];
176 v->u[0] = s;
177 v->u[1] = t;
178 v->p[0] = x;
179 v->p[1] = y;
180 }
181
182 /*---------------------------------------------------------------------------*/
183
draw_enable(GLboolean c,GLboolean u,GLboolean p)184 static void draw_enable(GLboolean c, GLboolean u, GLboolean p)
185 {
186 glBindBuffer_(GL_ARRAY_BUFFER, vert_vbo);
187 glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, vert_ebo);
188
189 if (c)
190 {
191 glEnableClientState(GL_COLOR_ARRAY);
192 glColorPointer (4, GL_UNSIGNED_BYTE, sizeof (struct vert),
193 (GLvoid *) offsetof (struct vert, c));
194 }
195 if (u)
196 {
197 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
198 glTexCoordPointer(2, GL_FLOAT, sizeof (struct vert),
199 (GLvoid *) offsetof (struct vert, u));
200 }
201 if (p)
202 {
203 glEnableClientState(GL_VERTEX_ARRAY);
204 glVertexPointer (2, GL_SHORT, sizeof (struct vert),
205 (GLvoid *) offsetof (struct vert, p));
206 }
207 }
208
draw_rect(int id)209 static void draw_rect(int id)
210 {
211 glDrawElements(GL_TRIANGLE_STRIP, RECT_ELEM, GL_UNSIGNED_SHORT,
212 (const GLvoid *) (id * WIDGET_ELEM * sizeof (GLushort)));
213 }
214
draw_text(int id)215 static void draw_text(int id)
216 {
217 glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_VERT + RECT_VERT, TEXT_VERT);
218 }
219
draw_image(int id)220 static void draw_image(int id)
221 {
222 glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_VERT + RECT_VERT, IMAGE_VERT);
223 }
224
draw_disable(void)225 static void draw_disable(void)
226 {
227 glBindBuffer_(GL_ARRAY_BUFFER, 0);
228 glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0);
229
230 glDisableClientState(GL_VERTEX_ARRAY);
231 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
232 glDisableClientState(GL_COLOR_ARRAY);
233 }
234
235 /*---------------------------------------------------------------------------*/
236
237 /*
238 * Generate vertices for a 3x3 rectangle. Vertices are arranged
239 * top-to-bottom and left-to-right, triangle strips are arranged
240 * left-to-right and top-to-bottom (one strip per row). Degenerate
241 * triangles (two extra indices per stitch) are inserted for a
242 * continous strip.
243 */
244
245 static const GLushort rect_elem_base[RECT_ELEM] = {
246 0, 1, 4, 5, 8, 9, 12, 13, 13, 1, /* Top */
247 1, 2, 5, 6, 9, 10, 13, 14, 14, 2, /* Middle */
248 2, 3, 6, 7, 10, 11, 14, 15 /* Bottom */
249 };
250
gui_geom_rect(int id,int x,int y,int w,int h,int f)251 static void gui_geom_rect(int id, int x, int y, int w, int h, int f)
252 {
253 GLushort rect_elem[RECT_ELEM];
254
255 struct vert *v = vert_buf + id * WIDGET_VERT;
256 struct vert *p = v;
257
258 int X[4];
259 int Y[4];
260
261 int i, j;
262
263 /* Generate vertex and element data for the widget's rectangle. */
264
265 X[0] = x;
266 X[1] = x + ((f & GUI_W) ? borders[0] : 0);
267 X[2] = x + w - ((f & GUI_E) ? borders[1] : 0);
268 X[3] = x + w;
269
270 Y[0] = y + h;
271 Y[1] = y + h - ((f & GUI_N) ? borders[2] : 0);
272 Y[2] = y + ((f & GUI_S) ? borders[3] : 0);
273 Y[3] = y;
274
275 for (i = 0; i < 4; i++)
276 for (j = 0; j < 4; j++)
277 set_vert(p++, X[i], Y[j], curr_theme.s[i], curr_theme.t[j], gui_wht);
278
279 for (i = 0; i < RECT_ELEM; i++)
280 rect_elem[i] = id * WIDGET_VERT + rect_elem_base[i];
281
282 /* Copy this off to the VBOs. */
283
284 glBindBuffer_ (GL_ARRAY_BUFFER, vert_vbo);
285 glBufferSubData_(GL_ARRAY_BUFFER,
286 id * WIDGET_VERT * sizeof (struct vert),
287 RECT_VERT * sizeof (struct vert), v);
288 glBindBuffer_ (GL_ARRAY_BUFFER, 0);
289
290 glBindBuffer_ (GL_ELEMENT_ARRAY_BUFFER, vert_ebo);
291 glBufferSubData_(GL_ELEMENT_ARRAY_BUFFER,
292 id * WIDGET_ELEM * sizeof (GLushort),
293 RECT_ELEM * sizeof (GLushort), rect_elem);
294 glBindBuffer_ (GL_ELEMENT_ARRAY_BUFFER, 0);
295 }
296
gui_geom_text(int id,int x,int y,int w,int h,const GLubyte * c0,const GLubyte * c1)297 static void gui_geom_text(int id, int x, int y, int w, int h,
298 const GLubyte *c0, const GLubyte *c1)
299 {
300 struct vert *v = vert_buf + id * WIDGET_VERT + RECT_VERT;
301
302 /* Assume the applied texture size is rect size rounded to power-of-two. */
303
304 int W;
305 int H;
306
307 image_size(&W, &H, w, h);
308
309 if (w > 0 && h > 0 && W > 0 && H > 0)
310 {
311 const int d = h / 16; /* Shadow offset */
312
313 const int ww = ((W - w) % 2) ? w + 1 : w;
314 const int hh = ((H - h) % 2) ? h + 1 : h;
315
316 const GLfloat s0 = 0.5f * (W - ww) / W;
317 const GLfloat t0 = 0.5f * (H - hh) / H;
318 const GLfloat s1 = 1.0f - s0;
319 const GLfloat t1 = 1.0f - t0;
320
321 /* Generate vertex data for the colored text and its shadow. */
322
323 set_vert(v + 0, x + d, y + hh - d, s0, t0, gui_shd);
324 set_vert(v + 1, x + d, y - d, s0, t1, gui_shd);
325 set_vert(v + 2, x + ww + d, y + hh - d, s1, t0, gui_shd);
326 set_vert(v + 3, x + ww + d, y - d, s1, t1, gui_shd);
327
328 set_vert(v + 4, x, y + hh, s0, t0, c1);
329 set_vert(v + 5, x, y, s0, t1, c0);
330 set_vert(v + 6, x + ww, y + hh, s1, t0, c1);
331 set_vert(v + 7, x + ww, y, s1, t1, c0);
332
333 }
334 else memset(v, 0, TEXT_VERT * sizeof (struct vert));
335
336 /* Copy this off to the VBO. */
337
338 glBindBuffer_ (GL_ARRAY_BUFFER, vert_vbo);
339 glBufferSubData_(GL_ARRAY_BUFFER,
340 (id * WIDGET_VERT + RECT_VERT) * sizeof (struct vert),
341 TEXT_VERT * sizeof (struct vert), v);
342 glBindBuffer_ (GL_ARRAY_BUFFER, 0);
343 }
344
gui_geom_image(int id,int x,int y,int w,int h,int f)345 static void gui_geom_image(int id, int x, int y, int w, int h, int f)
346 {
347 struct vert *v = vert_buf + id * WIDGET_VERT + RECT_VERT;
348
349 int X[2];
350 int Y[2];
351
352 /* Trace inner vertices of the background rectangle. */
353
354 X[0] = x + ((f & GUI_W) ? borders[0] : 0);
355 X[1] = x + w - ((f & GUI_E) ? borders[1] : 0);
356
357 Y[0] = y + h - ((f & GUI_N) ? borders[2] : 0);
358 Y[1] = y + ((f & GUI_S) ? borders[3] : 0);
359
360 set_vert(v + 0, X[0], Y[0], 0.0f, 1.0f, gui_wht);
361 set_vert(v + 1, X[0], Y[1], 0.0f, 0.0f, gui_wht);
362 set_vert(v + 2, X[1], Y[0], 1.0f, 1.0f, gui_wht);
363 set_vert(v + 3, X[1], Y[1], 1.0f, 0.0f, gui_wht);
364
365 /* Copy this off to the VBO. */
366
367 glBindBuffer_ (GL_ARRAY_BUFFER, vert_vbo);
368 glBufferSubData_(GL_ARRAY_BUFFER,
369 (id * WIDGET_VERT + RECT_VERT) * sizeof (struct vert),
370 IMAGE_VERT * sizeof (struct vert), v);
371 glBindBuffer_ (GL_ARRAY_BUFFER, 0);
372 }
373
gui_geom_widget(int id,int flags)374 static void gui_geom_widget(int id, int flags)
375 {
376 int jd;
377
378 int w = widget[id].w;
379 int h = widget[id].h;
380
381 int W = widget[id].text_w;
382 int H = widget[id].text_h;
383 int R = widget[id].rect;
384
385 const GLubyte *c0 = widget[id].color0;
386 const GLubyte *c1 = widget[id].color1;
387
388 if ((widget[id].flags & GUI_RECT) && !(flags & GUI_RECT))
389 {
390 gui_geom_rect(id, -w / 2, -h / 2, w, h, R);
391 flags |= GUI_RECT;
392 }
393
394 switch (widget[id].type)
395 {
396 case GUI_FILLER:
397 case GUI_SPACE:
398 break;
399
400 case GUI_HARRAY:
401 case GUI_VARRAY:
402 case GUI_HSTACK:
403 case GUI_VSTACK:
404
405 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
406 gui_geom_widget(jd, flags);
407
408 break;
409
410 case GUI_IMAGE:
411 gui_geom_image(id, -w / 2, -h / 2, w, h, R);
412 break;
413
414 default:
415 gui_geom_text(id, -W / 2, -H / 2, W, H, c0, c1);
416 break;
417 }
418 }
419
420 /*---------------------------------------------------------------------------*/
421
422 #define FONT_MAX 4
423
424 static struct font fonts[FONT_MAX];
425 static int fontc;
426
427 static int font_sizes[3];
428
gui_font_load(const char * path)429 static int gui_font_load(const char *path)
430 {
431 int i;
432
433 /* Find a previously loaded font. */
434
435 for (i = 0; i < fontc; i++)
436 if (strcmp(fonts[i].path, path) == 0)
437 return i;
438
439 /* Load a new font. */
440
441 if (fontc < FONT_MAX)
442 {
443 if (font_load(&fonts[fontc], path, font_sizes))
444 {
445 fontc++;
446 return fontc - 1;
447 }
448 }
449
450 /* Return index of default font. */
451
452 return 0;
453 }
454
455 static void gui_font_quit(void);
456
gui_font_init(void)457 static void gui_font_init(void)
458 {
459 gui_font_quit();
460
461 if (font_init())
462 {
463 int s = gui_size();
464
465 font_sizes[0] = s / 26;
466 font_sizes[1] = s / 13;
467 font_sizes[2] = s / 7;
468
469 /* Load the default font at index 0. */
470
471 gui_font_load(*curr_lang.font ? curr_lang.font : GUI_FACE);
472 }
473 }
474
gui_font_quit(void)475 static void gui_font_quit(void)
476 {
477 int i;
478
479 for (i = 0; i < fontc; i++)
480 font_free(&fonts[i]);
481
482 fontc = 0;
483
484 font_quit();
485 }
486
487 /*---------------------------------------------------------------------------*/
488
gui_theme_quit(void)489 static void gui_theme_quit(void)
490 {
491 theme_free(&curr_theme);
492 }
493
gui_theme_init(void)494 static void gui_theme_init(void)
495 {
496 gui_theme_quit();
497
498 theme_load(&curr_theme, config_get_s(CONFIG_THEME));
499 }
500
501 /*---------------------------------------------------------------------------*/
502
gui_init(void)503 void gui_init(void)
504 {
505 const int s = gui_size();
506
507 int i, j;
508
509 memset(widget, 0, sizeof (struct widget) * WIDGET_MAX);
510
511 /* Compute default widget/text padding. */
512
513 padding = s / 60;
514
515 for (i = 0; i < 4; i++)
516 borders[i] = padding;
517
518 /* Initialize font rendering. */
519
520 gui_font_init();
521
522 /* Initialize GUI theme. */
523
524 gui_theme_init();
525
526 /* Initialize the VBOs. */
527
528 memset(vert_buf, 0, sizeof (vert_buf));
529
530 glGenBuffers_(1, &vert_vbo);
531 glBindBuffer_(GL_ARRAY_BUFFER, vert_vbo);
532 glBufferData_(GL_ARRAY_BUFFER, sizeof (vert_buf), vert_buf, GL_STATIC_DRAW);
533 glBindBuffer_(GL_ARRAY_BUFFER, 0);
534
535 glGenBuffers_(1, &vert_ebo);
536 glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, vert_ebo);
537 glBufferData_(GL_ELEMENT_ARRAY_BUFFER,
538 WIDGET_MAX * WIDGET_ELEM * sizeof (GLushort),
539 NULL, GL_STATIC_DRAW);
540 glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0);
541
542 /* Cache digit glyphs for HUD rendering. */
543
544 for (i = 0; i < 3; i++)
545 {
546 digit_id[i][ 0] = gui_label(0, "0", i, 0, 0);
547 digit_id[i][ 1] = gui_label(0, "1", i, 0, 0);
548 digit_id[i][ 2] = gui_label(0, "2", i, 0, 0);
549 digit_id[i][ 3] = gui_label(0, "3", i, 0, 0);
550 digit_id[i][ 4] = gui_label(0, "4", i, 0, 0);
551 digit_id[i][ 5] = gui_label(0, "5", i, 0, 0);
552 digit_id[i][ 6] = gui_label(0, "6", i, 0, 0);
553 digit_id[i][ 7] = gui_label(0, "7", i, 0, 0);
554 digit_id[i][ 8] = gui_label(0, "8", i, 0, 0);
555 digit_id[i][ 9] = gui_label(0, "9", i, 0, 0);
556 digit_id[i][10] = gui_label(0, ":", i, 0, 0);
557 }
558
559 for (i = 0; i < 3; i++)
560 for (j = 0; j < 11; ++j)
561 gui_layout(digit_id[i][j], 0, 0);
562
563 /* Cache an image for the cursor. Scale it to the same size as a digit. */
564
565 if ((cursor_id = gui_image(0, "gui/cursor.png", widget[digit_id[1][0]].w,
566 widget[digit_id[1][0]].h)))
567 gui_layout(cursor_id, 0, 0);
568
569 active = 0;
570 }
571
gui_free(void)572 void gui_free(void)
573 {
574 int id;
575
576 /* Release the VBOs. */
577
578 glDeleteBuffers_(1, &vert_vbo);
579 glDeleteBuffers_(1, &vert_ebo);
580
581 /* Release any remaining widget texture and display list indices. */
582
583 for (id = 1; id < WIDGET_MAX; id++)
584 {
585 if (widget[id].image)
586 glDeleteTextures(1, &widget[id].image);
587
588 widget[id].type = GUI_FREE;
589 widget[id].flags = 0;
590 widget[id].image = 0;
591 widget[id].cdr = 0;
592 widget[id].car = 0;
593 }
594
595 /* Release all loaded fonts and finalize font rendering. */
596
597 gui_font_quit();
598
599 /* Release theme resources. */
600
601 gui_theme_quit();
602 }
603
604 /*---------------------------------------------------------------------------*/
605
gui_widget(int pd,int type)606 static int gui_widget(int pd, int type)
607 {
608 int id;
609
610 /* Find an unused entry in the widget table. */
611
612 for (id = 1; id < WIDGET_MAX; id++)
613 if (widget[id].type == GUI_FREE)
614 {
615 /* Set the type and default properties. */
616
617 widget[id].type = type;
618 widget[id].flags = 0;
619 widget[id].token = 0;
620 widget[id].value = 0;
621 widget[id].font = 0;
622 widget[id].size = 0;
623 widget[id].rect = GUI_ALL;
624 widget[id].w = 0;
625 widget[id].h = 0;
626 widget[id].image = 0;
627 widget[id].color0 = gui_wht;
628 widget[id].color1 = gui_wht;
629 widget[id].scale = 1.0f;
630 widget[id].trunc = TRUNC_NONE;
631 widget[id].text_w = 0;
632 widget[id].text_h = 0;
633
634 /* Insert the new widget into the parent's widget list. */
635
636 if (pd)
637 {
638 widget[id].car = 0;
639 widget[id].cdr = widget[pd].car;
640 widget[pd].car = id;
641 }
642 else
643 {
644 widget[id].car = 0;
645 widget[id].cdr = 0;
646 }
647
648 return id;
649 }
650
651 log_printf("Out of widget IDs\n");
652
653 return 0;
654 }
655
gui_harray(int pd)656 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
gui_varray(int pd)657 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
gui_hstack(int pd)658 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
gui_vstack(int pd)659 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
gui_filler(int pd)660 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
661
662 /*---------------------------------------------------------------------------*/
663
gui_measure_ttf(const char * text,TTF_Font * font)664 static struct size gui_measure_ttf(const char *text, TTF_Font *font)
665 {
666 struct size size = { 0, 0 };
667
668 if (font)
669 TTF_SizeUTF8(font, text, &size.w, &size.h);
670
671 return size;
672 }
673
gui_measure(const char * text,int size)674 struct size gui_measure(const char *text, int size)
675 {
676 return gui_measure_ttf(text, fonts[0].ttf[size]);
677 }
678
679 /*---------------------------------------------------------------------------*/
680
gui_trunc_head(const char * text,const int maxwidth,TTF_Font * font)681 static char *gui_trunc_head(const char *text,
682 const int maxwidth,
683 TTF_Font *font)
684 {
685 int left, right, mid;
686 char *str = NULL;
687
688 left = 0;
689 right = strlen(text);
690
691 while (right - left > 1)
692 {
693 mid = (left + right) / 2;
694
695 str = concat_string("...", text + mid, NULL);
696
697 if (gui_measure_ttf(str, font).w <= maxwidth)
698 right = mid;
699 else
700 left = mid;
701
702 free(str);
703 }
704
705 return concat_string("...", text + right, NULL);
706 }
707
gui_trunc_tail(const char * text,const int maxwidth,TTF_Font * font)708 static char *gui_trunc_tail(const char *text,
709 const int maxwidth,
710 TTF_Font *font)
711 {
712 int left, right, mid;
713 char *str = NULL;
714
715 left = 0;
716 right = strlen(text);
717
718 while (right - left > 1)
719 {
720 mid = (left + right) / 2;
721
722 str = malloc(mid + sizeof ("..."));
723
724 memcpy(str, text, mid);
725 memcpy(str + mid, "...", sizeof ("..."));
726
727 if (gui_measure_ttf(str, font).w <= maxwidth)
728 left = mid;
729 else
730 right = mid;
731
732 free(str);
733 }
734
735 str = malloc(left + sizeof ("..."));
736
737 memcpy(str, text, left);
738 memcpy(str + left, "...", sizeof ("..."));
739
740 return str;
741 }
742
gui_truncate(const char * text,const int maxwidth,TTF_Font * font,enum trunc trunc)743 static char *gui_truncate(const char *text,
744 const int maxwidth,
745 TTF_Font *font,
746 enum trunc trunc)
747 {
748 if (gui_measure_ttf(text, font).w <= maxwidth)
749 return strdup(text);
750
751 switch (trunc)
752 {
753 case TRUNC_NONE: return strdup(text); break;
754 case TRUNC_HEAD: return gui_trunc_head(text, maxwidth, font); break;
755 case TRUNC_TAIL: return gui_trunc_tail(text, maxwidth, font); break;
756 }
757
758 return NULL;
759 }
760
761 /*---------------------------------------------------------------------------*/
762
gui_set_image(int id,const char * file)763 void gui_set_image(int id, const char *file)
764 {
765 glDeleteTextures(1, &widget[id].image);
766
767 widget[id].image = make_image_from_file(file, IF_MIPMAP);
768 }
769
gui_set_label(int id,const char * text)770 void gui_set_label(int id, const char *text)
771 {
772 TTF_Font *ttf = fonts[widget[id].font].ttf[widget[id].size];
773
774 int w = 0;
775 int h = 0;
776
777 char *str;
778
779 glDeleteTextures(1, &widget[id].image);
780
781 str = gui_truncate(text, widget[id].w - padding, ttf, widget[id].trunc);
782
783 widget[id].image = make_image_from_font(NULL, NULL,
784 &widget[id].text_w,
785 &widget[id].text_h,
786 str, ttf, 0);
787 w = widget[id].text_w;
788 h = widget[id].text_h;
789
790 gui_geom_text(id, -w / 2, -h / 2, w, h,
791 widget[id].color0,
792 widget[id].color1);
793
794 free(str);
795 }
796
gui_set_count(int id,int value)797 void gui_set_count(int id, int value)
798 {
799 widget[id].value = value;
800 }
801
gui_set_clock(int id,int value)802 void gui_set_clock(int id, int value)
803 {
804 widget[id].value = value;
805 }
806
gui_set_color(int id,const GLubyte * c0,const GLubyte * c1)807 void gui_set_color(int id, const GLubyte *c0,
808 const GLubyte *c1)
809 {
810 if (id)
811 {
812 c0 = c0 ? c0 : gui_yel;
813 c1 = c1 ? c1 : gui_red;
814
815 if (widget[id].color0 != c0 || widget[id].color1 != c1)
816 {
817 int w = widget[id].text_w;
818 int h = widget[id].text_h;
819
820 widget[id].color0 = c0;
821 widget[id].color1 = c1;
822
823 gui_geom_text(id, -w / 2, -h / 2, w, h, c0, c1);
824 }
825 }
826 }
827
gui_set_multi(int id,const char * text)828 void gui_set_multi(int id, const char *text)
829 {
830 const char *p;
831
832 char s[GUI_LINES][MAXSTR];
833 int i, sc, lc, jd;
834
835 size_t n = 0;
836
837 /* Count available labels. */
838
839 for (lc = 0, jd = widget[id].car; jd; lc++, jd = widget[jd].cdr);
840
841 /* Copy each delimited string to a line buffer. */
842
843 for (p = text, sc = 0; *p && sc < lc; sc++)
844 {
845 strncpy(s[sc], p, (n = strcspn(p, "\\")));
846 s[sc][n] = 0;
847
848 if (*(p += n) == '\\') p++;
849 }
850
851 /* Set the label value for each line. */
852
853 for (i = lc - 1, jd = widget[id].car; i >= 0; i--, jd = widget[jd].cdr)
854 gui_set_label(jd, i < sc ? s[i] : "");
855 }
856
gui_set_trunc(int id,enum trunc trunc)857 void gui_set_trunc(int id, enum trunc trunc)
858 {
859 widget[id].trunc = trunc;
860 }
861
gui_set_font(int id,const char * path)862 void gui_set_font(int id, const char *path)
863 {
864 widget[id].font = gui_font_load(path);
865 }
866
gui_set_fill(int id)867 void gui_set_fill(int id)
868 {
869 widget[id].flags |= GUI_FILL;
870 }
871
872 /*
873 * Activate a widget, allowing it to behave as a normal state widget.
874 * This may be used to create image buttons, or cause an array of
875 * widgets to behave as a single state widget.
876 */
gui_set_state(int id,int token,int value)877 int gui_set_state(int id, int token, int value)
878 {
879 widget[id].flags |= GUI_STATE;
880 widget[id].token = token;
881 widget[id].value = value;
882
883 return id;
884 }
885
gui_set_hilite(int id,int hilite)886 void gui_set_hilite(int id, int hilite)
887 {
888 if (hilite)
889 widget[id].flags |= GUI_HILITE;
890 else
891 widget[id].flags &= ~GUI_HILITE;
892 }
893
gui_set_rect(int id,int rect)894 void gui_set_rect(int id, int rect)
895 {
896 widget[id].rect = rect;
897 widget[id].flags |= GUI_RECT;
898 }
899
gui_set_cursor(int st)900 void gui_set_cursor(int st)
901 {
902 cursor_st = st;
903 }
904
905 /*---------------------------------------------------------------------------*/
906
gui_image(int pd,const char * file,int w,int h)907 int gui_image(int pd, const char *file, int w, int h)
908 {
909 int id;
910
911 if ((id = gui_widget(pd, GUI_IMAGE)))
912 {
913 widget[id].image = make_image_from_file(file, IF_MIPMAP);
914 widget[id].w = w;
915 widget[id].h = h;
916 widget[id].flags |= GUI_RECT;
917 }
918 return id;
919 }
920
gui_start(int pd,const char * text,int size,int token,int value)921 int gui_start(int pd, const char *text, int size, int token, int value)
922 {
923 int id;
924
925 if ((id = gui_state(pd, text, size, token, value)))
926 active = id;
927
928 return id;
929 }
930
gui_state(int pd,const char * text,int size,int token,int value)931 int gui_state(int pd, const char *text, int size, int token, int value)
932 {
933 int id;
934
935 if ((id = gui_widget(pd, GUI_BUTTON)))
936 {
937 TTF_Font *ttf = fonts[widget[id].font].ttf[size];
938
939 widget[id].flags |= (GUI_STATE | GUI_RECT);
940
941 widget[id].image = make_image_from_font(NULL, NULL,
942 &widget[id].text_w,
943 &widget[id].text_h,
944 text, ttf, 0);
945 widget[id].w = widget[id].text_w;
946 widget[id].h = widget[id].text_h;
947 widget[id].size = size;
948 widget[id].token = token;
949 widget[id].value = value;
950 }
951 return id;
952 }
953
gui_label(int pd,const char * text,int size,const GLubyte * c0,const GLubyte * c1)954 int gui_label(int pd, const char *text, int size, const GLubyte *c0,
955 const GLubyte *c1)
956 {
957 int id;
958
959 if ((id = gui_widget(pd, GUI_LABEL)))
960 {
961 TTF_Font *ttf = fonts[widget[id].font].ttf[size];
962
963 widget[id].image = make_image_from_font(NULL, NULL,
964 &widget[id].text_w,
965 &widget[id].text_h,
966 text, ttf, 0);
967 widget[id].w = widget[id].text_w;
968 widget[id].h = widget[id].text_h;
969 widget[id].size = size;
970 widget[id].color0 = c0 ? c0 : gui_yel;
971 widget[id].color1 = c1 ? c1 : gui_red;
972 widget[id].flags |= GUI_RECT;
973 }
974 return id;
975 }
976
gui_count(int pd,int value,int size)977 int gui_count(int pd, int value, int size)
978 {
979 int i, id;
980
981 if ((id = gui_widget(pd, GUI_COUNT)))
982 {
983 for (i = value; i; i /= 10)
984 widget[id].w += widget[digit_id[size][0]].text_w;
985
986 widget[id].h = widget[digit_id[size][0]].text_h;
987 widget[id].value = value;
988 widget[id].size = size;
989 widget[id].color0 = gui_yel;
990 widget[id].color1 = gui_red;
991 widget[id].flags |= GUI_RECT;
992 }
993 return id;
994 }
995
gui_clock(int pd,int value,int size)996 int gui_clock(int pd, int value, int size)
997 {
998 int id;
999
1000 if ((id = gui_widget(pd, GUI_CLOCK)))
1001 {
1002 widget[id].w = widget[digit_id[size][0]].text_w * 6;
1003 widget[id].h = widget[digit_id[size][0]].text_h;
1004 widget[id].value = value;
1005 widget[id].size = size;
1006 widget[id].color0 = gui_yel;
1007 widget[id].color1 = gui_red;
1008 widget[id].flags |= GUI_RECT;
1009 }
1010 return id;
1011 }
1012
gui_space(int pd)1013 int gui_space(int pd)
1014 {
1015 int id;
1016
1017 if ((id = gui_widget(pd, GUI_SPACE)))
1018 {
1019 widget[id].w = 0;
1020 widget[id].h = 0;
1021 }
1022 return id;
1023 }
1024
1025 /*---------------------------------------------------------------------------*/
1026
1027 /*
1028 * Create a multi-line text box using a vertical array of labels.
1029 * Parse the text for '\' characters and treat them as line-breaks.
1030 * Preserve the rect specification across the entire array.
1031 */
1032
gui_multi(int pd,const char * text,int size,const GLubyte * c0,const GLubyte * c1)1033 int gui_multi(int pd, const char *text, int size, const GLubyte *c0,
1034 const GLubyte *c1)
1035 {
1036 int id = 0;
1037
1038 if (text && *text && (id = gui_varray(pd)))
1039 {
1040 const char *p;
1041
1042 char s[GUI_LINES][MAXSTR];
1043 int i, j;
1044
1045 size_t n = 0;
1046
1047 /* Copy each delimited string to a line buffer. */
1048
1049 for (p = text, j = 0; *p && j < GUI_LINES; j++)
1050 {
1051 strncpy(s[j], p, (n = strcspn(p, "\\")));
1052 s[j][n] = 0;
1053
1054 if (*(p += n) == '\\') p++;
1055 }
1056
1057 /* Create a label widget for each line. */
1058
1059 for (i = 0; i < j; i++)
1060 gui_label(id, s[i], size, c0, c1);
1061
1062 /* Set rectangle on the container. */
1063
1064 widget[id].flags |= GUI_RECT;
1065 }
1066 return id;
1067 }
1068
1069 /*---------------------------------------------------------------------------*/
1070 /*
1071 * The bottom-up pass determines the area of all widgets. The minimum
1072 * width and height of a leaf widget is given by the size of its
1073 * contents. Array and stack widths and heights are computed
1074 * recursively from these.
1075 */
1076
1077 static void gui_widget_up(int id);
1078
gui_harray_up(int id)1079 static void gui_harray_up(int id)
1080 {
1081 int jd, c = 0;
1082
1083 /* Find the widest child width and the highest child height. */
1084
1085 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1086 {
1087 gui_widget_up(jd);
1088
1089 if (widget[id].h < widget[jd].h)
1090 widget[id].h = widget[jd].h;
1091 if (widget[id].w < widget[jd].w)
1092 widget[id].w = widget[jd].w;
1093
1094 c++;
1095 }
1096
1097 /* Total width is the widest child width times the child count. */
1098
1099 widget[id].w *= c;
1100 }
1101
gui_varray_up(int id)1102 static void gui_varray_up(int id)
1103 {
1104 int jd, c = 0;
1105
1106 /* Find the widest child width and the highest child height. */
1107
1108 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1109 {
1110 gui_widget_up(jd);
1111
1112 if (widget[id].h < widget[jd].h)
1113 widget[id].h = widget[jd].h;
1114 if (widget[id].w < widget[jd].w)
1115 widget[id].w = widget[jd].w;
1116
1117 c++;
1118 }
1119
1120 /* Total height is the highest child height times the child count. */
1121
1122 widget[id].h *= c;
1123 }
1124
gui_hstack_up(int id)1125 static void gui_hstack_up(int id)
1126 {
1127 int jd;
1128
1129 /* Find the highest child height. Sum the child widths. */
1130
1131 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1132 {
1133 gui_widget_up(jd);
1134
1135 if (widget[id].h < widget[jd].h)
1136 widget[id].h = widget[jd].h;
1137
1138 widget[id].w += widget[jd].w;
1139 }
1140 }
1141
gui_vstack_up(int id)1142 static void gui_vstack_up(int id)
1143 {
1144 int jd;
1145
1146 /* Find the widest child width. Sum the child heights. */
1147
1148 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1149 {
1150 gui_widget_up(jd);
1151
1152 if (widget[id].w < widget[jd].w)
1153 widget[id].w = widget[jd].w;
1154
1155 widget[id].h += widget[jd].h;
1156 }
1157 }
1158
gui_button_up(int id)1159 static void gui_button_up(int id)
1160 {
1161 if (widget[id].w < widget[id].h && widget[id].w > 0)
1162 widget[id].w = widget[id].h;
1163
1164 /* Padded text elements look a little nicer. */
1165
1166 if (widget[id].w < video.device_w)
1167 widget[id].w += padding;
1168 if (widget[id].h < video.device_h)
1169 widget[id].h += padding;
1170
1171 /* A button should be at least wide enough to accomodate the borders. */
1172
1173 if (widget[id].w < borders[0] + borders[1])
1174 widget[id].w = borders[0] + borders[1];
1175 if (widget[id].h < borders[2] + borders[3])
1176 widget[id].h = borders[2] + borders[3];
1177 }
1178
gui_widget_up(int id)1179 static void gui_widget_up(int id)
1180 {
1181 if (id)
1182 switch (widget[id].type)
1183 {
1184 case GUI_HARRAY: gui_harray_up(id); break;
1185 case GUI_VARRAY: gui_varray_up(id); break;
1186 case GUI_HSTACK: gui_hstack_up(id); break;
1187 case GUI_VSTACK: gui_vstack_up(id); break;
1188 case GUI_FILLER: break;
1189 default: gui_button_up(id); break;
1190 }
1191 }
1192
1193 /*---------------------------------------------------------------------------*/
1194 /*
1195 * The top-down layout pass distributes available area as computed
1196 * during the bottom-up pass. Widgets use their area and position to
1197 * initialize rendering state.
1198 */
1199
1200 static void gui_widget_dn(int id, int x, int y, int w, int h);
1201
gui_harray_dn(int id,int x,int y,int w,int h)1202 static void gui_harray_dn(int id, int x, int y, int w, int h)
1203 {
1204 int jd, i = 0, c = 0;
1205
1206 widget[id].x = x;
1207 widget[id].y = y;
1208 widget[id].w = w;
1209 widget[id].h = h;
1210
1211 /* Count children. */
1212
1213 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1214 c += 1;
1215
1216 /* Distribute horizontal space evenly to all children. */
1217
1218 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
1219 {
1220 int x0 = x + i * w / c;
1221 int x1 = x + (i + 1) * w / c;
1222
1223 gui_widget_dn(jd, x0, y, x1 - x0, h);
1224 }
1225 }
1226
gui_varray_dn(int id,int x,int y,int w,int h)1227 static void gui_varray_dn(int id, int x, int y, int w, int h)
1228 {
1229 int jd, i = 0, c = 0;
1230
1231 widget[id].x = x;
1232 widget[id].y = y;
1233 widget[id].w = w;
1234 widget[id].h = h;
1235
1236 /* Count children. */
1237
1238 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1239 c += 1;
1240
1241 /* Distribute vertical space evenly to all children. */
1242
1243 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
1244 {
1245 int y0 = y + i * h / c;
1246 int y1 = y + (i + 1) * h / c;
1247
1248 gui_widget_dn(jd, x, y0, w, y1 - y0);
1249 }
1250 }
1251
gui_hstack_dn(int id,int x,int y,int w,int h)1252 static void gui_hstack_dn(int id, int x, int y, int w, int h)
1253 {
1254 int jd, jx = x, jw = 0, c = 0;
1255
1256 widget[id].x = x;
1257 widget[id].y = y;
1258 widget[id].w = w;
1259 widget[id].h = h;
1260
1261 /* Measure the total width requested by non-filler children. */
1262
1263 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1264 if (widget[jd].type == GUI_FILLER)
1265 c += 1;
1266 else if (widget[jd].flags & GUI_FILL)
1267 {
1268 c += 1;
1269 jw += widget[jd].w;
1270 }
1271 else
1272 jw += widget[jd].w;
1273
1274 /* Give non-filler children their requested space. */
1275 /* Distribute the rest evenly among filler children. */
1276
1277 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1278 {
1279 if (widget[jd].type == GUI_FILLER)
1280 gui_widget_dn(jd, jx, y, (w - jw) / c, h);
1281 else if (widget[jd].flags & GUI_FILL)
1282 gui_widget_dn(jd, jx, y, widget[jd].w + (w - jw) / c, h);
1283 else
1284 gui_widget_dn(jd, jx, y, widget[jd].w, h);
1285
1286 jx += widget[jd].w;
1287 }
1288 }
1289
gui_vstack_dn(int id,int x,int y,int w,int h)1290 static void gui_vstack_dn(int id, int x, int y, int w, int h)
1291 {
1292 int jd, jy = y, jh = 0, c = 0;
1293
1294 widget[id].x = x;
1295 widget[id].y = y;
1296 widget[id].w = w;
1297 widget[id].h = h;
1298
1299 /* Measure the total height requested by non-filler children. */
1300
1301 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1302 if (widget[jd].type == GUI_FILLER)
1303 c += 1;
1304 else if (widget[jd].flags & GUI_FILL)
1305 {
1306 c += 1;
1307 jh += widget[jd].h;
1308 }
1309 else
1310 jh += widget[jd].h;
1311
1312 /* Give non-filler children their requested space. */
1313 /* Distribute the rest evenly among filler children. */
1314
1315 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1316 {
1317 if (widget[jd].type == GUI_FILLER)
1318 gui_widget_dn(jd, x, jy, w, (h - jh) / c);
1319 else if (widget[jd].flags & GUI_FILL)
1320 gui_widget_dn(jd, x, jy, w, widget[jd].h + (h - jh) / c);
1321 else
1322 gui_widget_dn(jd, x, jy, w, widget[jd].h);
1323
1324 jy += widget[jd].h;
1325 }
1326 }
1327
gui_filler_dn(int id,int x,int y,int w,int h)1328 static void gui_filler_dn(int id, int x, int y, int w, int h)
1329 {
1330 /* Filler expands to whatever size it is given. */
1331
1332 widget[id].x = x;
1333 widget[id].y = y;
1334 widget[id].w = w;
1335 widget[id].h = h;
1336 }
1337
gui_button_dn(int id,int x,int y,int w,int h)1338 static void gui_button_dn(int id, int x, int y, int w, int h)
1339 {
1340 widget[id].x = x;
1341 widget[id].y = y;
1342 widget[id].w = w;
1343 widget[id].h = h;
1344 }
1345
gui_widget_dn(int id,int x,int y,int w,int h)1346 static void gui_widget_dn(int id, int x, int y, int w, int h)
1347 {
1348 if (id)
1349 switch (widget[id].type)
1350 {
1351 case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
1352 case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
1353 case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
1354 case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
1355 case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
1356 case GUI_SPACE: gui_filler_dn(id, x, y, w, h); break;
1357 default: gui_button_dn(id, x, y, w, h); break;
1358 }
1359 }
1360
1361 /*---------------------------------------------------------------------------*/
1362 /*
1363 * During GUI layout, we make a bottom-up pass to determine total area
1364 * requirements for the widget tree. We position this area to the
1365 * sides or center of the screen. Finally, we make a top-down pass to
1366 * distribute this area to each widget.
1367 */
1368
gui_layout(int id,int xd,int yd)1369 void gui_layout(int id, int xd, int yd)
1370 {
1371 int x, y;
1372
1373 int w, W = video.device_w;
1374 int h, H = video.device_h;
1375
1376 gui_widget_up(id);
1377
1378 w = widget[id].w;
1379 h = widget[id].h;
1380
1381 if (xd < 0) x = 0;
1382 else if (xd > 0) x = (W - w);
1383 else x = (W - w) / 2;
1384
1385 if (yd < 0) y = 0;
1386 else if (yd > 0) y = (H - h);
1387 else y = (H - h) / 2;
1388
1389 gui_widget_dn(id, x, y, w, h);
1390
1391 /* Set up GUI rendering state. */
1392
1393 gui_geom_widget(id, 0);
1394
1395 /* Hilite the widget under the cursor, if any. */
1396
1397 gui_point(id, -1, -1);
1398 }
1399
gui_search(int id,int x,int y)1400 int gui_search(int id, int x, int y)
1401 {
1402 int jd, kd;
1403
1404 /* Search the hierarchy for the widget containing the given point. */
1405
1406 if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
1407 widget[id].y <= y && y < widget[id].y + widget[id].h))
1408 {
1409 if (gui_hot(id))
1410 return id;
1411
1412 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1413 if ((kd = gui_search(jd, x, y)))
1414 return kd;
1415 }
1416 return 0;
1417 }
1418
gui_delete(int id)1419 int gui_delete(int id)
1420 {
1421 if (id)
1422 {
1423 /* Recursively delete all subwidgets. */
1424
1425 gui_delete(widget[id].cdr);
1426 gui_delete(widget[id].car);
1427
1428 /* Release any GL resources held by this widget. */
1429
1430 if (widget[id].image)
1431 glDeleteTextures(1, &widget[id].image);
1432
1433 /* Mark this widget unused. */
1434
1435 widget[id].type = GUI_FREE;
1436 widget[id].flags = 0;
1437 widget[id].image = 0;
1438 widget[id].cdr = 0;
1439 widget[id].car = 0;
1440
1441 /* Clear focus from this widget. */
1442
1443 if (active == id)
1444 active = 0;
1445 }
1446 return 0;
1447 }
1448
1449 /*---------------------------------------------------------------------------*/
1450
gui_paint_rect(int id,int st,int flags)1451 static void gui_paint_rect(int id, int st, int flags)
1452 {
1453 int jd, i = 0;
1454
1455 /* Use the widget status to determine the background color. */
1456
1457 i = st | (((widget[id].flags & GUI_HILITE) ? 2 : 0) |
1458 ((id == active) ? 1 : 0));
1459
1460 if ((widget[id].flags & GUI_RECT) && !(flags & GUI_RECT))
1461 {
1462 /* Draw a leaf's background, colored by widget state. */
1463
1464 glPushMatrix();
1465 {
1466 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1467 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1468
1469 glBindTexture(GL_TEXTURE_2D, curr_theme.tex[i]);
1470 draw_rect(id);
1471 }
1472 glPopMatrix();
1473
1474 flags |= GUI_RECT;
1475 }
1476
1477 switch (widget[id].type)
1478 {
1479 case GUI_HARRAY:
1480 case GUI_VARRAY:
1481 case GUI_HSTACK:
1482 case GUI_VSTACK:
1483
1484 /* Recursively paint all subwidgets. */
1485
1486 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1487 gui_paint_rect(jd, i, flags);
1488
1489 break;
1490 }
1491 }
1492
1493 /*---------------------------------------------------------------------------*/
1494
1495 static void gui_paint_text(int id);
1496
gui_paint_array(int id)1497 static void gui_paint_array(int id)
1498 {
1499 int jd;
1500
1501 glPushMatrix();
1502 {
1503 GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1504 GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1505 GLfloat ck = widget[id].scale;
1506
1507 if (1.0f < ck || ck < 1.0f)
1508 {
1509 glTranslatef(+cx, +cy, 0.0f);
1510 glScalef(ck, ck, ck);
1511 glTranslatef(-cx, -cy, 0.0f);
1512 }
1513
1514 /* Recursively paint all subwidgets. */
1515
1516 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1517 gui_paint_text(jd);
1518 }
1519 glPopMatrix();
1520 }
1521
gui_paint_image(int id)1522 static void gui_paint_image(int id)
1523 {
1524 /* Draw the widget rect, textured using the image. */
1525
1526 glPushMatrix();
1527 {
1528 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1529 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1530
1531 glScalef(widget[id].scale,
1532 widget[id].scale,
1533 widget[id].scale);
1534
1535 glBindTexture(GL_TEXTURE_2D, widget[id].image);
1536 glColor4ub(gui_wht[0], gui_wht[1], gui_wht[2], gui_wht[3]);
1537 draw_image(id);
1538 }
1539 glPopMatrix();
1540 }
1541
gui_paint_count(int id)1542 static void gui_paint_count(int id)
1543 {
1544 int j, i = widget[id].size;
1545
1546 glPushMatrix();
1547 {
1548 /* Translate to the widget center, and apply the pulse scale. */
1549
1550 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1551 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1552
1553 glScalef(widget[id].scale,
1554 widget[id].scale,
1555 widget[id].scale);
1556
1557 if (widget[id].value > 0)
1558 {
1559 /* Translate right by half the total width of the rendered value. */
1560
1561 GLfloat w = -widget[digit_id[i][0]].text_w * 0.5f;
1562
1563 for (j = widget[id].value; j; j /= 10)
1564 w += widget[digit_id[i][j % 10]].text_w * 0.5f;
1565
1566 glTranslatef(w, 0.0f, 0.0f);
1567
1568 /* Render each digit, moving left after each. */
1569
1570 for (j = widget[id].value; j; j /= 10)
1571 {
1572 int jd = digit_id[i][j % 10];
1573
1574 glBindTexture(GL_TEXTURE_2D, widget[jd].image);
1575 draw_text(jd);
1576 glTranslatef((GLfloat) -widget[jd].text_w, 0.0f, 0.0f);
1577 }
1578 }
1579 else if (widget[id].value == 0)
1580 {
1581 /* If the value is zero, just display a zero in place. */
1582
1583 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][0]].image);
1584 draw_text(digit_id[i][0]);
1585 }
1586 }
1587 glPopMatrix();
1588 }
1589
gui_paint_clock(int id)1590 static void gui_paint_clock(int id)
1591 {
1592 int i = widget[id].size;
1593 int mt = (widget[id].value / 6000) / 10;
1594 int mo = (widget[id].value / 6000) % 10;
1595 int st = ((widget[id].value % 6000) / 100) / 10;
1596 int so = ((widget[id].value % 6000) / 100) % 10;
1597 int ht = ((widget[id].value % 6000) % 100) / 10;
1598 int ho = ((widget[id].value % 6000) % 100) % 10;
1599
1600 GLfloat dx_large = (GLfloat) widget[digit_id[i][0]].text_w;
1601 GLfloat dx_small = (GLfloat) widget[digit_id[i][0]].text_w * 0.75f;
1602
1603 if (widget[id].value < 0)
1604 return;
1605
1606 glPushMatrix();
1607 {
1608 /* Translate to the widget center, and apply the pulse scale. */
1609
1610 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1611 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1612
1613 glScalef(widget[id].scale,
1614 widget[id].scale,
1615 widget[id].scale);
1616
1617 /* Translate left by half the total width of the rendered value. */
1618
1619 if (mt > 0)
1620 glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1621 else
1622 glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1623
1624 /* Render the minutes counter. */
1625
1626 if (mt > 0)
1627 {
1628 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][mt]].image);
1629 draw_text(digit_id[i][mt]);
1630 glTranslatef(dx_large, 0.0f, 0.0f);
1631 }
1632
1633 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][mo]].image);
1634 draw_text(digit_id[i][mo]);
1635 glTranslatef(dx_small, 0.0f, 0.0f);
1636
1637 /* Render the colon. */
1638
1639 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][10]].image);
1640 draw_text(digit_id[i][10]);
1641 glTranslatef(dx_small, 0.0f, 0.0f);
1642
1643 /* Render the seconds counter. */
1644
1645 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][st]].image);
1646 draw_text(digit_id[i][st]);
1647 glTranslatef(dx_large, 0.0f, 0.0f);
1648
1649 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][so]].image);
1650 draw_text(digit_id[i][so]);
1651 glTranslatef(dx_small, 0.0f, 0.0f);
1652
1653 /* Render hundredths counter half size. */
1654
1655 glScalef(0.5f, 0.5f, 1.0f);
1656
1657 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][ht]].image);
1658 draw_text(digit_id[i][ht]);
1659 glTranslatef(dx_large, 0.0f, 0.0f);
1660
1661 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][ho]].image);
1662 draw_text(digit_id[i][ho]);
1663 }
1664 glPopMatrix();
1665 }
1666
gui_paint_label(int id)1667 static void gui_paint_label(int id)
1668 {
1669 /* Short-circuit empty labels. */
1670
1671 if (widget[id].image == 0)
1672 return;
1673
1674 /* Draw the widget text box, textured using the glyph. */
1675
1676 glPushMatrix();
1677 {
1678 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1679 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1680
1681 glScalef(widget[id].scale,
1682 widget[id].scale,
1683 widget[id].scale);
1684
1685 glBindTexture(GL_TEXTURE_2D, widget[id].image);
1686 draw_text(id);
1687 }
1688 glPopMatrix();
1689 }
1690
gui_paint_text(int id)1691 static void gui_paint_text(int id)
1692 {
1693 switch (widget[id].type)
1694 {
1695 case GUI_SPACE: break;
1696 case GUI_FILLER: break;
1697 case GUI_HARRAY: gui_paint_array(id); break;
1698 case GUI_VARRAY: gui_paint_array(id); break;
1699 case GUI_HSTACK: gui_paint_array(id); break;
1700 case GUI_VSTACK: gui_paint_array(id); break;
1701 case GUI_IMAGE: gui_paint_image(id); break;
1702 case GUI_COUNT: gui_paint_count(id); break;
1703 case GUI_CLOCK: gui_paint_clock(id); break;
1704 default: gui_paint_label(id); break;
1705 }
1706 }
1707
gui_paint(int id)1708 void gui_paint(int id)
1709 {
1710 if (id)
1711 {
1712 video_push_ortho();
1713 {
1714 glDisable(GL_LIGHTING);
1715 glDisable(GL_DEPTH_TEST);
1716 {
1717 draw_enable(GL_FALSE, GL_TRUE, GL_TRUE);
1718 gui_paint_rect(id, 0, 0);
1719
1720 draw_enable(GL_TRUE, GL_TRUE, GL_TRUE);
1721 gui_paint_text(id);
1722
1723 if (cursor_st && cursor_id)
1724 gui_paint_image(cursor_id);
1725
1726 draw_disable();
1727 glColor4ub(gui_wht[0], gui_wht[1], gui_wht[2], gui_wht[3]);
1728 }
1729 glEnable(GL_DEPTH_TEST);
1730 glEnable(GL_LIGHTING);
1731 }
1732 video_pop_matrix();
1733 }
1734 }
1735
1736 /*---------------------------------------------------------------------------*/
1737
gui_dump(int id,int d)1738 void gui_dump(int id, int d)
1739 {
1740 int jd, i;
1741
1742 if (id)
1743 {
1744 char *type = "?";
1745
1746 switch (widget[id].type)
1747 {
1748 case GUI_HARRAY: type = "harray"; break;
1749 case GUI_VARRAY: type = "varray"; break;
1750 case GUI_HSTACK: type = "hstack"; break;
1751 case GUI_VSTACK: type = "vstack"; break;
1752 case GUI_FILLER: type = "filler"; break;
1753 case GUI_IMAGE: type = "image"; break;
1754 case GUI_LABEL: type = "label"; break;
1755 case GUI_COUNT: type = "count"; break;
1756 case GUI_CLOCK: type = "clock"; break;
1757 case GUI_BUTTON: type = "button"; break;
1758 }
1759
1760 for (i = 0; i < d; i++)
1761 printf(" ");
1762
1763 printf("%04d %s\n", id, type);
1764
1765 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1766 gui_dump(jd, d + 1);
1767 }
1768 }
1769
gui_pulse(int id,float k)1770 void gui_pulse(int id, float k)
1771 {
1772 if (id) widget[id].scale = k;
1773 }
1774
gui_timer(int id,float dt)1775 void gui_timer(int id, float dt)
1776 {
1777 int jd;
1778
1779 if (id)
1780 {
1781 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1782 gui_timer(jd, dt);
1783
1784 if (widget[id].scale - 1.0f < dt)
1785 widget[id].scale = 1.0f;
1786 else
1787 widget[id].scale -= dt;
1788 }
1789 }
1790
gui_point(int id,int x,int y)1791 int gui_point(int id, int x, int y)
1792 {
1793 static int x_cache = 0;
1794 static int y_cache = 0;
1795
1796 int jd;
1797
1798 /* Reuse the last coordinates if (x,y) == (-1,-1) */
1799
1800 if (x < 0 && y < 0)
1801 return gui_point(id, x_cache, y_cache);
1802
1803 x_cache = x;
1804 y_cache = y;
1805
1806 /* Move the cursor, if any. */
1807
1808 if (cursor_id)
1809 {
1810 widget[cursor_id].x = x - widget[cursor_id].w / 2;
1811 widget[cursor_id].y = y - widget[cursor_id].h / 2;
1812 }
1813
1814 /* Short-circuit check the current active widget. */
1815
1816 jd = gui_search(active, x, y);
1817
1818 /* If not still active, search the hierarchy for a new active widget. */
1819
1820 if (jd == 0)
1821 jd = gui_search(id, x, y);
1822
1823 /* Note hovered widget. */
1824
1825 hovered = jd;
1826
1827 /* If the active widget has changed, return the new active id. */
1828
1829 if (jd == 0 || jd == active)
1830 return 0;
1831 else
1832 return active = jd;
1833 }
1834
gui_focus(int i)1835 void gui_focus(int i)
1836 {
1837 active = i;
1838 }
1839
gui_active(void)1840 int gui_active(void)
1841 {
1842 return active;
1843 }
1844
gui_token(int id)1845 int gui_token(int id)
1846 {
1847 return id ? widget[id].token : 0;
1848 }
1849
gui_value(int id)1850 int gui_value(int id)
1851 {
1852 return id ? widget[id].value : 0;
1853 }
1854
gui_toggle(int id)1855 void gui_toggle(int id)
1856 {
1857 widget[id].flags ^= GUI_HILITE;
1858 }
1859
1860 /*---------------------------------------------------------------------------*/
1861
gui_vert_offset(int id,int jd)1862 static int gui_vert_offset(int id, int jd)
1863 {
1864 /* Vertical offset between bottom of id and top of jd */
1865
1866 return widget[id].y - (widget[jd].y + widget[jd].h);
1867 }
1868
gui_horz_offset(int id,int jd)1869 static int gui_horz_offset(int id, int jd)
1870 {
1871 /* Horizontal offset between left of id and right of jd */
1872
1873 return widget[id].x - (widget[jd].x + widget[jd].w);
1874 }
1875
gui_vert_overlap(int id,int jd)1876 static int gui_vert_overlap(int id, int jd)
1877 {
1878 /* Extent of vertical intersection of id and jd. */
1879
1880 const int a0 = widget[id].y;
1881 const int a1 = widget[id].y + widget[id].h;
1882 const int aw = widget[id].h;
1883 const int b0 = widget[jd].y;
1884 const int b1 = widget[jd].y + widget[jd].h;
1885 /* const int bw = widget[jd].h; */
1886
1887 return aw + MIN(b1 - a1, 0) - MAX(b0 - a0, 0);
1888 }
1889
gui_horz_overlap(int id,int jd)1890 static int gui_horz_overlap(int id, int jd)
1891 {
1892 /* Extent of horizontal intersection of id and jd. */
1893
1894 const int a0 = widget[id].x;
1895 const int a1 = widget[id].x + widget[id].w;
1896 const int aw = widget[id].w;
1897 const int b0 = widget[jd].x;
1898 const int b1 = widget[jd].x + widget[jd].w;
1899 /* const int bw = widget[jd].w; */
1900
1901 return aw + MIN(b1 - a1, 0) - MAX(b0 - a0, 0);
1902 }
1903
1904 /*---------------------------------------------------------------------------*/
1905
1906 /*
1907 * Widget navigation heuristics.
1908 *
1909 * People generally read left-to-right and top-to-bottom, and have
1910 * expectations on how navigation should behave. Thus, we hand-craft a
1911 * bunch of rules rather than devise a generic algorithm.
1912 *
1913 * Horizontal navigation only picks overlapping widgets. Closer is
1914 * better. Out of multiple closest widgets pick the topmost widget.
1915 *
1916 * Vertical navigation picks both overlapping and non-overlapping
1917 * widgets. Closer is better. Out of multiple closest widgets: if
1918 * overlapping, pick the leftmost widget; if not overlapping, pick the
1919 * one with the least negative overlap.
1920 */
1921
1922 /*
1923 * Leftmost/topmost is decided by the operator used to test
1924 * distance. A less-than will pick the first of a group of
1925 * equal-distance widgets, while a less-or-equal will pick the last
1926 * one.
1927 */
1928
1929 /* FIXME This isn't how you factor out reusable code. */
1930
1931 #define CHECK_HORIZONTAL \
1932 (o > 0 && (omin > 0 ? d <= dmin : 1))
1933
1934 #define CHECK_VERTICAL \
1935 (omin > 0 ? \
1936 d < dmin : \
1937 (o > 0 ? d <= dmin : (d < dmin || (d == dmin && o > omin))))
1938
gui_stick_L(int id,int dd)1939 static int gui_stick_L(int id, int dd)
1940 {
1941 int jd, kd, hd;
1942 int d, dmin, o, omin;
1943
1944 /* Find the closest "hot" widget to the left of dd (and inside id) */
1945
1946 if (id && gui_hot(id))
1947 return id;
1948
1949 hd = 0;
1950 dmin = widget[dd].x - widget[id].x + 1;
1951 omin = INT_MIN;
1952
1953 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1954 {
1955 kd = gui_stick_L(jd, dd);
1956
1957 if (kd && kd != dd)
1958 {
1959 d = gui_horz_offset(dd, kd);
1960 o = gui_vert_overlap(dd, kd);
1961
1962 if (d >= 0 && CHECK_HORIZONTAL)
1963 {
1964 hd = kd;
1965 dmin = d;
1966 omin = o;
1967 }
1968 }
1969 }
1970
1971 return hd;
1972 }
1973
gui_stick_R(int id,int dd)1974 static int gui_stick_R(int id, int dd)
1975 {
1976 int jd, kd, hd;
1977 int d, dmin, o, omin;
1978
1979 /* Find the closest "hot" widget to the right of dd (and inside id) */
1980
1981 if (id && gui_hot(id))
1982 return id;
1983
1984 hd = 0;
1985 dmin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
1986 omin = INT_MIN;
1987
1988 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1989 {
1990 kd = gui_stick_R(jd, dd);
1991
1992 if (kd && kd != dd)
1993 {
1994 d = gui_horz_offset(kd, dd);
1995 o = gui_vert_overlap(dd, kd);
1996
1997 if (d >= 0 && CHECK_HORIZONTAL)
1998 {
1999 hd = kd;
2000 dmin = d;
2001 omin = o;
2002 }
2003 }
2004 }
2005
2006 return hd;
2007 }
2008
gui_stick_D(int id,int dd)2009 static int gui_stick_D(int id, int dd)
2010 {
2011 int jd, kd, hd;
2012 int d, dmin, o, omin;
2013
2014 /* Find the closest "hot" widget below dd (and inside id) */
2015
2016 if (id && gui_hot(id))
2017 return id;
2018
2019 hd = 0;
2020 dmin = widget[dd].y - widget[id].y + 1;
2021 omin = INT_MIN;
2022
2023 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
2024 {
2025 kd = gui_stick_D(jd, dd);
2026
2027 if (kd && kd != dd)
2028 {
2029 d = gui_vert_offset(dd, kd);
2030 o = gui_horz_overlap(dd, kd);
2031
2032 if (d >= 0 && CHECK_VERTICAL)
2033 {
2034 hd = kd;
2035 dmin = d;
2036 omin = o;
2037 }
2038 }
2039 }
2040
2041 return hd;
2042 }
2043
gui_stick_U(int id,int dd)2044 static int gui_stick_U(int id, int dd)
2045 {
2046 int jd, kd, hd;
2047 int d, dmin, o, omin;
2048
2049 /* Find the closest "hot" widget above dd (and inside id) */
2050
2051 if (id && gui_hot(id))
2052 return id;
2053
2054 hd = 0;
2055 dmin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
2056 omin = INT_MIN;
2057
2058 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
2059 {
2060 kd = gui_stick_U(jd, dd);
2061
2062 if (kd && kd != dd)
2063 {
2064 d = gui_vert_offset(kd, dd);
2065 o = gui_horz_overlap(dd, kd);
2066
2067 if (d >= 0 && CHECK_VERTICAL)
2068 {
2069 hd = kd;
2070 dmin = d;
2071 omin = o;
2072 }
2073 }
2074 }
2075
2076 return hd;
2077 }
2078
2079 /*---------------------------------------------------------------------------*/
2080
gui_wrap_L(int id,int dd)2081 static int gui_wrap_L(int id, int dd)
2082 {
2083 int jd, kd;
2084
2085 if ((jd = gui_stick_L(id, dd)) == 0)
2086 for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
2087 ;
2088
2089 return jd;
2090 }
2091
gui_wrap_R(int id,int dd)2092 static int gui_wrap_R(int id, int dd)
2093 {
2094 int jd, kd;
2095
2096 if ((jd = gui_stick_R(id, dd)) == 0)
2097 for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
2098 ;
2099
2100 return jd;
2101 }
2102
gui_wrap_U(int id,int dd)2103 static int gui_wrap_U(int id, int dd)
2104 {
2105 int jd, kd;
2106
2107 if ((jd = gui_stick_U(id, dd)) == 0)
2108 for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
2109 ;
2110
2111 return jd;
2112 }
2113
gui_wrap_D(int id,int dd)2114 static int gui_wrap_D(int id, int dd)
2115 {
2116 int jd, kd;
2117
2118 if ((jd = gui_stick_D(id, dd)) == 0)
2119 for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
2120 ;
2121
2122 return jd;
2123 }
2124
2125 /*---------------------------------------------------------------------------*/
2126
gui_stick(int id,int a,float v,int bump)2127 int gui_stick(int id, int a, float v, int bump)
2128 {
2129 int jd = 0;
2130
2131 if (!bump)
2132 return 0;
2133
2134 /* Find a new active widget in the direction of joystick motion. */
2135
2136 if (config_tst_d(CONFIG_JOYSTICK_AXIS_X0, a))
2137 {
2138 if (v < 0) jd = gui_wrap_L(id, active);
2139 if (v > 0) jd = gui_wrap_R(id, active);
2140 }
2141 else if (config_tst_d(CONFIG_JOYSTICK_AXIS_Y0, a))
2142 {
2143 if (v < 0) jd = gui_wrap_U(id, active);
2144 if (v > 0) jd = gui_wrap_D(id, active);
2145 }
2146
2147 /* If the active widget has changed, return the new active id. */
2148
2149 if (jd == 0 || jd == active)
2150 return 0;
2151 else
2152 return active = jd;
2153 }
2154
gui_click(int b,int d)2155 int gui_click(int b, int d)
2156 {
2157 if (b == SDL_BUTTON_LEFT)
2158 {
2159 if (d)
2160 {
2161 clicked = hovered;
2162 return 0;
2163 }
2164 else
2165 {
2166 int c = (clicked && clicked == hovered);
2167 clicked = 0;
2168 return c;
2169 }
2170 }
2171 return 0;
2172 }
2173
2174 /*---------------------------------------------------------------------------*/
2175
gui_navig(int id,int total,int first,int step)2176 int gui_navig(int id, int total, int first, int step)
2177 {
2178 int pages = (int) ceil((double) total / step);
2179 int page = first / step + 1;
2180
2181 int prev = (page > 1);
2182 int next = (page < pages);
2183
2184 int jd, kd;
2185
2186 if ((jd = gui_hstack(id)))
2187 {
2188 if (next || prev)
2189 {
2190 gui_maybe(jd, " > ", GUI_NEXT, GUI_NONE, next);
2191
2192 if ((kd = gui_label(jd, "999/999", GUI_SML, gui_wht, gui_wht)))
2193 {
2194 char str[16];
2195 sprintf(str, "%d/%d", page, pages);
2196 gui_set_label(kd, str);
2197 }
2198
2199 gui_maybe(jd, " < ", GUI_PREV, GUI_NONE, prev);
2200 }
2201
2202 gui_space(jd);
2203
2204 gui_start(jd, _("Back"), GUI_SML, GUI_BACK, 0);
2205 }
2206 return jd;
2207 }
2208
gui_maybe(int id,const char * label,int etoken,int dtoken,int enabled)2209 int gui_maybe(int id, const char *label, int etoken, int dtoken, int enabled)
2210 {
2211 int bd;
2212
2213 if (!enabled)
2214 {
2215 bd = gui_state(id, label, GUI_SML, dtoken, 0);
2216 gui_set_color(bd, gui_gry, gui_gry);
2217 }
2218 else
2219 bd = gui_state(id, label, GUI_SML, etoken, 0);
2220
2221 return bd;
2222 }
2223
2224 /*---------------------------------------------------------------------------*/
2225