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