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, L_("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 fontrwops = SDL_RWFromConstMem(fontdata, fontdatalen);
272 /* Load small, medium, and large typefaces. */
274 font[GUI_SML] = TTF_OpenFontRW(fontrwops, 0, s0);
276 SDL_RWseek(fontrwops, 0, SEEK_SET);
277 font[GUI_MED] = TTF_OpenFontRW(fontrwops, 0, s1);
279 SDL_RWseek(fontrwops, 0, SEEK_SET);
280 font[GUI_LRG] = TTF_OpenFontRW(fontrwops, 0, s2);
282 /* fontrwops remains open. */
288 font[GUI_SML] = NULL;
289 font[GUI_MED] = NULL;
290 font[GUI_LRG] = NULL;
292 fprintf(stderr, L_("Could not load font '%s'.\n"), fontpath);
297 /* Initialize digit glyphs and lists for counters and clocks. */
299 for (i = 0; i < 3; i++)
303 /* Draw digits 0 through 9. */
305 for (j = 0; j < 10; j++)
307 text[0] = '0' + (char) j;
310 digit_text[i][j] = make_image_from_font(NULL, NULL,
314 digit_list[i][j] = gui_list(-digit_w[i][j] / 2,
317 +digit_h[i][j], c0, c1);
320 /* Draw the colon for the clock. */
322 digit_text[i][j] = make_image_from_font(NULL, NULL,
326 digit_list[i][j] = gui_list(-digit_w[i][10] / 2,
329 +digit_h[i][10], c0, c1);
340 /* Release any remaining widget texture and display list indices. */
342 for (id = 1; id < MAXWIDGET; id++)
344 if (glIsTexture(widget[id].text_img))
345 glDeleteTextures(1, &widget[id].text_img);
347 if (glIsList(widget[id].text_obj))
348 glDeleteLists(widget[id].text_obj, 1);
349 if (glIsList(widget[id].rect_obj))
350 glDeleteLists(widget[id].rect_obj, 1);
352 widget[id].type = GUI_FREE;
353 widget[id].text_img = 0;
354 widget[id].text_obj = 0;
355 widget[id].rect_obj = 0;
360 /* Release all digit textures and display lists. */
362 for (i = 0; i < 3; i++)
363 for (j = 0; j < 11; j++)
365 if (glIsTexture(digit_text[i][j]))
366 glDeleteTextures(1, &digit_text[i][j]);
368 if (glIsList(digit_list[i][j]))
369 glDeleteLists(digit_list[i][j], 1);
372 /* Release all loaded fonts and finalize font rendering. */
374 if (font[GUI_LRG]) TTF_CloseFont(font[GUI_LRG]);
375 if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
376 if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
378 if (fontrwops) SDL_RWclose(fontrwops);
379 if (fontdata) free(fontdata);
384 /*---------------------------------------------------------------------------*/
386 static int gui_widget(int pd, int type)
390 /* Find an unused entry in the widget table. */
392 for (id = 1; id < MAXWIDGET; id++)
393 if (widget[id].type == GUI_FREE)
395 /* Set the type and default properties. */
397 widget[id].type = type;
398 widget[id].token = 0;
399 widget[id].value = 0;
401 widget[id].rect = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
404 widget[id].text_img = 0;
405 widget[id].text_obj = 0;
406 widget[id].rect_obj = 0;
407 widget[id].color0 = gui_wht;
408 widget[id].color1 = gui_wht;
409 widget[id].scale = 1.0f;
410 widget[id].trunc = TRUNC_NONE;
412 widget[id].text_obj_w = 0;
413 widget[id].text_obj_h = 0;
415 /* Insert the new widget into the parent's widget list. */
420 widget[id].cdr = widget[pd].car;
432 fprintf(stderr, "Out of widget IDs\n");
437 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
438 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
439 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
440 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
441 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
443 /*---------------------------------------------------------------------------*/
451 static struct size gui_measure(const char *text, TTF_Font *font)
453 struct size size = { 0, 0 };
456 TTF_SizeUTF8(font, text, &size.w, &size.h);
461 static char *gui_trunc_head(const char *text,
465 int left, right, mid;
469 right = strlen(text);
471 while (right - left > 1)
473 mid = (left + right) / 2;
475 str = concat_string("...", text + mid, NULL);
477 if (gui_measure(str, font).w <= maxwidth)
485 return concat_string("...", text + right, NULL);
488 static char *gui_trunc_tail(const char *text,
492 int left, right, mid;
496 right = strlen(text);
498 while (right - left > 1)
500 mid = (left + right) / 2;
502 str = malloc(mid + sizeof ("..."));
504 memcpy(str, text, mid);
505 memcpy(str + mid, "...", sizeof ("..."));
507 if (gui_measure(str, font).w <= maxwidth)
515 str = malloc(left + sizeof ("..."));
517 memcpy(str, text, left);
518 memcpy(str + left, "...", sizeof ("..."));
523 static char *gui_truncate(const char *text,
528 if (gui_measure(text, font).w <= maxwidth)
533 case TRUNC_NONE: return strdup(text); break;
534 case TRUNC_HEAD: return gui_trunc_head(text, maxwidth, font); break;
535 case TRUNC_TAIL: return gui_trunc_tail(text, maxwidth, font); break;
541 /*---------------------------------------------------------------------------*/
543 void gui_set_image(int id, const char *file)
545 if (glIsTexture(widget[id].text_img))
546 glDeleteTextures(1, &widget[id].text_img);
548 widget[id].text_img = make_image_from_file(file);
551 void gui_set_label(int id, const char *text)
555 if (glIsTexture(widget[id].text_img))
556 glDeleteTextures(1, &widget[id].text_img);
557 if (glIsList(widget[id].text_obj))
558 glDeleteLists(widget[id].text_obj, 1);
560 text = gui_truncate(text, widget[id].w - radius,
561 font[widget[id].size],
564 widget[id].text_img = make_image_from_font(NULL, NULL, &w, &h,
565 text, font[widget[id].size]);
566 widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
567 widget[id].color0, widget[id].color1);
569 widget[id].text_obj_w = w;
570 widget[id].text_obj_h = h;
575 void gui_set_count(int id, int value)
577 widget[id].value = value;
580 void gui_set_clock(int id, int value)
582 widget[id].value = value;
585 void gui_set_color(int id, const float *c0,
590 c0 = c0 ? c0 : gui_yel;
591 c1 = c1 ? c1 : gui_red;
593 if (widget[id].color0 != c0 || widget[id].color1 != c1)
595 widget[id].color0 = c0;
596 widget[id].color1 = c1;
598 if (glIsList(widget[id].text_obj))
602 glDeleteLists(widget[id].text_obj, 1);
604 w = widget[id].text_obj_w;
605 h = widget[id].text_obj_h;
607 widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
615 void gui_set_multi(int id, const char *text)
624 /* Copy each delimited string to a line buffer. */
626 for (p = text, j = 0; *p && j < 8; j++)
628 strncpy(s[j], p, (n = strcspn(p, "\\")));
631 if (*(p += n) == '\\') p++;
634 /* Set the label value for each line. */
636 for (i = j - 1, jd = widget[id].car; i >= 0 && jd; i--, jd = widget[jd].cdr)
637 gui_set_label(jd, s[i]);
640 void gui_set_trunc(int id, enum trunc trunc)
642 widget[id].trunc = trunc;
645 /*---------------------------------------------------------------------------*/
647 int gui_image(int pd, const char *file, int w, int h)
651 if ((id = gui_widget(pd, GUI_IMAGE)))
653 widget[id].text_img = make_image_from_file(file);
660 int gui_start(int pd, const char *text, int size, int token, int value)
664 if ((id = gui_state(pd, text, size, token, value)))
670 int gui_state(int pd, const char *text, int size, int token, int value)
674 if ((id = gui_widget(pd, GUI_STATE)))
676 widget[id].text_img = make_image_from_font(NULL, NULL,
680 widget[id].size = size;
681 widget[id].token = token;
682 widget[id].value = value;
687 int gui_label(int pd, const char *text, int size, int rect, const float *c0,
692 if ((id = gui_widget(pd, GUI_LABEL)))
694 widget[id].text_img = make_image_from_font(NULL, NULL,
698 widget[id].size = size;
699 widget[id].color0 = c0 ? c0 : gui_yel;
700 widget[id].color1 = c1 ? c1 : gui_red;
701 widget[id].rect = rect;
706 int gui_count(int pd, int value, int size, int rect)
710 if ((id = gui_widget(pd, GUI_COUNT)))
712 for (i = value; i; i /= 10)
713 widget[id].w += digit_w[size][0];
715 widget[id].h = digit_h[size][0];
716 widget[id].value = value;
717 widget[id].size = size;
718 widget[id].color0 = gui_yel;
719 widget[id].color1 = gui_red;
720 widget[id].rect = rect;
725 int gui_clock(int pd, int value, int size, int rect)
729 if ((id = gui_widget(pd, GUI_CLOCK)))
731 widget[id].w = digit_w[size][0] * 6;
732 widget[id].h = digit_h[size][0];
733 widget[id].value = value;
734 widget[id].size = size;
735 widget[id].color0 = gui_yel;
736 widget[id].color1 = gui_red;
737 widget[id].rect = rect;
742 int gui_space(int pd)
746 if ((id = gui_widget(pd, GUI_SPACE)))
754 /*---------------------------------------------------------------------------*/
756 * Create a multi-line text box using a vertical array of labels.
757 * Parse the text for '\' characters and treat them as line-breaks.
758 * Preserve the rect specification across the entire array.
761 int gui_multi(int pd, const char *text, int size, int rect, const float *c0,
766 if (text && (id = gui_varray(pd)))
776 /* Copy each delimited string to a line buffer. */
778 for (p = text, j = 0; *p && j < 8; j++)
780 strncpy(s[j], p, (n = strcspn(p, "\\")));
784 if (*(p += n) == '\\') p++;
787 /* Set the curves for the first and last lines. */
791 r[0] |= rect & (GUI_NW | GUI_NE);
792 r[j - 1] |= rect & (GUI_SW | GUI_SE);
795 /* Create a label widget for each line. */
797 for (i = 0; i < j; i++)
798 gui_label(id, s[i], size, r[i], c0, c1);
803 /*---------------------------------------------------------------------------*/
805 * The bottom-up pass determines the area of all widgets. The minimum
806 * width and height of a leaf widget is given by the size of its
807 * contents. Array and stack widths and heights are computed
808 * recursively from these.
811 static void gui_widget_up(int id);
813 static void gui_harray_up(int id)
817 /* Find the widest child width and the highest child height. */
819 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
823 if (widget[id].h < widget[jd].h)
824 widget[id].h = widget[jd].h;
825 if (widget[id].w < widget[jd].w)
826 widget[id].w = widget[jd].w;
831 /* Total width is the widest child width times the child count. */
836 static void gui_varray_up(int id)
840 /* Find the widest child width and the highest child height. */
842 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
846 if (widget[id].h < widget[jd].h)
847 widget[id].h = widget[jd].h;
848 if (widget[id].w < widget[jd].w)
849 widget[id].w = widget[jd].w;
854 /* Total height is the highest child height times the child count. */
859 static void gui_hstack_up(int id)
863 /* Find the highest child height. Sum the child widths. */
865 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
869 if (widget[id].h < widget[jd].h)
870 widget[id].h = widget[jd].h;
872 widget[id].w += widget[jd].w;
876 static void gui_vstack_up(int id)
880 /* Find the widest child width. Sum the child heights. */
882 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
886 if (widget[id].w < widget[jd].w)
887 widget[id].w = widget[jd].w;
889 widget[id].h += widget[jd].h;
893 static void gui_button_up(int id)
895 /* Store width and height for later use in text rendering. */
897 widget[id].text_obj_w = widget[id].w;
898 widget[id].text_obj_h = widget[id].h;
900 if (widget[id].w < widget[id].h && widget[id].w > 0)
901 widget[id].w = widget[id].h;
903 /* Padded text elements look a little nicer. */
905 if (widget[id].w < config_get_d(CONFIG_WIDTH))
906 widget[id].w += radius;
907 if (widget[id].h < config_get_d(CONFIG_HEIGHT))
908 widget[id].h += radius;
910 /* A button should be at least wide enough to accomodate the rounding. */
912 if (widget[id].w < 2 * radius)
913 widget[id].w = 2 * radius;
914 if (widget[id].h < 2 * radius)
915 widget[id].h = 2 * radius;
918 static void gui_widget_up(int id)
921 switch (widget[id].type & GUI_TYPE)
923 case GUI_HARRAY: gui_harray_up(id); break;
924 case GUI_VARRAY: gui_varray_up(id); break;
925 case GUI_HSTACK: gui_hstack_up(id); break;
926 case GUI_VSTACK: gui_vstack_up(id); break;
927 case GUI_FILLER: break;
928 default: gui_button_up(id); break;
932 /*---------------------------------------------------------------------------*/
934 * The top-down layout pass distributes available area as computed
935 * during the bottom-up pass. Widgets use their area and position to
936 * initialize rendering state.
939 static void gui_widget_dn(int id, int x, int y, int w, int h);
941 static void gui_harray_dn(int id, int x, int y, int w, int h)
943 int jd, i = 0, c = 0;
950 /* Count children. */
952 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
955 /* Distribute horizontal space evenly to all children. */
957 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
959 int x0 = x + i * w / c;
960 int x1 = x + (i + 1) * w / c;
962 gui_widget_dn(jd, x0, y, x1 - x0, h);
966 static void gui_varray_dn(int id, int x, int y, int w, int h)
968 int jd, i = 0, c = 0;
975 /* Count children. */
977 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
980 /* Distribute vertical space evenly to all children. */
982 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
984 int y0 = y + i * h / c;
985 int y1 = y + (i + 1) * h / c;
987 gui_widget_dn(jd, x, y0, w, y1 - y0);
991 static void gui_hstack_dn(int id, int x, int y, int w, int h)
993 int jd, jx = x, jw = 0, c = 0;
1000 /* Measure the total width requested by non-filler children. */
1002 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1003 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1008 /* Give non-filler children their requested space. */
1009 /* Distribute the rest evenly among filler children. */
1011 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1013 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1014 gui_widget_dn(jd, jx, y, (w - jw) / c, h);
1016 gui_widget_dn(jd, jx, y, widget[jd].w, h);
1022 static void gui_vstack_dn(int id, int x, int y, int w, int h)
1024 int jd, jy = y, jh = 0, c = 0;
1031 /* Measure the total height requested by non-filler children. */
1033 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1034 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1039 /* Give non-filler children their requested space. */
1040 /* Distribute the rest evenly among filler children. */
1042 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1044 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1045 gui_widget_dn(jd, x, jy, w, (h - jh) / c);
1047 gui_widget_dn(jd, x, jy, w, widget[jd].h);
1053 static void gui_filler_dn(int id, int x, int y, int w, int h)
1055 /* Filler expands to whatever size it is given. */
1063 static void gui_button_dn(int id, int x, int y, int w, int h)
1065 /* Recall stored width and height for text rendering. */
1067 int W = widget[id].text_obj_w;
1068 int H = widget[id].text_obj_h;
1069 int R = widget[id].rect;
1071 const float *c0 = widget[id].color0;
1072 const float *c1 = widget[id].color1;
1079 /* Create display lists for the text area and rounded rectangle. */
1081 widget[id].text_obj = gui_list(-W / 2, -H / 2, W, H, c0, c1);
1082 widget[id].rect_obj = gui_rect(-w / 2, -h / 2, w, h, R, radius);
1085 static void gui_widget_dn(int id, int x, int y, int w, int h)
1088 switch (widget[id].type & GUI_TYPE)
1090 case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
1091 case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
1092 case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
1093 case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
1094 case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
1095 case GUI_SPACE: gui_filler_dn(id, x, y, w, h); break;
1096 default: gui_button_dn(id, x, y, w, h); break;
1100 /*---------------------------------------------------------------------------*/
1102 * During GUI layout, we make a bottom-up pass to determine total area
1103 * requirements for the widget tree. We position this area to the
1104 * sides or center of the screen. Finally, we make a top-down pass to
1105 * distribute this area to each widget.
1108 void gui_layout(int id, int xd, int yd)
1112 int w, W = config_get_d(CONFIG_WIDTH);
1113 int h, H = config_get_d(CONFIG_HEIGHT);
1121 else if (xd > 0) x = (W - w);
1122 else x = (W - w) / 2;
1125 else if (yd > 0) y = (H - h);
1126 else y = (H - h) / 2;
1128 gui_widget_dn(id, x, y, w, h);
1130 /* Hilite the widget under the cursor, if any. */
1132 gui_point(id, -1, -1);
1135 int gui_search(int id, int x, int y)
1139 /* Search the hierarchy for the widget containing the given point. */
1141 if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
1142 widget[id].y <= y && y < widget[id].y + widget[id].h))
1147 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1148 if ((kd = gui_search(jd, x, y)))
1155 * Activate a widget, allowing it to behave as a normal state widget.
1156 * This may be used to create image buttons, or cause an array of
1157 * widgets to behave as a single state widget.
1159 int gui_active(int id, int token, int value)
1161 widget[id].type |= GUI_STATE;
1162 widget[id].token = token;
1163 widget[id].value = value;
1168 int gui_delete(int id)
1172 /* Recursively delete all subwidgets. */
1174 gui_delete(widget[id].cdr);
1175 gui_delete(widget[id].car);
1177 /* Release any GL resources held by this widget. */
1179 if (glIsTexture(widget[id].text_img))
1180 glDeleteTextures(1, &widget[id].text_img);
1182 if (glIsList(widget[id].text_obj))
1183 glDeleteLists(widget[id].text_obj, 1);
1184 if (glIsList(widget[id].rect_obj))
1185 glDeleteLists(widget[id].rect_obj, 1);
1187 /* Mark this widget unused. */
1189 widget[id].type = GUI_FREE;
1190 widget[id].text_img = 0;
1191 widget[id].text_obj = 0;
1192 widget[id].rect_obj = 0;
1199 /*---------------------------------------------------------------------------*/
1201 static void gui_paint_rect(int id, int st)
1203 static const GLfloat back[4][4] = {
1204 { 0.1f, 0.1f, 0.1f, 0.5f }, /* off and inactive */
1205 { 0.5f, 0.5f, 0.5f, 0.8f }, /* off and active */
1206 { 1.0f, 0.7f, 0.3f, 0.5f }, /* on and inactive */
1207 { 1.0f, 0.7f, 0.3f, 0.8f }, /* on and active */
1212 /* Use the widget status to determine the background color. */
1215 i = st | (((widget[id].value) ? 2 : 0) |
1216 ((id == active) ? 1 : 0));
1218 switch (widget[id].type & GUI_TYPE)
1230 /* Recursively paint all subwidgets. */
1232 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1233 gui_paint_rect(jd, i);
1239 /* Draw a leaf's background, colored by widget state. */
1243 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1244 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1246 glColor4fv(back[i]);
1247 glCallList(widget[id].rect_obj);
1255 /*---------------------------------------------------------------------------*/
1257 static void gui_paint_text(int id);
1259 static void gui_paint_array(int id)
1265 GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1266 GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1267 GLfloat ck = widget[id].scale;
1269 glTranslatef(+cx, +cy, 0.0f);
1270 glScalef(ck, ck, ck);
1271 glTranslatef(-cx, -cy, 0.0f);
1273 /* Recursively paint all subwidgets. */
1275 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1281 static void gui_paint_image(int id)
1283 /* Draw the widget rect, textured using the image. */
1287 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1288 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1290 glScalef(widget[id].scale,
1294 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1295 glColor4fv(gui_wht);
1296 glCallList(widget[id].rect_obj);
1301 static void gui_paint_count(int id)
1303 int j, i = widget[id].size;
1307 glColor4fv(gui_wht);
1309 /* Translate to the widget center, and apply the pulse scale. */
1311 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1312 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1314 glScalef(widget[id].scale,
1318 if (widget[id].value > 0)
1320 /* Translate left by half the total width of the rendered value. */
1322 for (j = widget[id].value; j; j /= 10)
1323 glTranslatef((GLfloat) +digit_w[i][j % 10] / 2.0f, 0.0f, 0.0f);
1325 glTranslatef((GLfloat) -digit_w[i][0] / 2.0f, 0.0f, 0.0f);
1327 /* Render each digit, moving right after each. */
1329 for (j = widget[id].value; j; j /= 10)
1331 glBindTexture(GL_TEXTURE_2D, digit_text[i][j % 10]);
1332 glCallList(digit_list[i][j % 10]);
1333 glTranslatef((GLfloat) -digit_w[i][j % 10], 0.0f, 0.0f);
1336 else if (widget[id].value == 0)
1338 /* If the value is zero, just display a zero in place. */
1340 glBindTexture(GL_TEXTURE_2D, digit_text[i][0]);
1341 glCallList(digit_list[i][0]);
1347 static void gui_paint_clock(int id)
1349 int i = widget[id].size;
1350 int mt = (widget[id].value / 6000) / 10;
1351 int mo = (widget[id].value / 6000) % 10;
1352 int st = ((widget[id].value % 6000) / 100) / 10;
1353 int so = ((widget[id].value % 6000) / 100) % 10;
1354 int ht = ((widget[id].value % 6000) % 100) / 10;
1355 int ho = ((widget[id].value % 6000) % 100) % 10;
1357 GLfloat dx_large = (GLfloat) digit_w[i][0];
1358 GLfloat dx_small = (GLfloat) digit_w[i][0] * 0.75f;
1360 if (widget[id].value < 0)
1365 glColor4fv(gui_wht);
1367 /* Translate to the widget center, and apply the pulse scale. */
1369 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1370 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1372 glScalef(widget[id].scale,
1376 /* Translate left by half the total width of the rendered value. */
1379 glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1381 glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1383 /* Render the minutes counter. */
1387 glBindTexture(GL_TEXTURE_2D, digit_text[i][mt]);
1388 glCallList(digit_list[i][mt]);
1389 glTranslatef(dx_large, 0.0f, 0.0f);
1392 glBindTexture(GL_TEXTURE_2D, digit_text[i][mo]);
1393 glCallList(digit_list[i][mo]);
1394 glTranslatef(dx_small, 0.0f, 0.0f);
1396 /* Render the colon. */
1398 glBindTexture(GL_TEXTURE_2D, digit_text[i][10]);
1399 glCallList(digit_list[i][10]);
1400 glTranslatef(dx_small, 0.0f, 0.0f);
1402 /* Render the seconds counter. */
1404 glBindTexture(GL_TEXTURE_2D, digit_text[i][st]);
1405 glCallList(digit_list[i][st]);
1406 glTranslatef(dx_large, 0.0f, 0.0f);
1408 glBindTexture(GL_TEXTURE_2D, digit_text[i][so]);
1409 glCallList(digit_list[i][so]);
1410 glTranslatef(dx_small, 0.0f, 0.0f);
1412 /* Render hundredths counter half size. */
1414 glScalef(0.5f, 0.5f, 1.0f);
1416 glBindTexture(GL_TEXTURE_2D, digit_text[i][ht]);
1417 glCallList(digit_list[i][ht]);
1418 glTranslatef(dx_large, 0.0f, 0.0f);
1420 glBindTexture(GL_TEXTURE_2D, digit_text[i][ho]);
1421 glCallList(digit_list[i][ho]);
1426 static void gui_paint_label(int id)
1428 /* Draw the widget text box, textured using the glyph. */
1432 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1433 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1435 glScalef(widget[id].scale,
1439 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1440 glCallList(widget[id].text_obj);
1445 static void gui_paint_text(int id)
1447 switch (widget[id].type & GUI_TYPE)
1449 case GUI_SPACE: break;
1450 case GUI_FILLER: break;
1451 case GUI_HARRAY: gui_paint_array(id); break;
1452 case GUI_VARRAY: gui_paint_array(id); break;
1453 case GUI_HSTACK: gui_paint_array(id); break;
1454 case GUI_VSTACK: gui_paint_array(id); break;
1455 case GUI_IMAGE: gui_paint_image(id); break;
1456 case GUI_COUNT: gui_paint_count(id); break;
1457 case GUI_CLOCK: gui_paint_clock(id); break;
1458 default: gui_paint_label(id); break;
1462 void gui_paint(int id)
1468 glEnable(GL_COLOR_MATERIAL);
1469 glDisable(GL_LIGHTING);
1470 glDisable(GL_DEPTH_TEST);
1472 glDisable(GL_TEXTURE_2D);
1473 gui_paint_rect(id, 0);
1475 glEnable(GL_TEXTURE_2D);
1478 glColor4fv(gui_wht);
1480 glEnable(GL_DEPTH_TEST);
1481 glEnable(GL_LIGHTING);
1482 glDisable(GL_COLOR_MATERIAL);
1488 /*---------------------------------------------------------------------------*/
1490 void gui_dump(int id, int d)
1498 switch (widget[id].type & GUI_TYPE)
1500 case GUI_HARRAY: type = "harray"; break;
1501 case GUI_VARRAY: type = "varray"; break;
1502 case GUI_HSTACK: type = "hstack"; break;
1503 case GUI_VSTACK: type = "vstack"; break;
1504 case GUI_FILLER: type = "filler"; break;
1505 case GUI_IMAGE: type = "image"; break;
1506 case GUI_LABEL: type = "label"; break;
1507 case GUI_COUNT: type = "count"; break;
1508 case GUI_CLOCK: type = "clock"; break;
1511 for (i = 0; i < d; i++)
1514 printf("%04d %s\n", id, type);
1516 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1517 gui_dump(jd, d + 1);
1521 void gui_pulse(int id, float k)
1523 if (id) widget[id].scale = k;
1526 void gui_timer(int id, float dt)
1532 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1535 if (widget[id].scale - 1.0f < dt)
1536 widget[id].scale = 1.0f;
1538 widget[id].scale -= dt;
1542 int gui_point(int id, int x, int y)
1544 static int x_cache = 0;
1545 static int y_cache = 0;
1549 /* Reuse the last coordinates if (x,y) == (-1,-1) */
1552 return gui_point(id, x_cache, y_cache);
1557 /* Short-circuit check the current active widget. */
1559 jd = gui_search(active, x, y);
1561 /* If not still active, search the hierarchy for a new active widget. */
1564 jd = gui_search(id, x, y);
1566 /* If the active widget has changed, return the new active id. */
1568 if (jd == 0 || jd == active)
1574 void gui_focus(int i)
1584 int gui_token(int id)
1586 return id ? widget[id].token : 0;
1589 int gui_value(int id)
1591 return id ? widget[id].value : 0;
1594 void gui_toggle(int id)
1596 widget[id].value = widget[id].value ? 0 : 1;
1599 /*---------------------------------------------------------------------------*/
1601 static int gui_vert_offset(int id, int jd)
1603 /* Vertical offset between bottom of id and top of jd */
1605 return widget[id].y - (widget[jd].y + widget[jd].h);
1608 static int gui_horz_offset(int id, int jd)
1610 /* Horizontal offset between left of id and right of jd */
1612 return widget[id].x - (widget[jd].x + widget[jd].w);
1615 static int gui_vert_dist(int id, int jd)
1617 /* Vertical distance between the tops of id and jd */
1619 return abs((widget[id].y + widget[id].h) - (widget[jd].y + widget[jd].h));
1622 static int gui_horz_dist(int id, int jd)
1624 /* Horizontal distance between the left sides of id and jd */
1626 return abs(widget[id].x - widget[jd].x);
1629 /*---------------------------------------------------------------------------*/
1631 static int gui_stick_L(int id, int dd)
1634 int o, omin, d, dmin;
1636 /* Find the closest "hot" widget to the left of dd (and inside id) */
1638 if (id && gui_hot(id))
1642 omin = widget[dd].x - widget[id].x + 1;
1643 dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1645 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1647 kd = gui_stick_L(jd, dd);
1651 o = gui_horz_offset(dd, kd);
1652 d = gui_vert_dist(dd, kd);
1654 if (0 <= o && o <= omin && d <= dmin)
1666 static int gui_stick_R(int id, int dd)
1669 int o, omin, d, dmin;
1671 /* Find the closest "hot" widget to the right of dd (and inside id) */
1673 if (id && gui_hot(id))
1677 omin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
1678 dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1680 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1682 kd = gui_stick_R(jd, dd);
1686 o = gui_horz_offset(kd, dd);
1687 d = gui_vert_dist(dd, kd);
1689 if (0 <= o && o <= omin && d <= dmin)
1701 static int gui_stick_D(int id, int dd)
1704 int o, omin, d, dmin;
1706 /* Find the closest "hot" widget below dd (and inside id) */
1708 if (id && gui_hot(id))
1712 omin = widget[dd].y - widget[id].y + 1;
1713 dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1715 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1717 kd = gui_stick_D(jd, dd);
1721 o = gui_vert_offset(dd, kd);
1722 d = gui_horz_dist(dd, kd);
1724 if (0 <= o && o <= omin && d <= dmin)
1736 static int gui_stick_U(int id, int dd)
1739 int o, omin, d, dmin;
1741 /* Find the closest "hot" widget above dd (and inside id) */
1743 if (id && gui_hot(id))
1747 omin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
1748 dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1750 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1752 kd = gui_stick_U(jd, dd);
1756 o = gui_vert_offset(kd, dd);
1757 d = gui_horz_dist(dd, kd);
1759 if (0 <= o && o <= omin && d <= dmin)
1771 /*---------------------------------------------------------------------------*/
1773 static int gui_wrap_L(int id, int dd)
1777 if ((jd = gui_stick_L(id, dd)) == 0)
1778 for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
1784 static int gui_wrap_R(int id, int dd)
1788 if ((jd = gui_stick_R(id, dd)) == 0)
1789 for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
1795 static int gui_wrap_U(int id, int dd)
1799 if ((jd = gui_stick_U(id, dd)) == 0)
1800 for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
1806 static int gui_wrap_D(int id, int dd)
1810 if ((jd = gui_stick_D(id, dd)) == 0)
1811 for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
1817 /*---------------------------------------------------------------------------*/
1819 /* Flag the axes to prevent uncontrolled scrolling. */
1821 static int xflag = 1;
1822 static int yflag = 1;
1826 /* Force the user to recenter the joystick before the next GUI action. */
1832 int gui_stick(int id, int x, int y)
1836 /* Find a new active widget in the direction of joystick motion. */
1838 if (x && -JOY_MID <= x && x <= +JOY_MID)
1840 else if (x < -JOY_MID && xflag && (jd = gui_wrap_L(id, active)))
1842 else if (x > +JOY_MID && xflag && (jd = gui_wrap_R(id, active)))
1845 if (y && -JOY_MID <= y && y <= +JOY_MID)
1847 else if (y < -JOY_MID && yflag && (jd = gui_wrap_U(id, active)))
1849 else if (y > +JOY_MID && yflag && (jd = gui_wrap_D(id, active)))
1852 /* If the active widget has changed, return the new active id. */
1854 if (jd == 0 || jd == active)
1860 /*---------------------------------------------------------------------------*/