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