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