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