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 0xFFFC
43 #define GUI_HARRAY (1 << GUI_FLAGS)
44 #define GUI_VARRAY (2 << GUI_FLAGS)
45 #define GUI_HSTACK (3 << GUI_FLAGS)
46 #define GUI_VSTACK (4 << GUI_FLAGS)
47 #define GUI_FILLER (5 << GUI_FLAGS)
48 #define GUI_IMAGE (6 << GUI_FLAGS)
49 #define GUI_LABEL (7 << GUI_FLAGS)
50 #define GUI_COUNT (8 << GUI_FLAGS)
51 #define GUI_CLOCK (9 << GUI_FLAGS)
52 #define GUI_SPACE (10 << GUI_FLAGS)
73 const GLfloat *color0;
74 const GLfloat *color1;
84 /*---------------------------------------------------------------------------*/
86 const GLfloat gui_wht[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
87 const GLfloat gui_yel[4] = { 1.0f, 1.0f, 0.0f, 1.0f };
88 const GLfloat gui_red[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
89 const GLfloat gui_grn[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
90 const GLfloat gui_blu[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
91 const GLfloat gui_blk[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
92 const GLfloat gui_gry[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
94 /*---------------------------------------------------------------------------*/
96 static struct widget widget[MAXWIDGET];
99 static TTF_Font *font[3] = { NULL, NULL, NULL };
101 static void *fontdata;
102 static int fontdatalen;
103 static SDL_RWops *fontrwops;
105 static GLuint digit_text[3][11];
106 static GLuint digit_list[3][11];
107 static int digit_w[3][11];
108 static int digit_h[3][11];
110 /*---------------------------------------------------------------------------*/
112 static int gui_hot(int id)
114 return (widget[id].type & GUI_STATE);
117 /*---------------------------------------------------------------------------*/
119 * Initialize a display list containing a rectangle (x, y, w, h) to
120 * which a rendered-font texture may be applied. Colors c0 and c1
121 * determine the top-to-bottom color gradient of the text.
124 static GLuint gui_list(int x, int y,
125 int w, int h, const float *c0, const float *c1)
127 GLuint list = glGenLists(1);
132 int W, H, ww, hh, d = h / 16;
134 /* Assume the applied texture size is rect size rounded to power-of-two. */
136 image_size(&W, &H, w, h);
138 ww = ((W - w) % 2) ? w + 1 : w;
139 hh = ((H - h) % 2) ? h + 1 : h;
141 s0 = 0.5f * (W - ww) / W;
142 t0 = 0.5f * (H - hh) / H;
146 glNewList(list, GL_COMPILE);
150 glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
151 glTexCoord2f(s0, t1); glVertex2i(x + d, y - d);
152 glTexCoord2f(s1, t1); glVertex2i(x + ww + d, y - d);
153 glTexCoord2f(s1, t0); glVertex2i(x + ww + d, y + hh - d);
154 glTexCoord2f(s0, t0); glVertex2i(x + d, y + hh - d);
157 glTexCoord2f(s0, t1); glVertex2i(x, y);
158 glTexCoord2f(s1, t1); glVertex2i(x + ww, y);
161 glTexCoord2f(s1, t0); glVertex2i(x + ww, y + hh);
162 glTexCoord2f(s0, t0); glVertex2i(x, y + hh);
172 * Initialize a display list containing a rounded-corner rectangle (x,
173 * y, w, h). Generate texture coordinates to properly apply a texture
174 * map to the rectangle as though the corners were not rounded.
177 static GLuint gui_rect(int x, int y, int w, int h, int f, int r)
179 GLuint list = glGenLists(1);
184 glNewList(list, GL_COMPILE);
186 glBegin(GL_QUAD_STRIP);
190 for (i = 0; i <= n; i++)
192 float a = 0.5f * V_PI * (float) i / (float) n;
193 float s = r * fsinf(a);
194 float c = r * fcosf(a);
197 float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
198 float Yb = y + ((f & GUI_SW) ? (r - s) : 0);
200 glTexCoord2f((X - x) / w, (Ya - y) / h);
203 glTexCoord2f((X - x) / w, (Yb - y) / h);
207 /* ... Right side. */
209 for (i = 0; i <= n; i++)
211 float a = 0.5f * V_PI * (float) i / (float) n;
212 float s = r * fsinf(a);
213 float c = r * fcosf(a);
215 float X = x + w - r + s;
216 float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
217 float Yb = y + ((f & GUI_SE) ? (r - c) : 0);
219 glTexCoord2f((X - x) / w, (Ya - y) / h);
222 glTexCoord2f((X - x) / w, (Yb - y) / h);
233 /*---------------------------------------------------------------------------*/
235 static const char *pick_font_path(void)
241 if (!fs_exists(path))
243 fprintf(stderr, L_("Font '%s' doesn't exist, trying default font.\n"),
254 const float *c0 = gui_yel;
255 const float *c1 = gui_red;
257 int w = config_get_d(CONFIG_WIDTH);
258 int h = config_get_d(CONFIG_HEIGHT);
259 int i, j, s = (h < w) ? h : w;
261 /* Initialize font rendering. */
265 const char *fontpath = pick_font_path();
271 memset(widget, 0, sizeof (struct widget) * MAXWIDGET);
275 if ((fontdata = fs_load(fontpath, &fontdatalen)))
277 fontrwops = SDL_RWFromConstMem(fontdata, fontdatalen);
279 /* Load small, medium, and large typefaces. */
281 font[GUI_SML] = TTF_OpenFontRW(fontrwops, 0, s0);
283 SDL_RWseek(fontrwops, 0, SEEK_SET);
284 font[GUI_MED] = TTF_OpenFontRW(fontrwops, 0, s1);
286 SDL_RWseek(fontrwops, 0, SEEK_SET);
287 font[GUI_LRG] = TTF_OpenFontRW(fontrwops, 0, s2);
289 /* fontrwops remains open. */
295 font[GUI_SML] = NULL;
296 font[GUI_MED] = NULL;
297 font[GUI_LRG] = NULL;
299 fprintf(stderr, L_("Could not load font '%s'.\n"), fontpath);
304 /* Initialize digit glyphs and lists for counters and clocks. */
306 for (i = 0; i < 3; i++)
310 /* Draw digits 0 through 9. */
312 for (j = 0; j < 10; j++)
314 text[0] = '0' + (char) j;
317 digit_text[i][j] = make_image_from_font(NULL, NULL,
321 digit_list[i][j] = gui_list(-digit_w[i][j] / 2,
324 +digit_h[i][j], c0, c1);
327 /* Draw the colon for the clock. */
329 digit_text[i][j] = make_image_from_font(NULL, NULL,
333 digit_list[i][j] = gui_list(-digit_w[i][10] / 2,
336 +digit_h[i][10], c0, c1);
347 /* Release any remaining widget texture and display list indices. */
349 for (id = 1; id < MAXWIDGET; id++)
351 if (glIsTexture(widget[id].text_img))
352 glDeleteTextures(1, &widget[id].text_img);
354 if (glIsList(widget[id].text_obj))
355 glDeleteLists(widget[id].text_obj, 1);
356 if (glIsList(widget[id].rect_obj))
357 glDeleteLists(widget[id].rect_obj, 1);
359 widget[id].type = GUI_FREE;
360 widget[id].text_img = 0;
361 widget[id].text_obj = 0;
362 widget[id].rect_obj = 0;
367 /* Release all digit textures and display lists. */
369 for (i = 0; i < 3; i++)
370 for (j = 0; j < 11; j++)
372 if (glIsTexture(digit_text[i][j]))
373 glDeleteTextures(1, &digit_text[i][j]);
375 if (glIsList(digit_list[i][j]))
376 glDeleteLists(digit_list[i][j], 1);
379 /* Release all loaded fonts and finalize font rendering. */
381 if (font[GUI_LRG]) TTF_CloseFont(font[GUI_LRG]);
382 if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
383 if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
385 if (fontrwops) SDL_RWclose(fontrwops);
386 if (fontdata) free(fontdata);
391 /*---------------------------------------------------------------------------*/
393 static int gui_widget(int pd, int type)
397 /* Find an unused entry in the widget table. */
399 for (id = 1; id < MAXWIDGET; id++)
400 if (widget[id].type == GUI_FREE)
402 /* Set the type and default properties. */
404 widget[id].type = type;
405 widget[id].token = 0;
406 widget[id].value = 0;
408 widget[id].rect = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
411 widget[id].text_img = 0;
412 widget[id].text_obj = 0;
413 widget[id].rect_obj = 0;
414 widget[id].color0 = gui_wht;
415 widget[id].color1 = gui_wht;
416 widget[id].scale = 1.0f;
417 widget[id].trunc = TRUNC_NONE;
419 widget[id].text_obj_w = 0;
420 widget[id].text_obj_h = 0;
422 /* Insert the new widget into the parent's widget list. */
427 widget[id].cdr = widget[pd].car;
439 fprintf(stderr, "Out of widget IDs\n");
444 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
445 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
446 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
447 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
448 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
450 /*---------------------------------------------------------------------------*/
458 static struct size gui_measure(const char *text, TTF_Font *font)
460 struct size size = { 0, 0 };
463 TTF_SizeUTF8(font, text, &size.w, &size.h);
468 static char *gui_trunc_head(const char *text,
472 int left, right, mid;
476 right = strlen(text);
478 while (right - left > 1)
480 mid = (left + right) / 2;
482 str = concat_string("...", text + mid, NULL);
484 if (gui_measure(str, font).w <= maxwidth)
492 return concat_string("...", text + right, NULL);
495 static char *gui_trunc_tail(const char *text,
499 int left, right, mid;
503 right = strlen(text);
505 while (right - left > 1)
507 mid = (left + right) / 2;
509 str = malloc(mid + sizeof ("..."));
511 memcpy(str, text, mid);
512 memcpy(str + mid, "...", sizeof ("..."));
514 if (gui_measure(str, font).w <= maxwidth)
522 str = malloc(left + sizeof ("..."));
524 memcpy(str, text, left);
525 memcpy(str + left, "...", sizeof ("..."));
530 static char *gui_truncate(const char *text,
535 if (gui_measure(text, font).w <= maxwidth)
540 case TRUNC_NONE: return strdup(text); break;
541 case TRUNC_HEAD: return gui_trunc_head(text, maxwidth, font); break;
542 case TRUNC_TAIL: return gui_trunc_tail(text, maxwidth, font); break;
548 /*---------------------------------------------------------------------------*/
550 void gui_set_image(int id, const char *file)
552 if (glIsTexture(widget[id].text_img))
553 glDeleteTextures(1, &widget[id].text_img);
555 widget[id].text_img = make_image_from_file(file);
558 void gui_set_label(int id, const char *text)
562 if (glIsTexture(widget[id].text_img))
563 glDeleteTextures(1, &widget[id].text_img);
564 if (glIsList(widget[id].text_obj))
565 glDeleteLists(widget[id].text_obj, 1);
567 text = gui_truncate(text, widget[id].w - radius,
568 font[widget[id].size],
571 widget[id].text_img = make_image_from_font(NULL, NULL, &w, &h,
572 text, font[widget[id].size]);
573 widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
574 widget[id].color0, widget[id].color1);
576 widget[id].text_obj_w = w;
577 widget[id].text_obj_h = h;
582 void gui_set_count(int id, int value)
584 widget[id].value = value;
587 void gui_set_clock(int id, int value)
589 widget[id].value = value;
592 void gui_set_color(int id, const float *c0,
597 c0 = c0 ? c0 : gui_yel;
598 c1 = c1 ? c1 : gui_red;
600 if (widget[id].color0 != c0 || widget[id].color1 != c1)
602 widget[id].color0 = c0;
603 widget[id].color1 = c1;
605 if (glIsList(widget[id].text_obj))
609 glDeleteLists(widget[id].text_obj, 1);
611 w = widget[id].text_obj_w;
612 h = widget[id].text_obj_h;
614 widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
622 void gui_set_multi(int id, const char *text)
626 char s[GUI_LINES][MAXSTR];
631 /* Count available labels. */
633 for (lc = 0, jd = widget[id].car; jd; lc++, jd = widget[jd].cdr);
635 /* Copy each delimited string to a line buffer. */
637 for (p = text, sc = 0; *p && sc < lc; sc++)
639 strncpy(s[sc], p, (n = strcspn(p, "\\")));
642 if (*(p += n) == '\\') p++;
645 /* Set the label value for each line. */
647 for (i = lc - 1, jd = widget[id].car; i >= 0; i--, jd = widget[jd].cdr)
648 gui_set_label(jd, i < sc ? s[i] : "");
651 void gui_set_trunc(int id, enum trunc trunc)
653 widget[id].trunc = trunc;
656 void gui_set_fill(int id)
658 widget[id].type |= GUI_FILL;
661 /*---------------------------------------------------------------------------*/
663 int gui_image(int pd, const char *file, int w, int h)
667 if ((id = gui_widget(pd, GUI_IMAGE)))
669 widget[id].text_img = make_image_from_file(file);
676 int gui_start(int pd, const char *text, int size, int token, int value)
680 if ((id = gui_state(pd, text, size, token, value)))
686 int gui_state(int pd, const char *text, int size, int token, int value)
690 if ((id = gui_widget(pd, GUI_STATE)))
692 widget[id].text_img = make_image_from_font(NULL, NULL,
696 widget[id].size = size;
697 widget[id].token = token;
698 widget[id].value = value;
703 int gui_label(int pd, const char *text, int size, int rect, const float *c0,
708 if ((id = gui_widget(pd, GUI_LABEL)))
710 widget[id].text_img = make_image_from_font(NULL, NULL,
714 widget[id].size = size;
715 widget[id].color0 = c0 ? c0 : gui_yel;
716 widget[id].color1 = c1 ? c1 : gui_red;
717 widget[id].rect = rect;
722 int gui_count(int pd, int value, int size, int rect)
726 if ((id = gui_widget(pd, GUI_COUNT)))
728 for (i = value; i; i /= 10)
729 widget[id].w += digit_w[size][0];
731 widget[id].h = digit_h[size][0];
732 widget[id].value = value;
733 widget[id].size = size;
734 widget[id].color0 = gui_yel;
735 widget[id].color1 = gui_red;
736 widget[id].rect = rect;
741 int gui_clock(int pd, int value, int size, int rect)
745 if ((id = gui_widget(pd, GUI_CLOCK)))
747 widget[id].w = digit_w[size][0] * 6;
748 widget[id].h = digit_h[size][0];
749 widget[id].value = value;
750 widget[id].size = size;
751 widget[id].color0 = gui_yel;
752 widget[id].color1 = gui_red;
753 widget[id].rect = rect;
758 int gui_space(int pd)
762 if ((id = gui_widget(pd, GUI_SPACE)))
770 /*---------------------------------------------------------------------------*/
773 * Create a multi-line text box using a vertical array of labels.
774 * Parse the text for '\' characters and treat them as line-breaks.
775 * Preserve the rect specification across the entire array.
778 int gui_multi(int pd, const char *text, int size, int rect, const float *c0,
783 if (text && (id = gui_varray(pd)))
787 char s[GUI_LINES][MAXSTR];
793 /* Copy each delimited string to a line buffer. */
795 for (p = text, j = 0; *p && j < GUI_LINES; j++)
797 strncpy(s[j], p, (n = strcspn(p, "\\")));
801 if (*(p += n) == '\\') p++;
804 /* Set the curves for the first and last lines. */
808 r[0] |= rect & (GUI_NW | GUI_NE);
809 r[j - 1] |= rect & (GUI_SW | GUI_SE);
812 /* Create a label widget for each line. */
814 for (i = 0; i < j; i++)
815 gui_label(id, s[i], size, r[i], c0, c1);
820 /*---------------------------------------------------------------------------*/
822 * The bottom-up pass determines the area of all widgets. The minimum
823 * width and height of a leaf widget is given by the size of its
824 * contents. Array and stack widths and heights are computed
825 * recursively from these.
828 static void gui_widget_up(int id);
830 static void gui_harray_up(int id)
834 /* Find the widest child width and the highest child height. */
836 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
840 if (widget[id].h < widget[jd].h)
841 widget[id].h = widget[jd].h;
842 if (widget[id].w < widget[jd].w)
843 widget[id].w = widget[jd].w;
848 /* Total width is the widest child width times the child count. */
853 static void gui_varray_up(int id)
857 /* Find the widest child width and the highest child height. */
859 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
863 if (widget[id].h < widget[jd].h)
864 widget[id].h = widget[jd].h;
865 if (widget[id].w < widget[jd].w)
866 widget[id].w = widget[jd].w;
871 /* Total height is the highest child height times the child count. */
876 static void gui_hstack_up(int id)
880 /* Find the highest child height. Sum the child widths. */
882 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
886 if (widget[id].h < widget[jd].h)
887 widget[id].h = widget[jd].h;
889 widget[id].w += widget[jd].w;
893 static void gui_vstack_up(int id)
897 /* Find the widest child width. Sum the child heights. */
899 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
903 if (widget[id].w < widget[jd].w)
904 widget[id].w = widget[jd].w;
906 widget[id].h += widget[jd].h;
910 static void gui_button_up(int id)
912 /* Store width and height for later use in text rendering. */
914 widget[id].text_obj_w = widget[id].w;
915 widget[id].text_obj_h = widget[id].h;
917 if (widget[id].w < widget[id].h && widget[id].w > 0)
918 widget[id].w = widget[id].h;
920 /* Padded text elements look a little nicer. */
922 if (widget[id].w < config_get_d(CONFIG_WIDTH))
923 widget[id].w += radius;
924 if (widget[id].h < config_get_d(CONFIG_HEIGHT))
925 widget[id].h += radius;
927 /* A button should be at least wide enough to accomodate the rounding. */
929 if (widget[id].w < 2 * radius)
930 widget[id].w = 2 * radius;
931 if (widget[id].h < 2 * radius)
932 widget[id].h = 2 * radius;
935 static void gui_widget_up(int id)
938 switch (widget[id].type & GUI_TYPE)
940 case GUI_HARRAY: gui_harray_up(id); break;
941 case GUI_VARRAY: gui_varray_up(id); break;
942 case GUI_HSTACK: gui_hstack_up(id); break;
943 case GUI_VSTACK: gui_vstack_up(id); break;
944 case GUI_FILLER: break;
945 default: gui_button_up(id); break;
949 /*---------------------------------------------------------------------------*/
951 * The top-down layout pass distributes available area as computed
952 * during the bottom-up pass. Widgets use their area and position to
953 * initialize rendering state.
956 static void gui_widget_dn(int id, int x, int y, int w, int h);
958 static void gui_harray_dn(int id, int x, int y, int w, int h)
960 int jd, i = 0, c = 0;
967 /* Count children. */
969 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
972 /* Distribute horizontal space evenly to all children. */
974 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
976 int x0 = x + i * w / c;
977 int x1 = x + (i + 1) * w / c;
979 gui_widget_dn(jd, x0, y, x1 - x0, h);
983 static void gui_varray_dn(int id, int x, int y, int w, int h)
985 int jd, i = 0, c = 0;
992 /* Count children. */
994 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
997 /* Distribute vertical space evenly to all children. */
999 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
1001 int y0 = y + i * h / c;
1002 int y1 = y + (i + 1) * h / c;
1004 gui_widget_dn(jd, x, y0, w, y1 - y0);
1008 static void gui_hstack_dn(int id, int x, int y, int w, int h)
1010 int jd, jx = x, jw = 0, c = 0;
1017 /* Measure the total width requested by non-filler children. */
1019 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1020 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1022 else if (widget[jd].type & GUI_FILL)
1030 /* Give non-filler children their requested space. */
1031 /* Distribute the rest evenly among filler children. */
1033 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1035 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1036 gui_widget_dn(jd, jx, y, (w - jw) / c, h);
1037 else if (widget[jd].type & GUI_FILL)
1038 gui_widget_dn(jd, jx, y, widget[jd].w + (w - jw) / c, h);
1040 gui_widget_dn(jd, jx, y, widget[jd].w, h);
1046 static void gui_vstack_dn(int id, int x, int y, int w, int h)
1048 int jd, jy = y, jh = 0, c = 0;
1055 /* Measure the total height requested by non-filler children. */
1057 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1058 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1060 else if (widget[jd].type & GUI_FILL)
1068 /* Give non-filler children their requested space. */
1069 /* Distribute the rest evenly among filler children. */
1071 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1073 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1074 gui_widget_dn(jd, x, jy, w, (h - jh) / c);
1075 else if (widget[jd].type & GUI_FILL)
1076 gui_widget_dn(jd, x, jy, w, widget[jd].h + (h - jh) / c);
1078 gui_widget_dn(jd, x, jy, w, widget[jd].h);
1084 static void gui_filler_dn(int id, int x, int y, int w, int h)
1086 /* Filler expands to whatever size it is given. */
1094 static void gui_button_dn(int id, int x, int y, int w, int h)
1096 /* Recall stored width and height for text rendering. */
1098 int W = widget[id].text_obj_w;
1099 int H = widget[id].text_obj_h;
1100 int R = widget[id].rect;
1102 const float *c0 = widget[id].color0;
1103 const float *c1 = widget[id].color1;
1110 /* Create display lists for the text area and rounded rectangle. */
1112 widget[id].text_obj = gui_list(-W / 2, -H / 2, W, H, c0, c1);
1113 widget[id].rect_obj = gui_rect(-w / 2, -h / 2, w, h, R, radius);
1116 static void gui_widget_dn(int id, int x, int y, int w, int h)
1119 switch (widget[id].type & GUI_TYPE)
1121 case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
1122 case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
1123 case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
1124 case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
1125 case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
1126 case GUI_SPACE: gui_filler_dn(id, x, y, w, h); break;
1127 default: gui_button_dn(id, x, y, w, h); break;
1131 /*---------------------------------------------------------------------------*/
1133 * During GUI layout, we make a bottom-up pass to determine total area
1134 * requirements for the widget tree. We position this area to the
1135 * sides or center of the screen. Finally, we make a top-down pass to
1136 * distribute this area to each widget.
1139 void gui_layout(int id, int xd, int yd)
1143 int w, W = config_get_d(CONFIG_WIDTH);
1144 int h, H = config_get_d(CONFIG_HEIGHT);
1152 else if (xd > 0) x = (W - w);
1153 else x = (W - w) / 2;
1156 else if (yd > 0) y = (H - h);
1157 else y = (H - h) / 2;
1159 gui_widget_dn(id, x, y, w, h);
1161 /* Hilite the widget under the cursor, if any. */
1163 gui_point(id, -1, -1);
1166 int gui_search(int id, int x, int y)
1170 /* Search the hierarchy for the widget containing the given point. */
1172 if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
1173 widget[id].y <= y && y < widget[id].y + widget[id].h))
1178 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1179 if ((kd = gui_search(jd, x, y)))
1186 * Activate a widget, allowing it to behave as a normal state widget.
1187 * This may be used to create image buttons, or cause an array of
1188 * widgets to behave as a single state widget.
1190 int gui_active(int id, int token, int value)
1192 widget[id].type |= GUI_STATE;
1193 widget[id].token = token;
1194 widget[id].value = value;
1199 int gui_delete(int id)
1203 /* Recursively delete all subwidgets. */
1205 gui_delete(widget[id].cdr);
1206 gui_delete(widget[id].car);
1208 /* Release any GL resources held by this widget. */
1210 if (glIsTexture(widget[id].text_img))
1211 glDeleteTextures(1, &widget[id].text_img);
1213 if (glIsList(widget[id].text_obj))
1214 glDeleteLists(widget[id].text_obj, 1);
1215 if (glIsList(widget[id].rect_obj))
1216 glDeleteLists(widget[id].rect_obj, 1);
1218 /* Mark this widget unused. */
1220 widget[id].type = GUI_FREE;
1221 widget[id].text_img = 0;
1222 widget[id].text_obj = 0;
1223 widget[id].rect_obj = 0;
1230 /*---------------------------------------------------------------------------*/
1232 static void gui_paint_rect(int id, int st)
1234 static const GLfloat back[4][4] = {
1235 { 0.1f, 0.1f, 0.1f, 0.5f }, /* off and inactive */
1236 { 0.5f, 0.5f, 0.5f, 0.8f }, /* off and active */
1237 { 1.0f, 0.7f, 0.3f, 0.5f }, /* on and inactive */
1238 { 1.0f, 0.7f, 0.3f, 0.8f }, /* on and active */
1243 /* Use the widget status to determine the background color. */
1246 i = st | (((widget[id].value) ? 2 : 0) |
1247 ((id == active) ? 1 : 0));
1249 switch (widget[id].type & GUI_TYPE)
1261 /* Recursively paint all subwidgets. */
1263 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1264 gui_paint_rect(jd, i);
1270 /* Draw a leaf's background, colored by widget state. */
1274 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1275 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1277 glColor4fv(back[i]);
1278 glCallList(widget[id].rect_obj);
1286 /*---------------------------------------------------------------------------*/
1288 static void gui_paint_text(int id);
1290 static void gui_paint_array(int id)
1296 GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1297 GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1298 GLfloat ck = widget[id].scale;
1300 glTranslatef(+cx, +cy, 0.0f);
1301 glScalef(ck, ck, ck);
1302 glTranslatef(-cx, -cy, 0.0f);
1304 /* Recursively paint all subwidgets. */
1306 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1312 static void gui_paint_image(int id)
1314 /* Draw the widget rect, textured using the image. */
1318 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1319 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1321 glScalef(widget[id].scale,
1325 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1326 glColor4fv(gui_wht);
1327 glCallList(widget[id].rect_obj);
1332 static void gui_paint_count(int id)
1334 int j, i = widget[id].size;
1338 glColor4fv(gui_wht);
1340 /* Translate to the widget center, and apply the pulse scale. */
1342 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1343 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1345 glScalef(widget[id].scale,
1349 if (widget[id].value > 0)
1351 /* Translate left by half the total width of the rendered value. */
1353 for (j = widget[id].value; j; j /= 10)
1354 glTranslatef((GLfloat) +digit_w[i][j % 10] / 2.0f, 0.0f, 0.0f);
1356 glTranslatef((GLfloat) -digit_w[i][0] / 2.0f, 0.0f, 0.0f);
1358 /* Render each digit, moving right after each. */
1360 for (j = widget[id].value; j; j /= 10)
1362 glBindTexture(GL_TEXTURE_2D, digit_text[i][j % 10]);
1363 glCallList(digit_list[i][j % 10]);
1364 glTranslatef((GLfloat) -digit_w[i][j % 10], 0.0f, 0.0f);
1367 else if (widget[id].value == 0)
1369 /* If the value is zero, just display a zero in place. */
1371 glBindTexture(GL_TEXTURE_2D, digit_text[i][0]);
1372 glCallList(digit_list[i][0]);
1378 static void gui_paint_clock(int id)
1380 int i = widget[id].size;
1381 int mt = (widget[id].value / 6000) / 10;
1382 int mo = (widget[id].value / 6000) % 10;
1383 int st = ((widget[id].value % 6000) / 100) / 10;
1384 int so = ((widget[id].value % 6000) / 100) % 10;
1385 int ht = ((widget[id].value % 6000) % 100) / 10;
1386 int ho = ((widget[id].value % 6000) % 100) % 10;
1388 GLfloat dx_large = (GLfloat) digit_w[i][0];
1389 GLfloat dx_small = (GLfloat) digit_w[i][0] * 0.75f;
1391 if (widget[id].value < 0)
1396 glColor4fv(gui_wht);
1398 /* Translate to the widget center, and apply the pulse scale. */
1400 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1401 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1403 glScalef(widget[id].scale,
1407 /* Translate left by half the total width of the rendered value. */
1410 glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1412 glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1414 /* Render the minutes counter. */
1418 glBindTexture(GL_TEXTURE_2D, digit_text[i][mt]);
1419 glCallList(digit_list[i][mt]);
1420 glTranslatef(dx_large, 0.0f, 0.0f);
1423 glBindTexture(GL_TEXTURE_2D, digit_text[i][mo]);
1424 glCallList(digit_list[i][mo]);
1425 glTranslatef(dx_small, 0.0f, 0.0f);
1427 /* Render the colon. */
1429 glBindTexture(GL_TEXTURE_2D, digit_text[i][10]);
1430 glCallList(digit_list[i][10]);
1431 glTranslatef(dx_small, 0.0f, 0.0f);
1433 /* Render the seconds counter. */
1435 glBindTexture(GL_TEXTURE_2D, digit_text[i][st]);
1436 glCallList(digit_list[i][st]);
1437 glTranslatef(dx_large, 0.0f, 0.0f);
1439 glBindTexture(GL_TEXTURE_2D, digit_text[i][so]);
1440 glCallList(digit_list[i][so]);
1441 glTranslatef(dx_small, 0.0f, 0.0f);
1443 /* Render hundredths counter half size. */
1445 glScalef(0.5f, 0.5f, 1.0f);
1447 glBindTexture(GL_TEXTURE_2D, digit_text[i][ht]);
1448 glCallList(digit_list[i][ht]);
1449 glTranslatef(dx_large, 0.0f, 0.0f);
1451 glBindTexture(GL_TEXTURE_2D, digit_text[i][ho]);
1452 glCallList(digit_list[i][ho]);
1457 static void gui_paint_label(int id)
1459 /* Draw the widget text box, textured using the glyph. */
1463 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1464 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1466 glScalef(widget[id].scale,
1470 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1471 glCallList(widget[id].text_obj);
1476 static void gui_paint_text(int id)
1478 switch (widget[id].type & GUI_TYPE)
1480 case GUI_SPACE: break;
1481 case GUI_FILLER: break;
1482 case GUI_HARRAY: gui_paint_array(id); break;
1483 case GUI_VARRAY: gui_paint_array(id); break;
1484 case GUI_HSTACK: gui_paint_array(id); break;
1485 case GUI_VSTACK: gui_paint_array(id); break;
1486 case GUI_IMAGE: gui_paint_image(id); break;
1487 case GUI_COUNT: gui_paint_count(id); break;
1488 case GUI_CLOCK: gui_paint_clock(id); break;
1489 default: gui_paint_label(id); break;
1493 void gui_paint(int id)
1499 glEnable(GL_COLOR_MATERIAL);
1500 glDisable(GL_LIGHTING);
1501 glDisable(GL_DEPTH_TEST);
1503 glDisable(GL_TEXTURE_2D);
1504 gui_paint_rect(id, 0);
1506 glEnable(GL_TEXTURE_2D);
1509 glColor4fv(gui_wht);
1511 glEnable(GL_DEPTH_TEST);
1512 glEnable(GL_LIGHTING);
1513 glDisable(GL_COLOR_MATERIAL);
1519 /*---------------------------------------------------------------------------*/
1521 void gui_dump(int id, int d)
1529 switch (widget[id].type & GUI_TYPE)
1531 case GUI_HARRAY: type = "harray"; break;
1532 case GUI_VARRAY: type = "varray"; break;
1533 case GUI_HSTACK: type = "hstack"; break;
1534 case GUI_VSTACK: type = "vstack"; break;
1535 case GUI_FILLER: type = "filler"; break;
1536 case GUI_IMAGE: type = "image"; break;
1537 case GUI_LABEL: type = "label"; break;
1538 case GUI_COUNT: type = "count"; break;
1539 case GUI_CLOCK: type = "clock"; break;
1542 for (i = 0; i < d; i++)
1545 printf("%04d %s\n", id, type);
1547 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1548 gui_dump(jd, d + 1);
1552 void gui_pulse(int id, float k)
1554 if (id) widget[id].scale = k;
1557 void gui_timer(int id, float dt)
1563 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1566 if (widget[id].scale - 1.0f < dt)
1567 widget[id].scale = 1.0f;
1569 widget[id].scale -= dt;
1573 int gui_point(int id, int x, int y)
1575 static int x_cache = 0;
1576 static int y_cache = 0;
1580 /* Reuse the last coordinates if (x,y) == (-1,-1) */
1583 return gui_point(id, x_cache, y_cache);
1588 /* Short-circuit check the current active widget. */
1590 jd = gui_search(active, x, y);
1592 /* If not still active, search the hierarchy for a new active widget. */
1595 jd = gui_search(id, x, y);
1597 /* If the active widget has changed, return the new active id. */
1599 if (jd == 0 || jd == active)
1605 void gui_focus(int i)
1615 int gui_token(int id)
1617 return id ? widget[id].token : 0;
1620 int gui_value(int id)
1622 return id ? widget[id].value : 0;
1625 void gui_toggle(int id)
1627 widget[id].value = widget[id].value ? 0 : 1;
1630 /*---------------------------------------------------------------------------*/
1632 static int gui_vert_offset(int id, int jd)
1634 /* Vertical offset between bottom of id and top of jd */
1636 return widget[id].y - (widget[jd].y + widget[jd].h);
1639 static int gui_horz_offset(int id, int jd)
1641 /* Horizontal offset between left of id and right of jd */
1643 return widget[id].x - (widget[jd].x + widget[jd].w);
1646 static int gui_vert_dist(int id, int jd)
1648 /* Vertical distance between the tops of id and jd */
1650 return abs((widget[id].y + widget[id].h) - (widget[jd].y + widget[jd].h));
1653 static int gui_horz_dist(int id, int jd)
1655 /* Horizontal distance between the left sides of id and jd */
1657 return abs(widget[id].x - widget[jd].x);
1660 /*---------------------------------------------------------------------------*/
1662 static int gui_stick_L(int id, int dd)
1665 int o, omin, d, dmin;
1667 /* Find the closest "hot" widget to the left of dd (and inside id) */
1669 if (id && gui_hot(id))
1673 omin = widget[dd].x - widget[id].x + 1;
1674 dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1676 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1678 kd = gui_stick_L(jd, dd);
1682 o = gui_horz_offset(dd, kd);
1683 d = gui_vert_dist(dd, kd);
1685 if (0 <= o && o <= omin && d <= dmin)
1697 static int gui_stick_R(int id, int dd)
1700 int o, omin, d, dmin;
1702 /* Find the closest "hot" widget to the right of dd (and inside id) */
1704 if (id && gui_hot(id))
1708 omin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
1709 dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1711 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1713 kd = gui_stick_R(jd, dd);
1717 o = gui_horz_offset(kd, dd);
1718 d = gui_vert_dist(dd, kd);
1720 if (0 <= o && o <= omin && d <= dmin)
1732 static int gui_stick_D(int id, int dd)
1735 int o, omin, d, dmin;
1737 /* Find the closest "hot" widget below dd (and inside id) */
1739 if (id && gui_hot(id))
1743 omin = widget[dd].y - widget[id].y + 1;
1744 dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1746 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1748 kd = gui_stick_D(jd, dd);
1752 o = gui_vert_offset(dd, kd);
1753 d = gui_horz_dist(dd, kd);
1755 if (0 <= o && o <= omin && d <= dmin)
1767 static int gui_stick_U(int id, int dd)
1770 int o, omin, d, dmin;
1772 /* Find the closest "hot" widget above dd (and inside id) */
1774 if (id && gui_hot(id))
1778 omin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
1779 dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1781 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1783 kd = gui_stick_U(jd, dd);
1787 o = gui_vert_offset(kd, dd);
1788 d = gui_horz_dist(dd, kd);
1790 if (0 <= o && o <= omin && d <= dmin)
1802 /*---------------------------------------------------------------------------*/
1804 static int gui_wrap_L(int id, int dd)
1808 if ((jd = gui_stick_L(id, dd)) == 0)
1809 for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
1815 static int gui_wrap_R(int id, int dd)
1819 if ((jd = gui_stick_R(id, dd)) == 0)
1820 for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
1826 static int gui_wrap_U(int id, int dd)
1830 if ((jd = gui_stick_U(id, dd)) == 0)
1831 for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
1837 static int gui_wrap_D(int id, int dd)
1841 if ((jd = gui_stick_D(id, dd)) == 0)
1842 for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
1848 /*---------------------------------------------------------------------------*/
1850 int gui_stick(int id, int a, float v, int bump)
1857 /* Find a new active widget in the direction of joystick motion. */
1859 if (config_tst_d(CONFIG_JOYSTICK_AXIS_X, a))
1861 if (v < 0) jd = gui_wrap_L(id, active);
1862 if (v > 0) jd = gui_wrap_R(id, active);
1864 else if (config_tst_d(CONFIG_JOYSTICK_AXIS_Y, a))
1866 if (v < 0) jd = gui_wrap_U(id, active);
1867 if (v > 0) jd = gui_wrap_D(id, active);
1870 /* If the active widget has changed, return the new active id. */
1872 if (jd == 0 || jd == active)
1878 /*---------------------------------------------------------------------------*/