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