Import from neverball-1.3.1.tar.gz
[neverball-archive.git] / share / gui.c
blob8f146b1bbb1808f9a5be5da6a13530d13f384504
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 256
29 #define GUI_TYPE 0xFFFE
31 #define GUI_FREE 0
32 #define GUI_STATE 1
33 #define GUI_HARRAY 2
34 #define GUI_VARRAY 4
35 #define GUI_HSTACK 6
36 #define GUI_VSTACK 8
37 #define GUI_FILLER 10
38 #define GUI_IMAGE 12
39 #define GUI_LABEL 14
40 #define GUI_COUNT 16
41 #define GUI_CLOCK 18
42 #define GUI_SPACE 20
43 #define GUI_PAUSE 22
45 struct widget
47 int type;
48 int token;
49 int value;
50 int size;
51 int rect;
53 int x, y;
54 int w, h;
55 int car;
56 int cdr;
58 GLuint text_img;
59 GLuint text_obj;
60 GLuint rect_obj;
62 const GLfloat *color0;
63 const GLfloat *color1;
65 GLfloat scale;
68 /*---------------------------------------------------------------------------*/
70 const GLfloat gui_wht[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
71 const GLfloat gui_yel[4] = { 1.0f, 1.0f, 0.0f, 1.0f };
72 const GLfloat gui_red[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
73 const GLfloat gui_grn[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
74 const GLfloat gui_blu[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
75 const GLfloat gui_blk[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
76 const GLfloat gui_gry[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
78 /*---------------------------------------------------------------------------*/
80 static struct widget widget[MAXWIDGET];
81 static int active;
82 static int radius;
83 static TTF_Font *font[3] = { NULL, NULL, NULL };
85 static GLuint digit_text[3][11];
86 static GLuint digit_list[3][11];
87 static int digit_w[3][11];
88 static int digit_h[3][11];
90 static int pause_id;
92 /*---------------------------------------------------------------------------*/
94 static int gui_hot(int id)
96 return (widget[id].type & GUI_STATE);
99 /*---------------------------------------------------------------------------*/
101 * Initialize a display list containing a rectangle (x, y, w, h) to
102 * which a rendered-font texture may be applied. Colors c0 and c1
103 * determine the top-to-bottom color gradiant of the text.
106 static GLuint gui_list(int x, int y,
107 int w, int h, const float *c0, const float *c1)
109 GLuint list = glGenLists(1);
111 GLfloat s0, t0;
112 GLfloat s1, t1;
114 int W, H, d = h / 16;
116 /* Assume the applied texture size is rect size rounded to power-of-two. */
118 image_size(&W, &H, w, h);
120 s0 = 0.5f * (W - w) / W;
121 t0 = 0.5f * (H - h) / H;
122 s1 = 1.0f - s0;
123 t1 = 1.0f - t0;
125 glNewList(list, GL_COMPILE);
127 glBegin(GL_QUADS);
129 glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
130 glTexCoord2f(s0, t1); glVertex2i(x + d, y - d);
131 glTexCoord2f(s1, t1); glVertex2i(x + w + d, y - d);
132 glTexCoord2f(s1, t0); glVertex2i(x + w + d, y + h - d);
133 glTexCoord2f(s0, t0); glVertex2i(x + d, y + h - d);
135 glColor4fv(c0);
136 glTexCoord2f(s0, t1); glVertex2i(x, y);
137 glTexCoord2f(s1, t1); glVertex2i(x + w, y);
139 glColor4fv(c1);
140 glTexCoord2f(s1, t0); glVertex2i(x + w, y + h);
141 glTexCoord2f(s0, t0); glVertex2i(x, y + h);
143 glEnd();
145 glEndList();
147 return list;
151 * Initialize a display list containing a rounded-corner rectangle (x,
152 * y, w, h). Generate texture coordinates to properly apply a texture
153 * map to the rectangle as though the corners were not rounded.
156 static GLuint gui_rect(int x, int y, int w, int h, int f, int r)
158 GLuint list = glGenLists(1);
160 int n = 8;
161 int i;
163 glNewList(list, GL_COMPILE);
165 glBegin(GL_QUAD_STRIP);
167 /* Left side... */
169 for (i = 0; i <= n; i++)
171 float a = 0.5f * V_PI * (float) i / (float) n;
172 float s = r * fsinf(a);
173 float c = r * fcosf(a);
175 float X = x + r - c;
176 float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
177 float Yb = y + ((f & GUI_SW) ? (r - s) : 0);
179 glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
180 glVertex2f(X, Ya);
182 glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
183 glVertex2f(X, Yb);
186 /* ... Right side. */
188 for (i = 0; i <= n; i++)
190 float a = 0.5f * V_PI * (float) i / (float) n;
191 float s = r * fsinf(a);
192 float c = r * fcosf(a);
194 float X = x + w - r + s;
195 float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
196 float Yb = y + ((f & GUI_SE) ? (r - c) : 0);
198 glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
199 glVertex2f(X, Ya);
201 glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
202 glVertex2f(X, Yb);
205 glEnd();
207 glEndList();
209 return list;
212 /*---------------------------------------------------------------------------*/
214 void gui_init(void)
216 const float *c0 = gui_yel;
217 const float *c1 = gui_red;
219 int i, j, h = config_get_d(CONFIG_HEIGHT);
221 /* Initialize font rendering. */
223 if (TTF_Init() == 0)
225 memset(widget, 0, sizeof (struct widget) * MAXWIDGET);
227 /* Load small, medium, and large typefaces. */
229 font[GUI_SML] = TTF_OpenFont(config_data(GUI_FACE), h / 24);
230 font[GUI_MED] = TTF_OpenFont(config_data(GUI_FACE), h / 12);
231 font[GUI_LRG] = TTF_OpenFont(config_data(GUI_FACE), h / 6);
232 radius = h / 60;
234 /* Initialize the global pause GUI. */
236 if ((pause_id = gui_pause(0)))
237 gui_layout(pause_id, 0, 0);
239 /* Initialize digit glyphs and lists for counters and clocks. */
241 for (i = 0; i < 3; i++)
243 char text[2];
245 /* Draw digits 0 throught 9. */
247 for (j = 0; j < 10; j++)
249 text[0] = '0' + (char) j;
250 text[1] = 0;
252 digit_text[i][j] = make_image_from_font(NULL, NULL,
253 &digit_w[i][j],
254 &digit_h[i][j],
255 text, font[i]);
256 digit_list[i][j] = gui_list(-digit_w[i][j] / 2,
257 -digit_h[i][j] / 2,
258 +digit_w[i][j],
259 +digit_h[i][j], c0, c1);
262 /* Draw the colon for the clock. */
264 digit_text[i][j] = make_image_from_font(NULL, NULL,
265 &digit_w[i][10],
266 &digit_h[i][10],
267 ":", font[i]);
268 digit_list[i][j] = gui_list(-digit_w[i][10] / 2,
269 -digit_h[i][10] / 2,
270 +digit_w[i][10],
271 +digit_h[i][10], c0, c1);
275 active = 0;
278 void gui_free(void)
280 int i, j, id;
282 /* Release any remaining widget texture and display list indices. */
284 for (id = 1; id < MAXWIDGET; id++)
286 if (glIsTexture(widget[id].text_img))
287 glDeleteTextures(1, &widget[id].text_img);
289 if (glIsList(widget[id].text_obj))
290 glDeleteLists(widget[id].text_obj, 1);
291 if (glIsList(widget[id].rect_obj))
292 glDeleteLists(widget[id].rect_obj, 1);
294 widget[id].type = GUI_FREE;
295 widget[id].text_img = 0;
296 widget[id].text_obj = 0;
297 widget[id].rect_obj = 0;
298 widget[id].cdr = 0;
299 widget[id].car = 0;
302 /* Release all digit textures and display lists. */
304 for (i = 0; i < 3; i++)
305 for (j = 0; j < 11; j++)
307 if (glIsTexture(digit_text[i][j]))
308 glDeleteTextures(1, &digit_text[i][j]);
310 if (glIsList(digit_list[i][j]))
311 glDeleteLists(digit_list[i][j], 1);
314 /* Release all loaded fonts and finalize font rendering. */
316 if (font[GUI_LRG]) TTF_CloseFont(font[GUI_LRG]);
317 if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
318 if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
320 TTF_Quit();
323 /*---------------------------------------------------------------------------*/
325 static int gui_widget(int pd, int type)
327 int id;
329 /* Find an unused entry in the widget table. */
331 for (id = 1; id < MAXWIDGET; id++)
332 if (widget[id].type == GUI_FREE)
334 /* Set the type and default properties. */
336 widget[id].type = type;
337 widget[id].token = 0;
338 widget[id].value = 0;
339 widget[id].size = 0;
340 widget[id].rect = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
341 widget[id].w = 0;
342 widget[id].h = 0;
343 widget[id].text_img = 0;
344 widget[id].text_obj = 0;
345 widget[id].rect_obj = 0;
346 widget[id].color0 = gui_wht;
347 widget[id].color1 = gui_wht;
348 widget[id].scale = 1.0f;
350 /* Insert the new widget into the parents's widget list. */
352 if (pd)
354 widget[id].car = 0;
355 widget[id].cdr = widget[pd].car;
356 widget[pd].car = id;
358 else
360 widget[id].car = 0;
361 widget[id].cdr = 0;
364 return id;
367 fprintf(stderr, "Out of widget IDs\n");
369 return 0;
372 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
373 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
374 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
375 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
376 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
378 /*---------------------------------------------------------------------------*/
380 void gui_set_image(int id, const char *file)
382 if (glIsTexture(widget[id].text_img))
383 glDeleteTextures(1, &widget[id].text_img);
385 widget[id].text_img = make_image_from_file(NULL, NULL, NULL, NULL, file);
388 void gui_set_label(int id, const char *text)
390 int w, h;
392 if (glIsTexture(widget[id].text_img))
393 glDeleteTextures(1, &widget[id].text_img);
394 if (glIsList(widget[id].text_obj))
395 glDeleteLists(widget[id].text_obj, 1);
397 widget[id].text_img = make_image_from_font(NULL, NULL, &w, &h,
398 text, font[widget[id].size]);
399 widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
400 widget[id].color0, widget[id].color1);
403 void gui_set_count(int id, int value)
405 widget[id].value = value;
408 void gui_set_clock(int id, int value)
410 widget[id].value = value;
413 void gui_set_multi(int id, const char *text)
415 const char *p;
417 char s[8][MAXSTR];
418 int i, j, jd;
420 size_t n = 0;
422 /* Copy each delimited string to a line buffer. */
424 for (p = text, j = 0; *p && j < 8; j++)
426 strncpy(s[j], p, (n = strcspn(p, "\\")));
427 s[j][n] = 0;
429 if (*(p += n) == '\\') p++;
432 /* Set the label value for each line. */
434 for (i = j - 1, jd = widget[id].car; i >= 0 && jd; i--, jd = widget[jd].cdr)
435 gui_set_label(jd, s[i]);
438 /*---------------------------------------------------------------------------*/
440 int gui_image(int pd, const char *file, int w, int h)
442 int id;
444 if ((id = gui_widget(pd, GUI_IMAGE)))
446 widget[id].text_img = make_image_from_file(NULL, NULL,
447 NULL, NULL, file);
448 widget[id].w = w;
449 widget[id].h = h;
451 return id;
454 int gui_start(int pd, const char *text, int size, int token, int value)
456 int id;
458 if ((id = gui_state(pd, text, size, token, value)))
459 active = id;
461 return id;
464 int gui_state(int pd, const char *text, int size, int token, int value)
466 int id;
468 if ((id = gui_widget(pd, GUI_STATE)))
470 widget[id].text_img = make_image_from_font(NULL, NULL,
471 &widget[id].w,
472 &widget[id].h,
473 text, font[size]);
474 widget[id].size = size;
475 widget[id].token = token;
476 widget[id].value = value;
478 return id;
481 int gui_label(int pd, const char *text, int size, int rect, const float *c0,
482 const float *c1)
484 int id;
486 if ((id = gui_widget(pd, GUI_LABEL)))
488 widget[id].text_img = make_image_from_font(NULL, NULL,
489 &widget[id].w,
490 &widget[id].h,
491 text, font[size]);
492 widget[id].size = size;
493 widget[id].color0 = c0 ? c0 : gui_yel;
494 widget[id].color1 = c1 ? c1 : gui_red;
495 widget[id].rect = rect;
497 return id;
500 int gui_count(int pd, int value, int size, int rect)
502 int i, id;
504 if ((id = gui_widget(pd, GUI_COUNT)))
506 for (i = value; i; i /= 10)
507 widget[id].w += digit_w[size][0];
509 widget[id].h = digit_h[size][0];
510 widget[id].value = value;
511 widget[id].size = size;
512 widget[id].color0 = gui_yel;
513 widget[id].color1 = gui_red;
514 widget[id].rect = rect;
516 return id;
519 int gui_clock(int pd, int value, int size, int rect)
521 int id;
523 if ((id = gui_widget(pd, GUI_CLOCK)))
525 widget[id].w = digit_w[size][0] * 6;
526 widget[id].h = digit_h[size][0];
527 widget[id].value = value;
528 widget[id].size = size;
529 widget[id].color0 = gui_yel;
530 widget[id].color1 = gui_red;
531 widget[id].rect = rect;
533 return id;
536 int gui_space(int pd)
538 int id;
540 if ((id = gui_widget(pd, GUI_SPACE)))
542 widget[id].w = 0;
543 widget[id].h = 0;
545 return id;
548 int gui_pause(int pd)
550 const char *text = "Paused";
551 int id;
553 if ((id = gui_widget(pd, GUI_PAUSE)))
555 widget[id].text_img = make_image_from_font(NULL, NULL,
556 &widget[id].w,
557 &widget[id].h,
558 text, font[GUI_LRG]);
559 widget[id].color0 = gui_wht;
560 widget[id].color1 = gui_wht;
561 widget[id].value = 0;
562 widget[id].size = GUI_LRG;
563 widget[id].rect = GUI_ALL;
565 return id;
568 /*---------------------------------------------------------------------------*/
570 * Create a multi-line text box using a vertical array of labels.
571 * Parse the text for '\' characters and treat them as line-breaks.
572 * Preserve the rect specifation across the entire array.
575 int gui_multi(int pd, const char *text, int size, int rect, const float *c0,
576 const float *c1)
578 int id = 0;
580 if (text && (id = gui_varray(pd)))
582 const char *p;
584 char s[8][MAXSTR];
585 int r[8];
586 int i, j;
588 size_t n = 0;
590 /* Copy each delimited string to a line buffer. */
592 for (p = text, j = 0; *p && j < 8; j++)
594 strncpy(s[j], p, (n = strcspn(p, "\\")));
595 s[j][n] = 0;
596 r[j] = 0;
598 if (*(p += n) == '\\') p++;
601 /* Set the curves for the first and last lines. */
603 if (j > 0)
605 r[0] |= rect & (GUI_NW | GUI_NE);
606 r[j - 1] |= rect & (GUI_SW | GUI_SE);
609 /* Create a label widget for each line. */
611 for (i = 0; i < j; i++)
612 gui_label(id, s[i], size, r[i], c0, c1);
614 return id;
617 /*---------------------------------------------------------------------------*/
619 * The bottom-up pass determines the area of all widgets. The minimum
620 * width and height of a leaf widget is given by the size of its
621 * contents. Array and stack widths and heights are computed
622 * recursively from these.
625 static void gui_widget_up(int id);
627 static void gui_harray_up(int id)
629 int jd, c = 0;
631 /* Find the widest child width and the highest child height. */
633 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
635 gui_widget_up(jd);
637 if (widget[id].h < widget[jd].h)
638 widget[id].h = widget[jd].h;
639 if (widget[id].w < widget[jd].w)
640 widget[id].w = widget[jd].w;
642 c++;
645 /* Total width is the widest child width times the child count. */
647 widget[id].w *= c;
650 static void gui_varray_up(int id)
652 int jd, c = 0;
654 /* Find the widest child width and the highest child height. */
656 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
658 gui_widget_up(jd);
660 if (widget[id].h < widget[jd].h)
661 widget[id].h = widget[jd].h;
662 if (widget[id].w < widget[jd].w)
663 widget[id].w = widget[jd].w;
665 c++;
668 /* Total height is the highest child height times the child count. */
670 widget[id].h *= c;
673 static void gui_hstack_up(int id)
675 int jd;
677 /* Find the highest child height. Sum the child widths. */
679 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
681 gui_widget_up(jd);
683 if (widget[id].h < widget[jd].h)
684 widget[id].h = widget[jd].h;
686 widget[id].w += widget[jd].w;
690 static void gui_vstack_up(int id)
692 int jd;
694 /* Find the widest child width. Sum the child heights. */
696 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
698 gui_widget_up(jd);
700 if (widget[id].w < widget[jd].w)
701 widget[id].w = widget[jd].w;
703 widget[id].h += widget[jd].h;
707 static void gui_paused_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 /* The pause widget fills the screen. */
716 widget[id].w = config_get_d(CONFIG_WIDTH);
717 widget[id].h = config_get_d(CONFIG_HEIGHT);
720 static void gui_button_up(int id)
722 /* Store width and height for later use in text rendering. */
724 widget[id].x = widget[id].w;
725 widget[id].y = widget[id].h;
727 if (widget[id].w < widget[id].h && widget[id].w > 0)
728 widget[id].w = widget[id].h;
731 /* Padded text elements look a little nicer. */
733 if (widget[id].w < config_get_d(CONFIG_WIDTH))
734 widget[id].w += radius;
735 if (widget[id].h < config_get_d(CONFIG_HEIGHT))
736 widget[id].h += radius;
739 static void gui_widget_up(int id)
741 if (id)
742 switch (widget[id].type & GUI_TYPE)
744 case GUI_HARRAY: gui_harray_up(id); break;
745 case GUI_VARRAY: gui_varray_up(id); break;
746 case GUI_HSTACK: gui_hstack_up(id); break;
747 case GUI_VSTACK: gui_vstack_up(id); break;
748 case GUI_PAUSE: gui_paused_up(id); break;
749 default: gui_button_up(id); break;
753 /*---------------------------------------------------------------------------*/
755 * The top-down layout pass distributes available area as computed
756 * during the bottom-up pass. Widgets use their area and position to
757 * initialize rendering state.
760 static void gui_widget_dn(int id, int x, int y, int w, int h);
762 static void gui_harray_dn(int id, int x, int y, int w, int h)
764 int jd, i = 0, c = 0;
766 widget[id].x = x;
767 widget[id].y = y;
768 widget[id].w = w;
769 widget[id].h = h;
771 /* Count children. */
773 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
774 c += 1;
776 /* Distribute horizontal space evenly to all children. */
778 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
780 int x0 = x + i * w / c;
781 int x1 = x + (i + 1) * w / c;
783 gui_widget_dn(jd, x0, y, x1 - x0, h);
787 static void gui_varray_dn(int id, int x, int y, int w, int h)
789 int jd, i = 0, c = 0;
791 widget[id].x = x;
792 widget[id].y = y;
793 widget[id].w = w;
794 widget[id].h = h;
796 /* Count children. */
798 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
799 c += 1;
801 /* Distribute vertical space evenly to all children. */
803 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
805 int y0 = y + i * h / c;
806 int y1 = y + (i + 1) * h / c;
808 gui_widget_dn(jd, x, y0, w, y1 - y0);
812 static void gui_hstack_dn(int id, int x, int y, int w, int h)
814 int jd, jx = x, jw = 0, c = 0;
816 widget[id].x = x;
817 widget[id].y = y;
818 widget[id].w = w;
819 widget[id].h = h;
821 /* Measure the total width requested by non-filler children. */
823 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
824 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
825 c += 1;
826 else
827 jw += widget[jd].w;
829 /* Give non-filler children their requested space. */
830 /* Distribute the rest evenly among filler children. */
832 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
834 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
835 gui_widget_dn(jd, jx, y, (w - jw) / c, h);
836 else
837 gui_widget_dn(jd, jx, y, widget[jd].w, h);
839 jx += widget[jd].w;
843 static void gui_vstack_dn(int id, int x, int y, int w, int h)
845 int jd, jy = y, jh = 0, c = 0;
847 widget[id].x = x;
848 widget[id].y = y;
849 widget[id].w = w;
850 widget[id].h = h;
852 /* Measure the total height requested by non-filler children. */
854 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
855 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
856 c += 1;
857 else
858 jh += widget[jd].h;
860 /* Give non-filler children their requested space. */
861 /* Distribute the rest evenly among filler children. */
863 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
865 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
866 gui_widget_dn(jd, x, jy, w, (h - jh) / c);
867 else
868 gui_widget_dn(jd, x, jy, w, widget[jd].h);
870 jy += widget[jd].h;
874 static void gui_filler_dn(int id, int x, int y, int w, int h)
876 /* Filler expands to whatever size it is given. */
878 widget[id].x = x;
879 widget[id].y = y;
880 widget[id].w = w;
881 widget[id].h = h;
884 static void gui_button_dn(int id, int x, int y, int w, int h)
886 /* Recall stored width and height for text rendering. */
888 int W = widget[id].x;
889 int H = widget[id].y;
890 int R = widget[id].rect;
891 int r = ((widget[id].type & GUI_TYPE) == GUI_PAUSE ? radius * 4 : radius);
893 const float *c0 = widget[id].color0;
894 const float *c1 = widget[id].color1;
896 widget[id].x = x;
897 widget[id].y = y;
898 widget[id].w = w;
899 widget[id].h = h;
901 /* Create display lists for the text area and rounded rectangle. */
903 widget[id].text_obj = gui_list(-W / 2, -H / 2, W, H, c0, c1);
904 widget[id].rect_obj = gui_rect(-w / 2, -h / 2, w, h, R, r);
907 static void gui_widget_dn(int id, int x, int y, int w, int h)
909 if (id)
910 switch (widget[id].type & GUI_TYPE)
912 case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
913 case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
914 case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
915 case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
916 case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
917 case GUI_SPACE: gui_filler_dn(id, x, y, w, h); break;
918 default: gui_button_dn(id, x, y, w, h); break;
922 /*---------------------------------------------------------------------------*/
924 * During GUI layout, we make a bottom-up pass to determine total area
925 * requirements for the widget tree. We position this area to the
926 * sides or center of the screen. Finally, we make a top-down pass to
927 * distribute this area to each widget.
930 void gui_layout(int id, int xd, int yd)
932 int x, y;
934 int w, W = config_get_d(CONFIG_WIDTH);
935 int h, H = config_get_d(CONFIG_HEIGHT);
937 gui_widget_up(id);
939 w = widget[id].w;
940 h = widget[id].h;
942 if (xd < 0) x = 0;
943 else if (xd > 0) x = (W - w);
944 else x = (W - w) / 2;
946 if (yd < 0) y = 0;
947 else if (yd > 0) y = (H - h);
948 else y = (H - h) / 2;
950 gui_widget_dn(id, x, y, w, h);
953 int gui_search(int id, int x, int y)
955 int jd, kd;
957 /* Search the hierarchy for the widget containing the given point. */
959 if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
960 widget[id].y <= y && y < widget[id].y + widget[id].h))
962 if (gui_hot(id))
963 return id;
965 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
966 if ((kd = gui_search(jd, x, y)))
967 return kd;
969 return 0;
973 * Activate a widget, allowing it to behave as a normal state widget.
974 * This may be used to create image buttons, or cause an array of
975 * widgets to behave as a single state widget.
977 int gui_active(int id, int token, int value)
979 widget[id].type |= GUI_STATE;
980 widget[id].token = token;
981 widget[id].value = value;
983 return id;
986 int gui_delete(int id)
988 if (id)
990 /* Recursively delete all subwidgets. */
992 gui_delete(widget[id].cdr);
993 gui_delete(widget[id].car);
995 /* Release any GL resources held by this widget. */
997 if (glIsTexture(widget[id].text_img))
998 glDeleteTextures(1, &widget[id].text_img);
1000 if (glIsList(widget[id].text_obj))
1001 glDeleteLists(widget[id].text_obj, 1);
1002 if (glIsList(widget[id].rect_obj))
1003 glDeleteLists(widget[id].rect_obj, 1);
1005 /* Mark this widget unused. */
1007 widget[id].type = GUI_FREE;
1008 widget[id].text_img = 0;
1009 widget[id].text_obj = 0;
1010 widget[id].rect_obj = 0;
1011 widget[id].cdr = 0;
1012 widget[id].car = 0;
1014 return 0;
1017 /*---------------------------------------------------------------------------*/
1019 static void gui_paint_rect(int id, int st)
1021 #ifdef SNIP
1022 static const GLfloat back[4][4] = {
1023 { 0.1f, 0.1f, 0.1f, 0.5f }, /* off and inactive */
1024 { 0.3f, 0.3f, 0.3f, 0.5f }, /* off and active */
1025 { 0.7f, 0.3f, 0.0f, 0.5f }, /* on and inactive */
1026 { 1.0f, 0.7f, 0.3f, 0.5f }, /* on and active */
1028 #endif
1029 static const GLfloat back[4][4] = {
1030 { 0.1f, 0.1f, 0.1f, 0.5f }, /* off and inactive */
1031 { 0.5f, 0.5f, 0.5f, 0.8f }, /* off and active */
1032 { 1.0f, 0.7f, 0.3f, 0.5f }, /* on and inactive */
1033 { 1.0f, 0.7f, 0.3f, 0.8f }, /* on and active */
1036 int jd, i = 0;
1038 /* Use the widget status to determine the background color. */
1040 if (gui_hot(id))
1041 i = st | (((widget[id].value) ? 2 : 0) |
1042 ((id == active) ? 1 : 0));
1044 switch (widget[id].type & GUI_TYPE)
1046 case GUI_IMAGE:
1047 case GUI_SPACE:
1048 case GUI_FILLER:
1049 break;
1051 case GUI_HARRAY:
1052 case GUI_VARRAY:
1053 case GUI_HSTACK:
1054 case GUI_VSTACK:
1056 /* Recursively paint all subwidgets. */
1058 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1059 gui_paint_rect(jd, i);
1061 break;
1063 default:
1065 /* Draw a leaf's background, colored by widget state. */
1067 glPushMatrix();
1069 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1070 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1072 glColor4fv(back[i]);
1073 glCallList(widget[id].rect_obj);
1075 glPopMatrix();
1077 break;
1081 /*---------------------------------------------------------------------------*/
1083 static void gui_paint_text(int id);
1085 static void gui_paint_array(int id)
1087 int jd;
1089 glPushMatrix();
1091 GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1092 GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1093 GLfloat ck = widget[id].scale;
1095 glTranslatef(+cx, +cy, 0.0f);
1096 glScalef(ck, ck, ck);
1097 glTranslatef(-cx, -cy, 0.0f);
1099 /* Recursively paint all subwidgets. */
1101 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1102 gui_paint_text(jd);
1104 glPopMatrix();
1107 static void gui_paint_image(int id)
1109 /* Draw the widget rect, textured using the image. */
1111 glPushMatrix();
1113 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1114 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1116 glScalef(widget[id].scale,
1117 widget[id].scale,
1118 widget[id].scale);
1120 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1121 glColor4fv(gui_wht);
1122 glCallList(widget[id].rect_obj);
1124 glPopMatrix();
1127 static void gui_paint_count(int id)
1129 int j, i = widget[id].size;
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 if (widget[id].value)
1146 /* Translate left by half the total width of the rendered value. */
1148 for (j = widget[id].value; j; j /= 10)
1149 glTranslatef((GLfloat) +digit_w[i][j % 10] / 2.0f, 0.0f, 0.0f);
1151 glTranslatef((GLfloat) -digit_w[i][0] / 2.0f, 0.0f, 0.0f);
1153 /* Render each digit, moving right after each. */
1155 for (j = widget[id].value; j; j /= 10)
1157 glBindTexture(GL_TEXTURE_2D, digit_text[i][j % 10]);
1158 glCallList(digit_list[i][j % 10]);
1159 glTranslatef((GLfloat) -digit_w[i][j % 10], 0.0f, 0.0f);
1162 else
1164 /* If the value is zero, just display a zero in place. */
1166 glBindTexture(GL_TEXTURE_2D, digit_text[i][0]);
1167 glCallList(digit_list[i][0]);
1170 glPopMatrix();
1173 static void gui_paint_clock(int id)
1175 int i = widget[id].size;
1176 int mt = (widget[id].value / 6000) / 10;
1177 int mo = (widget[id].value / 6000) % 10;
1178 int st = ((widget[id].value % 6000) / 100) / 10;
1179 int so = ((widget[id].value % 6000) / 100) % 10;
1180 int ht = ((widget[id].value % 6000) % 100) / 10;
1181 int ho = ((widget[id].value % 6000) % 100) % 10;
1183 GLfloat dx_large = (GLfloat) digit_w[i][0];
1184 GLfloat dx_small = (GLfloat) digit_w[i][0] * 0.75f;
1186 glPushMatrix();
1188 glColor4fv(gui_wht);
1190 /* Translate to the widget center, and apply the pulse scale. */
1192 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1193 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1195 glScalef(widget[id].scale,
1196 widget[id].scale,
1197 widget[id].scale);
1199 /* Translate left by half the total width of the rendered value. */
1201 if (mt > 0)
1202 glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1203 else
1204 glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1206 /* Render the minutes counter. */
1208 if (mt > 0)
1210 glBindTexture(GL_TEXTURE_2D, digit_text[i][mt]);
1211 glCallList(digit_list[i][mt]);
1212 glTranslatef(dx_large, 0.0f, 0.0f);
1215 glBindTexture(GL_TEXTURE_2D, digit_text[i][mo]);
1216 glCallList(digit_list[i][mo]);
1217 glTranslatef(dx_small, 0.0f, 0.0f);
1219 /* Render the colon. */
1221 glBindTexture(GL_TEXTURE_2D, digit_text[i][10]);
1222 glCallList(digit_list[i][10]);
1223 glTranslatef(dx_small, 0.0f, 0.0f);
1225 /* Render the seconds counter. */
1227 glBindTexture(GL_TEXTURE_2D, digit_text[i][st]);
1228 glCallList(digit_list[i][st]);
1229 glTranslatef(dx_large, 0.0f, 0.0f);
1231 glBindTexture(GL_TEXTURE_2D, digit_text[i][so]);
1232 glCallList(digit_list[i][so]);
1233 glTranslatef(dx_small, 0.0f, 0.0f);
1235 /* Render hundredths counter half size. */
1237 glScalef(0.5f, 0.5f, 1.0f);
1239 glBindTexture(GL_TEXTURE_2D, digit_text[i][ht]);
1240 glCallList(digit_list[i][ht]);
1241 glTranslatef(dx_large, 0.0f, 0.0f);
1243 glBindTexture(GL_TEXTURE_2D, digit_text[i][ho]);
1244 glCallList(digit_list[i][ho]);
1246 glPopMatrix();
1249 static void gui_paint_label(int id)
1251 /* Draw the widget text box, textured using the glyph. */
1253 glPushMatrix();
1255 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1256 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1258 glScalef(widget[id].scale,
1259 widget[id].scale,
1260 widget[id].scale);
1262 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1263 glCallList(widget[id].text_obj);
1265 glPopMatrix();
1268 static void gui_paint_text(int id)
1270 switch (widget[id].type & GUI_TYPE)
1272 case GUI_SPACE: break;
1273 case GUI_FILLER: break;
1274 case GUI_HARRAY: gui_paint_array(id); break;
1275 case GUI_VARRAY: gui_paint_array(id); break;
1276 case GUI_HSTACK: gui_paint_array(id); break;
1277 case GUI_VSTACK: gui_paint_array(id); break;
1278 case GUI_IMAGE: gui_paint_image(id); break;
1279 case GUI_COUNT: gui_paint_count(id); break;
1280 case GUI_CLOCK: gui_paint_clock(id); break;
1281 default: gui_paint_label(id); break;
1285 void gui_paint(int id)
1287 if (id)
1289 glPushAttrib(GL_LIGHTING_BIT |
1290 GL_COLOR_BUFFER_BIT |
1291 GL_DEPTH_BUFFER_BIT);
1292 config_push_ortho();
1294 glEnable(GL_BLEND);
1295 glEnable(GL_COLOR_MATERIAL);
1296 glDisable(GL_LIGHTING);
1297 glDisable(GL_DEPTH_TEST);
1299 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1301 glPushAttrib(GL_TEXTURE_BIT);
1303 glDisable(GL_TEXTURE_2D);
1304 gui_paint_rect(id, 0);
1306 glPopAttrib();
1308 gui_paint_text(id);
1310 config_pop_matrix();
1311 glPopAttrib();
1315 void gui_blank(void)
1317 gui_paint(pause_id);
1320 /*---------------------------------------------------------------------------*/
1322 void gui_dump(int id, int d)
1324 int jd, i;
1326 if (id)
1328 char *type = "?";
1330 switch (widget[id].type & GUI_TYPE)
1332 case GUI_HARRAY: type = "harray"; break;
1333 case GUI_VARRAY: type = "varray"; break;
1334 case GUI_HSTACK: type = "hstack"; break;
1335 case GUI_VSTACK: type = "vstack"; break;
1336 case GUI_FILLER: type = "filler"; break;
1337 case GUI_IMAGE: type = "image"; break;
1338 case GUI_LABEL: type = "label"; break;
1339 case GUI_COUNT: type = "count"; break;
1340 case GUI_CLOCK: type = "clock"; break;
1343 for (i = 0; i < d; i++)
1344 printf(" ");
1346 printf("%04d %s\n", id, type);
1348 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1349 gui_dump(jd, d + 1);
1353 void gui_pulse(int id, float k)
1355 if (id) widget[id].scale = k;
1358 void gui_timer(int id, float dt)
1360 int jd;
1362 if (id)
1364 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1365 gui_timer(jd, dt);
1367 if (widget[id].scale - 1.0f < dt)
1368 widget[id].scale = 1.0f;
1369 else
1370 widget[id].scale -= dt;
1374 int gui_point(int id, int x, int y)
1376 /* Short-circuit check the current active widget. */
1378 int jd = gui_search(active, x, y);
1380 /* If not still active, search the hierarchy for a new active widget. */
1382 if (jd == 0)
1383 jd = gui_search(id, x, y);
1385 /* If the active widget has changed, return the new active id. */
1387 if (jd == 0 || jd == active)
1388 return 0;
1389 else
1390 return active = jd;
1393 int gui_click(void)
1395 return active;
1398 int gui_token(int id)
1400 return id ? widget[id].token : 0;
1403 int gui_value(int id)
1405 return id ? widget[id].value : 0;
1408 void gui_toggle(int id)
1410 widget[id].value = widget[id].value ? 0 : 1;
1413 /*---------------------------------------------------------------------------*/
1415 static int gui_vert_test(int id, int jd)
1417 /* Determine whether widget id is in vertical contact with widget jd. */
1419 if (id && gui_hot(id) && jd && gui_hot(jd))
1421 int i0 = widget[id].x;
1422 int i1 = widget[id].x + widget[id].w;
1423 int j0 = widget[jd].x;
1424 int j1 = widget[jd].x + widget[jd].w;
1426 /* Is widget id's top edge is in contact with jd's bottom edge? */
1428 if (widget[id].y + widget[id].h == widget[jd].y)
1430 /* Do widgets id and jd overlap horizontally? */
1432 if (j0 <= i0 && i0 < j1) return 1;
1433 if (j0 < i1 && i1 <= j1) return 1;
1434 if (i0 <= j0 && j0 < i1) return 1;
1435 if (i0 < j1 && j1 <= i1) return 1;
1438 return 0;
1441 static int gui_horz_test(int id, int jd)
1443 /* Determine whether widget id is in horizontal contact with widget jd. */
1445 if (id && gui_hot(id) && jd && gui_hot(jd))
1447 int i0 = widget[id].y;
1448 int i1 = widget[id].y + widget[id].h;
1449 int j0 = widget[jd].y;
1450 int j1 = widget[jd].y + widget[jd].h;
1452 /* Is widget id's right edge in contact with jd's left edge? */
1454 if (widget[id].x + widget[id].w == widget[jd].x)
1456 /* Do widgets id and jd overlap vertically? */
1458 if (j0 <= i0 && i0 < j1) return 1;
1459 if (j0 < i1 && i1 <= j1) return 1;
1460 if (i0 <= j0 && j0 < i1) return 1;
1461 if (i0 < j1 && j1 <= i1) return 1;
1464 return 0;
1467 /*---------------------------------------------------------------------------*/
1469 static int gui_stick_L(int id, int dd)
1471 int jd, kd;
1473 /* Find a widget to the left of widget dd. */
1475 if (gui_horz_test(id, dd))
1476 return id;
1478 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1479 if ((kd = gui_stick_L(jd, dd)))
1480 return kd;
1482 return 0;
1485 static int gui_stick_R(int id, int dd)
1487 int jd, kd;
1489 /* Find a widget to the right of widget dd. */
1491 if (gui_horz_test(dd, id))
1492 return id;
1494 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1495 if ((kd = gui_stick_R(jd, dd)))
1496 return kd;
1498 return 0;
1501 static int gui_stick_D(int id, int dd)
1503 int jd, kd;
1505 /* Find a widget below widget dd. */
1507 if (gui_vert_test(id, dd))
1508 return id;
1510 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1511 if ((kd = gui_stick_D(jd, dd)))
1512 return kd;
1514 return 0;
1517 static int gui_stick_U(int id, int dd)
1519 int jd, kd;
1521 /* Find a widget above widget dd. */
1523 if (gui_vert_test(dd, id))
1524 return id;
1526 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1527 if ((kd = gui_stick_U(jd, dd)))
1528 return kd;
1530 return 0;
1534 int gui_stick(int id, int x, int y)
1536 /* Flag the axes to prevent uncontrolled scrolling. */
1538 static int xflag = 1;
1539 static int yflag = 1;
1541 int jd = 0;
1543 /* Find a new active widget in the direction of joystick motion. */
1545 if (x && -JOY_MID <= x && x <= +JOY_MID)
1546 xflag = 1;
1547 else if (x < -JOY_MID && xflag && (jd = gui_stick_L(id, active)))
1548 xflag = 0;
1549 else if (x > +JOY_MID && xflag && (jd = gui_stick_R(id, active)))
1550 xflag = 0;
1552 if (y && -JOY_MID <= y && y <= +JOY_MID)
1553 yflag = 1;
1554 else if (y < -JOY_MID && yflag && (jd = gui_stick_U(id, active)))
1555 yflag = 0;
1556 else if (y > +JOY_MID && yflag && (jd = gui_stick_D(id, active)))
1557 yflag = 0;
1559 /* If the active widget has changed, return the new active id. */
1561 if (jd == 0 || jd == active)
1562 return 0;
1563 else
1564 return active = jd;
1567 /*---------------------------------------------------------------------------*/