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 #include <limits.h>
19 
20 #include "config.h"
21 #include "video.h"
22 #include "glext.h"
23 #include "image.h"
24 #include "vec3.h"
25 #include "gui.h"
26 #include "common.h"
27 #include "font.h"
28 #include "theme.h"
29 
30 #include "fs.h"
31 #include "fs_rwops.h"
32 
33 /*---------------------------------------------------------------------------*/
34 
35 /* Very pure colors for the GUI. I was watching BANZAI when I designed this. */
36 
37 const GLubyte gui_wht[4] = { 0xFF, 0xFF, 0xFF, 0xFF };  /* White  */
38 const GLubyte gui_yel[4] = { 0xFF, 0xFF, 0x00, 0xFF };  /* Yellow */
39 const GLubyte gui_red[4] = { 0xFF, 0x00, 0x00, 0xFF };  /* Red    */
40 const GLubyte gui_grn[4] = { 0x00, 0xFF, 0x00, 0xFF };  /* Green  */
41 const GLubyte gui_blu[4] = { 0x00, 0x00, 0xFF, 0xFF };  /* Blue   */
42 const GLubyte gui_blk[4] = { 0x00, 0x00, 0x00, 0xFF };  /* Black  */
43 const GLubyte gui_gry[4] = { 0x55, 0x55, 0x55, 0xFF };  /* Gray   */
44 const GLubyte gui_shd[4] = { 0x00, 0x00, 0x00, 0x80 };  /* Shadow */
45 
46 /*---------------------------------------------------------------------------*/
47 
48 #define WIDGET_MAX 256
49 
50 #define GUI_FREE   0
51 #define GUI_HARRAY 1
52 #define GUI_VARRAY 2
53 #define GUI_HSTACK 3
54 #define GUI_VSTACK 4
55 #define GUI_FILLER 5
56 #define GUI_IMAGE  6
57 #define GUI_LABEL  7
58 #define GUI_COUNT  8
59 #define GUI_CLOCK  9
60 #define GUI_SPACE  10
61 #define GUI_BUTTON 11
62 
63 #define GUI_STATE  1
64 #define GUI_FILL   2
65 #define GUI_HILITE 4
66 #define GUI_RECT   8
67 
68 #define GUI_LINES 8
69 
70 /*---------------------------------------------------------------------------*/
71 
72 struct widget
73 {
74     int     type;
75     int     flags;
76     int     token;
77     int     value;
78     int     font;
79     int     size;
80     int     rect;
81 
82     const GLubyte *color0;
83     const GLubyte *color1;
84 
85     int     x, y;
86     int     w, h;
87     int     car;
88     int     cdr;
89 
90     GLuint  image;
91     GLfloat scale;
92 
93     int     text_w;
94     int     text_h;
95 
96     enum trunc trunc;
97 };
98 
99 /*---------------------------------------------------------------------------*/
100 
101 /* GUI widget state */
102 
103 static struct widget widget[WIDGET_MAX];
104 static int           active;
105 static int           hovered;
106 static int           clicked;
107 static int           padding;
108 static int           borders[4];
109 
110 /* Digit widgets for the HUD. */
111 
112 static int digit_id[3][11];
113 
114 /* Cursor image. */
115 
116 static int cursor_id = 0;
117 static int cursor_st = 0;
118 
119 /* GUI theme. */
120 
121 static struct theme curr_theme;
122 
123 /*---------------------------------------------------------------------------*/
124 
gui_hot(int id)125 static int gui_hot(int id)
126 {
127     return (widget[id].flags & GUI_STATE);
128 }
129 
gui_size(void)130 static int gui_size(void)
131 {
132     const int w = video.device_w;
133     const int h = video.device_h;
134 
135     return MIN(w, h);
136 }
137 
138 /*---------------------------------------------------------------------------*/
139 
140 /* Vertex buffer definitions for widget rendering. */
141 
142 /* Vertex count */
143 
144 #define RECT_VERT 16
145 #define TEXT_VERT  8
146 #define IMAGE_VERT 4
147 
148 #define WIDGET_VERT (RECT_VERT + MAX(TEXT_VERT, IMAGE_VERT))
149 
150 /* Element count */
151 
152 #define RECT_ELEM 28
153 
154 #define WIDGET_ELEM (RECT_ELEM)
155 
156 struct vert
157 {
158     GLubyte c[4];
159     GLfloat u[2];
160     GLshort p[2];
161 };
162 
163 static struct vert vert_buf[WIDGET_MAX * WIDGET_VERT];
164 static GLuint      vert_vbo = 0;
165 static GLuint      vert_ebo = 0;
166 
167 /*---------------------------------------------------------------------------*/
168 
set_vert(struct vert * v,int x,int y,GLfloat s,GLfloat t,const GLubyte * c)169 static void set_vert(struct vert *v, int x, int y,
170                      GLfloat s, GLfloat t, const GLubyte *c)
171 {
172     v->c[0] = c[0];
173     v->c[1] = c[1];
174     v->c[2] = c[2];
175     v->c[3] = c[3];
176     v->u[0] = s;
177     v->u[1] = t;
178     v->p[0] = x;
179     v->p[1] = y;
180 }
181 
182 /*---------------------------------------------------------------------------*/
183 
draw_enable(GLboolean c,GLboolean u,GLboolean p)184 static void draw_enable(GLboolean c, GLboolean u, GLboolean p)
185 {
186     glBindBuffer_(GL_ARRAY_BUFFER,         vert_vbo);
187     glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, vert_ebo);
188 
189     if (c)
190     {
191         glEnableClientState(GL_COLOR_ARRAY);
192         glColorPointer   (4, GL_UNSIGNED_BYTE, sizeof (struct vert),
193                                   (GLvoid *) offsetof (struct vert, c));
194     }
195     if (u)
196     {
197         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
198         glTexCoordPointer(2, GL_FLOAT,         sizeof (struct vert),
199                                   (GLvoid *) offsetof (struct vert, u));
200     }
201     if (p)
202     {
203         glEnableClientState(GL_VERTEX_ARRAY);
204         glVertexPointer  (2, GL_SHORT,         sizeof (struct vert),
205                                   (GLvoid *) offsetof (struct vert, p));
206     }
207 }
208 
draw_rect(int id)209 static void draw_rect(int id)
210 {
211     glDrawElements(GL_TRIANGLE_STRIP, RECT_ELEM, GL_UNSIGNED_SHORT,
212                    (const GLvoid *) (id * WIDGET_ELEM * sizeof (GLushort)));
213 }
214 
draw_text(int id)215 static void draw_text(int id)
216 {
217     glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_VERT + RECT_VERT, TEXT_VERT);
218 }
219 
draw_image(int id)220 static void draw_image(int id)
221 {
222     glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_VERT + RECT_VERT, IMAGE_VERT);
223 }
224 
draw_disable(void)225 static void draw_disable(void)
226 {
227     glBindBuffer_(GL_ARRAY_BUFFER,         0);
228     glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0);
229 
230     glDisableClientState(GL_VERTEX_ARRAY);
231     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
232     glDisableClientState(GL_COLOR_ARRAY);
233 }
234 
235 /*---------------------------------------------------------------------------*/
236 
237 /*
238  * Generate vertices for a 3x3 rectangle. Vertices are arranged
239  * top-to-bottom and left-to-right, triangle strips are arranged
240  * left-to-right and top-to-bottom (one strip per row). Degenerate
241  * triangles (two extra indices per stitch) are inserted for a
242  * continous strip.
243  */
244 
245 static const GLushort rect_elem_base[RECT_ELEM] = {
246     0, 1, 4, 5, 8, 9, 12, 13, 13, 1,    /* Top    */
247     1, 2, 5, 6, 9, 10, 13, 14, 14, 2,   /* Middle */
248     2, 3, 6, 7, 10, 11, 14, 15          /* Bottom */
249 };
250 
gui_geom_rect(int id,int x,int y,int w,int h,int f)251 static void gui_geom_rect(int id, int x, int y, int w, int h, int f)
252 {
253     GLushort rect_elem[RECT_ELEM];
254 
255     struct vert *v = vert_buf + id * WIDGET_VERT;
256     struct vert *p = v;
257 
258     int X[4];
259     int Y[4];
260 
261     int i, j;
262 
263     /* Generate vertex and element data for the widget's rectangle. */
264 
265     X[0] = x;
266     X[1] = x +     ((f & GUI_W) ? borders[0] : 0);
267     X[2] = x + w - ((f & GUI_E) ? borders[1] : 0);
268     X[3] = x + w;
269 
270     Y[0] = y + h;
271     Y[1] = y + h - ((f & GUI_N) ? borders[2] : 0);
272     Y[2] = y +     ((f & GUI_S) ? borders[3] : 0);
273     Y[3] = y;
274 
275     for (i = 0; i < 4; i++)
276         for (j = 0; j < 4; j++)
277             set_vert(p++, X[i], Y[j], curr_theme.s[i], curr_theme.t[j], gui_wht);
278 
279     for (i = 0; i < RECT_ELEM; i++)
280         rect_elem[i] = id * WIDGET_VERT + rect_elem_base[i];
281 
282     /* Copy this off to the VBOs. */
283 
284     glBindBuffer_   (GL_ARRAY_BUFFER, vert_vbo);
285     glBufferSubData_(GL_ARRAY_BUFFER,
286                      id * WIDGET_VERT * sizeof (struct vert),
287                             RECT_VERT * sizeof (struct vert), v);
288     glBindBuffer_   (GL_ARRAY_BUFFER, 0);
289 
290     glBindBuffer_   (GL_ELEMENT_ARRAY_BUFFER, vert_ebo);
291     glBufferSubData_(GL_ELEMENT_ARRAY_BUFFER,
292                      id * WIDGET_ELEM * sizeof (GLushort),
293                             RECT_ELEM * sizeof (GLushort), rect_elem);
294     glBindBuffer_   (GL_ELEMENT_ARRAY_BUFFER, 0);
295 }
296 
gui_geom_text(int id,int x,int y,int w,int h,const GLubyte * c0,const GLubyte * c1)297 static void gui_geom_text(int id, int x, int y, int w, int h,
298                           const GLubyte *c0, const GLubyte *c1)
299 {
300     struct vert *v = vert_buf + id * WIDGET_VERT + RECT_VERT;
301 
302     /* Assume the applied texture size is rect size rounded to power-of-two. */
303 
304     int W;
305     int H;
306 
307     image_size(&W, &H, w, h);
308 
309     if (w > 0 && h > 0 && W > 0 && H > 0)
310     {
311         const int d = h / 16;  /* Shadow offset */
312 
313         const int ww = ((W - w) % 2) ? w + 1 : w;
314         const int hh = ((H - h) % 2) ? h + 1 : h;
315 
316         const GLfloat s0 = 0.5f * (W - ww) / W;
317         const GLfloat t0 = 0.5f * (H - hh) / H;
318         const GLfloat s1 = 1.0f - s0;
319         const GLfloat t1 = 1.0f - t0;
320 
321         /* Generate vertex data for the colored text and its shadow. */
322 
323         set_vert(v + 0, x      + d, y + hh - d, s0, t0, gui_shd);
324         set_vert(v + 1, x      + d, y      - d, s0, t1, gui_shd);
325         set_vert(v + 2, x + ww + d, y + hh - d, s1, t0, gui_shd);
326         set_vert(v + 3, x + ww + d, y      - d, s1, t1, gui_shd);
327 
328         set_vert(v + 4, x,          y + hh,     s0, t0, c1);
329         set_vert(v + 5, x,          y,          s0, t1, c0);
330         set_vert(v + 6, x + ww,     y + hh,     s1, t0, c1);
331         set_vert(v + 7, x + ww,     y,          s1, t1, c0);
332 
333     }
334     else memset(v, 0, TEXT_VERT * sizeof (struct vert));
335 
336     /* Copy this off to the VBO. */
337 
338     glBindBuffer_   (GL_ARRAY_BUFFER, vert_vbo);
339     glBufferSubData_(GL_ARRAY_BUFFER,
340                      (id * WIDGET_VERT + RECT_VERT) * sizeof (struct vert),
341                                          TEXT_VERT  * sizeof (struct vert), v);
342     glBindBuffer_   (GL_ARRAY_BUFFER, 0);
343 }
344 
gui_geom_image(int id,int x,int y,int w,int h,int f)345 static void gui_geom_image(int id, int x, int y, int w, int h, int f)
346 {
347     struct vert *v = vert_buf + id * WIDGET_VERT + RECT_VERT;
348 
349     int X[2];
350     int Y[2];
351 
352     /* Trace inner vertices of the background rectangle. */
353 
354     X[0] = x +     ((f & GUI_W) ? borders[0] : 0);
355     X[1] = x + w - ((f & GUI_E) ? borders[1] : 0);
356 
357     Y[0] = y + h - ((f & GUI_N) ? borders[2] : 0);
358     Y[1] = y +     ((f & GUI_S) ? borders[3] : 0);
359 
360     set_vert(v + 0, X[0], Y[0], 0.0f, 1.0f, gui_wht);
361     set_vert(v + 1, X[0], Y[1], 0.0f, 0.0f, gui_wht);
362     set_vert(v + 2, X[1], Y[0], 1.0f, 1.0f, gui_wht);
363     set_vert(v + 3, X[1], Y[1], 1.0f, 0.0f, gui_wht);
364 
365     /* Copy this off to the VBO. */
366 
367     glBindBuffer_   (GL_ARRAY_BUFFER, vert_vbo);
368     glBufferSubData_(GL_ARRAY_BUFFER,
369                      (id * WIDGET_VERT + RECT_VERT) * sizeof (struct vert),
370                      IMAGE_VERT * sizeof (struct vert), v);
371     glBindBuffer_   (GL_ARRAY_BUFFER, 0);
372 }
373 
gui_geom_widget(int id,int flags)374 static void gui_geom_widget(int id, int flags)
375 {
376     int jd;
377 
378     int w = widget[id].w;
379     int h = widget[id].h;
380 
381     int W = widget[id].text_w;
382     int H = widget[id].text_h;
383     int R = widget[id].rect;
384 
385     const GLubyte *c0 = widget[id].color0;
386     const GLubyte *c1 = widget[id].color1;
387 
388     if ((widget[id].flags & GUI_RECT) && !(flags & GUI_RECT))
389     {
390         gui_geom_rect(id, -w / 2, -h / 2, w, h, R);
391         flags |= GUI_RECT;
392     }
393 
394     switch (widget[id].type)
395     {
396     case GUI_FILLER:
397     case GUI_SPACE:
398         break;
399 
400     case GUI_HARRAY:
401     case GUI_VARRAY:
402     case GUI_HSTACK:
403     case GUI_VSTACK:
404 
405         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
406             gui_geom_widget(jd, flags);
407 
408         break;
409 
410     case GUI_IMAGE:
411         gui_geom_image(id, -w / 2, -h / 2, w, h, R);
412         break;
413 
414     default:
415         gui_geom_text(id, -W / 2, -H / 2, W, H, c0, c1);
416         break;
417     }
418 }
419 
420 /*---------------------------------------------------------------------------*/
421 
422 #define FONT_MAX 4
423 
424 static struct font fonts[FONT_MAX];
425 static int         fontc;
426 
427 static int font_sizes[3];
428 
gui_font_load(const char * path)429 static int gui_font_load(const char *path)
430 {
431     int i;
432 
433     /* Find a previously loaded font. */
434 
435     for (i = 0; i < fontc; i++)
436         if (strcmp(fonts[i].path, path) == 0)
437             return i;
438 
439     /* Load a new font. */
440 
441     if (fontc < FONT_MAX)
442     {
443         if (font_load(&fonts[fontc], path, font_sizes))
444         {
445             fontc++;
446             return fontc - 1;
447         }
448     }
449 
450     /* Return index of default font. */
451 
452     return 0;
453 }
454 
455 static void gui_font_quit(void);
456 
gui_font_init(void)457 static void gui_font_init(void)
458 {
459     gui_font_quit();
460 
461     if (font_init())
462     {
463         int s = gui_size();
464 
465         font_sizes[0] = s / 26;
466         font_sizes[1] = s / 13;
467         font_sizes[2] = s /  7;
468 
469         /* Load the default font at index 0. */
470 
471         gui_font_load(*curr_lang.font ? curr_lang.font : GUI_FACE);
472     }
473 }
474 
gui_font_quit(void)475 static void gui_font_quit(void)
476 {
477     int i;
478 
479     for (i = 0; i < fontc; i++)
480         font_free(&fonts[i]);
481 
482     fontc = 0;
483 
484     font_quit();
485 }
486 
487 /*---------------------------------------------------------------------------*/
488 
gui_theme_quit(void)489 static void gui_theme_quit(void)
490 {
491     theme_free(&curr_theme);
492 }
493 
gui_theme_init(void)494 static void gui_theme_init(void)
495 {
496     gui_theme_quit();
497 
498     theme_load(&curr_theme, config_get_s(CONFIG_THEME));
499 }
500 
501 /*---------------------------------------------------------------------------*/
502 
gui_init(void)503 void gui_init(void)
504 {
505     const int s = gui_size();
506 
507     int i, j;
508 
509     memset(widget, 0, sizeof (struct widget) * WIDGET_MAX);
510 
511     /* Compute default widget/text padding. */
512 
513     padding = s / 60;
514 
515     for (i = 0; i < 4; i++)
516         borders[i] = padding;
517 
518     /* Initialize font rendering. */
519 
520     gui_font_init();
521 
522     /* Initialize GUI theme. */
523 
524     gui_theme_init();
525 
526     /* Initialize the VBOs. */
527 
528     memset(vert_buf, 0, sizeof (vert_buf));
529 
530     glGenBuffers_(1,              &vert_vbo);
531     glBindBuffer_(GL_ARRAY_BUFFER, vert_vbo);
532     glBufferData_(GL_ARRAY_BUFFER, sizeof (vert_buf), vert_buf, GL_STATIC_DRAW);
533     glBindBuffer_(GL_ARRAY_BUFFER, 0);
534 
535     glGenBuffers_(1,                      &vert_ebo);
536     glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, vert_ebo);
537     glBufferData_(GL_ELEMENT_ARRAY_BUFFER,
538                   WIDGET_MAX * WIDGET_ELEM * sizeof (GLushort),
539                   NULL, GL_STATIC_DRAW);
540     glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0);
541 
542     /* Cache digit glyphs for HUD rendering. */
543 
544     for (i = 0; i < 3; i++)
545     {
546         digit_id[i][ 0] = gui_label(0, "0", i, 0, 0);
547         digit_id[i][ 1] = gui_label(0, "1", i, 0, 0);
548         digit_id[i][ 2] = gui_label(0, "2", i, 0, 0);
549         digit_id[i][ 3] = gui_label(0, "3", i, 0, 0);
550         digit_id[i][ 4] = gui_label(0, "4", i, 0, 0);
551         digit_id[i][ 5] = gui_label(0, "5", i, 0, 0);
552         digit_id[i][ 6] = gui_label(0, "6", i, 0, 0);
553         digit_id[i][ 7] = gui_label(0, "7", i, 0, 0);
554         digit_id[i][ 8] = gui_label(0, "8", i, 0, 0);
555         digit_id[i][ 9] = gui_label(0, "9", i, 0, 0);
556         digit_id[i][10] = gui_label(0, ":", i, 0, 0);
557     }
558 
559     for (i = 0; i < 3; i++)
560         for (j = 0; j < 11; ++j)
561             gui_layout(digit_id[i][j], 0, 0);
562 
563     /* Cache an image for the cursor. Scale it to the same size as a digit. */
564 
565     if ((cursor_id = gui_image(0, "gui/cursor.png", widget[digit_id[1][0]].w,
566                                                     widget[digit_id[1][0]].h)))
567         gui_layout(cursor_id, 0, 0);
568 
569     active = 0;
570 }
571 
gui_free(void)572 void gui_free(void)
573 {
574     int id;
575 
576     /* Release the VBOs. */
577 
578     glDeleteBuffers_(1, &vert_vbo);
579     glDeleteBuffers_(1, &vert_ebo);
580 
581     /* Release any remaining widget texture and display list indices. */
582 
583     for (id = 1; id < WIDGET_MAX; id++)
584     {
585         if (widget[id].image)
586             glDeleteTextures(1, &widget[id].image);
587 
588         widget[id].type  = GUI_FREE;
589         widget[id].flags = 0;
590         widget[id].image = 0;
591         widget[id].cdr   = 0;
592         widget[id].car   = 0;
593     }
594 
595     /* Release all loaded fonts and finalize font rendering. */
596 
597     gui_font_quit();
598 
599     /* Release theme resources. */
600 
601     gui_theme_quit();
602 }
603 
604 /*---------------------------------------------------------------------------*/
605 
gui_widget(int pd,int type)606 static int gui_widget(int pd, int type)
607 {
608     int id;
609 
610     /* Find an unused entry in the widget table. */
611 
612     for (id = 1; id < WIDGET_MAX; id++)
613         if (widget[id].type == GUI_FREE)
614         {
615             /* Set the type and default properties. */
616 
617             widget[id].type   = type;
618             widget[id].flags  = 0;
619             widget[id].token  = 0;
620             widget[id].value  = 0;
621             widget[id].font   = 0;
622             widget[id].size   = 0;
623             widget[id].rect   = GUI_ALL;
624             widget[id].w      = 0;
625             widget[id].h      = 0;
626             widget[id].image  = 0;
627             widget[id].color0 = gui_wht;
628             widget[id].color1 = gui_wht;
629             widget[id].scale  = 1.0f;
630             widget[id].trunc  = TRUNC_NONE;
631             widget[id].text_w = 0;
632             widget[id].text_h = 0;
633 
634             /* Insert the new widget into the parent's widget list. */
635 
636             if (pd)
637             {
638                 widget[id].car = 0;
639                 widget[id].cdr = widget[pd].car;
640                 widget[pd].car = id;
641             }
642             else
643             {
644                 widget[id].car = 0;
645                 widget[id].cdr = 0;
646             }
647 
648             return id;
649         }
650 
651     log_printf("Out of widget IDs\n");
652 
653     return 0;
654 }
655 
gui_harray(int pd)656 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
gui_varray(int pd)657 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
gui_hstack(int pd)658 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
gui_vstack(int pd)659 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
gui_filler(int pd)660 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
661 
662 /*---------------------------------------------------------------------------*/
663 
gui_measure_ttf(const char * text,TTF_Font * font)664 static struct size gui_measure_ttf(const char *text, TTF_Font *font)
665 {
666     struct size size = { 0, 0 };
667 
668     if (font)
669         TTF_SizeUTF8(font, text, &size.w, &size.h);
670 
671     return size;
672 }
673 
gui_measure(const char * text,int size)674 struct size gui_measure(const char *text, int size)
675 {
676     return gui_measure_ttf(text, fonts[0].ttf[size]);
677 }
678 
679 /*---------------------------------------------------------------------------*/
680 
gui_trunc_head(const char * text,const int maxwidth,TTF_Font * font)681 static char *gui_trunc_head(const char *text,
682                             const int maxwidth,
683                             TTF_Font *font)
684 {
685     int left, right, mid;
686     char *str = NULL;
687 
688     left  = 0;
689     right = strlen(text);
690 
691     while (right - left > 1)
692     {
693         mid = (left + right) / 2;
694 
695         str = concat_string("...", text + mid, NULL);
696 
697         if (gui_measure_ttf(str, font).w <= maxwidth)
698             right = mid;
699         else
700             left = mid;
701 
702         free(str);
703     }
704 
705     return concat_string("...", text + right, NULL);
706 }
707 
gui_trunc_tail(const char * text,const int maxwidth,TTF_Font * font)708 static char *gui_trunc_tail(const char *text,
709                             const int maxwidth,
710                             TTF_Font *font)
711 {
712     int left, right, mid;
713     char *str = NULL;
714 
715     left  = 0;
716     right = strlen(text);
717 
718     while (right - left > 1)
719     {
720         mid = (left + right) / 2;
721 
722         str = malloc(mid + sizeof ("..."));
723 
724         memcpy(str,       text,  mid);
725         memcpy(str + mid, "...", sizeof ("..."));
726 
727         if (gui_measure_ttf(str, font).w <= maxwidth)
728             left = mid;
729         else
730             right = mid;
731 
732         free(str);
733     }
734 
735     str = malloc(left + sizeof ("..."));
736 
737     memcpy(str,        text,  left);
738     memcpy(str + left, "...", sizeof ("..."));
739 
740     return str;
741 }
742 
gui_truncate(const char * text,const int maxwidth,TTF_Font * font,enum trunc trunc)743 static char *gui_truncate(const char *text,
744                           const int maxwidth,
745                           TTF_Font *font,
746                           enum trunc trunc)
747 {
748     if (gui_measure_ttf(text, font).w <= maxwidth)
749         return strdup(text);
750 
751     switch (trunc)
752     {
753     case TRUNC_NONE: return strdup(text);                         break;
754     case TRUNC_HEAD: return gui_trunc_head(text, maxwidth, font); break;
755     case TRUNC_TAIL: return gui_trunc_tail(text, maxwidth, font); break;
756     }
757 
758     return NULL;
759 }
760 
761 /*---------------------------------------------------------------------------*/
762 
gui_set_image(int id,const char * file)763 void gui_set_image(int id, const char *file)
764 {
765     glDeleteTextures(1, &widget[id].image);
766 
767     widget[id].image = make_image_from_file(file, IF_MIPMAP);
768 }
769 
gui_set_label(int id,const char * text)770 void gui_set_label(int id, const char *text)
771 {
772     TTF_Font *ttf = fonts[widget[id].font].ttf[widget[id].size];
773 
774     int w = 0;
775     int h = 0;
776 
777     char *str;
778 
779     glDeleteTextures(1, &widget[id].image);
780 
781     str = gui_truncate(text, widget[id].w - padding, ttf, widget[id].trunc);
782 
783     widget[id].image = make_image_from_font(NULL, NULL,
784                                             &widget[id].text_w,
785                                             &widget[id].text_h,
786                                             str, ttf, 0);
787     w = widget[id].text_w;
788     h = widget[id].text_h;
789 
790     gui_geom_text(id, -w / 2, -h / 2, w, h,
791                   widget[id].color0,
792                   widget[id].color1);
793 
794     free(str);
795 }
796 
gui_set_count(int id,int value)797 void gui_set_count(int id, int value)
798 {
799     widget[id].value = value;
800 }
801 
gui_set_clock(int id,int value)802 void gui_set_clock(int id, int value)
803 {
804     widget[id].value = value;
805 }
806 
gui_set_color(int id,const GLubyte * c0,const GLubyte * c1)807 void gui_set_color(int id, const GLubyte *c0,
808                            const GLubyte *c1)
809 {
810     if (id)
811     {
812         c0 = c0 ? c0 : gui_yel;
813         c1 = c1 ? c1 : gui_red;
814 
815         if (widget[id].color0 != c0 || widget[id].color1 != c1)
816         {
817             int w = widget[id].text_w;
818             int h = widget[id].text_h;
819 
820             widget[id].color0 = c0;
821             widget[id].color1 = c1;
822 
823             gui_geom_text(id, -w / 2, -h / 2, w, h, c0, c1);
824         }
825     }
826 }
827 
gui_set_multi(int id,const char * text)828 void gui_set_multi(int id, const char *text)
829 {
830     const char *p;
831 
832     char s[GUI_LINES][MAXSTR];
833     int i, sc, lc, jd;
834 
835     size_t n = 0;
836 
837     /* Count available labels. */
838 
839     for (lc = 0, jd = widget[id].car; jd; lc++, jd = widget[jd].cdr);
840 
841     /* Copy each delimited string to a line buffer. */
842 
843     for (p = text, sc = 0; *p && sc < lc; sc++)
844     {
845         strncpy(s[sc], p, (n = strcspn(p, "\\")));
846         s[sc][n] = 0;
847 
848         if (*(p += n) == '\\') p++;
849     }
850 
851     /* Set the label value for each line. */
852 
853     for (i = lc - 1, jd = widget[id].car; i >= 0; i--, jd = widget[jd].cdr)
854         gui_set_label(jd, i < sc ? s[i] : "");
855 }
856 
gui_set_trunc(int id,enum trunc trunc)857 void gui_set_trunc(int id, enum trunc trunc)
858 {
859     widget[id].trunc = trunc;
860 }
861 
gui_set_font(int id,const char * path)862 void gui_set_font(int id, const char *path)
863 {
864     widget[id].font = gui_font_load(path);
865 }
866 
gui_set_fill(int id)867 void gui_set_fill(int id)
868 {
869     widget[id].flags |= GUI_FILL;
870 }
871 
872 /*
873  * Activate a widget, allowing it  to behave as a normal state widget.
874  * This may  be used  to create  image buttons, or  cause an  array of
875  * widgets to behave as a single state widget.
876  */
gui_set_state(int id,int token,int value)877 int gui_set_state(int id, int token, int value)
878 {
879     widget[id].flags |= GUI_STATE;
880     widget[id].token  = token;
881     widget[id].value  = value;
882 
883     return id;
884 }
885 
gui_set_hilite(int id,int hilite)886 void gui_set_hilite(int id, int hilite)
887 {
888     if (hilite)
889         widget[id].flags |= GUI_HILITE;
890     else
891         widget[id].flags &= ~GUI_HILITE;
892 }
893 
gui_set_rect(int id,int rect)894 void gui_set_rect(int id, int rect)
895 {
896     widget[id].rect   = rect;
897     widget[id].flags |= GUI_RECT;
898 }
899 
gui_set_cursor(int st)900 void gui_set_cursor(int st)
901 {
902     cursor_st = st;
903 }
904 
905 /*---------------------------------------------------------------------------*/
906 
gui_image(int pd,const char * file,int w,int h)907 int gui_image(int pd, const char *file, int w, int h)
908 {
909     int id;
910 
911     if ((id = gui_widget(pd, GUI_IMAGE)))
912     {
913         widget[id].image  = make_image_from_file(file, IF_MIPMAP);
914         widget[id].w      = w;
915         widget[id].h      = h;
916         widget[id].flags |= GUI_RECT;
917     }
918     return id;
919 }
920 
gui_start(int pd,const char * text,int size,int token,int value)921 int gui_start(int pd, const char *text, int size, int token, int value)
922 {
923     int id;
924 
925     if ((id = gui_state(pd, text, size, token, value)))
926         active = id;
927 
928     return id;
929 }
930 
gui_state(int pd,const char * text,int size,int token,int value)931 int gui_state(int pd, const char *text, int size, int token, int value)
932 {
933     int id;
934 
935     if ((id = gui_widget(pd, GUI_BUTTON)))
936     {
937         TTF_Font *ttf = fonts[widget[id].font].ttf[size];
938 
939         widget[id].flags |= (GUI_STATE | GUI_RECT);
940 
941         widget[id].image = make_image_from_font(NULL, NULL,
942                                                 &widget[id].text_w,
943                                                 &widget[id].text_h,
944                                                 text, ttf, 0);
945         widget[id].w     = widget[id].text_w;
946         widget[id].h     = widget[id].text_h;
947         widget[id].size  = size;
948         widget[id].token = token;
949         widget[id].value = value;
950     }
951     return id;
952 }
953 
gui_label(int pd,const char * text,int size,const GLubyte * c0,const GLubyte * c1)954 int gui_label(int pd, const char *text, int size, const GLubyte *c0,
955                                                   const GLubyte *c1)
956 {
957     int id;
958 
959     if ((id = gui_widget(pd, GUI_LABEL)))
960     {
961         TTF_Font *ttf = fonts[widget[id].font].ttf[size];
962 
963         widget[id].image = make_image_from_font(NULL, NULL,
964                                                 &widget[id].text_w,
965                                                 &widget[id].text_h,
966                                                 text, ttf, 0);
967         widget[id].w      = widget[id].text_w;
968         widget[id].h      = widget[id].text_h;
969         widget[id].size   = size;
970         widget[id].color0 = c0 ? c0 : gui_yel;
971         widget[id].color1 = c1 ? c1 : gui_red;
972         widget[id].flags |= GUI_RECT;
973     }
974     return id;
975 }
976 
gui_count(int pd,int value,int size)977 int gui_count(int pd, int value, int size)
978 {
979     int i, id;
980 
981     if ((id = gui_widget(pd, GUI_COUNT)))
982     {
983         for (i = value; i; i /= 10)
984             widget[id].w += widget[digit_id[size][0]].text_w;
985 
986         widget[id].h      = widget[digit_id[size][0]].text_h;
987         widget[id].value  = value;
988         widget[id].size   = size;
989         widget[id].color0 = gui_yel;
990         widget[id].color1 = gui_red;
991         widget[id].flags |= GUI_RECT;
992     }
993     return id;
994 }
995 
gui_clock(int pd,int value,int size)996 int gui_clock(int pd, int value, int size)
997 {
998     int id;
999 
1000     if ((id = gui_widget(pd, GUI_CLOCK)))
1001     {
1002         widget[id].w      = widget[digit_id[size][0]].text_w * 6;
1003         widget[id].h      = widget[digit_id[size][0]].text_h;
1004         widget[id].value  = value;
1005         widget[id].size   = size;
1006         widget[id].color0 = gui_yel;
1007         widget[id].color1 = gui_red;
1008         widget[id].flags |= GUI_RECT;
1009     }
1010     return id;
1011 }
1012 
gui_space(int pd)1013 int gui_space(int pd)
1014 {
1015     int id;
1016 
1017     if ((id = gui_widget(pd, GUI_SPACE)))
1018     {
1019         widget[id].w = 0;
1020         widget[id].h = 0;
1021     }
1022     return id;
1023 }
1024 
1025 /*---------------------------------------------------------------------------*/
1026 
1027 /*
1028  * Create  a multi-line  text box  using a  vertical array  of labels.
1029  * Parse the  text for '\'  characters and treat them  as line-breaks.
1030  * Preserve the rect specification across the entire array.
1031  */
1032 
gui_multi(int pd,const char * text,int size,const GLubyte * c0,const GLubyte * c1)1033 int gui_multi(int pd, const char *text, int size, const GLubyte *c0,
1034                                                   const GLubyte *c1)
1035 {
1036     int id = 0;
1037 
1038     if (text && *text && (id = gui_varray(pd)))
1039     {
1040         const char *p;
1041 
1042         char s[GUI_LINES][MAXSTR];
1043         int  i, j;
1044 
1045         size_t n = 0;
1046 
1047         /* Copy each delimited string to a line buffer. */
1048 
1049         for (p = text, j = 0; *p && j < GUI_LINES; j++)
1050         {
1051             strncpy(s[j], p, (n = strcspn(p, "\\")));
1052             s[j][n] = 0;
1053 
1054             if (*(p += n) == '\\') p++;
1055         }
1056 
1057         /* Create a label widget for each line. */
1058 
1059         for (i = 0; i < j; i++)
1060             gui_label(id, s[i], size, c0, c1);
1061 
1062         /* Set rectangle on the container. */
1063 
1064         widget[id].flags |= GUI_RECT;
1065     }
1066     return id;
1067 }
1068 
1069 /*---------------------------------------------------------------------------*/
1070 /*
1071  * The bottom-up pass determines the area of all widgets.  The minimum
1072  * width  and height of  a leaf  widget is  given by  the size  of its
1073  * contents.   Array  and  stack   widths  and  heights  are  computed
1074  * recursively from these.
1075  */
1076 
1077 static void gui_widget_up(int id);
1078 
gui_harray_up(int id)1079 static void gui_harray_up(int id)
1080 {
1081     int jd, c = 0;
1082 
1083     /* Find the widest child width and the highest child height. */
1084 
1085     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1086     {
1087         gui_widget_up(jd);
1088 
1089         if (widget[id].h < widget[jd].h)
1090             widget[id].h = widget[jd].h;
1091         if (widget[id].w < widget[jd].w)
1092             widget[id].w = widget[jd].w;
1093 
1094         c++;
1095     }
1096 
1097     /* Total width is the widest child width times the child count. */
1098 
1099     widget[id].w *= c;
1100 }
1101 
gui_varray_up(int id)1102 static void gui_varray_up(int id)
1103 {
1104     int jd, c = 0;
1105 
1106     /* Find the widest child width and the highest child height. */
1107 
1108     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1109     {
1110         gui_widget_up(jd);
1111 
1112         if (widget[id].h < widget[jd].h)
1113             widget[id].h = widget[jd].h;
1114         if (widget[id].w < widget[jd].w)
1115             widget[id].w = widget[jd].w;
1116 
1117         c++;
1118     }
1119 
1120     /* Total height is the highest child height times the child count. */
1121 
1122     widget[id].h *= c;
1123 }
1124 
gui_hstack_up(int id)1125 static void gui_hstack_up(int id)
1126 {
1127     int jd;
1128 
1129     /* Find the highest child height.  Sum the child widths. */
1130 
1131     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1132     {
1133         gui_widget_up(jd);
1134 
1135         if (widget[id].h < widget[jd].h)
1136             widget[id].h = widget[jd].h;
1137 
1138         widget[id].w += widget[jd].w;
1139     }
1140 }
1141 
gui_vstack_up(int id)1142 static void gui_vstack_up(int id)
1143 {
1144     int jd;
1145 
1146     /* Find the widest child width.  Sum the child heights. */
1147 
1148     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1149     {
1150         gui_widget_up(jd);
1151 
1152         if (widget[id].w < widget[jd].w)
1153             widget[id].w = widget[jd].w;
1154 
1155         widget[id].h += widget[jd].h;
1156     }
1157 }
1158 
gui_button_up(int id)1159 static void gui_button_up(int id)
1160 {
1161     if (widget[id].w < widget[id].h && widget[id].w > 0)
1162         widget[id].w = widget[id].h;
1163 
1164     /* Padded text elements look a little nicer. */
1165 
1166     if (widget[id].w < video.device_w)
1167         widget[id].w += padding;
1168     if (widget[id].h < video.device_h)
1169         widget[id].h += padding;
1170 
1171     /* A button should be at least wide enough to accomodate the borders. */
1172 
1173     if (widget[id].w < borders[0] + borders[1])
1174         widget[id].w = borders[0] + borders[1];
1175     if (widget[id].h < borders[2] + borders[3])
1176         widget[id].h = borders[2] + borders[3];
1177 }
1178 
gui_widget_up(int id)1179 static void gui_widget_up(int id)
1180 {
1181     if (id)
1182         switch (widget[id].type)
1183         {
1184         case GUI_HARRAY: gui_harray_up(id); break;
1185         case GUI_VARRAY: gui_varray_up(id); break;
1186         case GUI_HSTACK: gui_hstack_up(id); break;
1187         case GUI_VSTACK: gui_vstack_up(id); break;
1188         case GUI_FILLER:                    break;
1189         default:         gui_button_up(id); break;
1190         }
1191 }
1192 
1193 /*---------------------------------------------------------------------------*/
1194 /*
1195  * The  top-down layout  pass distributes  available area  as computed
1196  * during the bottom-up pass.  Widgets  use their area and position to
1197  * initialize rendering state.
1198  */
1199 
1200 static void gui_widget_dn(int id, int x, int y, int w, int h);
1201 
gui_harray_dn(int id,int x,int y,int w,int h)1202 static void gui_harray_dn(int id, int x, int y, int w, int h)
1203 {
1204     int jd, i = 0, c = 0;
1205 
1206     widget[id].x = x;
1207     widget[id].y = y;
1208     widget[id].w = w;
1209     widget[id].h = h;
1210 
1211     /* Count children. */
1212 
1213     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1214         c += 1;
1215 
1216     /* Distribute horizontal space evenly to all children. */
1217 
1218     for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
1219     {
1220         int x0 = x +  i      * w / c;
1221         int x1 = x + (i + 1) * w / c;
1222 
1223         gui_widget_dn(jd, x0, y, x1 - x0, h);
1224     }
1225 }
1226 
gui_varray_dn(int id,int x,int y,int w,int h)1227 static void gui_varray_dn(int id, int x, int y, int w, int h)
1228 {
1229     int jd, i = 0, c = 0;
1230 
1231     widget[id].x = x;
1232     widget[id].y = y;
1233     widget[id].w = w;
1234     widget[id].h = h;
1235 
1236     /* Count children. */
1237 
1238     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1239         c += 1;
1240 
1241     /* Distribute vertical space evenly to all children. */
1242 
1243     for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
1244     {
1245         int y0 = y +  i      * h / c;
1246         int y1 = y + (i + 1) * h / c;
1247 
1248         gui_widget_dn(jd, x, y0, w, y1 - y0);
1249     }
1250 }
1251 
gui_hstack_dn(int id,int x,int y,int w,int h)1252 static void gui_hstack_dn(int id, int x, int y, int w, int h)
1253 {
1254     int jd, jx = x, jw = 0, c = 0;
1255 
1256     widget[id].x = x;
1257     widget[id].y = y;
1258     widget[id].w = w;
1259     widget[id].h = h;
1260 
1261     /* Measure the total width requested by non-filler children. */
1262 
1263     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1264         if (widget[jd].type == GUI_FILLER)
1265             c += 1;
1266         else if (widget[jd].flags & GUI_FILL)
1267         {
1268             c  += 1;
1269             jw += widget[jd].w;
1270         }
1271         else
1272             jw += widget[jd].w;
1273 
1274     /* Give non-filler children their requested space.   */
1275     /* Distribute the rest evenly among filler children. */
1276 
1277     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1278     {
1279         if (widget[jd].type == GUI_FILLER)
1280             gui_widget_dn(jd, jx, y, (w - jw) / c, h);
1281         else if (widget[jd].flags & GUI_FILL)
1282             gui_widget_dn(jd, jx, y, widget[jd].w + (w - jw) / c, h);
1283         else
1284             gui_widget_dn(jd, jx, y, widget[jd].w, h);
1285 
1286         jx += widget[jd].w;
1287     }
1288 }
1289 
gui_vstack_dn(int id,int x,int y,int w,int h)1290 static void gui_vstack_dn(int id, int x, int y, int w, int h)
1291 {
1292     int jd, jy = y, jh = 0, c = 0;
1293 
1294     widget[id].x = x;
1295     widget[id].y = y;
1296     widget[id].w = w;
1297     widget[id].h = h;
1298 
1299     /* Measure the total height requested by non-filler children. */
1300 
1301     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1302         if (widget[jd].type == GUI_FILLER)
1303             c += 1;
1304         else if (widget[jd].flags & GUI_FILL)
1305         {
1306             c  += 1;
1307             jh += widget[jd].h;
1308         }
1309         else
1310             jh += widget[jd].h;
1311 
1312     /* Give non-filler children their requested space.   */
1313     /* Distribute the rest evenly among filler children. */
1314 
1315     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1316     {
1317         if (widget[jd].type == GUI_FILLER)
1318             gui_widget_dn(jd, x, jy, w, (h - jh) / c);
1319         else if (widget[jd].flags & GUI_FILL)
1320             gui_widget_dn(jd, x, jy, w, widget[jd].h + (h - jh) / c);
1321         else
1322             gui_widget_dn(jd, x, jy, w, widget[jd].h);
1323 
1324         jy += widget[jd].h;
1325     }
1326 }
1327 
gui_filler_dn(int id,int x,int y,int w,int h)1328 static void gui_filler_dn(int id, int x, int y, int w, int h)
1329 {
1330     /* Filler expands to whatever size it is given. */
1331 
1332     widget[id].x = x;
1333     widget[id].y = y;
1334     widget[id].w = w;
1335     widget[id].h = h;
1336 }
1337 
gui_button_dn(int id,int x,int y,int w,int h)1338 static void gui_button_dn(int id, int x, int y, int w, int h)
1339 {
1340     widget[id].x = x;
1341     widget[id].y = y;
1342     widget[id].w = w;
1343     widget[id].h = h;
1344 }
1345 
gui_widget_dn(int id,int x,int y,int w,int h)1346 static void gui_widget_dn(int id, int x, int y, int w, int h)
1347 {
1348     if (id)
1349         switch (widget[id].type)
1350         {
1351         case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
1352         case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
1353         case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
1354         case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
1355         case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
1356         case GUI_SPACE:  gui_filler_dn(id, x, y, w, h); break;
1357         default:         gui_button_dn(id, x, y, w, h); break;
1358         }
1359 }
1360 
1361 /*---------------------------------------------------------------------------*/
1362 /*
1363  * During GUI layout, we make a bottom-up pass to determine total area
1364  * requirements for  the widget  tree.  We position  this area  to the
1365  * sides or center of the screen.  Finally, we make a top-down pass to
1366  * distribute this area to each widget.
1367  */
1368 
gui_layout(int id,int xd,int yd)1369 void gui_layout(int id, int xd, int yd)
1370 {
1371     int x, y;
1372 
1373     int w, W = video.device_w;
1374     int h, H = video.device_h;
1375 
1376     gui_widget_up(id);
1377 
1378     w = widget[id].w;
1379     h = widget[id].h;
1380 
1381     if      (xd < 0) x = 0;
1382     else if (xd > 0) x = (W - w);
1383     else             x = (W - w) / 2;
1384 
1385     if      (yd < 0) y = 0;
1386     else if (yd > 0) y = (H - h);
1387     else             y = (H - h) / 2;
1388 
1389     gui_widget_dn(id, x, y, w, h);
1390 
1391     /* Set up GUI rendering state. */
1392 
1393     gui_geom_widget(id, 0);
1394 
1395     /* Hilite the widget under the cursor, if any. */
1396 
1397     gui_point(id, -1, -1);
1398 }
1399 
gui_search(int id,int x,int y)1400 int gui_search(int id, int x, int y)
1401 {
1402     int jd, kd;
1403 
1404     /* Search the hierarchy for the widget containing the given point. */
1405 
1406     if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
1407                widget[id].y <= y && y < widget[id].y + widget[id].h))
1408     {
1409         if (gui_hot(id))
1410             return id;
1411 
1412         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1413             if ((kd = gui_search(jd, x, y)))
1414                 return kd;
1415     }
1416     return 0;
1417 }
1418 
gui_delete(int id)1419 int gui_delete(int id)
1420 {
1421     if (id)
1422     {
1423         /* Recursively delete all subwidgets. */
1424 
1425         gui_delete(widget[id].cdr);
1426         gui_delete(widget[id].car);
1427 
1428         /* Release any GL resources held by this widget. */
1429 
1430         if (widget[id].image)
1431             glDeleteTextures(1, &widget[id].image);
1432 
1433         /* Mark this widget unused. */
1434 
1435         widget[id].type  = GUI_FREE;
1436         widget[id].flags = 0;
1437         widget[id].image = 0;
1438         widget[id].cdr   = 0;
1439         widget[id].car   = 0;
1440 
1441         /* Clear focus from this widget. */
1442 
1443         if (active == id)
1444             active = 0;
1445     }
1446     return 0;
1447 }
1448 
1449 /*---------------------------------------------------------------------------*/
1450 
gui_paint_rect(int id,int st,int flags)1451 static void gui_paint_rect(int id, int st, int flags)
1452 {
1453     int jd, i = 0;
1454 
1455     /* Use the widget status to determine the background color. */
1456 
1457     i = st | (((widget[id].flags & GUI_HILITE) ? 2 : 0) |
1458               ((id == active)                  ? 1 : 0));
1459 
1460     if ((widget[id].flags & GUI_RECT) && !(flags & GUI_RECT))
1461     {
1462         /* Draw a leaf's background, colored by widget state. */
1463 
1464         glPushMatrix();
1465         {
1466             glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1467                          (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1468 
1469             glBindTexture(GL_TEXTURE_2D, curr_theme.tex[i]);
1470             draw_rect(id);
1471         }
1472         glPopMatrix();
1473 
1474         flags |= GUI_RECT;
1475     }
1476 
1477     switch (widget[id].type)
1478     {
1479     case GUI_HARRAY:
1480     case GUI_VARRAY:
1481     case GUI_HSTACK:
1482     case GUI_VSTACK:
1483 
1484         /* Recursively paint all subwidgets. */
1485 
1486         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1487             gui_paint_rect(jd, i, flags);
1488 
1489         break;
1490     }
1491 }
1492 
1493 /*---------------------------------------------------------------------------*/
1494 
1495 static void gui_paint_text(int id);
1496 
gui_paint_array(int id)1497 static void gui_paint_array(int id)
1498 {
1499     int jd;
1500 
1501     glPushMatrix();
1502     {
1503         GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1504         GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1505         GLfloat ck = widget[id].scale;
1506 
1507         if (1.0f < ck || ck < 1.0f)
1508         {
1509             glTranslatef(+cx, +cy, 0.0f);
1510             glScalef(ck, ck, ck);
1511             glTranslatef(-cx, -cy, 0.0f);
1512         }
1513 
1514         /* Recursively paint all subwidgets. */
1515 
1516         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1517             gui_paint_text(jd);
1518     }
1519     glPopMatrix();
1520 }
1521 
gui_paint_image(int id)1522 static void gui_paint_image(int id)
1523 {
1524     /* Draw the widget rect, textured using the image. */
1525 
1526     glPushMatrix();
1527     {
1528         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1529                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1530 
1531         glScalef(widget[id].scale,
1532                  widget[id].scale,
1533                  widget[id].scale);
1534 
1535         glBindTexture(GL_TEXTURE_2D, widget[id].image);
1536         glColor4ub(gui_wht[0], gui_wht[1], gui_wht[2], gui_wht[3]);
1537         draw_image(id);
1538     }
1539     glPopMatrix();
1540 }
1541 
gui_paint_count(int id)1542 static void gui_paint_count(int id)
1543 {
1544     int j, i = widget[id].size;
1545 
1546     glPushMatrix();
1547     {
1548         /* Translate to the widget center, and apply the pulse scale. */
1549 
1550         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1551                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1552 
1553         glScalef(widget[id].scale,
1554                  widget[id].scale,
1555                  widget[id].scale);
1556 
1557         if (widget[id].value > 0)
1558         {
1559             /* Translate right by half the total width of the rendered value. */
1560 
1561             GLfloat w = -widget[digit_id[i][0]].text_w * 0.5f;
1562 
1563             for (j = widget[id].value; j; j /= 10)
1564                 w += widget[digit_id[i][j % 10]].text_w * 0.5f;
1565 
1566             glTranslatef(w, 0.0f, 0.0f);
1567 
1568             /* Render each digit, moving left after each. */
1569 
1570             for (j = widget[id].value; j; j /= 10)
1571             {
1572                 int jd = digit_id[i][j % 10];
1573 
1574                 glBindTexture(GL_TEXTURE_2D, widget[jd].image);
1575                 draw_text(jd);
1576                 glTranslatef((GLfloat) -widget[jd].text_w, 0.0f, 0.0f);
1577             }
1578         }
1579         else if (widget[id].value == 0)
1580         {
1581             /* If the value is zero, just display a zero in place. */
1582 
1583             glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][0]].image);
1584             draw_text(digit_id[i][0]);
1585         }
1586     }
1587     glPopMatrix();
1588 }
1589 
gui_paint_clock(int id)1590 static void gui_paint_clock(int id)
1591 {
1592     int i  =   widget[id].size;
1593     int mt =  (widget[id].value / 6000) / 10;
1594     int mo =  (widget[id].value / 6000) % 10;
1595     int st = ((widget[id].value % 6000) / 100) / 10;
1596     int so = ((widget[id].value % 6000) / 100) % 10;
1597     int ht = ((widget[id].value % 6000) % 100) / 10;
1598     int ho = ((widget[id].value % 6000) % 100) % 10;
1599 
1600     GLfloat dx_large = (GLfloat) widget[digit_id[i][0]].text_w;
1601     GLfloat dx_small = (GLfloat) widget[digit_id[i][0]].text_w * 0.75f;
1602 
1603     if (widget[id].value < 0)
1604         return;
1605 
1606     glPushMatrix();
1607     {
1608         /* Translate to the widget center, and apply the pulse scale. */
1609 
1610         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1611                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1612 
1613         glScalef(widget[id].scale,
1614                  widget[id].scale,
1615                  widget[id].scale);
1616 
1617         /* Translate left by half the total width of the rendered value. */
1618 
1619         if (mt > 0)
1620             glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1621         else
1622             glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1623 
1624         /* Render the minutes counter. */
1625 
1626         if (mt > 0)
1627         {
1628             glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][mt]].image);
1629             draw_text(digit_id[i][mt]);
1630             glTranslatef(dx_large, 0.0f, 0.0f);
1631         }
1632 
1633         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][mo]].image);
1634         draw_text(digit_id[i][mo]);
1635         glTranslatef(dx_small, 0.0f, 0.0f);
1636 
1637         /* Render the colon. */
1638 
1639         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][10]].image);
1640         draw_text(digit_id[i][10]);
1641         glTranslatef(dx_small, 0.0f, 0.0f);
1642 
1643         /* Render the seconds counter. */
1644 
1645         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][st]].image);
1646         draw_text(digit_id[i][st]);
1647         glTranslatef(dx_large, 0.0f, 0.0f);
1648 
1649         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][so]].image);
1650         draw_text(digit_id[i][so]);
1651         glTranslatef(dx_small, 0.0f, 0.0f);
1652 
1653         /* Render hundredths counter half size. */
1654 
1655         glScalef(0.5f, 0.5f, 1.0f);
1656 
1657         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][ht]].image);
1658         draw_text(digit_id[i][ht]);
1659         glTranslatef(dx_large, 0.0f, 0.0f);
1660 
1661         glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][ho]].image);
1662         draw_text(digit_id[i][ho]);
1663     }
1664     glPopMatrix();
1665 }
1666 
gui_paint_label(int id)1667 static void gui_paint_label(int id)
1668 {
1669     /* Short-circuit empty labels. */
1670 
1671     if (widget[id].image == 0)
1672         return;
1673 
1674     /* Draw the widget text box, textured using the glyph. */
1675 
1676     glPushMatrix();
1677     {
1678         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1679                      (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1680 
1681         glScalef(widget[id].scale,
1682                  widget[id].scale,
1683                  widget[id].scale);
1684 
1685         glBindTexture(GL_TEXTURE_2D, widget[id].image);
1686         draw_text(id);
1687     }
1688     glPopMatrix();
1689 }
1690 
gui_paint_text(int id)1691 static void gui_paint_text(int id)
1692 {
1693     switch (widget[id].type)
1694     {
1695     case GUI_SPACE:  break;
1696     case GUI_FILLER: break;
1697     case GUI_HARRAY: gui_paint_array(id); break;
1698     case GUI_VARRAY: gui_paint_array(id); break;
1699     case GUI_HSTACK: gui_paint_array(id); break;
1700     case GUI_VSTACK: gui_paint_array(id); break;
1701     case GUI_IMAGE:  gui_paint_image(id); break;
1702     case GUI_COUNT:  gui_paint_count(id); break;
1703     case GUI_CLOCK:  gui_paint_clock(id); break;
1704     default:         gui_paint_label(id); break;
1705     }
1706 }
1707 
gui_paint(int id)1708 void gui_paint(int id)
1709 {
1710     if (id)
1711     {
1712         video_push_ortho();
1713         {
1714             glDisable(GL_LIGHTING);
1715             glDisable(GL_DEPTH_TEST);
1716             {
1717                 draw_enable(GL_FALSE, GL_TRUE, GL_TRUE);
1718                 gui_paint_rect(id, 0, 0);
1719 
1720                 draw_enable(GL_TRUE, GL_TRUE, GL_TRUE);
1721                 gui_paint_text(id);
1722 
1723                 if (cursor_st && cursor_id)
1724                     gui_paint_image(cursor_id);
1725 
1726                 draw_disable();
1727                 glColor4ub(gui_wht[0], gui_wht[1], gui_wht[2], gui_wht[3]);
1728             }
1729             glEnable(GL_DEPTH_TEST);
1730             glEnable(GL_LIGHTING);
1731         }
1732         video_pop_matrix();
1733     }
1734 }
1735 
1736 /*---------------------------------------------------------------------------*/
1737 
gui_dump(int id,int d)1738 void gui_dump(int id, int d)
1739 {
1740     int jd, i;
1741 
1742     if (id)
1743     {
1744         char *type = "?";
1745 
1746         switch (widget[id].type)
1747         {
1748         case GUI_HARRAY: type = "harray"; break;
1749         case GUI_VARRAY: type = "varray"; break;
1750         case GUI_HSTACK: type = "hstack"; break;
1751         case GUI_VSTACK: type = "vstack"; break;
1752         case GUI_FILLER: type = "filler"; break;
1753         case GUI_IMAGE:  type = "image";  break;
1754         case GUI_LABEL:  type = "label";  break;
1755         case GUI_COUNT:  type = "count";  break;
1756         case GUI_CLOCK:  type = "clock";  break;
1757         case GUI_BUTTON: type = "button"; break;
1758         }
1759 
1760         for (i = 0; i < d; i++)
1761             printf("    ");
1762 
1763         printf("%04d %s\n", id, type);
1764 
1765         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1766             gui_dump(jd, d + 1);
1767     }
1768 }
1769 
gui_pulse(int id,float k)1770 void gui_pulse(int id, float k)
1771 {
1772     if (id) widget[id].scale = k;
1773 }
1774 
gui_timer(int id,float dt)1775 void gui_timer(int id, float dt)
1776 {
1777     int jd;
1778 
1779     if (id)
1780     {
1781         for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1782             gui_timer(jd, dt);
1783 
1784         if (widget[id].scale - 1.0f < dt)
1785             widget[id].scale = 1.0f;
1786         else
1787             widget[id].scale -= dt;
1788     }
1789 }
1790 
gui_point(int id,int x,int y)1791 int gui_point(int id, int x, int y)
1792 {
1793     static int x_cache = 0;
1794     static int y_cache = 0;
1795 
1796     int jd;
1797 
1798     /* Reuse the last coordinates if (x,y) == (-1,-1) */
1799 
1800     if (x < 0 && y < 0)
1801         return gui_point(id, x_cache, y_cache);
1802 
1803     x_cache = x;
1804     y_cache = y;
1805 
1806     /* Move the cursor, if any. */
1807 
1808     if (cursor_id)
1809     {
1810         widget[cursor_id].x = x - widget[cursor_id].w / 2;
1811         widget[cursor_id].y = y - widget[cursor_id].h / 2;
1812     }
1813 
1814     /* Short-circuit check the current active widget. */
1815 
1816     jd = gui_search(active, x, y);
1817 
1818     /* If not still active, search the hierarchy for a new active widget. */
1819 
1820     if (jd == 0)
1821         jd = gui_search(id, x, y);
1822 
1823     /* Note hovered widget. */
1824 
1825     hovered = jd;
1826 
1827     /* If the active widget has changed, return the new active id. */
1828 
1829     if (jd == 0 || jd == active)
1830         return 0;
1831     else
1832         return active = jd;
1833 }
1834 
gui_focus(int i)1835 void gui_focus(int i)
1836 {
1837     active = i;
1838 }
1839 
gui_active(void)1840 int gui_active(void)
1841 {
1842     return active;
1843 }
1844 
gui_token(int id)1845 int gui_token(int id)
1846 {
1847     return id ? widget[id].token : 0;
1848 }
1849 
gui_value(int id)1850 int gui_value(int id)
1851 {
1852     return id ? widget[id].value : 0;
1853 }
1854 
gui_toggle(int id)1855 void gui_toggle(int id)
1856 {
1857     widget[id].flags ^= GUI_HILITE;
1858 }
1859 
1860 /*---------------------------------------------------------------------------*/
1861 
gui_vert_offset(int id,int jd)1862 static int gui_vert_offset(int id, int jd)
1863 {
1864     /* Vertical offset between bottom of id and top of jd */
1865 
1866     return  widget[id].y - (widget[jd].y + widget[jd].h);
1867 }
1868 
gui_horz_offset(int id,int jd)1869 static int gui_horz_offset(int id, int jd)
1870 {
1871     /* Horizontal offset between left of id and right of jd */
1872 
1873     return  widget[id].x - (widget[jd].x + widget[jd].w);
1874 }
1875 
gui_vert_overlap(int id,int jd)1876 static int gui_vert_overlap(int id, int jd)
1877 {
1878     /* Extent of vertical intersection of id and jd. */
1879 
1880     const int a0 = widget[id].y;
1881     const int a1 = widget[id].y + widget[id].h;
1882     const int aw = widget[id].h;
1883     const int b0 = widget[jd].y;
1884     const int b1 = widget[jd].y + widget[jd].h;
1885     /* const int bw = widget[jd].h; */
1886 
1887     return aw + MIN(b1 - a1, 0) - MAX(b0 - a0, 0);
1888 }
1889 
gui_horz_overlap(int id,int jd)1890 static int gui_horz_overlap(int id, int jd)
1891 {
1892     /* Extent of horizontal intersection of id and jd. */
1893 
1894     const int a0 = widget[id].x;
1895     const int a1 = widget[id].x + widget[id].w;
1896     const int aw = widget[id].w;
1897     const int b0 = widget[jd].x;
1898     const int b1 = widget[jd].x + widget[jd].w;
1899     /* const int bw = widget[jd].w; */
1900 
1901     return aw + MIN(b1 - a1, 0) - MAX(b0 - a0, 0);
1902 }
1903 
1904 /*---------------------------------------------------------------------------*/
1905 
1906 /*
1907  * Widget navigation heuristics.
1908  *
1909  * People generally read left-to-right and top-to-bottom, and have
1910  * expectations on how navigation should behave. Thus, we hand-craft a
1911  * bunch of rules rather than devise a generic algorithm.
1912  *
1913  * Horizontal navigation only picks overlapping widgets. Closer is
1914  * better. Out of multiple closest widgets pick the topmost widget.
1915  *
1916  * Vertical navigation picks both overlapping and non-overlapping
1917  * widgets. Closer is better. Out of multiple closest widgets: if
1918  * overlapping, pick the leftmost widget; if not overlapping, pick the
1919  * one with the least negative overlap.
1920  */
1921 
1922 /*
1923  * Leftmost/topmost is decided by the operator used to test
1924  * distance. A less-than will pick the first of a group of
1925  * equal-distance widgets, while a less-or-equal will pick the last
1926  * one.
1927  */
1928 
1929 /* FIXME This isn't how you factor out reusable code. */
1930 
1931 #define CHECK_HORIZONTAL \
1932     (o > 0 && (omin > 0 ? d <= dmin : 1))
1933 
1934 #define CHECK_VERTICAL                                                  \
1935     (omin > 0 ?                                                         \
1936      d < dmin :                                                         \
1937      (o > 0 ? d <= dmin : (d < dmin || (d == dmin && o > omin))))
1938 
gui_stick_L(int id,int dd)1939 static int gui_stick_L(int id, int dd)
1940 {
1941     int jd, kd, hd;
1942     int d, dmin, o, omin;
1943 
1944     /* Find the closest "hot" widget to the left of dd (and inside id) */
1945 
1946     if (id && gui_hot(id))
1947         return id;
1948 
1949     hd = 0;
1950     dmin = widget[dd].x - widget[id].x + 1;
1951     omin = INT_MIN;
1952 
1953     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1954     {
1955         kd = gui_stick_L(jd, dd);
1956 
1957         if (kd && kd != dd)
1958         {
1959             d = gui_horz_offset(dd, kd);
1960             o = gui_vert_overlap(dd, kd);
1961 
1962             if (d >= 0 && CHECK_HORIZONTAL)
1963             {
1964                 hd = kd;
1965                 dmin = d;
1966                 omin = o;
1967             }
1968         }
1969     }
1970 
1971     return hd;
1972 }
1973 
gui_stick_R(int id,int dd)1974 static int gui_stick_R(int id, int dd)
1975 {
1976     int jd, kd, hd;
1977     int d, dmin, o, omin;
1978 
1979     /* Find the closest "hot" widget to the right of dd (and inside id) */
1980 
1981     if (id && gui_hot(id))
1982         return id;
1983 
1984     hd = 0;
1985     dmin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
1986     omin = INT_MIN;
1987 
1988     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1989     {
1990         kd = gui_stick_R(jd, dd);
1991 
1992         if (kd && kd != dd)
1993         {
1994             d = gui_horz_offset(kd, dd);
1995             o = gui_vert_overlap(dd, kd);
1996 
1997             if (d >= 0 && CHECK_HORIZONTAL)
1998             {
1999                 hd = kd;
2000                 dmin = d;
2001                 omin = o;
2002             }
2003         }
2004     }
2005 
2006     return hd;
2007 }
2008 
gui_stick_D(int id,int dd)2009 static int gui_stick_D(int id, int dd)
2010 {
2011     int jd, kd, hd;
2012     int d, dmin, o, omin;
2013 
2014     /* Find the closest "hot" widget below dd (and inside id) */
2015 
2016     if (id && gui_hot(id))
2017         return id;
2018 
2019     hd = 0;
2020     dmin = widget[dd].y - widget[id].y + 1;
2021     omin = INT_MIN;
2022 
2023     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
2024     {
2025         kd = gui_stick_D(jd, dd);
2026 
2027         if (kd && kd != dd)
2028         {
2029             d = gui_vert_offset(dd, kd);
2030             o = gui_horz_overlap(dd, kd);
2031 
2032             if (d >= 0 && CHECK_VERTICAL)
2033             {
2034                 hd = kd;
2035                 dmin = d;
2036                 omin = o;
2037             }
2038         }
2039     }
2040 
2041     return hd;
2042 }
2043 
gui_stick_U(int id,int dd)2044 static int gui_stick_U(int id, int dd)
2045 {
2046     int jd, kd, hd;
2047     int d, dmin, o, omin;
2048 
2049     /* Find the closest "hot" widget above dd (and inside id) */
2050 
2051     if (id && gui_hot(id))
2052         return id;
2053 
2054     hd = 0;
2055     dmin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
2056     omin = INT_MIN;
2057 
2058     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
2059     {
2060         kd = gui_stick_U(jd, dd);
2061 
2062         if (kd && kd != dd)
2063         {
2064             d = gui_vert_offset(kd, dd);
2065             o = gui_horz_overlap(dd, kd);
2066 
2067             if (d >= 0 && CHECK_VERTICAL)
2068             {
2069                 hd = kd;
2070                 dmin = d;
2071                 omin = o;
2072             }
2073         }
2074     }
2075 
2076     return hd;
2077 }
2078 
2079 /*---------------------------------------------------------------------------*/
2080 
gui_wrap_L(int id,int dd)2081 static int gui_wrap_L(int id, int dd)
2082 {
2083     int jd, kd;
2084 
2085     if ((jd = gui_stick_L(id, dd)) == 0)
2086         for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
2087             ;
2088 
2089     return jd;
2090 }
2091 
gui_wrap_R(int id,int dd)2092 static int gui_wrap_R(int id, int dd)
2093 {
2094     int jd, kd;
2095 
2096     if ((jd = gui_stick_R(id, dd)) == 0)
2097         for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
2098             ;
2099 
2100     return jd;
2101 }
2102 
gui_wrap_U(int id,int dd)2103 static int gui_wrap_U(int id, int dd)
2104 {
2105     int jd, kd;
2106 
2107     if ((jd = gui_stick_U(id, dd)) == 0)
2108         for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
2109             ;
2110 
2111     return jd;
2112 }
2113 
gui_wrap_D(int id,int dd)2114 static int gui_wrap_D(int id, int dd)
2115 {
2116     int jd, kd;
2117 
2118     if ((jd = gui_stick_D(id, dd)) == 0)
2119         for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
2120             ;
2121 
2122     return jd;
2123 }
2124 
2125 /*---------------------------------------------------------------------------*/
2126 
gui_stick(int id,int a,float v,int bump)2127 int gui_stick(int id, int a, float v, int bump)
2128 {
2129     int jd = 0;
2130 
2131     if (!bump)
2132         return 0;
2133 
2134     /* Find a new active widget in the direction of joystick motion. */
2135 
2136     if      (config_tst_d(CONFIG_JOYSTICK_AXIS_X0, a))
2137     {
2138         if (v < 0) jd = gui_wrap_L(id, active);
2139         if (v > 0) jd = gui_wrap_R(id, active);
2140     }
2141     else if (config_tst_d(CONFIG_JOYSTICK_AXIS_Y0, a))
2142     {
2143         if (v < 0) jd = gui_wrap_U(id, active);
2144         if (v > 0) jd = gui_wrap_D(id, active);
2145     }
2146 
2147     /* If the active widget has changed, return the new active id. */
2148 
2149     if (jd == 0 || jd == active)
2150         return 0;
2151     else
2152         return active = jd;
2153 }
2154 
gui_click(int b,int d)2155 int gui_click(int b, int d)
2156 {
2157     if (b == SDL_BUTTON_LEFT)
2158     {
2159         if (d)
2160         {
2161             clicked = hovered;
2162             return 0;
2163         }
2164         else
2165         {
2166             int c = (clicked && clicked == hovered);
2167             clicked = 0;
2168             return c;
2169         }
2170     }
2171     return 0;
2172 }
2173 
2174 /*---------------------------------------------------------------------------*/
2175 
gui_navig(int id,int total,int first,int step)2176 int gui_navig(int id, int total, int first, int step)
2177 {
2178     int pages = (int) ceil((double) total / step);
2179     int page = first / step + 1;
2180 
2181     int prev = (page > 1);
2182     int next = (page < pages);
2183 
2184     int jd, kd;
2185 
2186     if ((jd = gui_hstack(id)))
2187     {
2188         if (next || prev)
2189         {
2190             gui_maybe(jd, " > ", GUI_NEXT, GUI_NONE, next);
2191 
2192             if ((kd = gui_label(jd, "999/999", GUI_SML, gui_wht, gui_wht)))
2193             {
2194                 char str[16];
2195                 sprintf(str, "%d/%d", page, pages);
2196                 gui_set_label(kd, str);
2197             }
2198 
2199             gui_maybe(jd, " < ", GUI_PREV, GUI_NONE, prev);
2200         }
2201 
2202         gui_space(jd);
2203 
2204         gui_start(jd, _("Back"), GUI_SML, GUI_BACK, 0);
2205     }
2206     return jd;
2207 }
2208 
gui_maybe(int id,const char * label,int etoken,int dtoken,int enabled)2209 int gui_maybe(int id, const char *label, int etoken, int dtoken, int enabled)
2210 {
2211     int bd;
2212 
2213     if (!enabled)
2214     {
2215         bd = gui_state(id, label, GUI_SML, dtoken, 0);
2216         gui_set_color(bd, gui_gry, gui_gry);
2217     }
2218     else
2219         bd = gui_state(id, label, GUI_SML, etoken, 0);
2220 
2221     return bd;
2222 }
2223 
2224 /*---------------------------------------------------------------------------*/
2225