68a9b25afd19e0a824e5842c0d03bba8d642fb01
[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 "glext.h"
21 #include "image.h"
22 #include "vec3.h"
23 #include "text.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, 1 - (Ya - y) / h);
181                 glVertex2f(X, Ya);
182
183                 glTexCoord2f((X - x) / w, 1 - (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, 1 - (Ya - y) / h);
200                 glVertex2f(X, Ya);
201
202                 glTexCoord2f((X - x) / w, 1 - (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
706     /* Padded text elements look a little nicer. */
707
708     if (widget[id].w < config_get_d(CONFIG_WIDTH))
709         widget[id].w += radius;
710     if (widget[id].h < config_get_d(CONFIG_HEIGHT))
711         widget[id].h += radius;
712 }
713
714 static void gui_widget_up(int id)
715 {
716     if (id)
717         switch (widget[id].type & GUI_TYPE)
718         {
719         case GUI_HARRAY: gui_harray_up(id); break;
720         case GUI_VARRAY: gui_varray_up(id); break;
721         case GUI_HSTACK: gui_hstack_up(id); break;
722         case GUI_VSTACK: gui_vstack_up(id); break;
723         default:         gui_button_up(id); break;
724         }
725 }
726
727 /*---------------------------------------------------------------------------*/
728 /*
729  * The  top-down layout  pass distributes  available area  as computed
730  * during the bottom-up pass.  Widgets  use their area and position to
731  * initialize rendering state.
732  */
733
734 static void gui_widget_dn(int id, int x, int y, int w, int h);
735
736 static void gui_harray_dn(int id, int x, int y, int w, int h)
737 {
738     int jd, i = 0, c = 0;
739
740     widget[id].x = x;
741     widget[id].y = y;
742     widget[id].w = w;
743     widget[id].h = h;
744
745     /* Count children. */
746
747     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
748         c += 1;
749
750     /* Distribute horizontal space evenly to all children. */
751
752     for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
753     {
754         int x0 = x +  i      * w / c;
755         int x1 = x + (i + 1) * w / c;
756
757         gui_widget_dn(jd, x0, y, x1 - x0, h);
758     }
759 }
760
761 static void gui_varray_dn(int id, int x, int y, int w, int h)
762 {
763     int jd, i = 0, c = 0;
764
765     widget[id].x = x;
766     widget[id].y = y;
767     widget[id].w = w;
768     widget[id].h = h;
769
770     /* Count children. */
771
772     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
773         c += 1;
774
775     /* Distribute vertical space evenly to all children. */
776
777     for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
778     {
779         int y0 = y +  i      * h / c;
780         int y1 = y + (i + 1) * h / c;
781
782         gui_widget_dn(jd, x, y0, w, y1 - y0);
783     }
784 }
785
786 static void gui_hstack_dn(int id, int x, int y, int w, int h)
787 {
788     int jd, jx = x, jw = 0, c = 0;
789
790     widget[id].x = x;
791     widget[id].y = y;
792     widget[id].w = w;
793     widget[id].h = h;
794
795     /* Measure the total width requested by non-filler children. */
796
797     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
798         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
799             c += 1;
800         else
801             jw += widget[jd].w;
802
803     /* Give non-filler children their requested space.   */
804     /* Distribute the rest evenly among filler children. */
805
806     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
807     {
808         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
809             gui_widget_dn(jd, jx, y, (w - jw) / c, h);
810         else
811             gui_widget_dn(jd, jx, y, widget[jd].w, h);
812
813         jx += widget[jd].w;
814     }
815 }
816
817 static void gui_vstack_dn(int id, int x, int y, int w, int h)
818 {
819     int jd, jy = y, jh = 0, c = 0;
820
821     widget[id].x = x;
822     widget[id].y = y;
823     widget[id].w = w;
824     widget[id].h = h;
825
826     /* Measure the total height requested by non-filler children. */
827
828     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
829         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
830             c += 1;
831         else
832             jh += widget[jd].h;
833
834     /* Give non-filler children their requested space.   */
835     /* Distribute the rest evenly among filler children. */
836
837     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
838     {
839         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
840             gui_widget_dn(jd, x, jy, w, (h - jh) / c);
841         else
842             gui_widget_dn(jd, x, jy, w, widget[jd].h);
843
844         jy += widget[jd].h;
845     }
846 }
847
848 static void gui_filler_dn(int id, int x, int y, int w, int h)
849 {
850     /* Filler expands to whatever size it is given. */
851
852     widget[id].x = x;
853     widget[id].y = y;
854     widget[id].w = w;
855     widget[id].h = h;
856 }
857
858 static void gui_button_dn(int id, int x, int y, int w, int h)
859 {
860     /* Recall stored width and height for text rendering. */
861
862     int W = widget[id].x;
863     int H = widget[id].y;
864     int R = widget[id].rect;
865
866     const float *c0 = widget[id].color0;
867     const float *c1 = widget[id].color1;
868
869     widget[id].x = x;
870     widget[id].y = y;
871     widget[id].w = w;
872     widget[id].h = h;
873
874     /* Create display lists for the text area and rounded rectangle. */
875
876     widget[id].text_obj = gui_list(-W / 2, -H / 2, W, H, c0, c1);
877     widget[id].rect_obj = gui_rect(-w / 2, -h / 2, w, h, R, radius);
878 }
879
880 static void gui_widget_dn(int id, int x, int y, int w, int h)
881 {
882     if (id)
883         switch (widget[id].type & GUI_TYPE)
884         {
885         case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
886         case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
887         case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
888         case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
889         case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
890         case GUI_SPACE:  gui_filler_dn(id, x, y, w, h); break;
891         default:         gui_button_dn(id, x, y, w, h); break;
892         }
893 }
894
895 /*---------------------------------------------------------------------------*/
896 /*
897  * During GUI layout, we make a bottom-up pass to determine total area
898  * requirements for  the widget  tree.  We position  this area  to the
899  * sides or center of the screen.  Finally, we make a top-down pass to
900  * distribute this area to each widget.
901  */
902
903 void gui_layout(int id, int xd, int yd)
904 {
905     int x, y;
906
907     int w, W = config_get_d(CONFIG_WIDTH);
908     int h, H = config_get_d(CONFIG_HEIGHT);
909
910     gui_widget_up(id);
911
912     w = widget[id].w;
913     h = widget[id].h;
914
915     if      (xd < 0) x = 0;
916     else if (xd > 0) x = (W - w);
917     else             x = (W - w) / 2;
918
919     if      (yd < 0) y = 0;
920     else if (yd > 0) y = (H - h);
921     else             y = (H - h) / 2;
922
923     gui_widget_dn(id, x, y, w, h);
924
925     /* Hilite the widget under the cursor, if any. */
926
927     gui_point(id, -1, -1);
928 }
929
930 int gui_search(int id, int x, int y)
931 {
932     int jd, kd;
933
934     /* Search the hierarchy for the widget containing the given point. */
935
936     if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
937                widget[id].y <= y && y < widget[id].y + widget[id].h))
938     {
939         if (gui_hot(id))
940             return id;
941
942         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
943             if ((kd = gui_search(jd, x, y)))
944                 return kd;
945     }
946     return 0;
947 }
948
949 /*
950  * Activate a widget, allowing it  to behave as a normal state widget.
951  * This may  be used  to create  image buttons, or  cause an  array of
952  * widgets to behave as a single state widget.
953  */
954 int gui_active(int id, int token, int value)
955 {
956     widget[id].type |= GUI_STATE;
957     widget[id].token = token;
958     widget[id].value = value;
959
960     return id;
961 }
962
963 int gui_delete(int id)
964 {
965     if (id)
966     {
967         /* Recursively delete all subwidgets. */
968
969         gui_delete(widget[id].cdr);
970         gui_delete(widget[id].car);
971
972         /* Release any GL resources held by this widget. */
973
974         if (glIsTexture(widget[id].text_img))
975             glDeleteTextures(1, &widget[id].text_img);
976
977         if (glIsList(widget[id].text_obj))
978             glDeleteLists(widget[id].text_obj, 1);
979         if (glIsList(widget[id].rect_obj))
980             glDeleteLists(widget[id].rect_obj, 1);
981
982         /* Mark this widget unused. */
983
984         widget[id].type     = GUI_FREE;
985         widget[id].text_img = 0;
986         widget[id].text_obj = 0;
987         widget[id].rect_obj = 0;
988         widget[id].cdr      = 0;
989         widget[id].car      = 0;
990     }
991     return 0;
992 }
993
994 /*---------------------------------------------------------------------------*/
995
996 static void gui_paint_rect(int id, int st)
997 {
998     static const GLfloat back[4][4] = {
999         { 0.1f, 0.1f, 0.1f, 0.5f },             /* off and inactive    */
1000         { 0.5f, 0.5f, 0.5f, 0.8f },             /* off and   active    */
1001         { 1.0f, 0.7f, 0.3f, 0.5f },             /* on  and inactive    */
1002         { 1.0f, 0.7f, 0.3f, 0.8f },             /* on  and   active    */
1003     };
1004
1005     int jd, i = 0;
1006
1007     /* Use the widget status to determine the background color. */
1008
1009     if (gui_hot(id))
1010         i = st | (((widget[id].value) ? 2 : 0) |
1011                   ((id == active)     ? 1 : 0));
1012
1013     switch (widget[id].type & GUI_TYPE)
1014     {
1015     case GUI_IMAGE:
1016     case GUI_SPACE:
1017     case GUI_FILLER:
1018         break;
1019
1020     case GUI_HARRAY:
1021     case GUI_VARRAY:
1022     case GUI_HSTACK:
1023     case GUI_VSTACK:
1024
1025         /* Recursively paint all subwidgets. */
1026
1027         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1028             gui_paint_rect(jd, i);
1029
1030         break;
1031
1032     default:
1033
1034         /* Draw a leaf's background, colored by widget state. */
1035
1036         glPushMatrix();
1037         {
1038             glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1039                          (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1040
1041             glColor4fv(back[i]);
1042             glCallList(widget[id].rect_obj);
1043         }
1044         glPopMatrix();
1045
1046         break;
1047     }
1048 }
1049
1050 /*---------------------------------------------------------------------------*/
1051
1052 static void gui_paint_text(int id);
1053
1054 static void gui_paint_array(int id)
1055 {
1056     int jd;
1057
1058     glPushMatrix();
1059     {
1060         GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1061         GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1062         GLfloat ck = widget[id].scale;
1063
1064         glTranslatef(+cx, +cy, 0.0f);
1065         glScalef(ck, ck, ck);
1066         glTranslatef(-cx, -cy, 0.0f);
1067
1068         /* Recursively paint all subwidgets. */
1069
1070         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1071             gui_paint_text(jd);
1072     }
1073     glPopMatrix();
1074 }
1075
1076 static void gui_paint_image(int id)
1077 {
1078     /* Draw the widget rect, textured using the image. */
1079
1080     glPushMatrix();
1081     {
1082         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1083                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1084
1085         glScalef(widget[id].scale,
1086                  widget[id].scale,
1087                  widget[id].scale);
1088
1089         glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1090         glColor4fv(gui_wht);
1091         glCallList(widget[id].rect_obj);
1092     }
1093     glPopMatrix();
1094 }
1095
1096 static void gui_paint_count(int id)
1097 {
1098     int j, i = widget[id].size;
1099
1100     glPushMatrix();
1101     {
1102         glColor4fv(gui_wht);
1103
1104         /* Translate to the widget center, and apply the pulse scale. */
1105
1106         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1107                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1108
1109         glScalef(widget[id].scale,
1110                  widget[id].scale,
1111                  widget[id].scale);
1112
1113         if (widget[id].value > 0)
1114         {
1115             /* Translate left by half the total width of the rendered value. */
1116
1117             for (j = widget[id].value; j; j /= 10)
1118                 glTranslatef((GLfloat) +digit_w[i][j % 10] / 2.0f, 0.0f, 0.0f);
1119
1120             glTranslatef((GLfloat) -digit_w[i][0] / 2.0f, 0.0f, 0.0f);
1121
1122             /* Render each digit, moving right after each. */
1123
1124             for (j = widget[id].value; j; j /= 10)
1125             {
1126                 glBindTexture(GL_TEXTURE_2D, digit_text[i][j % 10]);
1127                 glCallList(digit_list[i][j % 10]);
1128                 glTranslatef((GLfloat) -digit_w[i][j % 10], 0.0f, 0.0f);
1129             }
1130         }
1131         else if (widget[id].value == 0)
1132         {
1133             /* If the value is zero, just display a zero in place. */
1134
1135             glBindTexture(GL_TEXTURE_2D, digit_text[i][0]);
1136             glCallList(digit_list[i][0]);
1137         }
1138     }
1139     glPopMatrix();
1140 }
1141
1142 static void gui_paint_clock(int id)
1143 {
1144     int i  =   widget[id].size;
1145     int mt =  (widget[id].value / 6000) / 10;
1146     int mo =  (widget[id].value / 6000) % 10;
1147     int st = ((widget[id].value % 6000) / 100) / 10;
1148     int so = ((widget[id].value % 6000) / 100) % 10;
1149     int ht = ((widget[id].value % 6000) % 100) / 10;
1150     int ho = ((widget[id].value % 6000) % 100) % 10;
1151
1152     GLfloat dx_large = (GLfloat) digit_w[i][0];
1153     GLfloat dx_small = (GLfloat) digit_w[i][0] * 0.75f;
1154
1155     if (widget[id].value < 0)
1156         return;
1157
1158     glPushMatrix();
1159     {
1160         glColor4fv(gui_wht);
1161
1162         /* Translate to the widget center, and apply the pulse scale. */
1163
1164         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1165                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1166
1167         glScalef(widget[id].scale,
1168                  widget[id].scale,
1169                  widget[id].scale);
1170
1171         /* Translate left by half the total width of the rendered value. */
1172
1173         if (mt > 0)
1174             glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1175         else
1176             glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1177
1178         /* Render the minutes counter. */
1179
1180         if (mt > 0)
1181         {
1182             glBindTexture(GL_TEXTURE_2D, digit_text[i][mt]);
1183             glCallList(digit_list[i][mt]);
1184             glTranslatef(dx_large, 0.0f, 0.0f);
1185         }
1186
1187         glBindTexture(GL_TEXTURE_2D, digit_text[i][mo]);
1188         glCallList(digit_list[i][mo]);
1189         glTranslatef(dx_small, 0.0f, 0.0f);
1190
1191         /* Render the colon. */
1192
1193         glBindTexture(GL_TEXTURE_2D, digit_text[i][10]);
1194         glCallList(digit_list[i][10]);
1195         glTranslatef(dx_small, 0.0f, 0.0f);
1196
1197         /* Render the seconds counter. */
1198
1199         glBindTexture(GL_TEXTURE_2D, digit_text[i][st]);
1200         glCallList(digit_list[i][st]);
1201         glTranslatef(dx_large, 0.0f, 0.0f);
1202
1203         glBindTexture(GL_TEXTURE_2D, digit_text[i][so]);
1204         glCallList(digit_list[i][so]);
1205         glTranslatef(dx_small, 0.0f, 0.0f);
1206
1207         /* Render hundredths counter half size. */
1208
1209         glScalef(0.5f, 0.5f, 1.0f);
1210
1211         glBindTexture(GL_TEXTURE_2D, digit_text[i][ht]);
1212         glCallList(digit_list[i][ht]);
1213         glTranslatef(dx_large, 0.0f, 0.0f);
1214
1215         glBindTexture(GL_TEXTURE_2D, digit_text[i][ho]);
1216         glCallList(digit_list[i][ho]);
1217     }
1218     glPopMatrix();
1219 }
1220
1221 static void gui_paint_label(int id)
1222 {
1223     /* Draw the widget text box, textured using the glyph. */
1224
1225     glPushMatrix();
1226     {
1227         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1228                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1229
1230         glScalef(widget[id].scale,
1231                  widget[id].scale,
1232                  widget[id].scale);
1233
1234         glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1235         glCallList(widget[id].text_obj);
1236     }
1237     glPopMatrix();
1238 }
1239
1240 static void gui_paint_text(int id)
1241 {
1242     switch (widget[id].type & GUI_TYPE)
1243     {
1244     case GUI_SPACE:  break;
1245     case GUI_FILLER: break;
1246     case GUI_HARRAY: gui_paint_array(id); break;
1247     case GUI_VARRAY: gui_paint_array(id); break;
1248     case GUI_HSTACK: gui_paint_array(id); break;
1249     case GUI_VSTACK: gui_paint_array(id); break;
1250     case GUI_IMAGE:  gui_paint_image(id); break;
1251     case GUI_COUNT:  gui_paint_count(id); break;
1252     case GUI_CLOCK:  gui_paint_clock(id); break;
1253     default:         gui_paint_label(id); break;
1254     }
1255 }
1256
1257 void gui_paint(int id)
1258 {
1259     if (id)
1260     {
1261         config_push_ortho();
1262         {
1263             glEnable(GL_COLOR_MATERIAL);
1264             glDisable(GL_LIGHTING);
1265             glDisable(GL_DEPTH_TEST);
1266             {
1267                 glDisable(GL_TEXTURE_2D);
1268                 gui_paint_rect(id, 0);
1269
1270                 glEnable(GL_TEXTURE_2D);
1271                 gui_paint_text(id);
1272             }
1273             glEnable(GL_DEPTH_TEST);
1274             glEnable(GL_LIGHTING);
1275             glDisable(GL_COLOR_MATERIAL);
1276         }
1277         config_pop_matrix();
1278     }
1279 }
1280
1281 /*---------------------------------------------------------------------------*/
1282
1283 void gui_dump(int id, int d)
1284 {
1285     int jd, i;
1286
1287     if (id)
1288     {
1289         char *type = "?";
1290
1291         switch (widget[id].type & GUI_TYPE)
1292         {
1293         case GUI_HARRAY: type = "harray"; break;
1294         case GUI_VARRAY: type = "varray"; break;
1295         case GUI_HSTACK: type = "hstack"; break;
1296         case GUI_VSTACK: type = "vstack"; break;
1297         case GUI_FILLER: type = "filler"; break;
1298         case GUI_IMAGE:  type = "image";  break;
1299         case GUI_LABEL:  type = "label";  break;
1300         case GUI_COUNT:  type = "count";  break;
1301         case GUI_CLOCK:  type = "clock";  break;
1302         }
1303
1304         for (i = 0; i < d; i++)
1305             printf("    ");
1306
1307         printf("%04d %s\n", id, type);
1308
1309         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1310             gui_dump(jd, d + 1);
1311     }
1312 }
1313
1314 void gui_pulse(int id, float k)
1315 {
1316     if (id) widget[id].scale = k;
1317 }
1318
1319 void gui_timer(int id, float dt)
1320 {
1321     int jd;
1322
1323     if (id)
1324     {
1325         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1326             gui_timer(jd, dt);
1327
1328         if (widget[id].scale - 1.0f < dt)
1329             widget[id].scale = 1.0f;
1330         else
1331             widget[id].scale -= dt;
1332     }
1333 }
1334
1335 int gui_point(int id, int x, int y)
1336 {
1337     static int x_cache = 0;
1338     static int y_cache = 0;
1339
1340     int jd;
1341
1342     /* Reuse the last coordinates if (x,y) == (-1,-1) */
1343
1344     if (x < 0 && y < 0)
1345         return gui_point(id, x_cache, y_cache);
1346
1347     x_cache = x;
1348     y_cache = y;
1349
1350     /* Short-circuit check the current active widget. */
1351
1352     jd = gui_search(active, x, y);
1353
1354     /* If not still active, search the hierarchy for a new active widget. */
1355
1356     if (jd == 0)
1357         jd = gui_search(id, x, y);
1358
1359     /* If the active widget has changed, return the new active id. */
1360
1361     if (jd == 0 || jd == active)
1362         return 0;
1363     else
1364         return active = jd;
1365 }
1366
1367 void gui_focus(int i)
1368 {
1369     active = i;
1370 }
1371
1372 int gui_click(void)
1373 {
1374     return active;
1375 }
1376
1377 int gui_token(int id)
1378 {
1379     return id ? widget[id].token : 0;
1380 }
1381
1382 int gui_value(int id)
1383 {
1384     return id ? widget[id].value : 0;
1385 }
1386
1387 void gui_toggle(int id)
1388 {
1389     widget[id].value = widget[id].value ? 0 : 1;
1390 }
1391
1392 /*---------------------------------------------------------------------------*/
1393
1394 static int gui_vert_offset(int id, int jd)
1395 {
1396     /* Vertical offset between bottom of id and top of jd */
1397
1398     return  widget[id].y - (widget[jd].y + widget[jd].h);
1399 }
1400
1401 static int gui_horz_offset(int id, int jd)
1402 {
1403     /* Horizontal offset between left of id and right of jd */
1404
1405     return  widget[id].x - (widget[jd].x + widget[jd].w);
1406 }
1407
1408 static int gui_vert_dist(int id, int jd)
1409 {
1410     /* Vertical distance between the tops of id and jd */
1411
1412     return abs((widget[id].y + widget[id].h) - (widget[jd].y + widget[jd].h));
1413 }
1414
1415 static int gui_horz_dist(int id, int jd)
1416 {
1417     /* Horizontal distance between the left sides of id and jd */
1418
1419     return abs(widget[id].x - widget[jd].x);
1420 }
1421
1422 /*---------------------------------------------------------------------------*/
1423
1424 static int gui_stick_L(int id, int dd)
1425 {
1426     int jd, kd, hd;
1427     int o, omin, d, dmin;
1428
1429     /* Find the closest "hot" widget to the left of dd (and inside id) */
1430
1431     if (id && gui_hot(id))
1432         return id;
1433
1434     hd = 0;
1435     omin = widget[dd].x - widget[id].x + 1;
1436     dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1437
1438     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1439     {
1440         kd = gui_stick_L(jd, dd);
1441
1442         if (kd && kd != dd)
1443         {
1444             o = gui_horz_offset(dd, kd);
1445             d = gui_vert_dist(dd, kd);
1446
1447             if (0 <= o && o <= omin && d <= dmin)
1448             {
1449                 hd = kd;
1450                 omin = o;
1451                 dmin = d;
1452             }
1453         }
1454     }
1455
1456     return hd;
1457 }
1458
1459 static int gui_stick_R(int id, int dd)
1460 {
1461     int jd, kd, hd;
1462     int o, omin, d, dmin;
1463
1464     /* Find the closest "hot" widget to the right of dd (and inside id) */
1465
1466     if (id && gui_hot(id))
1467         return id;
1468
1469     hd = 0;
1470     omin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
1471     dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1472
1473     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1474     {
1475         kd = gui_stick_R(jd, dd);
1476
1477         if (kd && kd != dd)
1478         {
1479             o = gui_horz_offset(kd, dd);
1480             d = gui_vert_dist(dd, kd);
1481
1482             if (0 <= o && o <= omin && d <= dmin)
1483             {
1484                 hd = kd;
1485                 omin = o;
1486                 dmin = d;
1487             }
1488         }
1489     }
1490
1491     return hd;
1492 }
1493
1494 static int gui_stick_D(int id, int dd)
1495 {
1496     int jd, kd, hd;
1497     int o, omin, d, dmin;
1498
1499     /* Find the closest "hot" widget below dd (and inside id) */
1500
1501     if (id && gui_hot(id))
1502         return id;
1503
1504     hd = 0;
1505     omin = widget[dd].y - widget[id].y + 1;
1506     dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1507
1508     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1509     {
1510         kd = gui_stick_D(jd, dd);
1511
1512         if (kd && kd != dd)
1513         {
1514             o = gui_vert_offset(dd, kd);
1515             d = gui_horz_dist(dd, kd);
1516
1517             if (0 <= o && o <= omin && d <= dmin)
1518             {
1519                 hd = kd;
1520                 omin = o;
1521                 dmin = d;
1522             }
1523         }
1524     }
1525
1526     return hd;
1527 }
1528
1529 static int gui_stick_U(int id, int dd)
1530 {
1531     int jd, kd, hd;
1532     int o, omin, d, dmin;
1533
1534     /* Find the closest "hot" widget above dd (and inside id) */
1535
1536     if (id && gui_hot(id))
1537         return id;
1538
1539     hd = 0;
1540     omin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
1541     dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1542
1543     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1544     {
1545         kd = gui_stick_U(jd, dd);
1546
1547         if (kd && kd != dd)
1548         {
1549             o = gui_vert_offset(kd, dd);
1550             d = gui_horz_dist(dd, kd);
1551
1552             if (0 <= o && o <= omin && d <= dmin)
1553             {
1554                 hd = kd;
1555                 omin = o;
1556                 dmin = d;
1557             }
1558         }
1559     }
1560
1561     return hd;
1562 }
1563
1564 /*---------------------------------------------------------------------------*/
1565
1566 static int gui_wrap_L(int id, int dd)
1567 {
1568     int jd, kd;
1569
1570     if ((jd = gui_stick_L(id, dd)) == 0)
1571         for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
1572             ;
1573
1574     return jd;
1575 }
1576
1577 static int gui_wrap_R(int id, int dd)
1578 {
1579     int jd, kd;
1580
1581     if ((jd = gui_stick_R(id, dd)) == 0)
1582         for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
1583             ;
1584
1585     return jd;
1586 }
1587
1588 static int gui_wrap_U(int id, int dd)
1589 {
1590     int jd, kd;
1591
1592     if ((jd = gui_stick_U(id, dd)) == 0)
1593         for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
1594             ;
1595
1596     return jd;
1597 }
1598
1599 static int gui_wrap_D(int id, int dd)
1600 {
1601     int jd, kd;
1602
1603     if ((jd = gui_stick_D(id, dd)) == 0)
1604         for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
1605             ;
1606
1607     return jd;
1608 }
1609
1610 /*---------------------------------------------------------------------------*/
1611
1612 /* Flag the axes to prevent uncontrolled scrolling. */
1613
1614 static int xflag = 1;
1615 static int yflag = 1;
1616
1617 void gui_stuck()
1618 {
1619     /* Force the user to recenter the joystick before the next GUI action. */
1620
1621     xflag = 0;
1622     yflag = 0;
1623 }
1624
1625 int gui_stick(int id, int x, int y)
1626 {
1627     int jd = 0;
1628
1629     /* Find a new active widget in the direction of joystick motion. */
1630
1631     if (x && -JOY_MID <= x && x <= +JOY_MID)
1632         xflag = 1;
1633     else if (x < -JOY_MID && xflag && (jd = gui_wrap_L(id, active)))
1634         xflag = 0;
1635     else if (x > +JOY_MID && xflag && (jd = gui_wrap_R(id, active)))
1636         xflag = 0;
1637
1638     if (y && -JOY_MID <= y && y <= +JOY_MID)
1639         yflag = 1;
1640     else if (y < -JOY_MID && yflag && (jd = gui_wrap_U(id, active)))
1641         yflag = 0;
1642     else if (y > +JOY_MID && yflag && (jd = gui_wrap_D(id, active)))
1643         yflag = 0;
1644
1645     /* If the active widget has changed, return the new active id. */
1646
1647     if (jd == 0 || jd == active)
1648         return 0;
1649     else
1650         return active = jd;
1651 }
1652
1653 /*---------------------------------------------------------------------------*/