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.
30 /*---------------------------------------------------------------------------*/
34 #define GUI_TYPE 0xFFFE
66 const GLfloat *color0;
67 const GLfloat *color1;
77 /*---------------------------------------------------------------------------*/
79 const GLfloat gui_wht[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
80 const GLfloat gui_yel[4] = { 1.0f, 1.0f, 0.0f, 1.0f };
81 const GLfloat gui_red[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
82 const GLfloat gui_grn[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
83 const GLfloat gui_blu[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
84 const GLfloat gui_blk[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
85 const GLfloat gui_gry[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
87 /*---------------------------------------------------------------------------*/
89 static struct widget widget[MAXWIDGET];
92 static TTF_Font *font[3] = { NULL, NULL, NULL };
94 static void *fontdata;
95 static int fontdatalen;
96 static SDL_RWops *fontrwops;
98 static GLuint digit_text[3][11];
99 static GLuint digit_list[3][11];
100 static int digit_w[3][11];
101 static int digit_h[3][11];
103 /*---------------------------------------------------------------------------*/
105 static int gui_hot(int id)
107 return (widget[id].type & GUI_STATE);
110 /*---------------------------------------------------------------------------*/
112 * Initialize a display list containing a rectangle (x, y, w, h) to
113 * which a rendered-font texture may be applied. Colors c0 and c1
114 * determine the top-to-bottom color gradient of the text.
117 static GLuint gui_list(int x, int y,
118 int w, int h, const float *c0, const float *c1)
120 GLuint list = glGenLists(1);
125 int W, H, ww, hh, d = h / 16;
127 /* Assume the applied texture size is rect size rounded to power-of-two. */
129 image_size(&W, &H, w, h);
131 ww = ((W - w) % 2) ? w + 1 : w;
132 hh = ((H - h) % 2) ? h + 1 : h;
134 s0 = 0.5f * (W - ww) / W;
135 t0 = 0.5f * (H - hh) / H;
139 glNewList(list, GL_COMPILE);
143 glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
144 glTexCoord2f(s0, t1); glVertex2i(x + d, y - d);
145 glTexCoord2f(s1, t1); glVertex2i(x + ww + d, y - d);
146 glTexCoord2f(s1, t0); glVertex2i(x + ww + d, y + hh - d);
147 glTexCoord2f(s0, t0); glVertex2i(x + d, y + hh - d);
150 glTexCoord2f(s0, t1); glVertex2i(x, y);
151 glTexCoord2f(s1, t1); glVertex2i(x + ww, y);
154 glTexCoord2f(s1, t0); glVertex2i(x + ww, y + hh);
155 glTexCoord2f(s0, t0); glVertex2i(x, y + hh);
165 * Initialize a display list containing a rounded-corner rectangle (x,
166 * y, w, h). Generate texture coordinates to properly apply a texture
167 * map to the rectangle as though the corners were not rounded.
170 static GLuint gui_rect(int x, int y, int w, int h, int f, int r)
172 GLuint list = glGenLists(1);
177 glNewList(list, GL_COMPILE);
179 glBegin(GL_QUAD_STRIP);
183 for (i = 0; i <= n; i++)
185 float a = 0.5f * V_PI * (float) i / (float) n;
186 float s = r * fsinf(a);
187 float c = r * fcosf(a);
190 float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
191 float Yb = y + ((f & GUI_SW) ? (r - s) : 0);
193 glTexCoord2f((X - x) / w, (Ya - y) / h);
196 glTexCoord2f((X - x) / w, (Yb - y) / h);
200 /* ... Right side. */
202 for (i = 0; i <= n; i++)
204 float a = 0.5f * V_PI * (float) i / (float) n;
205 float s = r * fsinf(a);
206 float c = r * fcosf(a);
208 float X = x + w - r + s;
209 float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
210 float Yb = y + ((f & GUI_SE) ? (r - c) : 0);
212 glTexCoord2f((X - x) / w, (Ya - y) / h);
215 glTexCoord2f((X - x) / w, (Yb - y) / h);
226 /*---------------------------------------------------------------------------*/
228 static const char *pick_font_path(void)
234 if (!fs_exists(path))
236 fprintf(stderr, _("Font \"%s\" doesn't exist, trying default font.\n"),
247 const float *c0 = gui_yel;
248 const float *c1 = gui_red;
250 int w = config_get_d(CONFIG_WIDTH);
251 int h = config_get_d(CONFIG_HEIGHT);
252 int i, j, s = (h < w) ? h : w;
254 /* Initialize font rendering. */
258 const char *fontpath = pick_font_path();
264 memset(widget, 0, sizeof (struct widget) * MAXWIDGET);
268 if (!(fontdata = fs_load(fontpath, &fontdatalen)))
270 fprintf(stderr, _("Could not load font %s.\n"), fontpath);
271 /* Return or no return, we'll probably crash now. */
275 fontrwops = SDL_RWFromConstMem(fontdata, fontdatalen);
277 /* Load small, medium, and large typefaces. */
279 font[GUI_SML] = TTF_OpenFontRW(fontrwops, 0, s0);
281 SDL_RWseek(fontrwops, 0, SEEK_SET);
282 font[GUI_MED] = TTF_OpenFontRW(fontrwops, 0, s1);
284 SDL_RWseek(fontrwops, 0, SEEK_SET);
285 font[GUI_LRG] = TTF_OpenFontRW(fontrwops, 0, s2);
287 /* fontrwops remains open. */
291 /* Initialize digit glyphs and lists for counters and clocks. */
293 for (i = 0; i < 3; i++)
297 /* Draw digits 0 through 9. */
299 for (j = 0; j < 10; j++)
301 text[0] = '0' + (char) j;
304 digit_text[i][j] = make_image_from_font(NULL, NULL,
308 digit_list[i][j] = gui_list(-digit_w[i][j] / 2,
311 +digit_h[i][j], c0, c1);
314 /* Draw the colon for the clock. */
316 digit_text[i][j] = make_image_from_font(NULL, NULL,
320 digit_list[i][j] = gui_list(-digit_w[i][10] / 2,
323 +digit_h[i][10], c0, c1);
334 /* Release any remaining widget texture and display list indices. */
336 for (id = 1; id < MAXWIDGET; id++)
338 if (glIsTexture(widget[id].text_img))
339 glDeleteTextures(1, &widget[id].text_img);
341 if (glIsList(widget[id].text_obj))
342 glDeleteLists(widget[id].text_obj, 1);
343 if (glIsList(widget[id].rect_obj))
344 glDeleteLists(widget[id].rect_obj, 1);
346 widget[id].type = GUI_FREE;
347 widget[id].text_img = 0;
348 widget[id].text_obj = 0;
349 widget[id].rect_obj = 0;
354 /* Release all digit textures and display lists. */
356 for (i = 0; i < 3; i++)
357 for (j = 0; j < 11; j++)
359 if (glIsTexture(digit_text[i][j]))
360 glDeleteTextures(1, &digit_text[i][j]);
362 if (glIsList(digit_list[i][j]))
363 glDeleteLists(digit_list[i][j], 1);
366 /* Release all loaded fonts and finalize font rendering. */
368 if (font[GUI_LRG]) TTF_CloseFont(font[GUI_LRG]);
369 if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
370 if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
372 if (fontrwops) SDL_RWclose(fontrwops);
373 if (fontdata) free(fontdata);
378 /*---------------------------------------------------------------------------*/
380 static int gui_widget(int pd, int type)
384 /* Find an unused entry in the widget table. */
386 for (id = 1; id < MAXWIDGET; id++)
387 if (widget[id].type == GUI_FREE)
389 /* Set the type and default properties. */
391 widget[id].type = type;
392 widget[id].token = 0;
393 widget[id].value = 0;
395 widget[id].rect = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
398 widget[id].text_img = 0;
399 widget[id].text_obj = 0;
400 widget[id].rect_obj = 0;
401 widget[id].color0 = gui_wht;
402 widget[id].color1 = gui_wht;
403 widget[id].scale = 1.0f;
404 widget[id].trunc = TRUNC_NONE;
406 widget[id].text_obj_w = 0;
407 widget[id].text_obj_h = 0;
409 /* Insert the new widget into the parent's widget list. */
414 widget[id].cdr = widget[pd].car;
426 fprintf(stderr, "Out of widget IDs\n");
431 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
432 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
433 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
434 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
435 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
437 /*---------------------------------------------------------------------------*/
445 static struct size gui_measure(const char *text, TTF_Font *font)
447 struct size size = { 0, 0 };
448 TTF_SizeUTF8(font, text, &size.w, &size.h);
452 static char *gui_trunc_head(const char *text,
456 int left, right, mid;
460 right = strlen(text);
462 while (right - left > 1)
464 mid = (left + right) / 2;
466 str = concat_string("...", text + mid, NULL);
468 if (gui_measure(str, font).w <= maxwidth)
476 return concat_string("...", text + left, NULL);
479 static char *gui_trunc_tail(const char *text,
483 int left, right, mid;
487 right = strlen(text);
489 while (right - left > 1)
491 mid = (left + right) / 2;
493 str = malloc(mid + sizeof ("..."));
495 memcpy(str, text, mid);
496 memcpy(str + mid, "...", sizeof ("..."));
498 if (gui_measure(str, font).w <= maxwidth)
506 str = malloc(right + sizeof ("..."));
508 memcpy(str, text, right);
509 memcpy(str + right, "...", sizeof ("..."));
514 static char *gui_truncate(const char *text,
519 if (gui_measure(text, font).w <= maxwidth)
524 case TRUNC_NONE: return strdup(text); break;
525 case TRUNC_HEAD: return gui_trunc_head(text, maxwidth, font); break;
526 case TRUNC_TAIL: return gui_trunc_tail(text, maxwidth, font); break;
532 /*---------------------------------------------------------------------------*/
534 void gui_set_image(int id, const char *file)
536 if (glIsTexture(widget[id].text_img))
537 glDeleteTextures(1, &widget[id].text_img);
539 widget[id].text_img = make_image_from_file(file);
542 void gui_set_label(int id, const char *text)
546 if (glIsTexture(widget[id].text_img))
547 glDeleteTextures(1, &widget[id].text_img);
548 if (glIsList(widget[id].text_obj))
549 glDeleteLists(widget[id].text_obj, 1);
551 text = gui_truncate(text, widget[id].w - radius * 2,
552 font[widget[id].size],
555 widget[id].text_img = make_image_from_font(NULL, NULL, &w, &h,
556 text, font[widget[id].size]);
557 widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
558 widget[id].color0, widget[id].color1);
560 widget[id].text_obj_w = w;
561 widget[id].text_obj_h = h;
566 void gui_set_count(int id, int value)
568 widget[id].value = value;
571 void gui_set_clock(int id, int value)
573 widget[id].value = value;
576 void gui_set_color(int id, const float *c0,
581 c0 = c0 ? c0 : gui_yel;
582 c1 = c1 ? c1 : gui_red;
584 if (widget[id].color0 != c0 || widget[id].color1 != c1)
586 widget[id].color0 = c0;
587 widget[id].color1 = c1;
589 if (glIsList(widget[id].text_obj))
593 glDeleteLists(widget[id].text_obj, 1);
595 w = widget[id].text_obj_w;
596 h = widget[id].text_obj_h;
598 widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
606 void gui_set_multi(int id, const char *text)
615 /* Copy each delimited string to a line buffer. */
617 for (p = text, j = 0; *p && j < 8; j++)
619 strncpy(s[j], p, (n = strcspn(p, "\\")));
622 if (*(p += n) == '\\') p++;
625 /* Set the label value for each line. */
627 for (i = j - 1, jd = widget[id].car; i >= 0 && jd; i--, jd = widget[jd].cdr)
628 gui_set_label(jd, s[i]);
631 void gui_set_trunc(int id, enum trunc trunc)
633 widget[id].trunc = trunc;
636 /*---------------------------------------------------------------------------*/
638 int gui_image(int pd, const char *file, int w, int h)
642 if ((id = gui_widget(pd, GUI_IMAGE)))
644 widget[id].text_img = make_image_from_file(file);
651 int gui_start(int pd, const char *text, int size, int token, int value)
655 if ((id = gui_state(pd, text, size, token, value)))
661 int gui_state(int pd, const char *text, int size, int token, int value)
665 if ((id = gui_widget(pd, GUI_STATE)))
667 widget[id].text_img = make_image_from_font(NULL, NULL,
671 widget[id].size = size;
672 widget[id].token = token;
673 widget[id].value = value;
678 int gui_label(int pd, const char *text, int size, int rect, const float *c0,
683 if ((id = gui_widget(pd, GUI_LABEL)))
685 widget[id].text_img = make_image_from_font(NULL, NULL,
689 widget[id].size = size;
690 widget[id].color0 = c0 ? c0 : gui_yel;
691 widget[id].color1 = c1 ? c1 : gui_red;
692 widget[id].rect = rect;
697 int gui_count(int pd, int value, int size, int rect)
701 if ((id = gui_widget(pd, GUI_COUNT)))
703 for (i = value; i; i /= 10)
704 widget[id].w += digit_w[size][0];
706 widget[id].h = digit_h[size][0];
707 widget[id].value = value;
708 widget[id].size = size;
709 widget[id].color0 = gui_yel;
710 widget[id].color1 = gui_red;
711 widget[id].rect = rect;
716 int gui_clock(int pd, int value, int size, int rect)
720 if ((id = gui_widget(pd, GUI_CLOCK)))
722 widget[id].w = digit_w[size][0] * 6;
723 widget[id].h = digit_h[size][0];
724 widget[id].value = value;
725 widget[id].size = size;
726 widget[id].color0 = gui_yel;
727 widget[id].color1 = gui_red;
728 widget[id].rect = rect;
733 int gui_space(int pd)
737 if ((id = gui_widget(pd, GUI_SPACE)))
745 /*---------------------------------------------------------------------------*/
747 * Create a multi-line text box using a vertical array of labels.
748 * Parse the text for '\' characters and treat them as line-breaks.
749 * Preserve the rect specification across the entire array.
752 int gui_multi(int pd, const char *text, int size, int rect, const float *c0,
757 if (text && (id = gui_varray(pd)))
767 /* Copy each delimited string to a line buffer. */
769 for (p = text, j = 0; *p && j < 8; j++)
771 strncpy(s[j], p, (n = strcspn(p, "\\")));
775 if (*(p += n) == '\\') p++;
778 /* Set the curves for the first and last lines. */
782 r[0] |= rect & (GUI_NW | GUI_NE);
783 r[j - 1] |= rect & (GUI_SW | GUI_SE);
786 /* Create a label widget for each line. */
788 for (i = 0; i < j; i++)
789 gui_label(id, s[i], size, r[i], c0, c1);
794 /*---------------------------------------------------------------------------*/
796 * The bottom-up pass determines the area of all widgets. The minimum
797 * width and height of a leaf widget is given by the size of its
798 * contents. Array and stack widths and heights are computed
799 * recursively from these.
802 static void gui_widget_up(int id);
804 static void gui_harray_up(int id)
808 /* Find the widest child width and the highest child height. */
810 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
814 if (widget[id].h < widget[jd].h)
815 widget[id].h = widget[jd].h;
816 if (widget[id].w < widget[jd].w)
817 widget[id].w = widget[jd].w;
822 /* Total width is the widest child width times the child count. */
827 static void gui_varray_up(int id)
831 /* Find the widest child width and the highest child height. */
833 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
837 if (widget[id].h < widget[jd].h)
838 widget[id].h = widget[jd].h;
839 if (widget[id].w < widget[jd].w)
840 widget[id].w = widget[jd].w;
845 /* Total height is the highest child height times the child count. */
850 static void gui_hstack_up(int id)
854 /* Find the highest child height. Sum the child widths. */
856 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
860 if (widget[id].h < widget[jd].h)
861 widget[id].h = widget[jd].h;
863 widget[id].w += widget[jd].w;
867 static void gui_vstack_up(int id)
871 /* Find the widest child width. Sum the child heights. */
873 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
877 if (widget[id].w < widget[jd].w)
878 widget[id].w = widget[jd].w;
880 widget[id].h += widget[jd].h;
884 static void gui_button_up(int id)
886 /* Store width and height for later use in text rendering. */
888 widget[id].text_obj_w = widget[id].w;
889 widget[id].text_obj_h = widget[id].h;
891 if (widget[id].w < widget[id].h && widget[id].w > 0)
892 widget[id].w = widget[id].h;
894 /* Padded text elements look a little nicer. */
896 if (widget[id].w < config_get_d(CONFIG_WIDTH))
897 widget[id].w += radius;
898 if (widget[id].h < config_get_d(CONFIG_HEIGHT))
899 widget[id].h += radius;
901 /* A button should be at least wide enough to accomodate the rounding. */
903 if (widget[id].w < 2 * radius)
904 widget[id].w = 2 * radius;
905 if (widget[id].h < 2 * radius)
906 widget[id].h = 2 * radius;
909 static void gui_widget_up(int id)
912 switch (widget[id].type & GUI_TYPE)
914 case GUI_HARRAY: gui_harray_up(id); break;
915 case GUI_VARRAY: gui_varray_up(id); break;
916 case GUI_HSTACK: gui_hstack_up(id); break;
917 case GUI_VSTACK: gui_vstack_up(id); break;
918 default: gui_button_up(id); break;
922 /*---------------------------------------------------------------------------*/
924 * The top-down layout pass distributes available area as computed
925 * during the bottom-up pass. Widgets use their area and position to
926 * initialize rendering state.
929 static void gui_widget_dn(int id, int x, int y, int w, int h);
931 static void gui_harray_dn(int id, int x, int y, int w, int h)
933 int jd, i = 0, c = 0;
940 /* Count children. */
942 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
945 /* Distribute horizontal space evenly to all children. */
947 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
949 int x0 = x + i * w / c;
950 int x1 = x + (i + 1) * w / c;
952 gui_widget_dn(jd, x0, y, x1 - x0, h);
956 static void gui_varray_dn(int id, int x, int y, int w, int h)
958 int jd, i = 0, c = 0;
965 /* Count children. */
967 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
970 /* Distribute vertical space evenly to all children. */
972 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
974 int y0 = y + i * h / c;
975 int y1 = y + (i + 1) * h / c;
977 gui_widget_dn(jd, x, y0, w, y1 - y0);
981 static void gui_hstack_dn(int id, int x, int y, int w, int h)
983 int jd, jx = x, jw = 0, c = 0;
990 /* Measure the total width requested by non-filler children. */
992 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
993 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
998 /* Give non-filler children their requested space. */
999 /* Distribute the rest evenly among filler children. */
1001 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1003 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1004 gui_widget_dn(jd, jx, y, (w - jw) / c, h);
1006 gui_widget_dn(jd, jx, y, widget[jd].w, h);
1012 static void gui_vstack_dn(int id, int x, int y, int w, int h)
1014 int jd, jy = y, jh = 0, c = 0;
1021 /* Measure the total height requested by non-filler children. */
1023 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1024 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1029 /* Give non-filler children their requested space. */
1030 /* Distribute the rest evenly among filler children. */
1032 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1034 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1035 gui_widget_dn(jd, x, jy, w, (h - jh) / c);
1037 gui_widget_dn(jd, x, jy, w, widget[jd].h);
1043 static void gui_filler_dn(int id, int x, int y, int w, int h)
1045 /* Filler expands to whatever size it is given. */
1053 static void gui_button_dn(int id, int x, int y, int w, int h)
1055 /* Recall stored width and height for text rendering. */
1057 int W = widget[id].text_obj_w;
1058 int H = widget[id].text_obj_h;
1059 int R = widget[id].rect;
1061 const float *c0 = widget[id].color0;
1062 const float *c1 = widget[id].color1;
1069 /* Create display lists for the text area and rounded rectangle. */
1071 widget[id].text_obj = gui_list(-W / 2, -H / 2, W, H, c0, c1);
1072 widget[id].rect_obj = gui_rect(-w / 2, -h / 2, w, h, R, radius);
1075 static void gui_widget_dn(int id, int x, int y, int w, int h)
1078 switch (widget[id].type & GUI_TYPE)
1080 case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
1081 case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
1082 case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
1083 case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
1084 case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
1085 case GUI_SPACE: gui_filler_dn(id, x, y, w, h); break;
1086 default: gui_button_dn(id, x, y, w, h); break;
1090 /*---------------------------------------------------------------------------*/
1092 * During GUI layout, we make a bottom-up pass to determine total area
1093 * requirements for the widget tree. We position this area to the
1094 * sides or center of the screen. Finally, we make a top-down pass to
1095 * distribute this area to each widget.
1098 void gui_layout(int id, int xd, int yd)
1102 int w, W = config_get_d(CONFIG_WIDTH);
1103 int h, H = config_get_d(CONFIG_HEIGHT);
1111 else if (xd > 0) x = (W - w);
1112 else x = (W - w) / 2;
1115 else if (yd > 0) y = (H - h);
1116 else y = (H - h) / 2;
1118 gui_widget_dn(id, x, y, w, h);
1120 /* Hilite the widget under the cursor, if any. */
1122 gui_point(id, -1, -1);
1125 int gui_search(int id, int x, int y)
1129 /* Search the hierarchy for the widget containing the given point. */
1131 if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
1132 widget[id].y <= y && y < widget[id].y + widget[id].h))
1137 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1138 if ((kd = gui_search(jd, x, y)))
1145 * Activate a widget, allowing it to behave as a normal state widget.
1146 * This may be used to create image buttons, or cause an array of
1147 * widgets to behave as a single state widget.
1149 int gui_active(int id, int token, int value)
1151 widget[id].type |= GUI_STATE;
1152 widget[id].token = token;
1153 widget[id].value = value;
1158 int gui_delete(int id)
1162 /* Recursively delete all subwidgets. */
1164 gui_delete(widget[id].cdr);
1165 gui_delete(widget[id].car);
1167 /* Release any GL resources held by this widget. */
1169 if (glIsTexture(widget[id].text_img))
1170 glDeleteTextures(1, &widget[id].text_img);
1172 if (glIsList(widget[id].text_obj))
1173 glDeleteLists(widget[id].text_obj, 1);
1174 if (glIsList(widget[id].rect_obj))
1175 glDeleteLists(widget[id].rect_obj, 1);
1177 /* Mark this widget unused. */
1179 widget[id].type = GUI_FREE;
1180 widget[id].text_img = 0;
1181 widget[id].text_obj = 0;
1182 widget[id].rect_obj = 0;
1189 /*---------------------------------------------------------------------------*/
1191 static void gui_paint_rect(int id, int st)
1193 static const GLfloat back[4][4] = {
1194 { 0.1f, 0.1f, 0.1f, 0.5f }, /* off and inactive */
1195 { 0.5f, 0.5f, 0.5f, 0.8f }, /* off and active */
1196 { 1.0f, 0.7f, 0.3f, 0.5f }, /* on and inactive */
1197 { 1.0f, 0.7f, 0.3f, 0.8f }, /* on and active */
1202 /* Use the widget status to determine the background color. */
1205 i = st | (((widget[id].value) ? 2 : 0) |
1206 ((id == active) ? 1 : 0));
1208 switch (widget[id].type & GUI_TYPE)
1220 /* Recursively paint all subwidgets. */
1222 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1223 gui_paint_rect(jd, i);
1229 /* Draw a leaf's background, colored by widget state. */
1233 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1234 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1236 glColor4fv(back[i]);
1237 glCallList(widget[id].rect_obj);
1245 /*---------------------------------------------------------------------------*/
1247 static void gui_paint_text(int id);
1249 static void gui_paint_array(int id)
1255 GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1256 GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1257 GLfloat ck = widget[id].scale;
1259 glTranslatef(+cx, +cy, 0.0f);
1260 glScalef(ck, ck, ck);
1261 glTranslatef(-cx, -cy, 0.0f);
1263 /* Recursively paint all subwidgets. */
1265 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1271 static void gui_paint_image(int id)
1273 /* Draw the widget rect, textured using the image. */
1277 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1278 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1280 glScalef(widget[id].scale,
1284 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1285 glColor4fv(gui_wht);
1286 glCallList(widget[id].rect_obj);
1291 static void gui_paint_count(int id)
1293 int j, i = widget[id].size;
1297 glColor4fv(gui_wht);
1299 /* Translate to the widget center, and apply the pulse scale. */
1301 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1302 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1304 glScalef(widget[id].scale,
1308 if (widget[id].value > 0)
1310 /* Translate left by half the total width of the rendered value. */
1312 for (j = widget[id].value; j; j /= 10)
1313 glTranslatef((GLfloat) +digit_w[i][j % 10] / 2.0f, 0.0f, 0.0f);
1315 glTranslatef((GLfloat) -digit_w[i][0] / 2.0f, 0.0f, 0.0f);
1317 /* Render each digit, moving right after each. */
1319 for (j = widget[id].value; j; j /= 10)
1321 glBindTexture(GL_TEXTURE_2D, digit_text[i][j % 10]);
1322 glCallList(digit_list[i][j % 10]);
1323 glTranslatef((GLfloat) -digit_w[i][j % 10], 0.0f, 0.0f);
1326 else if (widget[id].value == 0)
1328 /* If the value is zero, just display a zero in place. */
1330 glBindTexture(GL_TEXTURE_2D, digit_text[i][0]);
1331 glCallList(digit_list[i][0]);
1337 static void gui_paint_clock(int id)
1339 int i = widget[id].size;
1340 int mt = (widget[id].value / 6000) / 10;
1341 int mo = (widget[id].value / 6000) % 10;
1342 int st = ((widget[id].value % 6000) / 100) / 10;
1343 int so = ((widget[id].value % 6000) / 100) % 10;
1344 int ht = ((widget[id].value % 6000) % 100) / 10;
1345 int ho = ((widget[id].value % 6000) % 100) % 10;
1347 GLfloat dx_large = (GLfloat) digit_w[i][0];
1348 GLfloat dx_small = (GLfloat) digit_w[i][0] * 0.75f;
1350 if (widget[id].value < 0)
1355 glColor4fv(gui_wht);
1357 /* Translate to the widget center, and apply the pulse scale. */
1359 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1360 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1362 glScalef(widget[id].scale,
1366 /* Translate left by half the total width of the rendered value. */
1369 glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1371 glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1373 /* Render the minutes counter. */
1377 glBindTexture(GL_TEXTURE_2D, digit_text[i][mt]);
1378 glCallList(digit_list[i][mt]);
1379 glTranslatef(dx_large, 0.0f, 0.0f);
1382 glBindTexture(GL_TEXTURE_2D, digit_text[i][mo]);
1383 glCallList(digit_list[i][mo]);
1384 glTranslatef(dx_small, 0.0f, 0.0f);
1386 /* Render the colon. */
1388 glBindTexture(GL_TEXTURE_2D, digit_text[i][10]);
1389 glCallList(digit_list[i][10]);
1390 glTranslatef(dx_small, 0.0f, 0.0f);
1392 /* Render the seconds counter. */
1394 glBindTexture(GL_TEXTURE_2D, digit_text[i][st]);
1395 glCallList(digit_list[i][st]);
1396 glTranslatef(dx_large, 0.0f, 0.0f);
1398 glBindTexture(GL_TEXTURE_2D, digit_text[i][so]);
1399 glCallList(digit_list[i][so]);
1400 glTranslatef(dx_small, 0.0f, 0.0f);
1402 /* Render hundredths counter half size. */
1404 glScalef(0.5f, 0.5f, 1.0f);
1406 glBindTexture(GL_TEXTURE_2D, digit_text[i][ht]);
1407 glCallList(digit_list[i][ht]);
1408 glTranslatef(dx_large, 0.0f, 0.0f);
1410 glBindTexture(GL_TEXTURE_2D, digit_text[i][ho]);
1411 glCallList(digit_list[i][ho]);
1416 static void gui_paint_label(int id)
1418 /* Draw the widget text box, textured using the glyph. */
1422 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1423 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1425 glScalef(widget[id].scale,
1429 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1430 glCallList(widget[id].text_obj);
1435 static void gui_paint_text(int id)
1437 switch (widget[id].type & GUI_TYPE)
1439 case GUI_SPACE: break;
1440 case GUI_FILLER: break;
1441 case GUI_HARRAY: gui_paint_array(id); break;
1442 case GUI_VARRAY: gui_paint_array(id); break;
1443 case GUI_HSTACK: gui_paint_array(id); break;
1444 case GUI_VSTACK: gui_paint_array(id); break;
1445 case GUI_IMAGE: gui_paint_image(id); break;
1446 case GUI_COUNT: gui_paint_count(id); break;
1447 case GUI_CLOCK: gui_paint_clock(id); break;
1448 default: gui_paint_label(id); break;
1452 void gui_paint(int id)
1458 glEnable(GL_COLOR_MATERIAL);
1459 glDisable(GL_LIGHTING);
1460 glDisable(GL_DEPTH_TEST);
1462 glDisable(GL_TEXTURE_2D);
1463 gui_paint_rect(id, 0);
1465 glEnable(GL_TEXTURE_2D);
1468 glColor4fv(gui_wht);
1470 glEnable(GL_DEPTH_TEST);
1471 glEnable(GL_LIGHTING);
1472 glDisable(GL_COLOR_MATERIAL);
1478 /*---------------------------------------------------------------------------*/
1480 void gui_dump(int id, int d)
1488 switch (widget[id].type & GUI_TYPE)
1490 case GUI_HARRAY: type = "harray"; break;
1491 case GUI_VARRAY: type = "varray"; break;
1492 case GUI_HSTACK: type = "hstack"; break;
1493 case GUI_VSTACK: type = "vstack"; break;
1494 case GUI_FILLER: type = "filler"; break;
1495 case GUI_IMAGE: type = "image"; break;
1496 case GUI_LABEL: type = "label"; break;
1497 case GUI_COUNT: type = "count"; break;
1498 case GUI_CLOCK: type = "clock"; break;
1501 for (i = 0; i < d; i++)
1504 printf("%04d %s\n", id, type);
1506 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1507 gui_dump(jd, d + 1);
1511 void gui_pulse(int id, float k)
1513 if (id) widget[id].scale = k;
1516 void gui_timer(int id, float dt)
1522 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1525 if (widget[id].scale - 1.0f < dt)
1526 widget[id].scale = 1.0f;
1528 widget[id].scale -= dt;
1532 int gui_point(int id, int x, int y)
1534 static int x_cache = 0;
1535 static int y_cache = 0;
1539 /* Reuse the last coordinates if (x,y) == (-1,-1) */
1542 return gui_point(id, x_cache, y_cache);
1547 /* Short-circuit check the current active widget. */
1549 jd = gui_search(active, x, y);
1551 /* If not still active, search the hierarchy for a new active widget. */
1554 jd = gui_search(id, x, y);
1556 /* If the active widget has changed, return the new active id. */
1558 if (jd == 0 || jd == active)
1564 void gui_focus(int i)
1574 int gui_token(int id)
1576 return id ? widget[id].token : 0;
1579 int gui_value(int id)
1581 return id ? widget[id].value : 0;
1584 void gui_toggle(int id)
1586 widget[id].value = widget[id].value ? 0 : 1;
1589 /*---------------------------------------------------------------------------*/
1591 static int gui_vert_offset(int id, int jd)
1593 /* Vertical offset between bottom of id and top of jd */
1595 return widget[id].y - (widget[jd].y + widget[jd].h);
1598 static int gui_horz_offset(int id, int jd)
1600 /* Horizontal offset between left of id and right of jd */
1602 return widget[id].x - (widget[jd].x + widget[jd].w);
1605 static int gui_vert_dist(int id, int jd)
1607 /* Vertical distance between the tops of id and jd */
1609 return abs((widget[id].y + widget[id].h) - (widget[jd].y + widget[jd].h));
1612 static int gui_horz_dist(int id, int jd)
1614 /* Horizontal distance between the left sides of id and jd */
1616 return abs(widget[id].x - widget[jd].x);
1619 /*---------------------------------------------------------------------------*/
1621 static int gui_stick_L(int id, int dd)
1624 int o, omin, d, dmin;
1626 /* Find the closest "hot" widget to the left of dd (and inside id) */
1628 if (id && gui_hot(id))
1632 omin = widget[dd].x - widget[id].x + 1;
1633 dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1635 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1637 kd = gui_stick_L(jd, dd);
1641 o = gui_horz_offset(dd, kd);
1642 d = gui_vert_dist(dd, kd);
1644 if (0 <= o && o <= omin && d <= dmin)
1656 static int gui_stick_R(int id, int dd)
1659 int o, omin, d, dmin;
1661 /* Find the closest "hot" widget to the right of dd (and inside id) */
1663 if (id && gui_hot(id))
1667 omin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
1668 dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1670 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1672 kd = gui_stick_R(jd, dd);
1676 o = gui_horz_offset(kd, dd);
1677 d = gui_vert_dist(dd, kd);
1679 if (0 <= o && o <= omin && d <= dmin)
1691 static int gui_stick_D(int id, int dd)
1694 int o, omin, d, dmin;
1696 /* Find the closest "hot" widget below dd (and inside id) */
1698 if (id && gui_hot(id))
1702 omin = widget[dd].y - widget[id].y + 1;
1703 dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1705 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1707 kd = gui_stick_D(jd, dd);
1711 o = gui_vert_offset(dd, kd);
1712 d = gui_horz_dist(dd, kd);
1714 if (0 <= o && o <= omin && d <= dmin)
1726 static int gui_stick_U(int id, int dd)
1729 int o, omin, d, dmin;
1731 /* Find the closest "hot" widget above dd (and inside id) */
1733 if (id && gui_hot(id))
1737 omin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
1738 dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1740 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1742 kd = gui_stick_U(jd, dd);
1746 o = gui_vert_offset(kd, dd);
1747 d = gui_horz_dist(dd, kd);
1749 if (0 <= o && o <= omin && d <= dmin)
1761 /*---------------------------------------------------------------------------*/
1763 static int gui_wrap_L(int id, int dd)
1767 if ((jd = gui_stick_L(id, dd)) == 0)
1768 for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
1774 static int gui_wrap_R(int id, int dd)
1778 if ((jd = gui_stick_R(id, dd)) == 0)
1779 for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
1785 static int gui_wrap_U(int id, int dd)
1789 if ((jd = gui_stick_U(id, dd)) == 0)
1790 for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
1796 static int gui_wrap_D(int id, int dd)
1800 if ((jd = gui_stick_D(id, dd)) == 0)
1801 for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
1807 /*---------------------------------------------------------------------------*/
1809 /* Flag the axes to prevent uncontrolled scrolling. */
1811 static int xflag = 1;
1812 static int yflag = 1;
1816 /* Force the user to recenter the joystick before the next GUI action. */
1822 int gui_stick(int id, int x, int y)
1826 /* Find a new active widget in the direction of joystick motion. */
1828 if (x && -JOY_MID <= x && x <= +JOY_MID)
1830 else if (x < -JOY_MID && xflag && (jd = gui_wrap_L(id, active)))
1832 else if (x > +JOY_MID && xflag && (jd = gui_wrap_R(id, active)))
1835 if (y && -JOY_MID <= y && y <= +JOY_MID)
1837 else if (y < -JOY_MID && yflag && (jd = gui_wrap_U(id, active)))
1839 else if (y > +JOY_MID && yflag && (jd = gui_wrap_D(id, active)))
1842 /* If the active widget has changed, return the new active id. */
1844 if (jd == 0 || jd == active)
1850 /*---------------------------------------------------------------------------*/