Import from neverball-1.2.6-9.tar.gz
[neverball-archive.git] / share / gui.c
blob0fb5c37d5994af2d191f66eb041009215b90bf38
1 /*
2 * Copyright (C) 2003 Robert Kooima
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.
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.
15 #include <stdlib.h>
16 #include <string.h>
17 #include <stdio.h>
19 #include "config.h"
20 #include "glext.h"
21 #include "image.h"
22 #include "vec3.h"
23 #include "gui.h"
25 /*---------------------------------------------------------------------------*/
27 #define MAXWIDGET 128
29 #define GUI_FREE 0
30 #define GUI_HARRAY 1
31 #define GUI_VARRAY 2
32 #define GUI_HSTACK 3
33 #define GUI_VSTACK 4
34 #define GUI_FILLER 5
35 #define GUI_STATE 6
36 #define GUI_IMAGE 7
37 #define GUI_LABEL 8
38 #define GUI_COUNT 9
39 #define GUI_CLOCK 10
40 #define GUI_SPACE 11
41 #define GUI_PAUSE 12
43 struct widget
45 int type;
46 int token;
47 int value;
48 int size;
49 int rect;
51 int x, y;
52 int w, h;
53 int car;
54 int cdr;
56 GLuint text_img;
57 GLuint text_obj;
58 GLuint rect_obj;
60 const GLfloat *color0;
61 const GLfloat *color1;
63 GLfloat scale;
66 /*---------------------------------------------------------------------------*/
68 const GLfloat gui_wht[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
69 const GLfloat gui_yel[4] = { 1.0f, 1.0f, 0.0f, 1.0f };
70 const GLfloat gui_red[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
71 const GLfloat gui_grn[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
72 const GLfloat gui_blu[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
73 const GLfloat gui_blk[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
74 const GLfloat gui_gry[4] = { 0.0f, 0.0f, 0.0f, 0.5f };
76 /*---------------------------------------------------------------------------*/
78 static struct widget widget[MAXWIDGET];
79 static int active;
80 static int radius;
81 static TTF_Font *font[3] = { NULL, NULL, NULL };
83 static GLuint digit_text[3][11];
84 static GLuint digit_list[3][11];
85 static int digit_w[3][11];
86 static int digit_h[3][11];
88 static int pause_id;
90 /*---------------------------------------------------------------------------*/
92 * Initialize a display list containing a rectangle (x, y, w, h) to
93 * which a rendered-font texture may be applied. Colors c0 and c1
94 * determine the top-to-bottom color gradiant of the text.
97 static GLuint gui_list(int x, int y,
98 int w, int h, const float *c0, const float *c1)
100 GLuint list = glGenLists(1);
102 GLfloat s0, t0;
103 GLfloat s1, t1;
105 int W, H, d = h / 16;
107 /* Assume the applied texture size is rect size rounded to power-of-two. */
109 image_size(&W, &H, w, h);
111 s0 = 0.5f * (W - w) / W;
112 t0 = 0.5f * (H - h) / H;
113 s1 = 1.0f - s0;
114 t1 = 1.0f - t0;
116 glNewList(list, GL_COMPILE);
118 glBegin(GL_QUADS);
120 glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
121 glTexCoord2f(s0, t1); glVertex2i(x + d, y - d);
122 glTexCoord2f(s1, t1); glVertex2i(x + w + d, y - d);
123 glTexCoord2f(s1, t0); glVertex2i(x + w + d, y + h - d);
124 glTexCoord2f(s0, t0); glVertex2i(x + d, y + h - d);
126 glColor4fv(c0);
127 glTexCoord2f(s0, t1); glVertex2i(x, y);
128 glTexCoord2f(s1, t1); glVertex2i(x + w, y);
130 glColor4fv(c1);
131 glTexCoord2f(s1, t0); glVertex2i(x + w, y + h);
132 glTexCoord2f(s0, t0); glVertex2i(x, y + h);
134 glEnd();
136 glEndList();
138 return list;
142 * Initialize a display list containing a rounded-corner rectangle (x,
143 * y, w, h). Generate texture coordinates to properly apply a texture
144 * map to the rectangle as though the corners were not rounded.
147 static GLuint gui_rect(int x, int y, int w, int h, int f, int r)
149 GLuint list = glGenLists(1);
151 int n = 8;
152 int i;
154 glNewList(list, GL_COMPILE);
156 glBegin(GL_QUAD_STRIP);
158 /* Left side... */
160 for (i = 0; i <= n; i++)
162 float a = 0.5f * V_PI * (float) i / (float) n;
163 float s = r * fsinf(a);
164 float c = r * fcosf(a);
166 float X = x + r - c;
167 float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
168 float Yb = y + ((f & GUI_SW) ? (r - s) : 0);
170 glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
171 glVertex2f(X, Ya);
173 glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
174 glVertex2f(X, Yb);
177 /* ... Right side. */
179 for (i = 0; i <= n; i++)
181 float a = 0.5f * V_PI * (float) i / (float) n;
182 float s = r * fsinf(a);
183 float c = r * fcosf(a);
185 float X = x + w - r + s;
186 float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
187 float Yb = y + ((f & GUI_SE) ? (r - c) : 0);
189 glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
190 glVertex2f(X, Ya);
192 glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
193 glVertex2f(X, Yb);
196 glEnd();
198 glEndList();
200 return list;
203 /*---------------------------------------------------------------------------*/
205 void gui_init(void)
207 const float *c0 = gui_yel;
208 const float *c1 = gui_red;
210 int i, j, h = config_get(CONFIG_HEIGHT);
212 /* Initialize font rendering. */
214 if (TTF_Init() == 0)
216 /* Load small, medium, and large typefaces. */
218 font[GUI_SML] = TTF_OpenFont(GUI_FACE, h / 24);
219 font[GUI_MED] = TTF_OpenFont(GUI_FACE, h / 12);
220 font[GUI_LRG] = TTF_OpenFont(GUI_FACE, h / 6);
221 radius = h / 60;
223 /* Initialize the global pause GUI. */
225 if ((pause_id = gui_pause(0)))
226 gui_layout(pause_id, 0, 0);
228 /* Initialize digit glyphs and lists for counters and clocks. */
230 for (i = 0; i < 3; i++)
232 char text[2];
234 /* Draw digits 0 throught 9. */
236 for (j = 0; j < 10; j++)
238 text[0] = '0' + (char) j;
239 text[1] = 0;
241 digit_text[i][j] = make_image_from_font(NULL, NULL,
242 &digit_w[i][j],
243 &digit_h[i][j],
244 text, font[i]);
245 digit_list[i][j] = gui_list(-digit_w[i][j] / 2,
246 -digit_h[i][j] / 2,
247 +digit_w[i][j],
248 +digit_h[i][j], c0, c1);
251 /* Draw the colon for the clock. */
253 digit_text[i][j] = make_image_from_font(NULL, NULL,
254 &digit_w[i][10],
255 &digit_h[i][10],
256 ":", font[i]);
257 digit_list[i][j] = gui_list(-digit_w[i][10] / 2,
258 -digit_h[i][10] / 2,
259 +digit_w[i][10],
260 +digit_h[i][10], c0, c1);
264 active = 0;
267 void gui_free(void)
269 int i, j, id;
271 /* Release any remaining widget texture and display list indices. */
273 for (id = 1; id < MAXWIDGET; id++)
275 if (glIsTexture(widget[id].text_img))
276 glDeleteTextures(1, &widget[id].text_img);
278 if (glIsList(widget[id].text_obj))
279 glDeleteLists(widget[id].text_obj, 1);
280 if (glIsList(widget[id].rect_obj))
281 glDeleteLists(widget[id].rect_obj, 1);
283 widget[id].type = GUI_FREE;
284 widget[id].text_img = 0;
285 widget[id].text_obj = 0;
286 widget[id].rect_obj = 0;
287 widget[id].cdr = 0;
288 widget[id].car = 0;
291 /* Release all digit textures and display lists. */
293 for (i = 0; i < 3; i++)
294 for (j = 0; j < 11; j++)
296 if (glIsTexture(digit_text[i][j]))
297 glDeleteTextures(1, &digit_text[i][j]);
299 if (glIsList(digit_list[i][j]))
300 glDeleteLists(digit_list[i][j], 1);
303 /* Release all loaded fonts and finalize font rendering. */
305 if (font[GUI_LRG]) TTF_CloseFont(font[GUI_LRG]);
306 if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
307 if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
309 TTF_Quit();
312 /*---------------------------------------------------------------------------*/
314 static int gui_widget(int pd, int type)
316 int id;
318 /* Find an unused entry in the widget table. */
320 for (id = 1; id < MAXWIDGET; id++)
321 if (widget[id].type == GUI_FREE)
323 /* Set the type and default properties. */
325 widget[id].type = type;
326 widget[id].token = 0;
327 widget[id].value = 0;
328 widget[id].size = 0;
329 widget[id].rect = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
330 widget[id].w = 0;
331 widget[id].h = 0;
332 widget[id].text_img = 0;
333 widget[id].text_obj = 0;
334 widget[id].rect_obj = 0;
335 widget[id].color0 = gui_wht;
336 widget[id].color1 = gui_wht;
337 widget[id].scale = 1.0f;
339 /* Insert the new widget into the parents's widget list. */
341 if (pd)
343 widget[id].car = 0;
344 widget[id].cdr = widget[pd].car;
345 widget[pd].car = id;
347 else
349 widget[id].car = 0;
350 widget[id].cdr = 0;
353 return id;
356 return 0;
359 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
360 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
361 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
362 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
363 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
365 /*---------------------------------------------------------------------------*/
367 void gui_set_image(int id, const char *file)
369 if (glIsTexture(widget[id].text_img))
370 glDeleteTextures(1, &widget[id].text_img);
372 widget[id].text_img = make_image_from_file(NULL, NULL, NULL, NULL, file);
375 void gui_set_label(int id, const char *text)
377 int w, h;
379 if (glIsTexture(widget[id].text_img))
380 glDeleteTextures(1, &widget[id].text_img);
381 if (glIsList(widget[id].text_obj))
382 glDeleteLists(widget[id].text_obj, 1);
384 widget[id].text_img = make_image_from_font(NULL, NULL, &w, &h,
385 text, font[widget[id].size]);
386 widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
387 widget[id].color0, widget[id].color1);
390 void gui_set_count(int id, int value)
392 widget[id].value = value;
395 void gui_set_clock(int id, int value)
397 widget[id].value = value;
400 void gui_set_multi(int id, const char *text)
402 const char *p;
404 char s[8][MAXSTR];
405 int i, j, jd;
407 size_t n = 0;
409 /* Copy each delimited string to a line buffer. */
411 for (p = text, j = 0; *p && j < 8; j++)
413 strncpy(s[j], p, (n = strcspn(p, "\\")));
414 s[j][n] = 0;
416 if (*(p += n) == '\\') p++;
419 /* Set the label value for each line. */
421 for (i = j - 1, jd = widget[id].car; i >= 0 && jd; i--, jd = widget[jd].cdr)
422 gui_set_label(jd, s[i]);
425 /*---------------------------------------------------------------------------*/
427 int gui_image(int pd, const char *file, int w, int h)
429 int id;
431 if ((id = gui_widget(pd, GUI_IMAGE)))
433 widget[id].text_img = make_image_from_file(NULL, NULL,
434 NULL, NULL, file);
435 widget[id].w = w;
436 widget[id].h = h;
438 return id;
441 int gui_start(int pd, const char *text, int size, int token, int value)
443 int id;
445 if ((id = gui_state(pd, text, size, token, value)))
446 active = id;
448 return id;
451 int gui_state(int pd, const char *text, int size, int token, int value)
453 int id;
455 if ((id = gui_widget(pd, GUI_STATE)))
457 widget[id].text_img = make_image_from_font(NULL, NULL,
458 &widget[id].w,
459 &widget[id].h,
460 text, font[size]);
461 widget[id].size = size;
462 widget[id].token = token;
463 widget[id].value = value;
465 return id;
468 int gui_label(int pd, const char *text, int size, int rect, const float *c0,
469 const float *c1)
471 int id;
473 if ((id = gui_widget(pd, GUI_LABEL)))
475 widget[id].text_img = make_image_from_font(NULL, NULL,
476 &widget[id].w,
477 &widget[id].h,
478 text, font[size]);
479 widget[id].size = size;
480 widget[id].color0 = c0 ? c0 : gui_yel;
481 widget[id].color1 = c1 ? c1 : gui_red;
482 widget[id].rect = rect;
484 return id;
487 int gui_count(int pd, int value, int size, int rect)
489 int i, id;
491 if ((id = gui_widget(pd, GUI_COUNT)))
493 for (i = value; i; i /= 10)
494 widget[id].w += digit_w[size][0];
496 widget[id].h = digit_h[size][0];
497 widget[id].value = value;
498 widget[id].size = size;
499 widget[id].color0 = gui_yel;
500 widget[id].color1 = gui_red;
501 widget[id].rect = rect;
503 return id;
506 int gui_clock(int pd, int value, int size, int rect)
508 int id;
510 if ((id = gui_widget(pd, GUI_CLOCK)))
512 widget[id].w = digit_w[size][0] * 6;
513 widget[id].h = digit_h[size][0];
514 widget[id].value = value;
515 widget[id].size = size;
516 widget[id].color0 = gui_yel;
517 widget[id].color1 = gui_red;
518 widget[id].rect = rect;
520 return id;
523 int gui_space(int pd)
525 int id;
527 if ((id = gui_widget(pd, GUI_SPACE)))
529 widget[id].w = 0;
530 widget[id].h = 0;
532 return id;
535 int gui_pause(int pd)
537 const char *text = "Paused";
538 int id;
540 if ((id = gui_widget(pd, GUI_PAUSE)))
542 widget[id].text_img = make_image_from_font(NULL, NULL,
543 &widget[id].w,
544 &widget[id].h,
545 text, font[GUI_LRG]);
546 widget[id].color0 = gui_wht;
547 widget[id].color1 = gui_wht;
548 widget[id].value = 0;
549 widget[id].size = GUI_LRG;
550 widget[id].rect = GUI_ALL;
552 return id;
555 /*---------------------------------------------------------------------------*/
557 * Create a multi-line text box using a vertical array of labels.
558 * Parse the text for '\' characters and treat them as line-breaks.
559 * Preserve the rect specifation across the entire array.
562 int gui_multi(int pd, const char *text, int size, int rect, const float *c0,
563 const float *c1)
565 int id = 0;
567 if (text && (id = gui_varray(pd)))
569 const char *p;
571 char s[8][MAXSTR];
572 int r[8];
573 int i, j;
575 size_t n = 0;
577 /* Copy each delimited string to a line buffer. */
579 for (p = text, j = 0; *p && j < 8; j++)
581 strncpy(s[j], p, (n = strcspn(p, "\\")));
582 s[j][n] = 0;
583 r[j] = 0;
585 if (*(p += n) == '\\') p++;
588 /* Set the curves for the first and last lines. */
590 if (j > 0)
592 r[0] |= rect & (GUI_NW | GUI_NE);
593 r[j - 1] |= rect & (GUI_SW | GUI_SE);
596 /* Create a label widget for each line. */
598 for (i = 0; i < j; i++)
599 gui_label(id, s[i], size, r[i], c0, c1);
601 return id;
604 /*---------------------------------------------------------------------------*/
606 * The bottom-up pass determines the area of all widgets. The minimum
607 * width and height of a leaf widget is given by the size of its
608 * contents. Array and stack widths and heights are computed
609 * recursively from these.
612 static void gui_widget_up(int id);
614 static void gui_harray_up(int id)
616 int jd, c = 0;
618 /* Find the widest child width and the highest child height. */
620 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
622 gui_widget_up(jd);
624 if (widget[id].h < widget[jd].h)
625 widget[id].h = widget[jd].h;
626 if (widget[id].w < widget[jd].w)
627 widget[id].w = widget[jd].w;
629 c++;
632 /* Total width is the widest child width times the child count. */
634 widget[id].w *= c;
637 static void gui_varray_up(int id)
639 int jd, c = 0;
641 /* Find the widest child width and the highest child height. */
643 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
645 gui_widget_up(jd);
647 if (widget[id].h < widget[jd].h)
648 widget[id].h = widget[jd].h;
649 if (widget[id].w < widget[jd].w)
650 widget[id].w = widget[jd].w;
652 c++;
655 /* Total height is the highest child height times the child count. */
657 widget[id].h *= c;
660 static void gui_hstack_up(int id)
662 int jd;
664 /* Find the highest child height. Sum the child widths. */
666 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
668 gui_widget_up(jd);
670 if (widget[id].h < widget[jd].h)
671 widget[id].h = widget[jd].h;
673 widget[id].w += widget[jd].w;
677 static void gui_vstack_up(int id)
679 int jd;
681 /* Find the widest child width. Sum the child heights. */
683 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
685 gui_widget_up(jd);
687 if (widget[id].w < widget[jd].w)
688 widget[id].w = widget[jd].w;
690 widget[id].h += widget[jd].h;
694 static void gui_paused_up(int id)
696 /* Store width and height for later use in text rendering. */
698 widget[id].x = widget[id].w;
699 widget[id].y = widget[id].h;
701 /* The pause widget fills the screen. */
703 widget[id].w = config_get(CONFIG_WIDTH);
704 widget[id].h = config_get(CONFIG_HEIGHT);
707 static void gui_button_up(int id)
709 /* Store width and height for later use in text rendering. */
711 widget[id].x = widget[id].w;
712 widget[id].y = widget[id].h;
714 /* Padded text elements look a little nicer. */
716 if (widget[id].w < config_get(CONFIG_WIDTH))
717 widget[id].w += radius;
718 if (widget[id].h < config_get(CONFIG_HEIGHT))
719 widget[id].h += radius;
722 static void gui_widget_up(int id)
724 if (id)
725 switch (widget[id].type)
727 case GUI_HARRAY: gui_harray_up(id); break;
728 case GUI_VARRAY: gui_varray_up(id); break;
729 case GUI_HSTACK: gui_hstack_up(id); break;
730 case GUI_VSTACK: gui_vstack_up(id); break;
731 case GUI_PAUSE: gui_paused_up(id); break;
732 default: gui_button_up(id); break;
736 /*---------------------------------------------------------------------------*/
738 * The top-down layout pass distributes available area as computed
739 * during the bottom-up pass. Widgets use their area and position to
740 * initialize rendering state.
743 static void gui_widget_dn(int id, int x, int y, int w, int h);
745 static void gui_harray_dn(int id, int x, int y, int w, int h)
747 int jd, i = 0, c = 0;
749 widget[id].x = x;
750 widget[id].y = y;
751 widget[id].w = w;
752 widget[id].h = h;
754 /* Count children. */
756 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
757 c += 1;
759 /* Distribute horizontal space evenly to all children. */
761 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
763 int x0 = x + i * w / c;
764 int x1 = x + (i + 1) * w / c;
766 gui_widget_dn(jd, x0, y, x1 - x0, h);
770 static void gui_varray_dn(int id, int x, int y, int w, int h)
772 int jd, i = 0, c = 0;
774 widget[id].x = x;
775 widget[id].y = y;
776 widget[id].w = w;
777 widget[id].h = h;
779 /* Count children. */
781 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
782 c += 1;
784 /* Distribute vertical space evenly to all children. */
786 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
788 int y0 = y + i * h / c;
789 int y1 = y + (i + 1) * h / c;
791 gui_widget_dn(jd, x, y0, w, y1 - y0);
795 static void gui_hstack_dn(int id, int x, int y, int w, int h)
797 int jd, jx = x, jw = 0, c = 0;
799 widget[id].x = x;
800 widget[id].y = y;
801 widget[id].w = w;
802 widget[id].h = h;
804 /* Measure the total width requested by non-filler children. */
806 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
807 if (widget[jd].type == GUI_FILLER)
808 c += 1;
809 else
810 jw += widget[jd].w;
812 /* Give non-filler children their requested space. */
813 /* Distribute the rest evenly among filler children. */
815 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
817 if (widget[jd].type == GUI_FILLER)
818 gui_widget_dn(jd, jx, y, (w - jw) / c, h);
819 else
820 gui_widget_dn(jd, jx, y, widget[jd].w, h);
822 jx += widget[jd].w;
826 static void gui_vstack_dn(int id, int x, int y, int w, int h)
828 int jd, jy = y, jh = 0, c = 0;
830 widget[id].x = x;
831 widget[id].y = y;
832 widget[id].w = w;
833 widget[id].h = h;
835 /* Measure the total height requested by non-filler children. */
837 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
838 if (widget[jd].type == GUI_FILLER)
839 c += 1;
840 else
841 jh += widget[jd].h;
843 /* Give non-filler children their requested space. */
844 /* Distribute the rest evenly among filler children. */
846 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
848 if (widget[jd].type == GUI_FILLER)
849 gui_widget_dn(jd, x, jy, w, (h - jh) / c);
850 else
851 gui_widget_dn(jd, x, jy, w, widget[jd].h);
853 jy += widget[jd].h;
857 static void gui_filler_dn(int id, int x, int y, int w, int h)
859 /* Filler expands to whatever size it is given. */
861 widget[id].x = x;
862 widget[id].y = y;
863 widget[id].w = w;
864 widget[id].h = h;
867 static void gui_button_dn(int id, int x, int y, int w, int h)
869 /* Recall stored width and height for text rendering. */
871 int W = widget[id].x;
872 int H = widget[id].y;
873 int R = widget[id].rect;
874 int r = (widget[id].type == GUI_PAUSE ? radius * 4 : radius);
876 const float *c0 = widget[id].color0;
877 const float *c1 = widget[id].color1;
879 widget[id].x = x;
880 widget[id].y = y;
881 widget[id].w = w;
882 widget[id].h = h;
884 /* Create display lists for the text area and rounded rectangle. */
886 widget[id].text_obj = gui_list(-W / 2, -H / 2, W, H, c0, c1);
887 widget[id].rect_obj = gui_rect(-w / 2, -h / 2, w, h, R, r);
890 static void gui_widget_dn(int id, int x, int y, int w, int h)
892 if (id)
893 switch (widget[id].type)
895 case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
896 case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
897 case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
898 case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
899 case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
900 case GUI_SPACE: gui_filler_dn(id, x, y, w, h); break;
901 default: gui_button_dn(id, x, y, w, h); break;
905 /*---------------------------------------------------------------------------*/
907 * During GUI layout, we make a bottom-up pass to determine total area
908 * requirements for the widget tree. We position this area to the
909 * sides or center of the screen. Finally, we make a top-down pass to
910 * distribute this area to each widget.
913 void gui_layout(int id, int xd, int yd)
915 int x, y;
917 int w, W = config_get(CONFIG_WIDTH);
918 int h, H = config_get(CONFIG_HEIGHT);
920 gui_widget_up(id);
922 w = widget[id].w;
923 h = widget[id].h;
925 if (xd < 0) x = 0;
926 else if (xd > 0) x = (W - w);
927 else x = (W - w) / 2;
929 if (yd < 0) y = 0;
930 else if (yd > 0) y = (H - h);
931 else y = (H - h) / 2;
933 gui_widget_dn(id, x, y, w, h);
936 int gui_search(int id, int x, int y)
938 int jd, kd;
940 /* Search the hierarchy for the widget containing the given point. */
942 if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
943 widget[id].y <= y && y < widget[id].y + widget[id].h))
945 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
946 if ((kd = gui_search(jd, x, y)))
947 return kd;
949 if (widget[id].type == GUI_STATE)
950 return id;
952 return 0;
955 int gui_delete(int id)
957 if (id)
959 /* Recursively delete all subwidgets. */
961 gui_delete(widget[id].cdr);
962 gui_delete(widget[id].car);
964 /* Release any GL resources held by this widget. */
966 if (glIsTexture(widget[id].text_img))
967 glDeleteTextures(1, &widget[id].text_img);
969 if (glIsList(widget[id].text_obj))
970 glDeleteLists(widget[id].text_obj, 1);
971 if (glIsList(widget[id].rect_obj))
972 glDeleteLists(widget[id].rect_obj, 1);
974 /* Mark this widget unused. */
976 widget[id].type = GUI_FREE;
977 widget[id].text_img = 0;
978 widget[id].text_obj = 0;
979 widget[id].rect_obj = 0;
980 widget[id].cdr = 0;
981 widget[id].car = 0;
983 return 0;
986 /*---------------------------------------------------------------------------*/
988 static void gui_paint_rect(int id)
990 static const GLfloat back[4][4] = {
991 { 0.1f, 0.1f, 0.1f, 0.5f }, /* off and inactive */
992 { 0.3f, 0.3f, 0.3f, 0.5f }, /* off and active */
993 { 0.7f, 0.3f, 0.0f, 0.5f }, /* on and inactive */
994 { 1.0f, 0.7f, 0.3f, 0.5f }, /* on and active */
997 int jd;
999 switch (widget[id].type)
1001 case GUI_IMAGE:
1002 case GUI_SPACE:
1003 case GUI_FILLER:
1004 break;
1006 case GUI_HARRAY:
1007 case GUI_VARRAY:
1008 case GUI_HSTACK:
1009 case GUI_VSTACK:
1011 /* Recursively paint all subwidgets. */
1013 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1014 gui_paint_rect(jd);
1016 break;
1018 default:
1020 /* Draw a leaf's background, colored by widget state. */
1022 glPushMatrix();
1024 int i = 0;
1026 if (widget[id].type == GUI_STATE)
1027 i = (((widget[id].value) ? 2 : 0) +
1028 ((id == active) ? 1 : 0));
1030 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1031 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1033 glColor4fv(back[i]);
1034 glCallList(widget[id].rect_obj);
1036 glPopMatrix();
1038 break;
1042 /*---------------------------------------------------------------------------*/
1044 static void gui_paint_text(int id);
1046 static void gui_paint_array(int id)
1048 int jd;
1050 /* Recursively paint all subwidgets. */
1052 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1053 gui_paint_text(jd);
1056 static void gui_paint_image(int id)
1058 /* Draw the widget rect, textured using the image. */
1060 glPushMatrix();
1062 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1063 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1065 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1066 glColor4fv(gui_wht);
1067 glCallList(widget[id].rect_obj);
1069 glPopMatrix();
1072 static void gui_paint_count(int id)
1074 int j, i = widget[id].size;
1076 glPushMatrix();
1078 glColor4fv(gui_wht);
1080 /* Translate to the widget center, and apply the pulse scale. */
1082 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1083 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1085 glScalef(widget[id].scale,
1086 widget[id].scale,
1087 widget[id].scale);
1089 if (widget[id].value)
1091 /* Translate left by half the total width of the rendered value. */
1093 for (j = widget[id].value; j; j /= 10)
1094 glTranslatef((GLfloat) +digit_w[i][j % 10] / 2.0f, 0.0f, 0.0f);
1096 glTranslatef((GLfloat) -digit_w[i][0] / 2.0f, 0.0f, 0.0f);
1098 /* Render each digit, moving right after each. */
1100 for (j = widget[id].value; j; j /= 10)
1102 glBindTexture(GL_TEXTURE_2D, digit_text[i][j % 10]);
1103 glCallList(digit_list[i][j % 10]);
1104 glTranslatef((GLfloat) -digit_w[i][j % 10], 0.0f, 0.0f);
1107 else
1109 /* If the value is zero, just display a zero in place. */
1111 glBindTexture(GL_TEXTURE_2D, digit_text[i][0]);
1112 glCallList(digit_list[i][0]);
1115 glPopMatrix();
1118 static void gui_paint_clock(int id)
1120 int i = widget[id].size;
1121 int mt = (widget[id].value / 6000) / 10;
1122 int mo = (widget[id].value / 6000) % 10;
1123 int st = ((widget[id].value % 6000) / 100) / 10;
1124 int so = ((widget[id].value % 6000) / 100) % 10;
1125 int ht = ((widget[id].value % 6000) % 100) / 10;
1126 int ho = ((widget[id].value % 6000) % 100) % 10;
1128 GLfloat dx_large = (GLfloat) digit_w[i][0];
1129 GLfloat dx_small = (GLfloat) digit_w[i][0] * 0.75f;
1131 glPushMatrix();
1133 glColor4fv(gui_wht);
1135 /* Translate to the widget center, and apply the pulse scale. */
1137 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1138 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1140 glScalef(widget[id].scale,
1141 widget[id].scale,
1142 widget[id].scale);
1144 /* Translate left by half the total width of the rendered value. */
1146 if (mt > 0)
1147 glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1148 else
1149 glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1151 /* Render the minutes counter. */
1153 if (mt > 0)
1155 glBindTexture(GL_TEXTURE_2D, digit_text[i][mt]);
1156 glCallList(digit_list[i][mt]);
1157 glTranslatef(dx_large, 0.0f, 0.0f);
1160 glBindTexture(GL_TEXTURE_2D, digit_text[i][mo]);
1161 glCallList(digit_list[i][mo]);
1162 glTranslatef(dx_small, 0.0f, 0.0f);
1164 /* Render the colon. */
1166 glBindTexture(GL_TEXTURE_2D, digit_text[i][10]);
1167 glCallList(digit_list[i][10]);
1168 glTranslatef(dx_small, 0.0f, 0.0f);
1170 /* Render the seconds counter. */
1172 glBindTexture(GL_TEXTURE_2D, digit_text[i][st]);
1173 glCallList(digit_list[i][st]);
1174 glTranslatef(dx_large, 0.0f, 0.0f);
1176 glBindTexture(GL_TEXTURE_2D, digit_text[i][so]);
1177 glCallList(digit_list[i][so]);
1178 glTranslatef(dx_small, 0.0f, 0.0f);
1180 /* Render hundredths counter half size. */
1182 glScalef(0.5f, 0.5f, 1.0f);
1184 glBindTexture(GL_TEXTURE_2D, digit_text[i][ht]);
1185 glCallList(digit_list[i][ht]);
1186 glTranslatef(dx_large, 0.0f, 0.0f);
1188 glBindTexture(GL_TEXTURE_2D, digit_text[i][ho]);
1189 glCallList(digit_list[i][ho]);
1191 glPopMatrix();
1194 static void gui_paint_label(int id)
1196 /* Draw the widget text box, textured using the glyph. */
1198 glPushMatrix();
1200 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1201 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1203 glScalef(widget[id].scale,
1204 widget[id].scale,
1205 widget[id].scale);
1207 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1208 glCallList(widget[id].text_obj);
1210 glPopMatrix();
1213 static void gui_paint_text(int id)
1215 switch (widget[id].type)
1217 case GUI_SPACE: break;
1218 case GUI_FILLER: break;
1219 case GUI_HARRAY: gui_paint_array(id); break;
1220 case GUI_VARRAY: gui_paint_array(id); break;
1221 case GUI_HSTACK: gui_paint_array(id); break;
1222 case GUI_VSTACK: gui_paint_array(id); break;
1223 case GUI_IMAGE: gui_paint_image(id); break;
1224 case GUI_COUNT: gui_paint_count(id); break;
1225 case GUI_CLOCK: gui_paint_clock(id); break;
1226 default: gui_paint_label(id); break;
1230 void gui_paint(int id)
1232 if (id)
1234 glPushAttrib(GL_LIGHTING_BIT);
1235 glPushAttrib(GL_COLOR_BUFFER_BIT);
1236 glPushAttrib(GL_DEPTH_BUFFER_BIT);
1237 config_push_ortho();
1239 glEnable(GL_BLEND);
1240 glEnable(GL_COLOR_MATERIAL);
1241 glDisable(GL_LIGHTING);
1242 glDisable(GL_DEPTH_TEST);
1244 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1246 glPushAttrib(GL_TEXTURE_BIT);
1248 glDisable(GL_TEXTURE_2D);
1249 gui_paint_rect(id);
1251 glPopAttrib();
1253 gui_paint_text(id);
1255 config_pop_matrix();
1256 glPopAttrib();
1257 glPopAttrib();
1258 glPopAttrib();
1262 void gui_blank(void)
1264 gui_paint(pause_id);
1267 /*---------------------------------------------------------------------------*/
1269 void gui_dump(int id, int d)
1271 int jd, i;
1273 if (id)
1275 char *type = "?";
1277 switch (widget[id].type)
1279 case GUI_HARRAY: type = "harray"; break;
1280 case GUI_VARRAY: type = "varray"; break;
1281 case GUI_HSTACK: type = "hstack"; break;
1282 case GUI_VSTACK: type = "vstack"; break;
1283 case GUI_FILLER: type = "filler"; break;
1284 case GUI_STATE: type = "state"; break;
1285 case GUI_IMAGE: type = "image"; break;
1286 case GUI_LABEL: type = "label"; break;
1287 case GUI_COUNT: type = "count"; break;
1288 case GUI_CLOCK: type = "clock"; break;
1291 for (i = 0; i < d; i++)
1292 printf(" ");
1294 printf("%04d %s\n", id, type);
1296 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1297 gui_dump(jd, d + 1);
1301 void gui_pulse(int id, float k)
1303 if (id) widget[id].scale = k;
1306 void gui_timer(int id, float dt)
1308 int jd;
1310 if (id)
1312 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1313 gui_timer(jd, dt);
1315 if (widget[id].scale - 1.0f < dt)
1316 widget[id].scale = 1.0f;
1317 else
1318 widget[id].scale -= dt;
1322 int gui_point(int id, int x, int y)
1324 /* Short-circuit check the current active widget. */
1326 int jd = gui_search(active, x, y);
1328 /* If not still active, search the hierarchy for a new active widget. */
1330 if (jd == 0)
1331 jd = gui_search(id, x, y);
1333 /* If the active widget has changed, return the new active id. */
1335 if (jd == 0 || jd == active)
1336 return 0;
1337 else
1338 return active = jd;
1341 int gui_click(void)
1343 return active;
1346 int gui_token(int id)
1348 return id ? widget[id].token : 0;
1351 int gui_value(int id)
1353 return id ? widget[id].value : 0;
1356 void gui_toggle(int id)
1358 widget[id].value = widget[id].value ? 0 : 1;
1361 /*---------------------------------------------------------------------------*/
1363 static int gui_vert_test(int id, int jd)
1365 /* Determine whether widget id is in vertical contact with widget jd. */
1367 if (id && widget[id].type == GUI_STATE &&
1368 jd && widget[jd].type == GUI_STATE)
1370 int i0 = widget[id].x;
1371 int i1 = widget[id].x + widget[id].w;
1372 int j0 = widget[jd].x;
1373 int j1 = widget[jd].x + widget[jd].w;
1375 /* Is widget id's top edge is in contact with jd's bottom edge? */
1377 if (widget[id].y + widget[id].h == widget[jd].y)
1379 /* Do widgets id and jd overlap horizontally? */
1381 if (j0 <= i0 && i0 < j1) return 1;
1382 if (j0 < i1 && i1 <= j1) return 1;
1383 if (i0 <= j0 && j0 < i1) return 1;
1384 if (i0 < j1 && j1 <= i1) return 1;
1387 return 0;
1390 static int gui_horz_test(int id, int jd)
1392 /* Determine whether widget id is in horizontal contact with widget jd. */
1394 if (id && widget[id].type == GUI_STATE &&
1395 jd && widget[jd].type == GUI_STATE)
1397 int i0 = widget[id].y;
1398 int i1 = widget[id].y + widget[id].h;
1399 int j0 = widget[jd].y;
1400 int j1 = widget[jd].y + widget[jd].h;
1402 /* Is widget id's right edge in contact with jd's left edge? */
1404 if (widget[id].x + widget[id].w == widget[jd].x)
1406 /* Do widgets id and jd overlap vertically? */
1408 if (j0 <= i0 && i0 < j1) return 1;
1409 if (j0 < i1 && i1 <= j1) return 1;
1410 if (i0 <= j0 && j0 < i1) return 1;
1411 if (i0 < j1 && j1 <= i1) return 1;
1414 return 0;
1417 /*---------------------------------------------------------------------------*/
1419 static int gui_stick_L(int id, int dd)
1421 int jd, kd;
1423 /* Find a widget to the left of widget dd. */
1425 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1426 if ((kd = gui_stick_L(jd, dd)))
1427 return kd;
1429 return (gui_horz_test(id, dd)) ? id : 0;
1432 static int gui_stick_R(int id, int dd)
1434 int jd, kd;
1436 /* Find a widget to the right of widget dd. */
1438 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1439 if ((kd = gui_stick_R(jd, dd)))
1440 return kd;
1442 return (gui_horz_test(dd, id)) ? id : 0;
1445 static int gui_stick_D(int id, int dd)
1447 int jd, kd;
1449 /* Find a widget below widget dd. */
1451 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1452 if ((kd = gui_stick_D(jd, dd)))
1453 return kd;
1455 return (gui_vert_test(id, dd)) ? id : 0;
1458 static int gui_stick_U(int id, int dd)
1460 int jd, kd;
1462 /* Find a widget above widget dd. */
1464 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1465 if ((kd = gui_stick_U(jd, dd)))
1466 return kd;
1468 return (gui_vert_test(dd, id)) ? id : 0;
1472 int gui_stick(int id, int x, int y)
1474 /* Flag the axes to prevent uncontrolled scrolling. */
1476 static int xflag = 1;
1477 static int yflag = 1;
1479 int jd = 0;
1481 /* Find a new active widget in the direction of joystick motion. */
1483 if (-JOY_MID <= x && x <= +JOY_MID)
1484 xflag = 1;
1485 else if (x < -JOY_MID && xflag && (jd = gui_stick_L(id, active)))
1486 xflag = 0;
1487 else if (x > +JOY_MID && xflag && (jd = gui_stick_R(id, active)))
1488 xflag = 0;
1490 if (-JOY_MID <= y && y <= +JOY_MID)
1491 yflag = 1;
1492 else if (y < -JOY_MID && yflag && (jd = gui_stick_U(id, active)))
1493 yflag = 0;
1494 else if (y > +JOY_MID && yflag && (jd = gui_stick_D(id, active)))
1495 yflag = 0;
1497 /* If the active widget has changed, return the new active id. */
1499 if (jd == 0 || jd == active)
1500 return 0;
1501 else
1502 return active = jd;
1505 /*---------------------------------------------------------------------------*/