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