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