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 /*---------------------------------------------------------------------------*/
32 /* Very pure colors for the GUI. I was watching BANZAI when I designed this. */
34 const GLubyte gui_wht[4] = { 0xFF, 0xFF, 0xFF, 0xFF }; /* White */
35 const GLubyte gui_yel[4] = { 0xFF, 0xFF, 0x00, 0xFF }; /* Yellow */
36 const GLubyte gui_red[4] = { 0xFF, 0x00, 0x00, 0xFF }; /* Red */
37 const GLubyte gui_grn[4] = { 0x00, 0xFF, 0x00, 0xFF }; /* Green */
38 const GLubyte gui_blu[4] = { 0x00, 0x00, 0xFF, 0xFF }; /* Blue */
39 const GLubyte gui_blk[4] = { 0x00, 0x00, 0x00, 0xFF }; /* Black */
40 const GLubyte gui_gry[4] = { 0x55, 0x55, 0x55, 0xFF }; /* Gray */
41 const GLubyte gui_shd[4] = { 0x00, 0x00, 0x00, 0x80 }; /* Shadow */
43 /*---------------------------------------------------------------------------*/
45 #define WIDGET_MAX 256
47 #define GUI_TYPE 0xFFFC
56 #define GUI_HARRAY (1 << GUI_FLAGS)
57 #define GUI_VARRAY (2 << GUI_FLAGS)
58 #define GUI_HSTACK (3 << GUI_FLAGS)
59 #define GUI_VSTACK (4 << GUI_FLAGS)
60 #define GUI_FILLER (5 << GUI_FLAGS)
61 #define GUI_IMAGE (6 << GUI_FLAGS)
62 #define GUI_LABEL (7 << GUI_FLAGS)
63 #define GUI_COUNT (8 << GUI_FLAGS)
64 #define GUI_CLOCK (9 << GUI_FLAGS)
65 #define GUI_SPACE (10 << GUI_FLAGS)
69 /*---------------------------------------------------------------------------*/
79 const GLubyte *color0;
80 const GLubyte *color1;
96 /*---------------------------------------------------------------------------*/
98 /* GUI widget state */
100 static struct widget widget[WIDGET_MAX];
103 static TTF_Font *font[3] = { NULL, NULL, NULL };
105 /* Font data access. */
107 static void *fontdata;
108 static int fontdatalen;
109 static SDL_RWops *fontrwops;
111 /* Digit glyphs for the HUD. */
113 static GLuint digit_text[3][11];
114 static GLuint digit_list[3][11];
115 static int digit_w[3][11];
116 static int digit_h[3][11];
118 /*---------------------------------------------------------------------------*/
120 static int gui_hot(int id)
122 return (widget[id].type & GUI_STATE);
125 /*---------------------------------------------------------------------------*/
127 /* Vertex buffer definitions for widget rendering. */
131 #define WIDGET_LEN (RECT_LEN + TEXT_LEN)
140 static struct vert vert_buf[WIDGET_MAX * WIDGET_LEN];
141 static GLuint vert_obj = 0;
143 /*---------------------------------------------------------------------------*/
145 static void set_vert(struct vert *v, int x, int y,
146 GLfloat s, GLfloat t, const GLubyte *c)
158 /*---------------------------------------------------------------------------*/
160 static void draw_enable(GLboolean c, GLboolean u, GLboolean p)
162 glBindBuffer(GL_ARRAY_BUFFER, vert_obj);
166 glEnableClientState(GL_COLOR_ARRAY);
167 glColorPointer (4, GL_UNSIGNED_BYTE, sizeof (struct vert),
168 (GLvoid *) offsetof (struct vert, c));
172 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
173 glTexCoordPointer(2, GL_FLOAT, sizeof (struct vert),
174 (GLvoid *) offsetof (struct vert, u));
178 glEnableClientState(GL_VERTEX_ARRAY);
179 glVertexPointer (2, GL_SHORT, sizeof (struct vert),
180 (GLvoid *) offsetof (struct vert, p));
184 static void draw_rect(int id)
186 glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_LEN, RECT_LEN);
189 static void draw_text(int id)
191 glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_LEN + RECT_LEN, TEXT_LEN);
194 static void draw_disable(void)
196 glBindBuffer(GL_ARRAY_BUFFER, 0);
198 glDisableClientState(GL_VERTEX_ARRAY);
199 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
200 glDisableClientState(GL_COLOR_ARRAY);
203 /*---------------------------------------------------------------------------*/
205 static void gui_rect(int id, int x, int y, int w, int h, int f, int r)
207 struct vert *v = vert_buf + id * WIDGET_LEN;
210 /* Generate vertex data for the widget's rounded rectangle. */
217 for (i = 0; i <= n; i++)
219 float a = 0.5f * V_PI * (float) i / (float) n;
220 float s = r * fsinf(a);
221 float c = r * fcosf(a);
224 float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
225 float Yb = y + ((f & GUI_SW) ? (r - s) : 0);
227 set_vert(p++, X, Ya, (X - x) / w, (Ya - y) / h, gui_wht);
228 set_vert(p++, X, Yb, (X - x) / w, (Yb - y) / h, gui_wht);
233 for (i = 0; i <= n; i++)
235 float a = 0.5f * V_PI * (float) i / (float) n;
236 float s = r * fsinf(a);
237 float c = r * fcosf(a);
239 float X = x + w - r + s;
240 float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
241 float Yb = y + ((f & GUI_SE) ? (r - c) : 0);
243 set_vert(p++, X, Ya, (X - x) / w, (Ya - y) / h, gui_wht);
244 set_vert(p++, X, Yb, (X - x) / w, (Yb - y) / h, gui_wht);
247 /* Copy this off to the VBO. */
249 glBindBuffer (GL_ARRAY_BUFFER, vert_obj);
250 glBufferSubData(GL_ARRAY_BUFFER,
251 id * WIDGET_LEN * sizeof (struct vert),
252 RECT_LEN * sizeof (struct vert), v);
255 static void gui_text(int id, int x, int y,
256 int w, int h, const GLubyte *c0, const GLubyte *c1)
258 struct vert *v = vert_buf + id * WIDGET_LEN + RECT_LEN;
260 /* Assume the applied texture size is rect size rounded to power-of-two. */
265 image_size(&W, &H, w, h);
267 if (w > 0 && h > 0 && W > 0 && H > 0)
269 const int d = h / 16; /* Shadow offset */
271 const int ww = ((W - w) % 2) ? w + 1 : w;
272 const int hh = ((H - h) % 2) ? h + 1 : h;
274 const GLfloat s0 = 0.5f * (W - ww) / W;
275 const GLfloat t0 = 0.5f * (H - hh) / H;
276 const GLfloat s1 = 1.0f - s0;
277 const GLfloat t1 = 1.0f - t0;
279 /* Generate vertex data for the colored text and its shadow. */
281 set_vert(v + 0, x + d, y + hh - d, s0, t0, gui_shd);
282 set_vert(v + 1, x + d, y - d, s0, t1, gui_shd);
283 set_vert(v + 2, x + ww + d, y + hh - d, s1, t0, gui_shd);
284 set_vert(v + 3, x + ww + d, y - d, s1, t1, gui_shd);
286 set_vert(v + 4, x, y + hh, s0, t0, c1);
287 set_vert(v + 5, x, y, s0, t1, c0);
288 set_vert(v + 6, x + ww, y + hh, s1, t0, c1);
289 set_vert(v + 7, x + ww, y, s1, t1, c0);
292 else memset(v, 0, TEXT_LEN * sizeof (struct vert));
294 /* Copy this off to the VBO. */
296 glBindBuffer (GL_ARRAY_BUFFER, vert_obj);
297 glBufferSubData(GL_ARRAY_BUFFER,
298 (id * WIDGET_LEN + RECT_LEN) * sizeof (struct vert),
299 TEXT_LEN * sizeof (struct vert), v);
302 /*---------------------------------------------------------------------------*/
304 static const char *pick_font_path(void)
310 if (!fs_exists(path))
312 fprintf(stderr, L_("Font '%s' doesn't exist, trying default font.\n"),
323 int w = config_get_d(CONFIG_WIDTH);
324 int h = config_get_d(CONFIG_HEIGHT);
325 int s = (h < w) ? h : w;
327 /* Initialize font rendering. */
331 const char *fontpath = pick_font_path();
337 memset(widget, 0, sizeof (struct widget) * WIDGET_MAX);
341 if ((fontdata = fs_load(fontpath, &fontdatalen)))
343 fontrwops = SDL_RWFromConstMem(fontdata, fontdatalen);
345 /* Load small, medium, and large typefaces. */
347 font[GUI_SML] = TTF_OpenFontRW(fontrwops, 0, s0);
349 SDL_RWseek(fontrwops, 0, SEEK_SET);
350 font[GUI_MED] = TTF_OpenFontRW(fontrwops, 0, s1);
352 SDL_RWseek(fontrwops, 0, SEEK_SET);
353 font[GUI_LRG] = TTF_OpenFontRW(fontrwops, 0, s2);
355 /* fontrwops remains open. */
361 font[GUI_SML] = NULL;
362 font[GUI_MED] = NULL;
363 font[GUI_LRG] = NULL;
365 fprintf(stderr, L_("Could not load font '%s'.\n"), fontpath);
371 /* Initialize the VBOs. */
373 memset(vert_buf, 0, sizeof (vert_buf));
375 glGenBuffers(1, &vert_obj);
376 glBindBuffer(GL_ARRAY_BUFFER, vert_obj);
377 glBufferData(GL_ARRAY_BUFFER, sizeof (vert_buf), vert_buf, GL_STATIC_DRAW);
378 glBindBuffer(GL_ARRAY_BUFFER, 0);
387 /* Release the VBOs. */
389 if (glIsBuffer(vert_obj))
390 glDeleteBuffers(1, &vert_obj);
392 /* Release any remaining widget texture and display list indices. */
394 for (id = 1; id < WIDGET_MAX; id++)
396 if (glIsTexture(widget[id].image))
397 glDeleteTextures(1, &widget[id].image);
399 widget[id].type = GUI_FREE;
400 widget[id].image = 0;
405 /* Release all loaded fonts and finalize font rendering. */
407 if (font[GUI_LRG]) TTF_CloseFont(font[GUI_LRG]);
408 if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
409 if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
411 if (fontrwops) SDL_RWclose(fontrwops);
412 if (fontdata) free(fontdata);
417 /*---------------------------------------------------------------------------*/
419 static int gui_widget(int pd, int type)
423 /* Find an unused entry in the widget table. */
425 for (id = 1; id < WIDGET_MAX; id++)
426 if (widget[id].type == GUI_FREE)
428 /* Set the type and default properties. */
430 widget[id].type = type;
431 widget[id].token = 0;
432 widget[id].value = 0;
434 widget[id].rect = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
437 widget[id].image = 0;
438 widget[id].color0 = gui_wht;
439 widget[id].color1 = gui_wht;
440 widget[id].scale = 1.0f;
441 widget[id].trunc = TRUNC_NONE;
442 widget[id].text_w = 0;
443 widget[id].text_h = 0;
445 /* Insert the new widget into the parent's widget list. */
450 widget[id].cdr = widget[pd].car;
462 fprintf(stderr, "Out of widget IDs\n");
467 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
468 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
469 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
470 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
471 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
473 /*---------------------------------------------------------------------------*/
480 static struct size gui_measure(const char *text, TTF_Font *font)
482 struct size size = { 0, 0 };
485 TTF_SizeUTF8(font, text, &size.w, &size.h);
490 static char *gui_trunc_head(const char *text,
494 int left, right, mid;
498 right = strlen(text);
500 while (right - left > 1)
502 mid = (left + right) / 2;
504 str = concat_string("...", text + mid, NULL);
506 if (gui_measure(str, font).w <= maxwidth)
514 return concat_string("...", text + right, NULL);
517 static char *gui_trunc_tail(const char *text,
521 int left, right, mid;
525 right = strlen(text);
527 while (right - left > 1)
529 mid = (left + right) / 2;
531 str = malloc(mid + sizeof ("..."));
533 memcpy(str, text, mid);
534 memcpy(str + mid, "...", sizeof ("..."));
536 if (gui_measure(str, font).w <= maxwidth)
544 str = malloc(left + sizeof ("..."));
546 memcpy(str, text, left);
547 memcpy(str + left, "...", sizeof ("..."));
552 static char *gui_truncate(const char *text,
557 if (gui_measure(text, font).w <= maxwidth)
562 case TRUNC_NONE: return strdup(text); break;
563 case TRUNC_HEAD: return gui_trunc_head(text, maxwidth, font); break;
564 case TRUNC_TAIL: return gui_trunc_tail(text, maxwidth, font); break;
570 /*---------------------------------------------------------------------------*/
572 void gui_set_image(int id, const char *file)
574 if (glIsTexture(widget[id].image))
575 glDeleteTextures(1, &widget[id].image);
577 widget[id].image = make_image_from_file(file);
580 void gui_set_label(int id, const char *text)
585 if (glIsTexture(widget[id].image))
586 glDeleteTextures(1, &widget[id].image);
588 text = gui_truncate(text, widget[id].w - radius,
589 font[widget[id].size],
592 widget[id].image = make_image_from_font(NULL, NULL, &w, &h,
593 text, font[widget[id].size]);
594 widget[id].text_w = w;
595 widget[id].text_h = h;
597 gui_text(id, -w / 2, -h / 2, w, h, widget[id].color0, widget[id].color1);
599 free((void *) text); /* Really? */
602 void gui_set_count(int id, int value)
604 widget[id].value = value;
607 void gui_set_clock(int id, int value)
609 widget[id].value = value;
612 void gui_set_color(int id, const GLubyte *c0,
617 c0 = c0 ? c0 : gui_yel;
618 c1 = c1 ? c1 : gui_red;
620 if (widget[id].color0 != c0 || widget[id].color1 != c1)
622 int w = widget[id].text_w;
623 int h = widget[id].text_h;
625 widget[id].color0 = c0;
626 widget[id].color1 = c1;
628 gui_text(id, -w / 2, -h / 2, w, h, c0, c1);
633 void gui_set_multi(int id, const char *text)
637 char s[GUI_LINES][MAXSTR];
642 /* Count available labels. */
644 for (lc = 0, jd = widget[id].car; jd; lc++, jd = widget[jd].cdr);
646 /* Copy each delimited string to a line buffer. */
648 for (p = text, sc = 0; *p && sc < lc; sc++)
650 strncpy(s[sc], p, (n = strcspn(p, "\\")));
653 if (*(p += n) == '\\') p++;
656 /* Set the label value for each line. */
658 for (i = lc - 1, jd = widget[id].car; i >= 0; i--, jd = widget[jd].cdr)
659 gui_set_label(jd, i < sc ? s[i] : "");
662 void gui_set_trunc(int id, enum trunc trunc)
664 widget[id].trunc = trunc;
667 void gui_set_fill(int id)
669 widget[id].type |= GUI_FILL;
672 /*---------------------------------------------------------------------------*/
674 int gui_image(int pd, const char *file, int w, int h)
678 if ((id = gui_widget(pd, GUI_IMAGE)))
680 widget[id].image = make_image_from_file(file);
687 int gui_start(int pd, const char *text, int size, int token, int value)
691 if ((id = gui_state(pd, text, size, token, value)))
697 int gui_state(int pd, const char *text, int size, int token, int value)
701 if ((id = gui_widget(pd, GUI_STATE)))
703 widget[id].image = make_image_from_font(NULL, NULL,
707 widget[id].size = size;
708 widget[id].token = token;
709 widget[id].value = value;
714 int gui_label(int pd, const char *text, int size, int rect, const GLubyte *c0,
719 if ((id = gui_widget(pd, GUI_LABEL)))
721 widget[id].image = make_image_from_font(NULL, NULL,
725 widget[id].size = size;
726 widget[id].color0 = c0 ? c0 : gui_yel;
727 widget[id].color1 = c1 ? c1 : gui_red;
728 widget[id].rect = rect;
733 int gui_count(int pd, int value, int size, int rect)
738 if ((id = gui_widget(pd, GUI_COUNT)))
740 for (i = value; i; i /= 10)
741 widget[id].w += digit_w[size][0];
743 widget[id].h = digit_h[size][0];
744 widget[id].value = value;
745 widget[id].size = size;
746 widget[id].color0 = gui_yel;
747 widget[id].color1 = gui_red;
748 widget[id].rect = rect;
752 return gui_label(pd, "FIXME", size, rect, gui_red, gui_red);
755 int gui_clock(int pd, int value, int size, int rect)
760 if ((id = gui_widget(pd, GUI_CLOCK)))
762 widget[id].w = digit_w[size][0] * 6;
763 widget[id].h = digit_h[size][0];
764 widget[id].value = value;
765 widget[id].size = size;
766 widget[id].color0 = gui_yel;
767 widget[id].color1 = gui_red;
768 widget[id].rect = rect;
772 return gui_label(pd, "FIXME", size, rect, gui_red, gui_red);
775 int gui_space(int pd)
779 if ((id = gui_widget(pd, GUI_SPACE)))
787 /*---------------------------------------------------------------------------*/
790 * Create a multi-line text box using a vertical array of labels.
791 * Parse the text for '\' characters and treat them as line-breaks.
792 * Preserve the rect specification across the entire array.
795 int gui_multi(int pd, const char *text, int size, int rect, const GLubyte *c0,
800 if (text && (id = gui_varray(pd)))
804 char s[GUI_LINES][MAXSTR];
810 /* Copy each delimited string to a line buffer. */
812 for (p = text, j = 0; *p && j < GUI_LINES; j++)
814 strncpy(s[j], p, (n = strcspn(p, "\\")));
818 if (*(p += n) == '\\') p++;
821 /* Set the curves for the first and last lines. */
825 r[0] |= rect & (GUI_NW | GUI_NE);
826 r[j - 1] |= rect & (GUI_SW | GUI_SE);
829 /* Create a label widget for each line. */
831 for (i = 0; i < j; i++)
832 gui_label(id, s[i], size, r[i], c0, c1);
837 /*---------------------------------------------------------------------------*/
839 * The bottom-up pass determines the area of all widgets. The minimum
840 * width and height of a leaf widget is given by the size of its
841 * contents. Array and stack widths and heights are computed
842 * recursively from these.
845 static void gui_widget_up(int id);
847 static void gui_harray_up(int id)
851 /* Find the widest child width and the highest child height. */
853 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
857 if (widget[id].h < widget[jd].h)
858 widget[id].h = widget[jd].h;
859 if (widget[id].w < widget[jd].w)
860 widget[id].w = widget[jd].w;
865 /* Total width is the widest child width times the child count. */
870 static void gui_varray_up(int id)
874 /* Find the widest child width and the highest child height. */
876 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
880 if (widget[id].h < widget[jd].h)
881 widget[id].h = widget[jd].h;
882 if (widget[id].w < widget[jd].w)
883 widget[id].w = widget[jd].w;
888 /* Total height is the highest child height times the child count. */
893 static void gui_hstack_up(int id)
897 /* Find the highest child height. Sum the child widths. */
899 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
903 if (widget[id].h < widget[jd].h)
904 widget[id].h = widget[jd].h;
906 widget[id].w += widget[jd].w;
910 static void gui_vstack_up(int id)
914 /* Find the widest child width. Sum the child heights. */
916 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
920 if (widget[id].w < widget[jd].w)
921 widget[id].w = widget[jd].w;
923 widget[id].h += widget[jd].h;
927 static void gui_button_up(int id)
929 /* Store width and height for later use in text rendering. */
931 widget[id].text_w = widget[id].w;
932 widget[id].text_h = widget[id].h;
934 if (widget[id].w < widget[id].h && widget[id].w > 0)
935 widget[id].w = widget[id].h;
937 /* Padded text elements look a little nicer. */
939 if (widget[id].w < config_get_d(CONFIG_WIDTH))
940 widget[id].w += radius;
941 if (widget[id].h < config_get_d(CONFIG_HEIGHT))
942 widget[id].h += radius;
944 /* A button should be at least wide enough to accomodate the rounding. */
946 if (widget[id].w < 2 * radius)
947 widget[id].w = 2 * radius;
948 if (widget[id].h < 2 * radius)
949 widget[id].h = 2 * radius;
952 static void gui_widget_up(int id)
955 switch (widget[id].type & GUI_TYPE)
957 case GUI_HARRAY: gui_harray_up(id); break;
958 case GUI_VARRAY: gui_varray_up(id); break;
959 case GUI_HSTACK: gui_hstack_up(id); break;
960 case GUI_VSTACK: gui_vstack_up(id); break;
961 case GUI_FILLER: break;
962 default: gui_button_up(id); break;
966 /*---------------------------------------------------------------------------*/
968 * The top-down layout pass distributes available area as computed
969 * during the bottom-up pass. Widgets use their area and position to
970 * initialize rendering state.
973 static void gui_widget_dn(int id, int x, int y, int w, int h);
975 static void gui_harray_dn(int id, int x, int y, int w, int h)
977 int jd, i = 0, c = 0;
984 /* Count children. */
986 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
989 /* Distribute horizontal space evenly to all children. */
991 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
993 int x0 = x + i * w / c;
994 int x1 = x + (i + 1) * w / c;
996 gui_widget_dn(jd, x0, y, x1 - x0, h);
1000 static void gui_varray_dn(int id, int x, int y, int w, int h)
1002 int jd, i = 0, c = 0;
1009 /* Count children. */
1011 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1014 /* Distribute vertical space evenly to all children. */
1016 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
1018 int y0 = y + i * h / c;
1019 int y1 = y + (i + 1) * h / c;
1021 gui_widget_dn(jd, x, y0, w, y1 - y0);
1025 static void gui_hstack_dn(int id, int x, int y, int w, int h)
1027 int jd, jx = x, jw = 0, c = 0;
1034 /* Measure the total width requested by non-filler children. */
1036 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1037 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1039 else if (widget[jd].type & GUI_FILL)
1047 /* Give non-filler children their requested space. */
1048 /* Distribute the rest evenly among filler children. */
1050 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1052 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1053 gui_widget_dn(jd, jx, y, (w - jw) / c, h);
1054 else if (widget[jd].type & GUI_FILL)
1055 gui_widget_dn(jd, jx, y, widget[jd].w + (w - jw) / c, h);
1057 gui_widget_dn(jd, jx, y, widget[jd].w, h);
1063 static void gui_vstack_dn(int id, int x, int y, int w, int h)
1065 int jd, jy = y, jh = 0, c = 0;
1072 /* Measure the total height requested by non-filler children. */
1074 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1075 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1077 else if (widget[jd].type & GUI_FILL)
1085 /* Give non-filler children their requested space. */
1086 /* Distribute the rest evenly among filler children. */
1088 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1090 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1091 gui_widget_dn(jd, x, jy, w, (h - jh) / c);
1092 else if (widget[jd].type & GUI_FILL)
1093 gui_widget_dn(jd, x, jy, w, widget[jd].h + (h - jh) / c);
1095 gui_widget_dn(jd, x, jy, w, widget[jd].h);
1101 static void gui_filler_dn(int id, int x, int y, int w, int h)
1103 /* Filler expands to whatever size it is given. */
1111 static void gui_button_dn(int id, int x, int y, int w, int h)
1113 /* Recall stored width and height for text rendering. */
1115 int W = widget[id].text_w;
1116 int H = widget[id].text_h;
1117 int R = widget[id].rect;
1119 const GLubyte *c0 = widget[id].color0;
1120 const GLubyte *c1 = widget[id].color1;
1127 /* Create vertex array data for the text area and rounded rectangle. */
1129 gui_rect(id, -w / 2, -h / 2, w, h, R, radius);
1130 gui_text(id, -W / 2, -H / 2, W, H, c0, c1);
1133 static void gui_widget_dn(int id, int x, int y, int w, int h)
1136 switch (widget[id].type & GUI_TYPE)
1138 case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
1139 case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
1140 case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
1141 case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
1142 case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
1143 case GUI_SPACE: gui_filler_dn(id, x, y, w, h); break;
1144 default: gui_button_dn(id, x, y, w, h); break;
1148 /*---------------------------------------------------------------------------*/
1150 * During GUI layout, we make a bottom-up pass to determine total area
1151 * requirements for the widget tree. We position this area to the
1152 * sides or center of the screen. Finally, we make a top-down pass to
1153 * distribute this area to each widget.
1156 void gui_layout(int id, int xd, int yd)
1160 int w, W = config_get_d(CONFIG_WIDTH);
1161 int h, H = config_get_d(CONFIG_HEIGHT);
1169 else if (xd > 0) x = (W - w);
1170 else x = (W - w) / 2;
1173 else if (yd > 0) y = (H - h);
1174 else y = (H - h) / 2;
1176 gui_widget_dn(id, x, y, w, h);
1178 /* Hilite the widget under the cursor, if any. */
1180 gui_point(id, -1, -1);
1183 int gui_search(int id, int x, int y)
1187 /* Search the hierarchy for the widget containing the given point. */
1189 if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
1190 widget[id].y <= y && y < widget[id].y + widget[id].h))
1195 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1196 if ((kd = gui_search(jd, x, y)))
1203 * Activate a widget, allowing it to behave as a normal state widget.
1204 * This may be used to create image buttons, or cause an array of
1205 * widgets to behave as a single state widget.
1207 int gui_active(int id, int token, int value)
1209 widget[id].type |= GUI_STATE;
1210 widget[id].token = token;
1211 widget[id].value = value;
1216 int gui_delete(int id)
1220 /* Recursively delete all subwidgets. */
1222 gui_delete(widget[id].cdr);
1223 gui_delete(widget[id].car);
1225 /* Release any GL resources held by this widget. */
1227 if (glIsTexture(widget[id].image))
1228 glDeleteTextures(1, &widget[id].image);
1230 /* Mark this widget unused. */
1232 widget[id].type = GUI_FREE;
1233 widget[id].image = 0;
1240 /*---------------------------------------------------------------------------*/
1242 static void gui_paint_rect(int id, int st)
1244 static const GLfloat back[4][4] = {
1245 { 0.1f, 0.1f, 0.1f, 0.5f }, /* off and inactive */
1246 { 0.5f, 0.5f, 0.5f, 0.8f }, /* off and active */
1247 { 1.0f, 0.7f, 0.3f, 0.5f }, /* on and inactive */
1248 { 1.0f, 0.7f, 0.3f, 0.8f }, /* on and active */
1253 /* Use the widget status to determine the background color. */
1256 i = st | (((widget[id].value) ? 2 : 0) |
1257 ((id == active) ? 1 : 0));
1259 switch (widget[id].type & GUI_TYPE)
1271 /* Recursively paint all subwidgets. */
1273 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1274 gui_paint_rect(jd, i);
1280 /* Draw a leaf's background, colored by widget state. */
1284 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1285 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1287 glColor4fv(back[i]);
1296 /*---------------------------------------------------------------------------*/
1298 static void gui_paint_text(int id);
1300 static void gui_paint_array(int id)
1306 GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1307 GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1308 GLfloat ck = widget[id].scale;
1310 glTranslatef(+cx, +cy, 0.0f);
1311 glScalef(ck, ck, ck);
1312 glTranslatef(-cx, -cy, 0.0f);
1314 /* Recursively paint all subwidgets. */
1316 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1322 static void gui_paint_image(int id)
1324 /* Draw the widget rect, textured using the image. */
1328 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1329 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1331 glScalef(widget[id].scale,
1335 glBindTexture(GL_TEXTURE_2D, widget[id].image);
1336 glColor4ubv(gui_wht);
1342 static void gui_paint_count(int id)
1346 static void gui_paint_clock(int id)
1350 static void gui_paint_label(int id)
1352 /* Draw the widget text box, textured using the glyph. */
1356 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1357 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1359 glScalef(widget[id].scale,
1363 glBindTexture(GL_TEXTURE_2D, widget[id].image);
1369 static void gui_paint_text(int id)
1371 switch (widget[id].type & GUI_TYPE)
1373 case GUI_SPACE: break;
1374 case GUI_FILLER: break;
1375 case GUI_HARRAY: gui_paint_array(id); break;
1376 case GUI_VARRAY: gui_paint_array(id); break;
1377 case GUI_HSTACK: gui_paint_array(id); break;
1378 case GUI_VSTACK: gui_paint_array(id); break;
1379 case GUI_IMAGE: gui_paint_image(id); break;
1380 case GUI_COUNT: gui_paint_count(id); break;
1381 case GUI_CLOCK: gui_paint_clock(id); break;
1382 default: gui_paint_label(id); break;
1386 void gui_paint(int id)
1392 glEnable(GL_COLOR_MATERIAL);
1393 glDisable(GL_LIGHTING);
1394 glDisable(GL_DEPTH_TEST);
1396 draw_enable(GL_FALSE, GL_FALSE, GL_TRUE);
1397 glDisable(GL_TEXTURE_2D);
1398 gui_paint_rect(id, 0);
1400 draw_enable(GL_TRUE, GL_TRUE, GL_TRUE);
1401 glEnable(GL_TEXTURE_2D);
1405 glColor4ubv(gui_wht);
1407 glEnable(GL_DEPTH_TEST);
1408 glEnable(GL_LIGHTING);
1409 glDisable(GL_COLOR_MATERIAL);
1415 /*---------------------------------------------------------------------------*/
1417 void gui_dump(int id, int d)
1425 switch (widget[id].type & GUI_TYPE)
1427 case GUI_HARRAY: type = "harray"; break;
1428 case GUI_VARRAY: type = "varray"; break;
1429 case GUI_HSTACK: type = "hstack"; break;
1430 case GUI_VSTACK: type = "vstack"; break;
1431 case GUI_FILLER: type = "filler"; break;
1432 case GUI_IMAGE: type = "image"; break;
1433 case GUI_LABEL: type = "label"; break;
1434 case GUI_COUNT: type = "count"; break;
1435 case GUI_CLOCK: type = "clock"; break;
1438 for (i = 0; i < d; i++)
1441 printf("%04d %s\n", id, type);
1443 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1444 gui_dump(jd, d + 1);
1448 void gui_pulse(int id, float k)
1450 if (id) widget[id].scale = k;
1453 void gui_timer(int id, float dt)
1459 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1462 if (widget[id].scale - 1.0f < dt)
1463 widget[id].scale = 1.0f;
1465 widget[id].scale -= dt;
1469 int gui_point(int id, int x, int y)
1471 static int x_cache = 0;
1472 static int y_cache = 0;
1476 /* Reuse the last coordinates if (x,y) == (-1,-1) */
1479 return gui_point(id, x_cache, y_cache);
1484 /* Short-circuit check the current active widget. */
1486 jd = gui_search(active, x, y);
1488 /* If not still active, search the hierarchy for a new active widget. */
1491 jd = gui_search(id, x, y);
1493 /* If the active widget has changed, return the new active id. */
1495 if (jd == 0 || jd == active)
1501 void gui_focus(int i)
1511 int gui_token(int id)
1513 return id ? widget[id].token : 0;
1516 int gui_value(int id)
1518 return id ? widget[id].value : 0;
1521 void gui_toggle(int id)
1523 widget[id].value = widget[id].value ? 0 : 1;
1526 /*---------------------------------------------------------------------------*/
1528 static int gui_vert_offset(int id, int jd)
1530 /* Vertical offset between bottom of id and top of jd */
1532 return widget[id].y - (widget[jd].y + widget[jd].h);
1535 static int gui_horz_offset(int id, int jd)
1537 /* Horizontal offset between left of id and right of jd */
1539 return widget[id].x - (widget[jd].x + widget[jd].w);
1542 static int gui_vert_dist(int id, int jd)
1544 /* Vertical distance between the tops of id and jd */
1546 return abs((widget[id].y + widget[id].h) - (widget[jd].y + widget[jd].h));
1549 static int gui_horz_dist(int id, int jd)
1551 /* Horizontal distance between the left sides of id and jd */
1553 return abs(widget[id].x - widget[jd].x);
1556 /*---------------------------------------------------------------------------*/
1558 static int gui_stick_L(int id, int dd)
1561 int o, omin, d, dmin;
1563 /* Find the closest "hot" widget to the left of dd (and inside id) */
1565 if (id && gui_hot(id))
1569 omin = widget[dd].x - widget[id].x + 1;
1570 dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1572 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1574 kd = gui_stick_L(jd, dd);
1578 o = gui_horz_offset(dd, kd);
1579 d = gui_vert_dist(dd, kd);
1581 if (0 <= o && o <= omin && d <= dmin)
1593 static int gui_stick_R(int id, int dd)
1596 int o, omin, d, dmin;
1598 /* Find the closest "hot" widget to the right of dd (and inside id) */
1600 if (id && gui_hot(id))
1604 omin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
1605 dmin = (widget[dd].y + widget[dd].h) + (widget[id].y + widget[id].h);
1607 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1609 kd = gui_stick_R(jd, dd);
1613 o = gui_horz_offset(kd, dd);
1614 d = gui_vert_dist(dd, kd);
1616 if (0 <= o && o <= omin && d <= dmin)
1628 static int gui_stick_D(int id, int dd)
1631 int o, omin, d, dmin;
1633 /* Find the closest "hot" widget below dd (and inside id) */
1635 if (id && gui_hot(id))
1639 omin = widget[dd].y - widget[id].y + 1;
1640 dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1642 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1644 kd = gui_stick_D(jd, dd);
1648 o = gui_vert_offset(dd, kd);
1649 d = gui_horz_dist(dd, kd);
1651 if (0 <= o && o <= omin && d <= dmin)
1663 static int gui_stick_U(int id, int dd)
1666 int o, omin, d, dmin;
1668 /* Find the closest "hot" widget above dd (and inside id) */
1670 if (id && gui_hot(id))
1674 omin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
1675 dmin = (widget[dd].x + widget[dd].w) + (widget[id].x + widget[id].w);
1677 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1679 kd = gui_stick_U(jd, dd);
1683 o = gui_vert_offset(kd, dd);
1684 d = gui_horz_dist(dd, kd);
1686 if (0 <= o && o <= omin && d <= dmin)
1698 /*---------------------------------------------------------------------------*/
1700 static int gui_wrap_L(int id, int dd)
1704 if ((jd = gui_stick_L(id, dd)) == 0)
1705 for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
1711 static int gui_wrap_R(int id, int dd)
1715 if ((jd = gui_stick_R(id, dd)) == 0)
1716 for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
1722 static int gui_wrap_U(int id, int dd)
1726 if ((jd = gui_stick_U(id, dd)) == 0)
1727 for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
1733 static int gui_wrap_D(int id, int dd)
1737 if ((jd = gui_stick_D(id, dd)) == 0)
1738 for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
1744 /*---------------------------------------------------------------------------*/
1746 int gui_stick(int id, int a, float v, int bump)
1753 /* Find a new active widget in the direction of joystick motion. */
1755 if (config_tst_d(CONFIG_JOYSTICK_AXIS_X, a))
1757 if (v < 0) jd = gui_wrap_L(id, active);
1758 if (v > 0) jd = gui_wrap_R(id, active);
1760 else if (config_tst_d(CONFIG_JOYSTICK_AXIS_Y, a))
1762 if (v < 0) jd = gui_wrap_U(id, active);
1763 if (v > 0) jd = gui_wrap_D(id, active);
1766 /* If the active widget has changed, return the new active id. */
1768 if (jd == 0 || jd == active)
1774 /*---------------------------------------------------------------------------*/