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 /* Digit widgets for the HUD. */
107 static int digit_id[3][11];
109 /* Font data access. */
111 static void *fontdata;
112 static int fontdatalen;
113 static SDL_RWops *fontrwops;
115 /*---------------------------------------------------------------------------*/
117 static int gui_hot(int id)
119 return (widget[id].type & GUI_STATE);
122 /*---------------------------------------------------------------------------*/
124 /* Vertex buffer definitions for widget rendering. */
128 #define WIDGET_LEN (RECT_LEN + TEXT_LEN)
137 static struct vert vert_buf[WIDGET_MAX * WIDGET_LEN];
138 static GLuint vert_obj = 0;
140 /*---------------------------------------------------------------------------*/
142 static void set_vert(struct vert *v, int x, int y,
143 GLfloat s, GLfloat t, const GLubyte *c)
155 /*---------------------------------------------------------------------------*/
157 static void draw_enable(GLboolean c, GLboolean u, GLboolean p)
159 glBindBuffer_(GL_ARRAY_BUFFER, vert_obj);
163 glEnableClientState(GL_COLOR_ARRAY);
164 glColorPointer (4, GL_UNSIGNED_BYTE, sizeof (struct vert),
165 (GLvoid *) offsetof (struct vert, c));
169 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
170 glTexCoordPointer(2, GL_FLOAT, sizeof (struct vert),
171 (GLvoid *) offsetof (struct vert, u));
175 glEnableClientState(GL_VERTEX_ARRAY);
176 glVertexPointer (2, GL_SHORT, sizeof (struct vert),
177 (GLvoid *) offsetof (struct vert, p));
181 static void draw_rect(int id)
183 glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_LEN, RECT_LEN);
186 static void draw_text(int id)
188 glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_LEN + RECT_LEN, TEXT_LEN);
191 static void draw_disable(void)
193 glBindBuffer_(GL_ARRAY_BUFFER, 0);
195 glDisableClientState(GL_VERTEX_ARRAY);
196 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
197 glDisableClientState(GL_COLOR_ARRAY);
200 /*---------------------------------------------------------------------------*/
202 static void gui_rect(int id, int x, int y, int w, int h, int f, int r)
204 struct vert *v = vert_buf + id * WIDGET_LEN;
207 /* Generate vertex data for the widget's rounded rectangle. */
214 for (i = 0; i <= n; i++)
216 float a = 0.5f * V_PI * (float) i / (float) n;
217 float s = r * fsinf(a);
218 float c = r * fcosf(a);
221 float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
222 float Yb = y + ((f & GUI_SW) ? (r - s) : 0);
224 set_vert(p++, X, Ya, (X - x) / w, (Ya - y) / h, gui_wht);
225 set_vert(p++, X, Yb, (X - x) / w, (Yb - y) / h, gui_wht);
230 for (i = 0; i <= n; i++)
232 float a = 0.5f * V_PI * (float) i / (float) n;
233 float s = r * fsinf(a);
234 float c = r * fcosf(a);
236 float X = x + w - r + s;
237 float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
238 float Yb = y + ((f & GUI_SE) ? (r - c) : 0);
240 set_vert(p++, X, Ya, (X - x) / w, (Ya - y) / h, gui_wht);
241 set_vert(p++, X, Yb, (X - x) / w, (Yb - y) / h, gui_wht);
244 /* Copy this off to the VBO. */
246 glBindBuffer_ (GL_ARRAY_BUFFER, vert_obj);
247 glBufferSubData_(GL_ARRAY_BUFFER,
248 id * WIDGET_LEN * sizeof (struct vert),
249 RECT_LEN * sizeof (struct vert), v);
252 static void gui_text(int id, int x, int y,
253 int w, int h, const GLubyte *c0, const GLubyte *c1)
255 struct vert *v = vert_buf + id * WIDGET_LEN + RECT_LEN;
257 /* Assume the applied texture size is rect size rounded to power-of-two. */
262 image_size(&W, &H, w, h);
264 if (w > 0 && h > 0 && W > 0 && H > 0)
266 const int d = h / 16; /* Shadow offset */
268 const int ww = ((W - w) % 2) ? w + 1 : w;
269 const int hh = ((H - h) % 2) ? h + 1 : h;
271 const GLfloat s0 = 0.5f * (W - ww) / W;
272 const GLfloat t0 = 0.5f * (H - hh) / H;
273 const GLfloat s1 = 1.0f - s0;
274 const GLfloat t1 = 1.0f - t0;
276 /* Generate vertex data for the colored text and its shadow. */
278 set_vert(v + 0, x + d, y + hh - d, s0, t0, gui_shd);
279 set_vert(v + 1, x + d, y - d, s0, t1, gui_shd);
280 set_vert(v + 2, x + ww + d, y + hh - d, s1, t0, gui_shd);
281 set_vert(v + 3, x + ww + d, y - d, s1, t1, gui_shd);
283 set_vert(v + 4, x, y + hh, s0, t0, c1);
284 set_vert(v + 5, x, y, s0, t1, c0);
285 set_vert(v + 6, x + ww, y + hh, s1, t0, c1);
286 set_vert(v + 7, x + ww, y, s1, t1, c0);
289 else memset(v, 0, TEXT_LEN * sizeof (struct vert));
291 /* Copy this off to the VBO. */
293 glBindBuffer_ (GL_ARRAY_BUFFER, vert_obj);
294 glBufferSubData_(GL_ARRAY_BUFFER,
295 (id * WIDGET_LEN + RECT_LEN) * sizeof (struct vert),
296 TEXT_LEN * sizeof (struct vert), v);
299 /*---------------------------------------------------------------------------*/
301 static const char *pick_font_path(void)
307 if (!fs_exists(path))
309 fprintf(stderr, L_("Font '%s' doesn't exist, trying default font.\n"),
320 int w = config_get_d(CONFIG_WIDTH);
321 int h = config_get_d(CONFIG_HEIGHT);
322 int s = (h < w) ? h : w;
325 /* Initialize font rendering. */
329 const char *fontpath = pick_font_path();
335 memset(widget, 0, sizeof (struct widget) * WIDGET_MAX);
339 if ((fontdata = fs_load(fontpath, &fontdatalen)))
341 fontrwops = SDL_RWFromConstMem(fontdata, fontdatalen);
343 /* Load small, medium, and large typefaces. */
345 font[GUI_SML] = TTF_OpenFontRW(fontrwops, 0, s0);
347 SDL_RWseek(fontrwops, 0, SEEK_SET);
348 font[GUI_MED] = TTF_OpenFontRW(fontrwops, 0, s1);
350 SDL_RWseek(fontrwops, 0, SEEK_SET);
351 font[GUI_LRG] = TTF_OpenFontRW(fontrwops, 0, s2);
353 /* fontrwops remains open. */
359 font[GUI_SML] = NULL;
360 font[GUI_MED] = NULL;
361 font[GUI_LRG] = NULL;
363 fprintf(stderr, L_("Could not load font '%s'.\n"), fontpath);
369 /* Initialize the VBOs. */
371 memset(vert_buf, 0, sizeof (vert_buf));
373 glGenBuffers_(1, &vert_obj);
374 glBindBuffer_(GL_ARRAY_BUFFER, vert_obj);
375 glBufferData_(GL_ARRAY_BUFFER, sizeof (vert_buf), vert_buf, GL_STATIC_DRAW);
376 glBindBuffer_(GL_ARRAY_BUFFER, 0);
378 /* Cache digit glyphs for HUD rendering. */
380 for (i = 0; i < 3; i++)
382 digit_id[i][ 0] = gui_label(0, "0", i, 0, 0, 0);
383 digit_id[i][ 1] = gui_label(0, "1", i, 0, 0, 0);
384 digit_id[i][ 2] = gui_label(0, "2", i, 0, 0, 0);
385 digit_id[i][ 3] = gui_label(0, "3", i, 0, 0, 0);
386 digit_id[i][ 4] = gui_label(0, "4", i, 0, 0, 0);
387 digit_id[i][ 5] = gui_label(0, "5", i, 0, 0, 0);
388 digit_id[i][ 6] = gui_label(0, "6", i, 0, 0, 0);
389 digit_id[i][ 7] = gui_label(0, "7", i, 0, 0, 0);
390 digit_id[i][ 8] = gui_label(0, "8", i, 0, 0, 0);
391 digit_id[i][ 9] = gui_label(0, "9", i, 0, 0, 0);
392 digit_id[i][10] = gui_label(0, ":", i, 0, 0, 0);
395 for (i = 0; i < 3; i++)
396 for (j = 0; j < 11; ++j)
397 gui_layout(digit_id[i][j], 0, 0);
406 /* Release the VBOs. */
408 if (glIsBuffer_(vert_obj))
409 glDeleteBuffers_(1, &vert_obj);
411 /* Release any remaining widget texture and display list indices. */
413 for (id = 1; id < WIDGET_MAX; id++)
415 if (glIsTexture(widget[id].image))
416 glDeleteTextures(1, &widget[id].image);
418 widget[id].type = GUI_FREE;
419 widget[id].image = 0;
424 /* Release all loaded fonts and finalize font rendering. */
426 if (font[GUI_LRG]) TTF_CloseFont(font[GUI_LRG]);
427 if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
428 if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
430 if (fontrwops) SDL_RWclose(fontrwops);
431 if (fontdata) free(fontdata);
436 /*---------------------------------------------------------------------------*/
438 static int gui_widget(int pd, int type)
442 /* Find an unused entry in the widget table. */
444 for (id = 1; id < WIDGET_MAX; id++)
445 if (widget[id].type == GUI_FREE)
447 /* Set the type and default properties. */
449 widget[id].type = type;
450 widget[id].token = 0;
451 widget[id].value = 0;
453 widget[id].rect = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
456 widget[id].image = 0;
457 widget[id].color0 = gui_wht;
458 widget[id].color1 = gui_wht;
459 widget[id].scale = 1.0f;
460 widget[id].trunc = TRUNC_NONE;
461 widget[id].text_w = 0;
462 widget[id].text_h = 0;
464 /* Insert the new widget into the parent's widget list. */
469 widget[id].cdr = widget[pd].car;
481 fprintf(stderr, "Out of widget IDs\n");
486 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
487 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
488 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
489 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
490 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
492 /*---------------------------------------------------------------------------*/
499 static struct size gui_measure(const char *text, TTF_Font *font)
501 struct size size = { 0, 0 };
504 TTF_SizeUTF8(font, text, &size.w, &size.h);
509 static char *gui_trunc_head(const char *text,
513 int left, right, mid;
517 right = strlen(text);
519 while (right - left > 1)
521 mid = (left + right) / 2;
523 str = concat_string("...", text + mid, NULL);
525 if (gui_measure(str, font).w <= maxwidth)
533 return concat_string("...", text + right, NULL);
536 static char *gui_trunc_tail(const char *text,
540 int left, right, mid;
544 right = strlen(text);
546 while (right - left > 1)
548 mid = (left + right) / 2;
550 str = malloc(mid + sizeof ("..."));
552 memcpy(str, text, mid);
553 memcpy(str + mid, "...", sizeof ("..."));
555 if (gui_measure(str, font).w <= maxwidth)
563 str = malloc(left + sizeof ("..."));
565 memcpy(str, text, left);
566 memcpy(str + left, "...", sizeof ("..."));
571 static char *gui_truncate(const char *text,
576 if (gui_measure(text, font).w <= maxwidth)
581 case TRUNC_NONE: return strdup(text); break;
582 case TRUNC_HEAD: return gui_trunc_head(text, maxwidth, font); break;
583 case TRUNC_TAIL: return gui_trunc_tail(text, maxwidth, font); break;
589 /*---------------------------------------------------------------------------*/
591 void gui_set_image(int id, const char *file)
593 if (glIsTexture(widget[id].image))
594 glDeleteTextures(1, &widget[id].image);
596 widget[id].image = make_image_from_file(file);
599 void gui_set_label(int id, const char *text)
604 if (glIsTexture(widget[id].image))
605 glDeleteTextures(1, &widget[id].image);
607 text = gui_truncate(text, widget[id].w - radius,
608 font[widget[id].size],
611 widget[id].image = make_image_from_font(NULL, NULL, &w, &h,
612 text, font[widget[id].size]);
613 widget[id].text_w = w;
614 widget[id].text_h = h;
616 gui_text(id, -w / 2, -h / 2, w, h, widget[id].color0, widget[id].color1);
618 free((void *) text); /* Really? */
621 void gui_set_count(int id, int value)
623 widget[id].value = value;
626 void gui_set_clock(int id, int value)
628 widget[id].value = value;
631 void gui_set_color(int id, const GLubyte *c0,
636 c0 = c0 ? c0 : gui_yel;
637 c1 = c1 ? c1 : gui_red;
639 if (widget[id].color0 != c0 || widget[id].color1 != c1)
641 int w = widget[id].text_w;
642 int h = widget[id].text_h;
644 widget[id].color0 = c0;
645 widget[id].color1 = c1;
647 gui_text(id, -w / 2, -h / 2, w, h, c0, c1);
652 void gui_set_multi(int id, const char *text)
656 char s[GUI_LINES][MAXSTR];
661 /* Count available labels. */
663 for (lc = 0, jd = widget[id].car; jd; lc++, jd = widget[jd].cdr);
665 /* Copy each delimited string to a line buffer. */
667 for (p = text, sc = 0; *p && sc < lc; sc++)
669 strncpy(s[sc], p, (n = strcspn(p, "\\")));
672 if (*(p += n) == '\\') p++;
675 /* Set the label value for each line. */
677 for (i = lc - 1, jd = widget[id].car; i >= 0; i--, jd = widget[jd].cdr)
678 gui_set_label(jd, i < sc ? s[i] : "");
681 void gui_set_trunc(int id, enum trunc trunc)
683 widget[id].trunc = trunc;
686 void gui_set_fill(int id)
688 widget[id].type |= GUI_FILL;
691 /*---------------------------------------------------------------------------*/
693 int gui_image(int pd, const char *file, int w, int h)
697 if ((id = gui_widget(pd, GUI_IMAGE)))
699 widget[id].image = make_image_from_file(file);
706 int gui_start(int pd, const char *text, int size, int token, int value)
710 if ((id = gui_state(pd, text, size, token, value)))
716 int gui_state(int pd, const char *text, int size, int token, int value)
720 if ((id = gui_widget(pd, GUI_STATE)))
722 widget[id].image = make_image_from_font(NULL, NULL,
726 widget[id].size = size;
727 widget[id].token = token;
728 widget[id].value = value;
733 int gui_label(int pd, const char *text, int size, int rect, const GLubyte *c0,
738 if ((id = gui_widget(pd, GUI_LABEL)))
740 widget[id].image = make_image_from_font(NULL, NULL,
744 widget[id].size = size;
745 widget[id].color0 = c0 ? c0 : gui_yel;
746 widget[id].color1 = c1 ? c1 : gui_red;
747 widget[id].rect = rect;
752 int gui_count(int pd, int value, int size, int rect)
756 if ((id = gui_widget(pd, GUI_COUNT)))
758 for (i = value; i; i /= 10)
759 widget[id].w += widget[digit_id[size][0]].text_w;
761 widget[id].h = widget[digit_id[size][0]].text_h;
762 widget[id].value = value;
763 widget[id].size = size;
764 widget[id].color0 = gui_yel;
765 widget[id].color1 = gui_red;
766 widget[id].rect = rect;
771 int gui_clock(int pd, int value, int size, int rect)
775 if ((id = gui_widget(pd, GUI_CLOCK)))
777 widget[id].w = widget[digit_id[size][0]].text_w * 6;
778 widget[id].h = widget[digit_id[size][0]].text_h;
779 widget[id].value = value;
780 widget[id].size = size;
781 widget[id].color0 = gui_yel;
782 widget[id].color1 = gui_red;
783 widget[id].rect = rect;
788 int gui_space(int pd)
792 if ((id = gui_widget(pd, GUI_SPACE)))
800 /*---------------------------------------------------------------------------*/
803 * Create a multi-line text box using a vertical array of labels.
804 * Parse the text for '\' characters and treat them as line-breaks.
805 * Preserve the rect specification across the entire array.
808 int gui_multi(int pd, const char *text, int size, int rect, const GLubyte *c0,
813 if (text && (id = gui_varray(pd)))
817 char s[GUI_LINES][MAXSTR];
823 /* Copy each delimited string to a line buffer. */
825 for (p = text, j = 0; *p && j < GUI_LINES; j++)
827 strncpy(s[j], p, (n = strcspn(p, "\\")));
831 if (*(p += n) == '\\') p++;
834 /* Set the curves for the first and last lines. */
838 r[0] |= rect & (GUI_NW | GUI_NE);
839 r[j - 1] |= rect & (GUI_SW | GUI_SE);
842 /* Create a label widget for each line. */
844 for (i = 0; i < j; i++)
845 gui_label(id, s[i], size, r[i], c0, c1);
850 /*---------------------------------------------------------------------------*/
852 * The bottom-up pass determines the area of all widgets. The minimum
853 * width and height of a leaf widget is given by the size of its
854 * contents. Array and stack widths and heights are computed
855 * recursively from these.
858 static void gui_widget_up(int id);
860 static void gui_harray_up(int id)
864 /* Find the widest child width and the highest child height. */
866 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
870 if (widget[id].h < widget[jd].h)
871 widget[id].h = widget[jd].h;
872 if (widget[id].w < widget[jd].w)
873 widget[id].w = widget[jd].w;
878 /* Total width is the widest child width times the child count. */
883 static void gui_varray_up(int id)
887 /* Find the widest child width and the highest child height. */
889 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
893 if (widget[id].h < widget[jd].h)
894 widget[id].h = widget[jd].h;
895 if (widget[id].w < widget[jd].w)
896 widget[id].w = widget[jd].w;
901 /* Total height is the highest child height times the child count. */
906 static void gui_hstack_up(int id)
910 /* Find the highest child height. Sum the child widths. */
912 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
916 if (widget[id].h < widget[jd].h)
917 widget[id].h = widget[jd].h;
919 widget[id].w += widget[jd].w;
923 static void gui_vstack_up(int id)
927 /* Find the widest child width. Sum the child heights. */
929 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
933 if (widget[id].w < widget[jd].w)
934 widget[id].w = widget[jd].w;
936 widget[id].h += widget[jd].h;
940 static void gui_button_up(int id)
942 /* Store width and height for later use in text rendering. */
944 widget[id].text_w = widget[id].w;
945 widget[id].text_h = widget[id].h;
947 if (widget[id].w < widget[id].h && widget[id].w > 0)
948 widget[id].w = widget[id].h;
950 /* Padded text elements look a little nicer. */
952 if (widget[id].w < config_get_d(CONFIG_WIDTH))
953 widget[id].w += radius;
954 if (widget[id].h < config_get_d(CONFIG_HEIGHT))
955 widget[id].h += radius;
957 /* A button should be at least wide enough to accomodate the rounding. */
959 if (widget[id].w < 2 * radius)
960 widget[id].w = 2 * radius;
961 if (widget[id].h < 2 * radius)
962 widget[id].h = 2 * radius;
965 static void gui_widget_up(int id)
968 switch (widget[id].type & GUI_TYPE)
970 case GUI_HARRAY: gui_harray_up(id); break;
971 case GUI_VARRAY: gui_varray_up(id); break;
972 case GUI_HSTACK: gui_hstack_up(id); break;
973 case GUI_VSTACK: gui_vstack_up(id); break;
974 case GUI_FILLER: break;
975 default: gui_button_up(id); break;
979 /*---------------------------------------------------------------------------*/
981 * The top-down layout pass distributes available area as computed
982 * during the bottom-up pass. Widgets use their area and position to
983 * initialize rendering state.
986 static void gui_widget_dn(int id, int x, int y, int w, int h);
988 static void gui_harray_dn(int id, int x, int y, int w, int h)
990 int jd, i = 0, c = 0;
997 /* Count children. */
999 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1002 /* Distribute horizontal space evenly to all children. */
1004 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
1006 int x0 = x + i * w / c;
1007 int x1 = x + (i + 1) * w / c;
1009 gui_widget_dn(jd, x0, y, x1 - x0, h);
1013 static void gui_varray_dn(int id, int x, int y, int w, int h)
1015 int jd, i = 0, c = 0;
1022 /* Count children. */
1024 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1027 /* Distribute vertical space evenly to all children. */
1029 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
1031 int y0 = y + i * h / c;
1032 int y1 = y + (i + 1) * h / c;
1034 gui_widget_dn(jd, x, y0, w, y1 - y0);
1038 static void gui_hstack_dn(int id, int x, int y, int w, int h)
1040 int jd, jx = x, jw = 0, c = 0;
1047 /* Measure the total width requested by non-filler children. */
1049 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1050 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1052 else if (widget[jd].type & GUI_FILL)
1060 /* Give non-filler children their requested space. */
1061 /* Distribute the rest evenly among filler children. */
1063 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1065 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1066 gui_widget_dn(jd, jx, y, (w - jw) / c, h);
1067 else if (widget[jd].type & GUI_FILL)
1068 gui_widget_dn(jd, jx, y, widget[jd].w + (w - jw) / c, h);
1070 gui_widget_dn(jd, jx, y, widget[jd].w, h);
1076 static void gui_vstack_dn(int id, int x, int y, int w, int h)
1078 int jd, jy = y, jh = 0, c = 0;
1085 /* Measure the total height requested by non-filler children. */
1087 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1088 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1090 else if (widget[jd].type & GUI_FILL)
1098 /* Give non-filler children their requested space. */
1099 /* Distribute the rest evenly among filler children. */
1101 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1103 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
1104 gui_widget_dn(jd, x, jy, w, (h - jh) / c);
1105 else if (widget[jd].type & GUI_FILL)
1106 gui_widget_dn(jd, x, jy, w, widget[jd].h + (h - jh) / c);
1108 gui_widget_dn(jd, x, jy, w, widget[jd].h);
1114 static void gui_filler_dn(int id, int x, int y, int w, int h)
1116 /* Filler expands to whatever size it is given. */
1124 static void gui_button_dn(int id, int x, int y, int w, int h)
1126 /* Recall stored width and height for text rendering. */
1128 int W = widget[id].text_w;
1129 int H = widget[id].text_h;
1130 int R = widget[id].rect;
1132 const GLubyte *c0 = widget[id].color0;
1133 const GLubyte *c1 = widget[id].color1;
1140 /* Create vertex array data for the text area and rounded rectangle. */
1142 gui_rect(id, -w / 2, -h / 2, w, h, R, radius);
1143 gui_text(id, -W / 2, -H / 2, W, H, c0, c1);
1146 static void gui_widget_dn(int id, int x, int y, int w, int h)
1149 switch (widget[id].type & GUI_TYPE)
1151 case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
1152 case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
1153 case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
1154 case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
1155 case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
1156 case GUI_SPACE: gui_filler_dn(id, x, y, w, h); break;
1157 default: gui_button_dn(id, x, y, w, h); break;
1161 /*---------------------------------------------------------------------------*/
1163 * During GUI layout, we make a bottom-up pass to determine total area
1164 * requirements for the widget tree. We position this area to the
1165 * sides or center of the screen. Finally, we make a top-down pass to
1166 * distribute this area to each widget.
1169 void gui_layout(int id, int xd, int yd)
1173 int w, W = config_get_d(CONFIG_WIDTH);
1174 int h, H = config_get_d(CONFIG_HEIGHT);
1182 else if (xd > 0) x = (W - w);
1183 else x = (W - w) / 2;
1186 else if (yd > 0) y = (H - h);
1187 else y = (H - h) / 2;
1189 gui_widget_dn(id, x, y, w, h);
1191 /* Hilite the widget under the cursor, if any. */
1193 gui_point(id, -1, -1);
1196 int gui_search(int id, int x, int y)
1200 /* Search the hierarchy for the widget containing the given point. */
1202 if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
1203 widget[id].y <= y && y < widget[id].y + widget[id].h))
1208 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1209 if ((kd = gui_search(jd, x, y)))
1216 * Activate a widget, allowing it to behave as a normal state widget.
1217 * This may be used to create image buttons, or cause an array of
1218 * widgets to behave as a single state widget.
1220 int gui_active(int id, int token, int value)
1222 widget[id].type |= GUI_STATE;
1223 widget[id].token = token;
1224 widget[id].value = value;
1229 int gui_delete(int id)
1233 /* Recursively delete all subwidgets. */
1235 gui_delete(widget[id].cdr);
1236 gui_delete(widget[id].car);
1238 /* Release any GL resources held by this widget. */
1240 if (glIsTexture(widget[id].image))
1241 glDeleteTextures(1, &widget[id].image);
1243 /* Mark this widget unused. */
1245 widget[id].type = GUI_FREE;
1246 widget[id].image = 0;
1253 /*---------------------------------------------------------------------------*/
1255 static void gui_paint_rect(int id, int st)
1257 static const GLfloat back[4][4] = {
1258 { 0.1f, 0.1f, 0.1f, 0.5f }, /* off and inactive */
1259 { 0.5f, 0.5f, 0.5f, 0.8f }, /* off and active */
1260 { 1.0f, 0.7f, 0.3f, 0.5f }, /* on and inactive */
1261 { 1.0f, 0.7f, 0.3f, 0.8f }, /* on and active */
1266 /* Use the widget status to determine the background color. */
1269 i = st | (((widget[id].value) ? 2 : 0) |
1270 ((id == active) ? 1 : 0));
1272 switch (widget[id].type & GUI_TYPE)
1284 /* Recursively paint all subwidgets. */
1286 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1287 gui_paint_rect(jd, i);
1293 /* Draw a leaf's background, colored by widget state. */
1297 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1298 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1300 glColor4f(back[i][0], back[i][1], back[i][2], back[i][3]);
1309 /*---------------------------------------------------------------------------*/
1311 static void gui_paint_text(int id);
1313 static void gui_paint_array(int id)
1319 GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1320 GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1321 GLfloat ck = widget[id].scale;
1323 if (1.0 < ck || ck < 1.0)
1325 glTranslatef(+cx, +cy, 0.0f);
1326 glScalef(ck, ck, ck);
1327 glTranslatef(-cx, -cy, 0.0f);
1330 /* Recursively paint all subwidgets. */
1332 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1338 static void gui_paint_image(int id)
1340 /* Draw the widget rect, textured using the image. */
1344 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1345 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1347 glScalef(widget[id].scale,
1351 glBindTexture(GL_TEXTURE_2D, widget[id].image);
1352 glColor4ub(gui_wht[0], gui_wht[1], gui_wht[2], gui_wht[3]);
1358 static void gui_paint_count(int id)
1360 int j, i = widget[id].size;
1364 /* Translate to the widget center, and apply the pulse scale. */
1366 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1367 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1369 glScalef(widget[id].scale,
1373 if (widget[id].value > 0)
1375 /* Translate right by half the total width of the rendered value. */
1377 GLfloat w = -widget[digit_id[i][0]].text_w * 0.5f;
1379 for (j = widget[id].value; j; j /= 10)
1380 w += widget[digit_id[i][j % 10]].text_w * 0.5f;
1382 glTranslatef(w, 0.0f, 0.0f);
1384 /* Render each digit, moving left after each. */
1386 for (j = widget[id].value; j; j /= 10)
1388 int id = digit_id[i][j % 10];
1390 glBindTexture(GL_TEXTURE_2D, widget[id].image);
1392 glTranslatef((GLfloat) -widget[id].text_w, 0.0f, 0.0f);
1395 else if (widget[id].value == 0)
1397 /* If the value is zero, just display a zero in place. */
1399 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][0]].image);
1400 draw_text(digit_id[i][0]);
1406 static void gui_paint_clock(int id)
1408 int i = widget[id].size;
1409 int mt = (widget[id].value / 6000) / 10;
1410 int mo = (widget[id].value / 6000) % 10;
1411 int st = ((widget[id].value % 6000) / 100) / 10;
1412 int so = ((widget[id].value % 6000) / 100) % 10;
1413 int ht = ((widget[id].value % 6000) % 100) / 10;
1414 int ho = ((widget[id].value % 6000) % 100) % 10;
1416 GLfloat dx_large = (GLfloat) widget[digit_id[i][0]].text_w;
1417 GLfloat dx_small = (GLfloat) widget[digit_id[i][0]].text_w * 0.75f;
1419 if (widget[id].value < 0)
1424 /* Translate to the widget center, and apply the pulse scale. */
1426 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1427 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1429 glScalef(widget[id].scale,
1433 /* Translate left by half the total width of the rendered value. */
1436 glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1438 glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1440 /* Render the minutes counter. */
1444 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][mt]].image);
1445 draw_text(digit_id[i][mt]);
1446 glTranslatef(dx_large, 0.0f, 0.0f);
1449 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][mo]].image);
1450 draw_text(digit_id[i][mo]);
1451 glTranslatef(dx_small, 0.0f, 0.0f);
1453 /* Render the colon. */
1455 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][10]].image);
1456 draw_text(digit_id[i][10]);
1457 glTranslatef(dx_small, 0.0f, 0.0f);
1459 /* Render the seconds counter. */
1461 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][st]].image);
1462 draw_text(digit_id[i][st]);
1463 glTranslatef(dx_large, 0.0f, 0.0f);
1465 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][so]].image);
1466 draw_text(digit_id[i][so]);
1467 glTranslatef(dx_small, 0.0f, 0.0f);
1469 /* Render hundredths counter half size. */
1471 glScalef(0.5f, 0.5f, 1.0f);
1473 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][ht]].image);
1474 draw_text(digit_id[i][ht]);
1475 glTranslatef(dx_large, 0.0f, 0.0f);
1477 glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][ho]].image);
1478 draw_text(digit_id[i][ho]);
1483 static void gui_paint_label(int id)
1485 /* Draw the widget text box, textured using the glyph. */
1489 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1490 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1492 glScalef(widget[id].scale,
1496 glBindTexture(GL_TEXTURE_2D, widget[id].image);
1502 static void gui_paint_text(int id)
1504 switch (widget[id].type & GUI_TYPE)
1506 case GUI_SPACE: break;
1507 case GUI_FILLER: break;
1508 case GUI_HARRAY: gui_paint_array(id); break;
1509 case GUI_VARRAY: gui_paint_array(id); break;
1510 case GUI_HSTACK: gui_paint_array(id); break;
1511 case GUI_VSTACK: gui_paint_array(id); break;
1512 case GUI_IMAGE: gui_paint_image(id); break;
1513 case GUI_COUNT: gui_paint_count(id); break;
1514 case GUI_CLOCK: gui_paint_clock(id); break;
1515 default: gui_paint_label(id); break;
1519 void gui_paint(int id)
1525 glEnable(GL_COLOR_MATERIAL);
1526 glDisable(GL_LIGHTING);
1527 glDisable(GL_DEPTH_TEST);
1529 draw_enable(GL_FALSE, GL_FALSE, GL_TRUE);
1530 glDisable(GL_TEXTURE_2D);
1531 gui_paint_rect(id, 0);
1533 draw_enable(GL_TRUE, GL_TRUE, GL_TRUE);
1534 glEnable(GL_TEXTURE_2D);
1538 glColor4ub(gui_wht[0], gui_wht[1], gui_wht[2], gui_wht[3]);
1540 glEnable(GL_DEPTH_TEST);
1541 glEnable(GL_LIGHTING);
1542 glDisable(GL_COLOR_MATERIAL);
1548 /*---------------------------------------------------------------------------*/
1550 void gui_dump(int id, int d)
1558 switch (widget[id].type & GUI_TYPE)
1560 case GUI_HARRAY: type = "harray"; break;
1561 case GUI_VARRAY: type = "varray"; break;
1562 case GUI_HSTACK: type = "hstack"; break;
1563 case GUI_VSTACK: type = "vstack"; break;
1564 case GUI_FILLER: type = "filler"; break;
1565 case GUI_IMAGE: type = "image"; break;
1566 case GUI_LABEL: type = "label"; break;
1567 case GUI_COUNT: type = "count"; break;
1568 case GUI_CLOCK: type = "clock"; break;
1571 for (i = 0; i < d; i++)
1574 printf("%04d %s\n", id, type);
1576 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1577 gui_dump(jd, d + 1);
1581 void gui_pulse(int id, float k)
1583 if (id) widget[id].scale = k;
1586 void gui_timer(int id, float dt)
1592 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1595 if (widget[id].scale - 1.0f < dt)
1596 widget[id].scale = 1.0f;
1598 widget[id].scale -= dt;
1602 int gui_point(int id, int x, int y)
1604 static int x_cache = 0;
1605 static int y_cache = 0;
1609 /* Reuse the last coordinates if (x,y) == (-1,-1) */
1612 return gui_point(id, x_cache, y_cache);
1617 /* Short-circuit check the current active widget. */
1619 jd = gui_search(active, x, y);
1621 /* If not still active, search the hierarchy for a new active widget. */
1624 jd = gui_search(id, x, y);
1626 /* If the active widget has changed, return the new active id. */
1628 if (jd == 0 || jd == active)
1634 void gui_focus(int i)
1644 int gui_token(int id)
1646 return id ? widget[id].token : 0;
1649 int gui_value(int id)
1651 return id ? widget[id].value : 0;
1654 void gui_toggle(int id)
1656 widget[id].value = widget[id].value ? 0 : 1;
1659 /*---------------------------------------------------------------------------*/
1661 static int gui_vert_offset(int id, int jd)
1663 /* Vertical offset between bottom of id and top of jd */
1665 return widget[id].y - (widget[jd].y + widget[jd].h);
1668 static int gui_horz_offset(int id, int jd)
1670 /* Horizontal offset between left of id and right of jd */
1672 return widget[id].x - (widget[jd].x + widget[jd].w);
1675 static int gui_vert_dist(int id, int jd)
1677 /* Vertical distance between the tops of id and jd */
1679 return abs((widget[id].y + widget[id].h) - (widget[jd].y + widget[jd].h));
1682 static int gui_horz_dist(int id, int jd)
1684 /* Horizontal distance between the left sides of id and jd */
1686 return abs(widget[id].x - widget[jd].x);
1689 /*---------------------------------------------------------------------------*/
1691 static int gui_stick_L(int id, int dd)
1694 int o, omin, d, dmin;
1696 /* Find the closest "hot" widget to the left of dd (and inside id) */
1698 if (id && gui_hot(id))
1702 omin = widget[dd].x - widget[id].x + 1;
1703 dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1705 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1707 kd = gui_stick_L(jd, dd);
1711 o = gui_horz_offset(dd, kd);
1712 d = gui_vert_dist(dd, kd);
1714 if (0 <= o && o <= omin && d <= dmin)
1726 static int gui_stick_R(int id, int dd)
1729 int o, omin, d, dmin;
1731 /* Find the closest "hot" widget to the right of dd (and inside id) */
1733 if (id && gui_hot(id))
1737 omin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
1738 dmin = (widget[dd].y + widget[dd].h) + (widget[id].y + widget[id].h);
1740 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1742 kd = gui_stick_R(jd, dd);
1746 o = gui_horz_offset(kd, dd);
1747 d = gui_vert_dist(dd, kd);
1749 if (0 <= o && o <= omin && d <= dmin)
1761 static int gui_stick_D(int id, int dd)
1764 int o, omin, d, dmin;
1766 /* Find the closest "hot" widget below dd (and inside id) */
1768 if (id && gui_hot(id))
1772 omin = widget[dd].y - widget[id].y + 1;
1773 dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1775 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1777 kd = gui_stick_D(jd, dd);
1781 o = gui_vert_offset(dd, kd);
1782 d = gui_horz_dist(dd, kd);
1784 if (0 <= o && o <= omin && d <= dmin)
1796 static int gui_stick_U(int id, int dd)
1799 int o, omin, d, dmin;
1801 /* Find the closest "hot" widget above dd (and inside id) */
1803 if (id && gui_hot(id))
1807 omin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
1808 dmin = (widget[dd].x + widget[dd].w) + (widget[id].x + widget[id].w);
1810 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1812 kd = gui_stick_U(jd, dd);
1816 o = gui_vert_offset(kd, dd);
1817 d = gui_horz_dist(dd, kd);
1819 if (0 <= o && o <= omin && d <= dmin)
1831 /*---------------------------------------------------------------------------*/
1833 static int gui_wrap_L(int id, int dd)
1837 if ((jd = gui_stick_L(id, dd)) == 0)
1838 for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
1844 static int gui_wrap_R(int id, int dd)
1848 if ((jd = gui_stick_R(id, dd)) == 0)
1849 for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
1855 static int gui_wrap_U(int id, int dd)
1859 if ((jd = gui_stick_U(id, dd)) == 0)
1860 for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
1866 static int gui_wrap_D(int id, int dd)
1870 if ((jd = gui_stick_D(id, dd)) == 0)
1871 for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
1877 /*---------------------------------------------------------------------------*/
1879 int gui_stick(int id, int a, float v, int bump)
1886 /* Find a new active widget in the direction of joystick motion. */
1888 if (config_tst_d(CONFIG_JOYSTICK_AXIS_X, a))
1890 if (v < 0) jd = gui_wrap_L(id, active);
1891 if (v > 0) jd = gui_wrap_R(id, active);
1893 else if (config_tst_d(CONFIG_JOYSTICK_AXIS_Y, a))
1895 if (v < 0) jd = gui_wrap_U(id, active);
1896 if (v > 0) jd = gui_wrap_D(id, active);
1899 /* If the active widget has changed, return the new active id. */
1901 if (jd == 0 || jd == active)
1907 /*---------------------------------------------------------------------------*/