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