54181bf60885c7a5b748be02da226b83134b7c7b
[neverball] / share / gui.c
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
19 #include "config.h"
20 #include "video.h"
21 #include "glext.h"
22 #include "image.h"
23 #include "vec3.h"
24 #include "gui.h"
25 #include "common.h"
26
27 #include "fs.h"
28 #include "fs_rwops.h"
29
30 /*---------------------------------------------------------------------------*/
31
32 /* Very pure colors for the GUI. I was watching BANZAI when I designed this. */
33
34 const GLubyte gui_wht[4] = { 0xFF, 0xFF, 0xFF, 0xFF };  /* White  */
35 const GLubyte gui_yel[4] = { 0xFF, 0xFF, 0x00, 0xFF };  /* Yellow */
36 const GLubyte gui_red[4] = { 0xFF, 0x00, 0x00, 0xFF };  /* Red    */
37 const GLubyte gui_grn[4] = { 0x00, 0xFF, 0x00, 0xFF };  /* Green  */
38 const GLubyte gui_blu[4] = { 0x00, 0x00, 0xFF, 0xFF };  /* Blue   */
39 const GLubyte gui_blk[4] = { 0x00, 0x00, 0x00, 0xFF };  /* Black  */
40 const GLubyte gui_gry[4] = { 0x55, 0x55, 0x55, 0xFF };  /* Gray   */
41 const GLubyte gui_shd[4] = { 0x00, 0x00, 0x00, 0x80 };  /* Shadow */
42
43 /*---------------------------------------------------------------------------*/
44
45 #define WIDGET_MAX 256
46
47 #define GUI_TYPE 0xFFFC
48
49 #define GUI_FREE  0
50
51 #define GUI_STATE 1
52 #define GUI_FILL  2
53
54 #define GUI_FLAGS 2
55
56 #define GUI_HARRAY (1  << GUI_FLAGS)
57 #define GUI_VARRAY (2  << GUI_FLAGS)
58 #define GUI_HSTACK (3  << GUI_FLAGS)
59 #define GUI_VSTACK (4  << GUI_FLAGS)
60 #define GUI_FILLER (5  << GUI_FLAGS)
61 #define GUI_IMAGE  (6  << GUI_FLAGS)
62 #define GUI_LABEL  (7  << GUI_FLAGS)
63 #define GUI_COUNT  (8  << GUI_FLAGS)
64 #define GUI_CLOCK  (9  << GUI_FLAGS)
65 #define GUI_SPACE  (10 << GUI_FLAGS)
66
67 #define GUI_LINES 8
68
69 /*---------------------------------------------------------------------------*/
70
71 struct widget
72 {
73     int     type;
74     int     token;
75     int     value;
76     int     size;
77     int     rect;
78
79     const GLubyte *color0;
80     const GLubyte *color1;
81
82     int     x, y;
83     int     w, h;
84     int     car;
85     int     cdr;
86
87     GLuint  image;
88     GLfloat scale;
89
90     int     text_w;
91     int     text_h;
92
93     enum trunc trunc;
94 };
95
96 /*---------------------------------------------------------------------------*/
97
98 /* GUI widget state */
99
100 static struct widget widget[WIDGET_MAX];
101 static int           active;
102 static int           radius;
103 static TTF_Font     *font[3] = { NULL, NULL, NULL };
104
105 /* Digit widgets for the HUD. */
106
107 static int digit_id[3][11];
108
109 /* Font data access. */
110
111 static void      *fontdata;
112 static int        fontdatalen;
113 static SDL_RWops *fontrwops;
114
115 /*---------------------------------------------------------------------------*/
116
117 static int gui_hot(int id)
118 {
119     return (widget[id].type & GUI_STATE);
120 }
121
122 /*---------------------------------------------------------------------------*/
123
124 /* Vertex buffer definitions for widget rendering. */
125
126 #define RECT_LEN 36
127 #define TEXT_LEN 8
128 #define WIDGET_LEN (RECT_LEN + TEXT_LEN)
129
130 struct vert
131 {
132     GLubyte c[4];
133     GLfloat u[2];
134     GLshort p[2];
135 };
136
137 static struct vert vert_buf[WIDGET_MAX * WIDGET_LEN];
138 static GLuint      vert_obj = 0;
139
140 /*---------------------------------------------------------------------------*/
141
142 static void set_vert(struct vert *v, int x, int y,
143                      GLfloat s, GLfloat t, const GLubyte *c)
144 {
145     v->c[0] = c[0];
146     v->c[1] = c[1];
147     v->c[2] = c[2];
148     v->c[3] = c[3];
149     v->u[0] = s;
150     v->u[1] = t;
151     v->p[0] = x;
152     v->p[1] = y;
153 }
154
155 /*---------------------------------------------------------------------------*/
156
157 static void draw_enable(GLboolean c, GLboolean u, GLboolean p)
158 {
159     glBindBuffer_(GL_ARRAY_BUFFER, vert_obj);
160
161     if (c)
162     {
163         glEnableClientState(GL_COLOR_ARRAY);
164         glColorPointer   (4, GL_UNSIGNED_BYTE, sizeof (struct vert),
165                                   (GLvoid *) offsetof (struct vert, c));
166     }
167     if (u)
168     {
169         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
170         glTexCoordPointer(2, GL_FLOAT,         sizeof (struct vert),
171                                   (GLvoid *) offsetof (struct vert, u));
172     }
173     if (p)
174     {
175         glEnableClientState(GL_VERTEX_ARRAY);
176         glVertexPointer  (2, GL_SHORT,         sizeof (struct vert),
177                                   (GLvoid *) offsetof (struct vert, p));
178     }
179 }
180
181 static void draw_rect(int id)
182 {
183     glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_LEN,  RECT_LEN);
184 }
185
186 static void draw_text(int id)
187 {
188     glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_LEN + RECT_LEN, TEXT_LEN);
189 }
190
191 static void draw_disable(void)
192 {
193     glBindBuffer_(GL_ARRAY_BUFFER, 0);
194
195     glDisableClientState(GL_VERTEX_ARRAY);
196     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
197     glDisableClientState(GL_COLOR_ARRAY);
198 }
199
200 /*---------------------------------------------------------------------------*/
201
202 static void gui_rect(int id, int x, int y, int w, int h, int f, int r)
203 {
204     struct vert *v = vert_buf + id * WIDGET_LEN;
205     struct vert *p = v;
206
207     /* Generate vertex data for the widget's rounded rectangle. */
208
209     int n = 8;
210     int i;
211
212     /* Left side... */
213
214     for (i = 0; i <= n; i++)
215     {
216         float a = 0.5f * V_PI * (float) i / (float) n;
217         float s = r * fsinf(a);
218         float c = r * fcosf(a);
219
220         float X  = x     + r - c;
221         float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
222         float Yb = y     + ((f & GUI_SW) ? (r - s) : 0);
223
224         set_vert(p++, X, Ya, (X - x) / w, (Ya - y) / h, gui_wht);
225         set_vert(p++, X, Yb, (X - x) / w, (Yb - y) / h, gui_wht);
226     }
227
228     /* Right side... */
229
230     for (i = 0; i <= n; i++)
231     {
232         float a = 0.5f * V_PI * (float) i / (float) n;
233         float s = r * fsinf(a);
234         float c = r * fcosf(a);
235
236         float X  = x + w - r + s;
237         float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
238         float Yb = y     + ((f & GUI_SE) ? (r - c) : 0);
239
240         set_vert(p++, X, Ya, (X - x) / w, (Ya - y) / h, gui_wht);
241         set_vert(p++, X, Yb, (X - x) / w, (Yb - y) / h, gui_wht);
242     }
243
244     /* Copy this off to the VBO. */
245
246     glBindBuffer_   (GL_ARRAY_BUFFER, vert_obj);
247     glBufferSubData_(GL_ARRAY_BUFFER,
248                      id * WIDGET_LEN * sizeof (struct vert),
249                             RECT_LEN * sizeof (struct vert), v);
250 }
251
252 static void gui_text(int id, int x, int y,
253                              int w, int h, const GLubyte *c0, const GLubyte *c1)
254 {
255     struct vert *v = vert_buf + id * WIDGET_LEN + RECT_LEN;
256
257     /* Assume the applied texture size is rect size rounded to power-of-two. */
258
259     int W;
260     int H;
261
262     image_size(&W, &H, w, h);
263
264     if (w > 0 && h > 0 && W > 0 && H > 0)
265     {
266         const int d = h / 16;  /* Shadow offset */
267
268         const int ww = ((W - w) % 2) ? w + 1 : w;
269         const int hh = ((H - h) % 2) ? h + 1 : h;
270
271         const GLfloat s0 = 0.5f * (W - ww) / W;
272         const GLfloat t0 = 0.5f * (H - hh) / H;
273         const GLfloat s1 = 1.0f - s0;
274         const GLfloat t1 = 1.0f - t0;
275
276         /* Generate vertex data for the colored text and its shadow. */
277
278         set_vert(v + 0, x      + d, y + hh - d, s0, t0, gui_shd);
279         set_vert(v + 1, x      + d, y      - d, s0, t1, gui_shd);
280         set_vert(v + 2, x + ww + d, y + hh - d, s1, t0, gui_shd);
281         set_vert(v + 3, x + ww + d, y      - d, s1, t1, gui_shd);
282
283         set_vert(v + 4, x,          y + hh,     s0, t0, c1);
284         set_vert(v + 5, x,          y,          s0, t1, c0);
285         set_vert(v + 6, x + ww,     y + hh,     s1, t0, c1);
286         set_vert(v + 7, x + ww,     y,          s1, t1, c0);
287
288     }
289     else memset(v, 0, TEXT_LEN * sizeof (struct vert));
290
291     /* Copy this off to the VBO. */
292
293     glBindBuffer_   (GL_ARRAY_BUFFER, vert_obj);
294     glBufferSubData_(GL_ARRAY_BUFFER,
295                      (id * WIDGET_LEN + RECT_LEN) * sizeof (struct vert),
296                                         TEXT_LEN  * sizeof (struct vert), v);
297 }
298
299 /*---------------------------------------------------------------------------*/
300
301 static const char *pick_font_path(void)
302 {
303     const char *path;
304
305     path = _(GUI_FACE);
306
307     if (!fs_exists(path))
308     {
309         fprintf(stderr, L_("Font '%s' doesn't exist, trying default font.\n"),
310                 path);
311
312         path = GUI_FACE;
313     }
314
315     return path;
316 }
317
318 void gui_init(void)
319 {
320     int w = config_get_d(CONFIG_WIDTH);
321     int h = config_get_d(CONFIG_HEIGHT);
322     int s = (h < w) ? h : w;
323     int i, j;
324
325     /* Initialize font rendering. */
326
327     if (TTF_Init() == 0)
328     {
329         const char *fontpath = pick_font_path();
330
331         int s0 = s / 26;
332         int s1 = s / 13;
333         int s2 = s /  7;
334
335         memset(widget, 0, sizeof (struct widget) * WIDGET_MAX);
336
337         /* Load the font. */
338
339         if ((fontdata = fs_load(fontpath, &fontdatalen)))
340         {
341             fontrwops = SDL_RWFromConstMem(fontdata, fontdatalen);
342
343             /* Load small, medium, and large typefaces. */
344
345             font[GUI_SML] = TTF_OpenFontRW(fontrwops, 0, s0);
346
347             SDL_RWseek(fontrwops, 0, SEEK_SET);
348             font[GUI_MED] = TTF_OpenFontRW(fontrwops, 0, s1);
349
350             SDL_RWseek(fontrwops, 0, SEEK_SET);
351             font[GUI_LRG] = TTF_OpenFontRW(fontrwops, 0, s2);
352
353             /* fontrwops remains open. */
354         }
355         else
356         {
357             fontrwops = NULL;
358
359             font[GUI_SML] = NULL;
360             font[GUI_MED] = NULL;
361             font[GUI_LRG] = NULL;
362
363             fprintf(stderr, L_("Could not load font '%s'.\n"), fontpath);
364         }
365
366         radius = s / 60;
367     }
368
369     /* Initialize the VBOs. */
370
371     memset(vert_buf, 0, sizeof (vert_buf));
372
373     glGenBuffers_(1,              &vert_obj);
374     glBindBuffer_(GL_ARRAY_BUFFER, vert_obj);
375     glBufferData_(GL_ARRAY_BUFFER, sizeof (vert_buf), vert_buf, GL_STATIC_DRAW);
376     glBindBuffer_(GL_ARRAY_BUFFER, 0);
377
378     /* Cache digit glyphs for HUD rendering. */
379
380     for (i = 0; i < 3; i++)
381     {
382         digit_id[i][ 0] = gui_label(0, "0", i, 0, 0, 0);
383         digit_id[i][ 1] = gui_label(0, "1", i, 0, 0, 0);
384         digit_id[i][ 2] = gui_label(0, "2", i, 0, 0, 0);
385         digit_id[i][ 3] = gui_label(0, "3", i, 0, 0, 0);
386         digit_id[i][ 4] = gui_label(0, "4", i, 0, 0, 0);
387         digit_id[i][ 5] = gui_label(0, "5", i, 0, 0, 0);
388         digit_id[i][ 6] = gui_label(0, "6", i, 0, 0, 0);
389         digit_id[i][ 7] = gui_label(0, "7", i, 0, 0, 0);
390         digit_id[i][ 8] = gui_label(0, "8", i, 0, 0, 0);
391         digit_id[i][ 9] = gui_label(0, "9", i, 0, 0, 0);
392         digit_id[i][10] = gui_label(0, ":", i, 0, 0, 0);
393     }
394
395     for (i = 0; i < 3; i++)
396         for (j = 0; j < 11; ++j)
397             gui_layout(digit_id[i][j], 0, 0);
398
399     active = 0;
400 }
401
402 void gui_free(void)
403 {
404     int id;
405
406     /* Release the VBOs. */
407
408     if (glIsBuffer_(vert_obj))
409         glDeleteBuffers_(1, &vert_obj);
410
411     /* Release any remaining widget texture and display list indices. */
412
413     for (id = 1; id < WIDGET_MAX; id++)
414     {
415         if (glIsTexture(widget[id].image))
416             glDeleteTextures(1, &widget[id].image);
417
418         widget[id].type  = GUI_FREE;
419         widget[id].image = 0;
420         widget[id].cdr   = 0;
421         widget[id].car   = 0;
422     }
423
424     /* Release all loaded fonts and finalize font rendering. */
425
426     if (font[GUI_LRG]) TTF_CloseFont(font[GUI_LRG]);
427     if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
428     if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
429
430     if (fontrwops) SDL_RWclose(fontrwops);
431     if (fontdata)  free(fontdata);
432
433     TTF_Quit();
434 }
435
436 /*---------------------------------------------------------------------------*/
437
438 static int gui_widget(int pd, int type)
439 {
440     int id;
441
442     /* Find an unused entry in the widget table. */
443
444     for (id = 1; id < WIDGET_MAX; id++)
445         if (widget[id].type == GUI_FREE)
446         {
447             /* Set the type and default properties. */
448
449             widget[id].type   = type;
450             widget[id].token  = 0;
451             widget[id].value  = 0;
452             widget[id].size   = 0;
453             widget[id].rect   = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
454             widget[id].w      = 0;
455             widget[id].h      = 0;
456             widget[id].image  = 0;
457             widget[id].color0 = gui_wht;
458             widget[id].color1 = gui_wht;
459             widget[id].scale  = 1.0f;
460             widget[id].trunc  = TRUNC_NONE;
461             widget[id].text_w = 0;
462             widget[id].text_h = 0;
463
464             /* Insert the new widget into the parent's widget list. */
465
466             if (pd)
467             {
468                 widget[id].car = 0;
469                 widget[id].cdr = widget[pd].car;
470                 widget[pd].car = id;
471             }
472             else
473             {
474                 widget[id].car = 0;
475                 widget[id].cdr = 0;
476             }
477
478             return id;
479         }
480
481     fprintf(stderr, "Out of widget IDs\n");
482
483     return 0;
484 }
485
486 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
487 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
488 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
489 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
490 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
491
492 /*---------------------------------------------------------------------------*/
493
494 struct size
495 {
496     int w, h;
497 };
498
499 static struct size gui_measure(const char *text, TTF_Font *font)
500 {
501     struct size size = { 0, 0 };
502
503     if (font)
504         TTF_SizeUTF8(font, text, &size.w, &size.h);
505
506     return size;
507 }
508
509 static char *gui_trunc_head(const char *text,
510                             const int maxwidth,
511                             TTF_Font *font)
512 {
513     int left, right, mid;
514     char *str = NULL;
515
516     left  = 0;
517     right = strlen(text);
518
519     while (right - left > 1)
520     {
521         mid = (left + right) / 2;
522
523         str = concat_string("...", text + mid, NULL);
524
525         if (gui_measure(str, font).w <= maxwidth)
526             right = mid;
527         else
528             left = mid;
529
530         free(str);
531     }
532
533     return concat_string("...", text + right, NULL);
534 }
535
536 static char *gui_trunc_tail(const char *text,
537                             const int maxwidth,
538                             TTF_Font *font)
539 {
540     int left, right, mid;
541     char *str = NULL;
542
543     left  = 0;
544     right = strlen(text);
545
546     while (right - left > 1)
547     {
548         mid = (left + right) / 2;
549
550         str = malloc(mid + sizeof ("..."));
551
552         memcpy(str,       text,  mid);
553         memcpy(str + mid, "...", sizeof ("..."));
554
555         if (gui_measure(str, font).w <= maxwidth)
556             left = mid;
557         else
558             right = mid;
559
560         free(str);
561     }
562
563     str = malloc(left + sizeof ("..."));
564
565     memcpy(str,        text,  left);
566     memcpy(str + left, "...", sizeof ("..."));
567
568     return str;
569 }
570
571 static char *gui_truncate(const char *text,
572                           const int maxwidth,
573                           TTF_Font *font,
574                           enum trunc trunc)
575 {
576     if (gui_measure(text, font).w <= maxwidth)
577         return strdup(text);
578
579     switch (trunc)
580     {
581     case TRUNC_NONE: return strdup(text);                         break;
582     case TRUNC_HEAD: return gui_trunc_head(text, maxwidth, font); break;
583     case TRUNC_TAIL: return gui_trunc_tail(text, maxwidth, font); break;
584     }
585
586     return NULL;
587 }
588
589 /*---------------------------------------------------------------------------*/
590
591 void gui_set_image(int id, const char *file)
592 {
593     if (glIsTexture(widget[id].image))
594         glDeleteTextures(1, &widget[id].image);
595
596     widget[id].image = make_image_from_file(file);
597 }
598
599 void gui_set_label(int id, const char *text)
600 {
601     int w = 0;
602     int h = 0;
603
604     if (glIsTexture(widget[id].image))
605         glDeleteTextures(1, &widget[id].image);
606
607     text = gui_truncate(text, widget[id].w - radius,
608                         font[widget[id].size],
609                         widget[id].trunc);
610
611     widget[id].image = make_image_from_font(NULL, NULL, &w, &h,
612                                             text, font[widget[id].size]);
613     widget[id].text_w = w;
614     widget[id].text_h = h;
615
616     gui_text(id, -w / 2, -h / 2, w, h, widget[id].color0, widget[id].color1);
617
618     free((void *) text); /* Really? */
619 }
620
621 void gui_set_count(int id, int value)
622 {
623     widget[id].value = value;
624 }
625
626 void gui_set_clock(int id, int value)
627 {
628     widget[id].value = value;
629 }
630
631 void gui_set_color(int id, const GLubyte *c0,
632                            const GLubyte *c1)
633 {
634     if (id)
635     {
636         c0 = c0 ? c0 : gui_yel;
637         c1 = c1 ? c1 : gui_red;
638
639         if (widget[id].color0 != c0 || widget[id].color1 != c1)
640         {
641             int w = widget[id].text_w;
642             int h = widget[id].text_h;
643
644             widget[id].color0 = c0;
645             widget[id].color1 = c1;
646
647             gui_text(id, -w / 2, -h / 2, w, h, c0, c1);
648         }
649     }
650 }
651
652 void gui_set_multi(int id, const char *text)
653 {
654     const char *p;
655
656     char s[GUI_LINES][MAXSTR];
657     int i, sc, lc, jd;
658
659     size_t n = 0;
660
661     /* Count available labels. */
662
663     for (lc = 0, jd = widget[id].car; jd; lc++, jd = widget[jd].cdr);
664
665     /* Copy each delimited string to a line buffer. */
666
667     for (p = text, sc = 0; *p && sc < lc; sc++)
668     {
669         strncpy(s[sc], p, (n = strcspn(p, "\\")));
670         s[sc][n] = 0;
671
672         if (*(p += n) == '\\') p++;
673     }
674
675     /* Set the label value for each line. */
676
677     for (i = lc - 1, jd = widget[id].car; i >= 0; i--, jd = widget[jd].cdr)
678         gui_set_label(jd, i < sc ? s[i] : "");
679 }
680
681 void gui_set_trunc(int id, enum trunc trunc)
682 {
683     widget[id].trunc = trunc;
684 }
685
686 void gui_set_fill(int id)
687 {
688     widget[id].type |= GUI_FILL;
689 }
690
691 /*---------------------------------------------------------------------------*/
692
693 int gui_image(int pd, const char *file, int w, int h)
694 {
695     int id;
696
697     if ((id = gui_widget(pd, GUI_IMAGE)))
698     {
699         widget[id].image = make_image_from_file(file);
700         widget[id].w = w;
701         widget[id].h = h;
702     }
703     return id;
704 }
705
706 int gui_start(int pd, const char *text, int size, int token, int value)
707 {
708     int id;
709
710     if ((id = gui_state(pd, text, size, token, value)))
711         active = id;
712
713     return id;
714 }
715
716 int gui_state(int pd, const char *text, int size, int token, int value)
717 {
718     int id;
719
720     if ((id = gui_widget(pd, GUI_STATE)))
721     {
722         widget[id].image = make_image_from_font(NULL, NULL,
723                                                    &widget[id].w,
724                                                    &widget[id].h,
725                                                    text, font[size]);
726         widget[id].size  = size;
727         widget[id].token = token;
728         widget[id].value = value;
729     }
730     return id;
731 }
732
733 int gui_label(int pd, const char *text, int size, int rect, const GLubyte *c0,
734                                                             const GLubyte *c1)
735 {
736     int id;
737
738     if ((id = gui_widget(pd, GUI_LABEL)))
739     {
740         widget[id].image = make_image_from_font(NULL, NULL,
741                                                 &widget[id].w,
742                                                 &widget[id].h,
743                                                 text, font[size]);
744         widget[id].size   = size;
745         widget[id].color0 = c0 ? c0 : gui_yel;
746         widget[id].color1 = c1 ? c1 : gui_red;
747         widget[id].rect   = rect;
748     }
749     return id;
750 }
751
752 int gui_count(int pd, int value, int size, int rect)
753 {
754     int i, id;
755
756     if ((id = gui_widget(pd, GUI_COUNT)))
757     {
758         for (i = value; i; i /= 10)
759             widget[id].w += widget[digit_id[size][0]].text_w;
760
761         widget[id].h      = widget[digit_id[size][0]].text_h;
762         widget[id].value  = value;
763         widget[id].size   = size;
764         widget[id].color0 = gui_yel;
765         widget[id].color1 = gui_red;
766         widget[id].rect   = rect;
767     }
768     return id;
769 }
770
771 int gui_clock(int pd, int value, int size, int rect)
772 {
773     int id;
774
775     if ((id = gui_widget(pd, GUI_CLOCK)))
776     {
777         widget[id].w      = widget[digit_id[size][0]].text_w * 6;
778         widget[id].h      = widget[digit_id[size][0]].text_h;
779         widget[id].value  = value;
780         widget[id].size   = size;
781         widget[id].color0 = gui_yel;
782         widget[id].color1 = gui_red;
783         widget[id].rect   = rect;
784     }
785     return id;
786 }
787
788 int gui_space(int pd)
789 {
790     int id;
791
792     if ((id = gui_widget(pd, GUI_SPACE)))
793     {
794         widget[id].w = 0;
795         widget[id].h = 0;
796     }
797     return id;
798 }
799
800 /*---------------------------------------------------------------------------*/
801
802 /*
803  * Create  a multi-line  text box  using a  vertical array  of labels.
804  * Parse the  text for '\'  characters and treat them  as line-breaks.
805  * Preserve the rect specification across the entire array.
806  */
807
808 int gui_multi(int pd, const char *text, int size, int rect, const GLubyte *c0,
809                                                             const GLubyte *c1)
810 {
811     int id = 0;
812
813     if (text && (id = gui_varray(pd)))
814     {
815         const char *p;
816
817         char s[GUI_LINES][MAXSTR];
818         int  r[GUI_LINES];
819         int  i, j;
820
821         size_t n = 0;
822
823         /* Copy each delimited string to a line buffer. */
824
825         for (p = text, j = 0; *p && j < GUI_LINES; j++)
826         {
827             strncpy(s[j], p, (n = strcspn(p, "\\")));
828             s[j][n] = 0;
829             r[j]    = 0;
830
831             if (*(p += n) == '\\') p++;
832         }
833
834         /* Set the curves for the first and last lines. */
835
836         if (j > 0)
837         {
838             r[0]     |= rect & (GUI_NW | GUI_NE);
839             r[j - 1] |= rect & (GUI_SW | GUI_SE);
840         }
841
842         /* Create a label widget for each line. */
843
844         for (i = 0; i < j; i++)
845             gui_label(id, s[i], size, r[i], c0, c1);
846     }
847     return id;
848 }
849
850 /*---------------------------------------------------------------------------*/
851 /*
852  * The bottom-up pass determines the area of all widgets.  The minimum
853  * width  and height of  a leaf  widget is  given by  the size  of its
854  * contents.   Array  and  stack   widths  and  heights  are  computed
855  * recursively from these.
856  */
857
858 static void gui_widget_up(int id);
859
860 static void gui_harray_up(int id)
861 {
862     int jd, c = 0;
863
864     /* Find the widest child width and the highest child height. */
865
866     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
867     {
868         gui_widget_up(jd);
869
870         if (widget[id].h < widget[jd].h)
871             widget[id].h = widget[jd].h;
872         if (widget[id].w < widget[jd].w)
873             widget[id].w = widget[jd].w;
874
875         c++;
876     }
877
878     /* Total width is the widest child width times the child count. */
879
880     widget[id].w *= c;
881 }
882
883 static void gui_varray_up(int id)
884 {
885     int jd, c = 0;
886
887     /* Find the widest child width and the highest child height. */
888
889     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
890     {
891         gui_widget_up(jd);
892
893         if (widget[id].h < widget[jd].h)
894             widget[id].h = widget[jd].h;
895         if (widget[id].w < widget[jd].w)
896             widget[id].w = widget[jd].w;
897
898         c++;
899     }
900
901     /* Total height is the highest child height times the child count. */
902
903     widget[id].h *= c;
904 }
905
906 static void gui_hstack_up(int id)
907 {
908     int jd;
909
910     /* Find the highest child height.  Sum the child widths. */
911
912     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
913     {
914         gui_widget_up(jd);
915
916         if (widget[id].h < widget[jd].h)
917             widget[id].h = widget[jd].h;
918
919         widget[id].w += widget[jd].w;
920     }
921 }
922
923 static void gui_vstack_up(int id)
924 {
925     int jd;
926
927     /* Find the widest child width.  Sum the child heights. */
928
929     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
930     {
931         gui_widget_up(jd);
932
933         if (widget[id].w < widget[jd].w)
934             widget[id].w = widget[jd].w;
935
936         widget[id].h += widget[jd].h;
937     }
938 }
939
940 static void gui_button_up(int id)
941 {
942     /* Store width and height for later use in text rendering. */
943
944     widget[id].text_w = widget[id].w;
945     widget[id].text_h = widget[id].h;
946
947     if (widget[id].w < widget[id].h && widget[id].w > 0)
948         widget[id].w = widget[id].h;
949
950     /* Padded text elements look a little nicer. */
951
952     if (widget[id].w < config_get_d(CONFIG_WIDTH))
953         widget[id].w += radius;
954     if (widget[id].h < config_get_d(CONFIG_HEIGHT))
955         widget[id].h += radius;
956
957     /* A button should be at least wide enough to accomodate the rounding. */
958
959     if (widget[id].w < 2 * radius)
960         widget[id].w = 2 * radius;
961     if (widget[id].h < 2 * radius)
962         widget[id].h = 2 * radius;
963 }
964
965 static void gui_widget_up(int id)
966 {
967     if (id)
968         switch (widget[id].type & GUI_TYPE)
969         {
970         case GUI_HARRAY: gui_harray_up(id); break;
971         case GUI_VARRAY: gui_varray_up(id); break;
972         case GUI_HSTACK: gui_hstack_up(id); break;
973         case GUI_VSTACK: gui_vstack_up(id); break;
974         case GUI_FILLER:                    break;
975         default:         gui_button_up(id); break;
976         }
977 }
978
979 /*---------------------------------------------------------------------------*/
980 /*
981  * The  top-down layout  pass distributes  available area  as computed
982  * during the bottom-up pass.  Widgets  use their area and position to
983  * initialize rendering state.
984  */
985
986 static void gui_widget_dn(int id, int x, int y, int w, int h);
987
988 static void gui_harray_dn(int id, int x, int y, int w, int h)
989 {
990     int jd, i = 0, c = 0;
991
992     widget[id].x = x;
993     widget[id].y = y;
994     widget[id].w = w;
995     widget[id].h = h;
996
997     /* Count children. */
998
999     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1000         c += 1;
1001
1002     /* Distribute horizontal space evenly to all children. */
1003
1004     for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
1005     {
1006         int x0 = x +  i      * w / c;
1007         int x1 = x + (i + 1) * w / c;
1008
1009         gui_widget_dn(jd, x0, y, x1 - x0, h);
1010     }
1011 }
1012
1013 static void gui_varray_dn(int id, int x, int y, int w, int h)
1014 {
1015     int jd, i = 0, c = 0;
1016
1017     widget[id].x = x;
1018     widget[id].y = y;
1019     widget[id].w = w;
1020     widget[id].h = h;
1021
1022     /* Count children. */
1023
1024     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1025         c += 1;
1026
1027     /* Distribute vertical space evenly to all children. */
1028
1029     for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
1030     {
1031         int y0 = y +  i      * h / c;
1032         int y1 = y + (i + 1) * h / c;
1033
1034         gui_widget_dn(jd, x, y0, w, y1 - y0);
1035     }
1036 }
1037
1038 static void gui_hstack_dn(int id, int x, int y, int w, int h)
1039 {
1040     int jd, jx = x, jw = 0, c = 0;
1041
1042     widget[id].x = x;
1043     widget[id].y = y;
1044     widget[id].w = w;
1045     widget[id].h = h;
1046
1047     /* Measure the total width requested by non-filler children. */
1048
1049     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1050         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1051             c += 1;
1052         else if (widget[jd].type & GUI_FILL)
1053         {
1054             c  += 1;
1055             jw += widget[jd].w;
1056         }
1057         else
1058             jw += widget[jd].w;
1059
1060     /* Give non-filler children their requested space.   */
1061     /* Distribute the rest evenly among filler children. */
1062
1063     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1064     {
1065         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1066             gui_widget_dn(jd, jx, y, (w - jw) / c, h);
1067         else if (widget[jd].type & GUI_FILL)
1068             gui_widget_dn(jd, jx, y, widget[jd].w + (w - jw) / c, h);
1069         else
1070             gui_widget_dn(jd, jx, y, widget[jd].w, h);
1071
1072         jx += widget[jd].w;
1073     }
1074 }
1075
1076 static void gui_vstack_dn(int id, int x, int y, int w, int h)
1077 {
1078     int jd, jy = y, jh = 0, c = 0;
1079
1080     widget[id].x = x;
1081     widget[id].y = y;
1082     widget[id].w = w;
1083     widget[id].h = h;
1084
1085     /* Measure the total height requested by non-filler children. */
1086
1087     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1088         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1089             c += 1;
1090         else if (widget[jd].type & GUI_FILL)
1091         {
1092             c  += 1;
1093             jh += widget[jd].h;
1094         }
1095         else
1096             jh += widget[jd].h;
1097
1098     /* Give non-filler children their requested space.   */
1099     /* Distribute the rest evenly among filler children. */
1100
1101     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1102     {
1103         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1104             gui_widget_dn(jd, x, jy, w, (h - jh) / c);
1105         else if (widget[jd].type & GUI_FILL)
1106             gui_widget_dn(jd, x, jy, w, widget[jd].h + (h - jh) / c);
1107         else
1108             gui_widget_dn(jd, x, jy, w, widget[jd].h);
1109
1110         jy += widget[jd].h;
1111     }
1112 }
1113
1114 static void gui_filler_dn(int id, int x, int y, int w, int h)
1115 {
1116     /* Filler expands to whatever size it is given. */
1117
1118     widget[id].x = x;
1119     widget[id].y = y;
1120     widget[id].w = w;
1121     widget[id].h = h;
1122 }
1123
1124 static void gui_button_dn(int id, int x, int y, int w, int h)
1125 {
1126     /* Recall stored width and height for text rendering. */
1127
1128     int W = widget[id].text_w;
1129     int H = widget[id].text_h;
1130     int R = widget[id].rect;
1131
1132     const GLubyte *c0 = widget[id].color0;
1133     const GLubyte *c1 = widget[id].color1;
1134
1135     widget[id].x = x;
1136     widget[id].y = y;
1137     widget[id].w = w;
1138     widget[id].h = h;
1139
1140     /* Create vertex array data for the text area and rounded rectangle. */
1141
1142     gui_rect(id, -w / 2, -h / 2, w, h, R, radius);
1143     gui_text(id, -W / 2, -H / 2, W, H, c0, c1);
1144 }
1145
1146 static void gui_widget_dn(int id, int x, int y, int w, int h)
1147 {
1148     if (id)
1149         switch (widget[id].type & GUI_TYPE)
1150         {
1151         case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
1152         case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
1153         case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
1154         case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
1155         case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
1156         case GUI_SPACE:  gui_filler_dn(id, x, y, w, h); break;
1157         default:         gui_button_dn(id, x, y, w, h); break;
1158         }
1159 }
1160
1161 /*---------------------------------------------------------------------------*/
1162 /*
1163  * During GUI layout, we make a bottom-up pass to determine total area
1164  * requirements for  the widget  tree.  We position  this area  to the
1165  * sides or center of the screen.  Finally, we make a top-down pass to
1166  * distribute this area to each widget.
1167  */
1168
1169 void gui_layout(int id, int xd, int yd)
1170 {
1171     int x, y;
1172
1173     int w, W = config_get_d(CONFIG_WIDTH);
1174     int h, H = config_get_d(CONFIG_HEIGHT);
1175
1176     gui_widget_up(id);
1177
1178     w = widget[id].w;
1179     h = widget[id].h;
1180
1181     if      (xd < 0) x = 0;
1182     else if (xd > 0) x = (W - w);
1183     else             x = (W - w) / 2;
1184
1185     if      (yd < 0) y = 0;
1186     else if (yd > 0) y = (H - h);
1187     else             y = (H - h) / 2;
1188
1189     gui_widget_dn(id, x, y, w, h);
1190
1191     /* Hilite the widget under the cursor, if any. */
1192
1193     gui_point(id, -1, -1);
1194 }
1195
1196 int gui_search(int id, int x, int y)
1197 {
1198     int jd, kd;
1199
1200     /* Search the hierarchy for the widget containing the given point. */
1201
1202     if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
1203                widget[id].y <= y && y < widget[id].y + widget[id].h))
1204     {
1205         if (gui_hot(id))
1206             return id;
1207
1208         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1209             if ((kd = gui_search(jd, x, y)))
1210                 return kd;
1211     }
1212     return 0;
1213 }
1214
1215 /*
1216  * Activate a widget, allowing it  to behave as a normal state widget.
1217  * This may  be used  to create  image buttons, or  cause an  array of
1218  * widgets to behave as a single state widget.
1219  */
1220 int gui_active(int id, int token, int value)
1221 {
1222     widget[id].type |= GUI_STATE;
1223     widget[id].token = token;
1224     widget[id].value = value;
1225
1226     return id;
1227 }
1228
1229 int gui_delete(int id)
1230 {
1231     if (id)
1232     {
1233         /* Recursively delete all subwidgets. */
1234
1235         gui_delete(widget[id].cdr);
1236         gui_delete(widget[id].car);
1237
1238         /* Release any GL resources held by this widget. */
1239
1240         if (glIsTexture(widget[id].image))
1241             glDeleteTextures(1, &widget[id].image);
1242
1243         /* Mark this widget unused. */
1244
1245         widget[id].type  = GUI_FREE;
1246         widget[id].image = 0;
1247         widget[id].cdr   = 0;
1248         widget[id].car   = 0;
1249     }
1250     return 0;
1251 }
1252
1253 /*---------------------------------------------------------------------------*/
1254
1255 static void gui_paint_rect(int id, int st)
1256 {
1257     static const GLfloat back[4][4] = {
1258         { 0.1f, 0.1f, 0.1f, 0.5f },             /* off and inactive    */
1259         { 0.5f, 0.5f, 0.5f, 0.8f },             /* off and   active    */
1260         { 1.0f, 0.7f, 0.3f, 0.5f },             /* on  and inactive    */
1261         { 1.0f, 0.7f, 0.3f, 0.8f },             /* on  and   active    */
1262     };
1263
1264     int jd, i = 0;
1265
1266     /* Use the widget status to determine the background color. */
1267
1268     if (gui_hot(id))
1269         i = st | (((widget[id].value) ? 2 : 0) |
1270                   ((id == active)     ? 1 : 0));
1271
1272     switch (widget[id].type & GUI_TYPE)
1273     {
1274     case GUI_IMAGE:
1275     case GUI_SPACE:
1276     case GUI_FILLER:
1277         break;
1278
1279     case GUI_HARRAY:
1280     case GUI_VARRAY:
1281     case GUI_HSTACK:
1282     case GUI_VSTACK:
1283
1284         /* Recursively paint all subwidgets. */
1285
1286         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1287             gui_paint_rect(jd, i);
1288
1289         break;
1290
1291     default:
1292
1293         /* Draw a leaf's background, colored by widget state. */
1294
1295         glPushMatrix();
1296         {
1297             glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1298                          (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1299
1300             glColor4fv(back[i]);
1301             draw_rect(id);
1302         }
1303         glPopMatrix();
1304
1305         break;
1306     }
1307 }
1308
1309 /*---------------------------------------------------------------------------*/
1310
1311 static void gui_paint_text(int id);
1312
1313 static void gui_paint_array(int id)
1314 {
1315     int jd;
1316
1317     glPushMatrix();
1318     {
1319         GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1320         GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1321         GLfloat ck = widget[id].scale;
1322
1323         if (1.0 < ck || ck < 1.0)
1324         {
1325             glTranslatef(+cx, +cy, 0.0f);
1326             glScalef(ck, ck, ck);
1327             glTranslatef(-cx, -cy, 0.0f);
1328         }
1329
1330         /* Recursively paint all subwidgets. */
1331
1332         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1333             gui_paint_text(jd);
1334     }
1335     glPopMatrix();
1336 }
1337
1338 static void gui_paint_image(int id)
1339 {
1340     /* Draw the widget rect, textured using the image. */
1341
1342     glPushMatrix();
1343     {
1344         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1345                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1346
1347         glScalef(widget[id].scale,
1348                  widget[id].scale,
1349                  widget[id].scale);
1350
1351         glBindTexture(GL_TEXTURE_2D, widget[id].image);
1352         glColor4ubv(gui_wht);
1353         draw_rect(id);
1354     }
1355     glPopMatrix();
1356 }
1357
1358 static void gui_paint_count(int id)
1359 {
1360     int j, i = widget[id].size;
1361
1362     glPushMatrix();
1363     {
1364         /* Translate to the widget center, and apply the pulse scale. */
1365
1366         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1367                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1368
1369         glScalef(widget[id].scale,
1370                  widget[id].scale,
1371                  widget[id].scale);
1372
1373         if (widget[id].value > 0)
1374         {
1375             /* Translate right by half the total width of the rendered value. */
1376
1377             GLfloat w = -widget[digit_id[i][0]].text_w * 0.5f;
1378
1379             for (j = widget[id].value; j; j /= 10)
1380                 w += widget[digit_id[i][j % 10]].text_w * 0.5f;
1381
1382             glTranslatef(w, 0.0f, 0.0f);
1383
1384             /* Render each digit, moving left after each. */
1385
1386             for (j = widget[id].value; j; j /= 10)
1387             {
1388                 int id = digit_id[i][j % 10];
1389
1390                 glBindTexture(GL_TEXTURE_2D, widget[id].image);
1391                 draw_text(id);
1392                 glTranslatef((GLfloat) -widget[id].text_w, 0.0f, 0.0f);
1393             }
1394         }
1395         else if (widget[id].value == 0)
1396         {
1397             /* If the value is zero, just display a zero in place. */
1398
1399             glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][0]].image);
1400             draw_text(digit_id[i][0]);
1401         }
1402     }
1403     glPopMatrix();
1404 }
1405
1406 static void gui_paint_clock(int id)
1407 {
1408     int i  =   widget[id].size;
1409     int mt =  (widget[id].value / 6000) / 10;
1410     int mo =  (widget[id].value / 6000) % 10;
1411     int st = ((widget[id].value % 6000) / 100) / 10;
1412     int so = ((widget[id].value % 6000) / 100) % 10;
1413     int ht = ((widget[id].value % 6000) % 100) / 10;
1414     int ho = ((widget[id].value % 6000) % 100) % 10;
1415
1416     GLfloat dx_large = (GLfloat) widget[digit_id[i][0]].text_w;
1417     GLfloat dx_small = (GLfloat) widget[digit_id[i][0]].text_w * 0.75f;
1418
1419     if (widget[id].value < 0)
1420         return;
1421
1422     glPushMatrix();
1423     {
1424         /* Translate to the widget center, and apply the pulse scale. */
1425
1426         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1427                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1428
1429         glScalef(widget[id].scale,
1430                  widget[id].scale,
1431                  widget[id].scale);
1432
1433         /* Translate left by half the total width of the rendered value. */
1434
1435         if (mt > 0)
1436             glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1437         else
1438             glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1439
1440         /* Render the minutes counter. */
1441
1442         if (mt > 0)
1443         {
1444             glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][mt]].image);
1445             draw_text(digit_id[i][mt]);
1446             glTranslatef(dx_large, 0.0f, 0.0f);
1447         }
1448
1449         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][mo]].image);
1450         draw_text(digit_id[i][mo]);
1451         glTranslatef(dx_small, 0.0f, 0.0f);
1452
1453         /* Render the colon. */
1454
1455         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][10]].image);
1456         draw_text(digit_id[i][10]);
1457         glTranslatef(dx_small, 0.0f, 0.0f);
1458
1459         /* Render the seconds counter. */
1460
1461         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][st]].image);
1462         draw_text(digit_id[i][st]);
1463         glTranslatef(dx_large, 0.0f, 0.0f);
1464
1465         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][so]].image);
1466         draw_text(digit_id[i][so]);
1467         glTranslatef(dx_small, 0.0f, 0.0f);
1468
1469         /* Render hundredths counter half size. */
1470
1471         glScalef(0.5f, 0.5f, 1.0f);
1472
1473         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][ht]].image);
1474         draw_text(digit_id[i][ht]);
1475         glTranslatef(dx_large, 0.0f, 0.0f);
1476
1477         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][ho]].image);
1478         draw_text(digit_id[i][ho]);
1479     }
1480     glPopMatrix();
1481 }
1482
1483 static void gui_paint_label(int id)
1484 {
1485     /* Draw the widget text box, textured using the glyph. */
1486
1487     glPushMatrix();
1488     {
1489         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1490                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1491
1492         glScalef(widget[id].scale,
1493                  widget[id].scale,
1494                  widget[id].scale);
1495
1496         glBindTexture(GL_TEXTURE_2D, widget[id].image);
1497         draw_text(id);
1498     }
1499     glPopMatrix();
1500 }
1501
1502 static void gui_paint_text(int id)
1503 {
1504     switch (widget[id].type & GUI_TYPE)
1505     {
1506     case GUI_SPACE:  break;
1507     case GUI_FILLER: break;
1508     case GUI_HARRAY: gui_paint_array(id); break;
1509     case GUI_VARRAY: gui_paint_array(id); break;
1510     case GUI_HSTACK: gui_paint_array(id); break;
1511     case GUI_VSTACK: gui_paint_array(id); break;
1512     case GUI_IMAGE:  gui_paint_image(id); break;
1513     case GUI_COUNT:  gui_paint_count(id); break;
1514     case GUI_CLOCK:  gui_paint_clock(id); break;
1515     default:         gui_paint_label(id); break;
1516     }
1517 }
1518
1519 void gui_paint(int id)
1520 {
1521     if (id)
1522     {
1523         video_push_ortho();
1524         {
1525             glEnable(GL_COLOR_MATERIAL);
1526             glDisable(GL_LIGHTING);
1527             glDisable(GL_DEPTH_TEST);
1528             {
1529                 draw_enable(GL_FALSE, GL_FALSE, GL_TRUE);
1530                 glDisable(GL_TEXTURE_2D);
1531                 gui_paint_rect(id, 0);
1532
1533                 draw_enable(GL_TRUE, GL_TRUE, GL_TRUE);
1534                 glEnable(GL_TEXTURE_2D);
1535                 gui_paint_text(id);
1536
1537                 draw_disable();
1538                 glColor4ubv(gui_wht);
1539             }
1540             glEnable(GL_DEPTH_TEST);
1541             glEnable(GL_LIGHTING);
1542             glDisable(GL_COLOR_MATERIAL);
1543         }
1544         video_pop_matrix();
1545     }
1546 }
1547
1548 /*---------------------------------------------------------------------------*/
1549
1550 void gui_dump(int id, int d)
1551 {
1552     int jd, i;
1553
1554     if (id)
1555     {
1556         char *type = "?";
1557
1558         switch (widget[id].type & GUI_TYPE)
1559         {
1560         case GUI_HARRAY: type = "harray"; break;
1561         case GUI_VARRAY: type = "varray"; break;
1562         case GUI_HSTACK: type = "hstack"; break;
1563         case GUI_VSTACK: type = "vstack"; break;
1564         case GUI_FILLER: type = "filler"; break;
1565         case GUI_IMAGE:  type = "image";  break;
1566         case GUI_LABEL:  type = "label";  break;
1567         case GUI_COUNT:  type = "count";  break;
1568         case GUI_CLOCK:  type = "clock";  break;
1569         }
1570
1571         for (i = 0; i < d; i++)
1572             printf("    ");
1573
1574         printf("%04d %s\n", id, type);
1575
1576         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1577             gui_dump(jd, d + 1);
1578     }
1579 }
1580
1581 void gui_pulse(int id, float k)
1582 {
1583     if (id) widget[id].scale = k;
1584 }
1585
1586 void gui_timer(int id, float dt)
1587 {
1588     int jd;
1589
1590     if (id)
1591     {
1592         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1593             gui_timer(jd, dt);
1594
1595         if (widget[id].scale - 1.0f < dt)
1596             widget[id].scale = 1.0f;
1597         else
1598             widget[id].scale -= dt;
1599     }
1600 }
1601
1602 int gui_point(int id, int x, int y)
1603 {
1604     static int x_cache = 0;
1605     static int y_cache = 0;
1606
1607     int jd;
1608
1609     /* Reuse the last coordinates if (x,y) == (-1,-1) */
1610
1611     if (x < 0 && y < 0)
1612         return gui_point(id, x_cache, y_cache);
1613
1614     x_cache = x;
1615     y_cache = y;
1616
1617     /* Short-circuit check the current active widget. */
1618
1619     jd = gui_search(active, x, y);
1620
1621     /* If not still active, search the hierarchy for a new active widget. */
1622
1623     if (jd == 0)
1624         jd = gui_search(id, x, y);
1625
1626     /* If the active widget has changed, return the new active id. */
1627
1628     if (jd == 0 || jd == active)
1629         return 0;
1630     else
1631         return active = jd;
1632 }
1633
1634 void gui_focus(int i)
1635 {
1636     active = i;
1637 }
1638
1639 int gui_click(void)
1640 {
1641     return active;
1642 }
1643
1644 int gui_token(int id)
1645 {
1646     return id ? widget[id].token : 0;
1647 }
1648
1649 int gui_value(int id)
1650 {
1651     return id ? widget[id].value : 0;
1652 }
1653
1654 void gui_toggle(int id)
1655 {
1656     widget[id].value = widget[id].value ? 0 : 1;
1657 }
1658
1659 /*---------------------------------------------------------------------------*/
1660
1661 static int gui_vert_offset(int id, int jd)
1662 {
1663     /* Vertical offset between bottom of id and top of jd */
1664
1665     return  widget[id].y - (widget[jd].y + widget[jd].h);
1666 }
1667
1668 static int gui_horz_offset(int id, int jd)
1669 {
1670     /* Horizontal offset between left of id and right of jd */
1671
1672     return  widget[id].x - (widget[jd].x + widget[jd].w);
1673 }
1674
1675 static int gui_vert_dist(int id, int jd)
1676 {
1677     /* Vertical distance between the tops of id and jd */
1678
1679     return abs((widget[id].y + widget[id].h) - (widget[jd].y + widget[jd].h));
1680 }
1681
1682 static int gui_horz_dist(int id, int jd)
1683 {
1684     /* Horizontal distance between the left sides of id and jd */
1685
1686     return abs(widget[id].x - widget[jd].x);
1687 }
1688
1689 /*---------------------------------------------------------------------------*/
1690
1691 static int gui_stick_L(int id, int dd)
1692 {
1693     int jd, kd, hd;
1694     int o, omin, d, dmin;
1695
1696     /* Find the closest "hot" widget to the left of dd (and inside id) */
1697
1698     if (id && gui_hot(id))
1699         return id;
1700
1701     hd = 0;
1702     omin = widget[dd].x - widget[id].x + 1;
1703     dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1704
1705     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1706     {
1707         kd = gui_stick_L(jd, dd);
1708
1709         if (kd && kd != dd)
1710         {
1711             o = gui_horz_offset(dd, kd);
1712             d = gui_vert_dist(dd, kd);
1713
1714             if (0 <= o && o <= omin && d <= dmin)
1715             {
1716                 hd = kd;
1717                 omin = o;
1718                 dmin = d;
1719             }
1720         }
1721     }
1722
1723     return hd;
1724 }
1725
1726 static int gui_stick_R(int id, int dd)
1727 {
1728     int jd, kd, hd;
1729     int o, omin, d, dmin;
1730
1731     /* Find the closest "hot" widget to the right of dd (and inside id) */
1732
1733     if (id && gui_hot(id))
1734         return id;
1735
1736     hd = 0;
1737     omin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
1738     dmin = (widget[dd].y + widget[dd].h) + (widget[id].y + widget[id].h);
1739
1740     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1741     {
1742         kd = gui_stick_R(jd, dd);
1743
1744         if (kd && kd != dd)
1745         {
1746             o = gui_horz_offset(kd, dd);
1747             d = gui_vert_dist(dd, kd);
1748
1749             if (0 <= o && o <= omin && d <= dmin)
1750             {
1751                 hd = kd;
1752                 omin = o;
1753                 dmin = d;
1754             }
1755         }
1756     }
1757
1758     return hd;
1759 }
1760
1761 static int gui_stick_D(int id, int dd)
1762 {
1763     int jd, kd, hd;
1764     int o, omin, d, dmin;
1765
1766     /* Find the closest "hot" widget below dd (and inside id) */
1767
1768     if (id && gui_hot(id))
1769         return id;
1770
1771     hd = 0;
1772     omin = widget[dd].y - widget[id].y + 1;
1773     dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1774
1775     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1776     {
1777         kd = gui_stick_D(jd, dd);
1778
1779         if (kd && kd != dd)
1780         {
1781             o = gui_vert_offset(dd, kd);
1782             d = gui_horz_dist(dd, kd);
1783
1784             if (0 <= o && o <= omin && d <= dmin)
1785             {
1786                 hd = kd;
1787                 omin = o;
1788                 dmin = d;
1789             }
1790         }
1791     }
1792
1793     return hd;
1794 }
1795
1796 static int gui_stick_U(int id, int dd)
1797 {
1798     int jd, kd, hd;
1799     int o, omin, d, dmin;
1800
1801     /* Find the closest "hot" widget above dd (and inside id) */
1802
1803     if (id && gui_hot(id))
1804         return id;
1805
1806     hd = 0;
1807     omin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
1808     dmin = (widget[dd].x + widget[dd].w) + (widget[id].x + widget[id].w);
1809
1810     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1811     {
1812         kd = gui_stick_U(jd, dd);
1813
1814         if (kd && kd != dd)
1815         {
1816             o = gui_vert_offset(kd, dd);
1817             d = gui_horz_dist(dd, kd);
1818
1819             if (0 <= o && o <= omin && d <= dmin)
1820             {
1821                 hd = kd;
1822                 omin = o;
1823                 dmin = d;
1824             }
1825         }
1826     }
1827
1828     return hd;
1829 }
1830
1831 /*---------------------------------------------------------------------------*/
1832
1833 static int gui_wrap_L(int id, int dd)
1834 {
1835     int jd, kd;
1836
1837     if ((jd = gui_stick_L(id, dd)) == 0)
1838         for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
1839             ;
1840
1841     return jd;
1842 }
1843
1844 static int gui_wrap_R(int id, int dd)
1845 {
1846     int jd, kd;
1847
1848     if ((jd = gui_stick_R(id, dd)) == 0)
1849         for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
1850             ;
1851
1852     return jd;
1853 }
1854
1855 static int gui_wrap_U(int id, int dd)
1856 {
1857     int jd, kd;
1858
1859     if ((jd = gui_stick_U(id, dd)) == 0)
1860         for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
1861             ;
1862
1863     return jd;
1864 }
1865
1866 static int gui_wrap_D(int id, int dd)
1867 {
1868     int jd, kd;
1869
1870     if ((jd = gui_stick_D(id, dd)) == 0)
1871         for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
1872             ;
1873
1874     return jd;
1875 }
1876
1877 /*---------------------------------------------------------------------------*/
1878
1879 int gui_stick(int id, int a, float v, int bump)
1880 {
1881     int jd = 0;
1882
1883     if (!bump)
1884         return 0;
1885
1886     /* Find a new active widget in the direction of joystick motion. */
1887
1888     if (config_tst_d(CONFIG_JOYSTICK_AXIS_X, a))
1889     {
1890         if (v < 0) jd = gui_wrap_L(id, active);
1891         if (v > 0) jd = gui_wrap_R(id, active);
1892     }
1893     else if (config_tst_d(CONFIG_JOYSTICK_AXIS_Y, a))
1894     {
1895         if (v < 0) jd = gui_wrap_U(id, active);
1896         if (v > 0) jd = gui_wrap_D(id, active);
1897     }
1898
1899     /* If the active widget has changed, return the new active id. */
1900
1901     if (jd == 0 || jd == active)
1902         return 0;
1903     else
1904         return active = jd;
1905 }
1906
1907 /*---------------------------------------------------------------------------*/