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.
25 /*---------------------------------------------------------------------------*/
29 #define GUI_TYPE 0xFFFE
62 const GLfloat *color0;
63 const GLfloat *color1;
68 /*---------------------------------------------------------------------------*/
70 const GLfloat gui_wht[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
71 const GLfloat gui_yel[4] = { 1.0f, 1.0f, 0.0f, 1.0f };
72 const GLfloat gui_red[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
73 const GLfloat gui_grn[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
74 const GLfloat gui_blu[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
75 const GLfloat gui_blk[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
76 const GLfloat gui_gry[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
78 /*---------------------------------------------------------------------------*/
80 static struct widget widget[MAXWIDGET];
83 static TTF_Font *font[3] = { NULL, NULL, NULL };
84 static int scale[3] = { 1, 1, 1 };
86 static GLuint digit_text[3][11];
87 static GLuint digit_list[3][11];
88 static int digit_w[3][11];
89 static int digit_h[3][11];
93 /*---------------------------------------------------------------------------*/
95 static int gui_hot(int id)
97 return (widget[id].type & GUI_STATE);
100 /*---------------------------------------------------------------------------*/
102 * Initialize a display list containing a rectangle (x, y, w, h) to
103 * which a rendered-font texture may be applied. Colors c0 and c1
104 * determine the top-to-bottom color gradient of the text.
107 static GLuint gui_list(int x, int y,
108 int w, int h, const float *c0, const float *c1)
110 GLuint list = glGenLists(1);
115 int W, H, ww, hh, d = h / 16;
117 /* Assume the applied texture size is rect size rounded to power-of-two. */
119 image_size(&W, &H, w, h);
121 ww = ((W - w) % 2) ? w + 1 : w;
122 hh = ((H - h) % 2) ? h + 1 : h;
124 s0 = 0.5f * (W - ww) / W;
125 t0 = 0.5f * (H - hh) / H;
129 glNewList(list, GL_COMPILE);
133 glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
134 glTexCoord2f(s0, t1); glVertex2i(x + d, y - d);
135 glTexCoord2f(s1, t1); glVertex2i(x + ww + d, y - d);
136 glTexCoord2f(s1, t0); glVertex2i(x + ww + d, y + hh - d);
137 glTexCoord2f(s0, t0); glVertex2i(x + d, y + hh - d);
140 glTexCoord2f(s0, t1); glVertex2i(x, y);
141 glTexCoord2f(s1, t1); glVertex2i(x + ww, y);
144 glTexCoord2f(s1, t0); glVertex2i(x + ww, y + hh);
145 glTexCoord2f(s0, t0); glVertex2i(x, y + hh);
155 * Initialize a display list containing a rounded-corner rectangle (x,
156 * y, w, h). Generate texture coordinates to properly apply a texture
157 * map to the rectangle as though the corners were not rounded.
160 static GLuint gui_rect(int x, int y, int w, int h, int f, int r)
162 GLuint list = glGenLists(1);
167 glNewList(list, GL_COMPILE);
169 glBegin(GL_QUAD_STRIP);
173 for (i = 0; i <= n; i++)
175 float a = 0.5f * V_PI * (float) i / (float) n;
176 float s = r * fsinf(a);
177 float c = r * fcosf(a);
180 float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
181 float Yb = y + ((f & GUI_SW) ? (r - s) : 0);
183 glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
186 glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
190 /* ... Right side. */
192 for (i = 0; i <= n; i++)
194 float a = 0.5f * V_PI * (float) i / (float) n;
195 float s = r * fsinf(a);
196 float c = r * fcosf(a);
198 float X = x + w - r + s;
199 float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
200 float Yb = y + ((f & GUI_SE) ? (r - c) : 0);
202 glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
205 glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
216 /*---------------------------------------------------------------------------*/
220 const float *c0 = gui_yel;
221 const float *c1 = gui_red;
223 int w = config_get_d(CONFIG_WIDTH);
224 int h = config_get_d(CONFIG_HEIGHT);
225 int i, j, s = (h < w) ? h : w;
227 /* Initialize font rendering. */
236 /* Make sure text size doesn't exceed the maximum texture size. */
238 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m);
244 while (s0 > m) { s0 /= 2; scale[0] *= 2; }
245 while (s1 > m) { s1 /= 2; scale[1] *= 2; }
246 while (s2 > m) { s2 /= 2; scale[2] *= 2; }
248 memset(widget, 0, sizeof (struct widget) * MAXWIDGET);
250 /* Load small, medium, and large typefaces. */
252 font[GUI_SML] = TTF_OpenFont(config_data(GUI_FACE), s0);
253 font[GUI_MED] = TTF_OpenFont(config_data(GUI_FACE), s1);
254 font[GUI_LRG] = TTF_OpenFont(config_data(GUI_FACE), s2);
257 /* Initialize the global pause GUI. */
259 if ((pause_id = gui_pause(0)))
260 gui_layout(pause_id, 0, 0);
262 /* Initialize digit glyphs and lists for counters and clocks. */
264 for (i = 0; i < 3; i++)
268 /* Draw digits 0 through 9. */
270 for (j = 0; j < 10; j++)
272 text[0] = '0' + (char) j;
275 digit_text[i][j] = make_image_from_font(NULL, NULL,
278 text, font[i], scale[i]);
279 digit_list[i][j] = gui_list(-digit_w[i][j] / 2,
282 +digit_h[i][j], c0, c1);
285 /* Draw the colon for the clock. */
287 digit_text[i][j] = make_image_from_font(NULL, NULL,
290 ":", font[i], scale[i]);
291 digit_list[i][j] = gui_list(-digit_w[i][10] / 2,
294 +digit_h[i][10], c0, c1);
305 /* Release any remaining widget texture and display list indices. */
307 for (id = 1; id < MAXWIDGET; id++)
309 if (glIsTexture(widget[id].text_img))
310 glDeleteTextures(1, &widget[id].text_img);
312 if (glIsList(widget[id].text_obj))
313 glDeleteLists(widget[id].text_obj, 1);
314 if (glIsList(widget[id].rect_obj))
315 glDeleteLists(widget[id].rect_obj, 1);
317 widget[id].type = GUI_FREE;
318 widget[id].text_img = 0;
319 widget[id].text_obj = 0;
320 widget[id].rect_obj = 0;
325 /* Release all digit textures and display lists. */
327 for (i = 0; i < 3; i++)
328 for (j = 0; j < 11; j++)
330 if (glIsTexture(digit_text[i][j]))
331 glDeleteTextures(1, &digit_text[i][j]);
333 if (glIsList(digit_list[i][j]))
334 glDeleteLists(digit_list[i][j], 1);
337 /* Release all loaded fonts and finalize font rendering. */
339 if (font[GUI_LRG]) TTF_CloseFont(font[GUI_LRG]);
340 if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
341 if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
346 /*---------------------------------------------------------------------------*/
348 static int gui_widget(int pd, int type)
352 /* Find an unused entry in the widget table. */
354 for (id = 1; id < MAXWIDGET; id++)
355 if (widget[id].type == GUI_FREE)
357 /* Set the type and default properties. */
359 widget[id].type = type;
360 widget[id].token = 0;
361 widget[id].value = 0;
363 widget[id].rect = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
366 widget[id].text_img = 0;
367 widget[id].text_obj = 0;
368 widget[id].rect_obj = 0;
369 widget[id].color0 = gui_wht;
370 widget[id].color1 = gui_wht;
371 widget[id].scale = 1.0f;
373 /* Insert the new widget into the parent's widget list. */
378 widget[id].cdr = widget[pd].car;
390 fprintf(stderr, _("Out of widget IDs\n"));
395 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
396 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
397 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
398 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
399 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
401 /*---------------------------------------------------------------------------*/
403 void gui_set_image(int id, const char *file)
405 if (glIsTexture(widget[id].text_img))
406 glDeleteTextures(1, &widget[id].text_img);
408 widget[id].text_img = make_image_from_file(NULL, NULL, NULL, NULL, file);
411 void gui_set_label(int id, const char *text)
415 if (glIsTexture(widget[id].text_img))
416 glDeleteTextures(1, &widget[id].text_img);
417 if (glIsList(widget[id].text_obj))
418 glDeleteLists(widget[id].text_obj, 1);
420 widget[id].text_img = make_image_from_font(NULL, NULL, &w, &h,
421 text, font[widget[id].size],
422 scale[widget[id].size]);
423 widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
424 widget[id].color0, widget[id].color1);
427 void gui_set_count(int id, int value)
429 widget[id].value = value;
432 void gui_set_clock(int id, int value)
434 widget[id].value = value;
437 void gui_set_color(int id, const float *c0,
440 widget[id].color0 = c0 ? c0 : gui_yel;
441 widget[id].color1 = c1 ? c1 : gui_red;
444 void gui_set_multi(int id, const char *text)
453 /* Copy each delimited string to a line buffer. */
455 for (p = text, j = 0; *p && j < 8; j++)
457 strncpy(s[j], p, (n = strcspn(p, "\\")));
460 if (*(p += n) == '\\') p++;
463 /* Set the label value for each line. */
465 for (i = j - 1, jd = widget[id].car; i >= 0 && jd; i--, jd = widget[jd].cdr)
466 gui_set_label(jd, s[i]);
469 /*---------------------------------------------------------------------------*/
471 int gui_image(int pd, const char *file, int w, int h)
475 if ((id = gui_widget(pd, GUI_IMAGE)))
477 widget[id].text_img = make_image_from_file(NULL, NULL,
485 int gui_start(int pd, const char *text, int size, int token, int value)
489 if ((id = gui_state(pd, text, size, token, value)))
495 int gui_state(int pd, const char *text, int size, int token, int value)
499 if ((id = gui_widget(pd, GUI_STATE)))
501 widget[id].text_img = make_image_from_font(NULL, NULL,
506 widget[id].size = size;
507 widget[id].token = token;
508 widget[id].value = value;
513 int gui_label(int pd, const char *text, int size, int rect, const float *c0,
518 if ((id = gui_widget(pd, GUI_LABEL)))
520 widget[id].text_img = make_image_from_font(NULL, NULL,
525 widget[id].size = size;
526 widget[id].color0 = c0 ? c0 : gui_yel;
527 widget[id].color1 = c1 ? c1 : gui_red;
528 widget[id].rect = rect;
533 int gui_count(int pd, int value, int size, int rect)
537 if ((id = gui_widget(pd, GUI_COUNT)))
539 for (i = value; i; i /= 10)
540 widget[id].w += digit_w[size][0];
542 widget[id].h = digit_h[size][0];
543 widget[id].value = value;
544 widget[id].size = size;
545 widget[id].color0 = gui_yel;
546 widget[id].color1 = gui_red;
547 widget[id].rect = rect;
552 int gui_clock(int pd, int value, int size, int rect)
556 if ((id = gui_widget(pd, GUI_CLOCK)))
558 widget[id].w = digit_w[size][0] * 6;
559 widget[id].h = digit_h[size][0];
560 widget[id].value = value;
561 widget[id].size = size;
562 widget[id].color0 = gui_yel;
563 widget[id].color1 = gui_red;
564 widget[id].rect = rect;
569 int gui_space(int pd)
573 if ((id = gui_widget(pd, GUI_SPACE)))
581 int gui_pause(int pd)
583 const char *text = _("Paused");
586 if ((id = gui_widget(pd, GUI_PAUSE)))
588 widget[id].text_img = make_image_from_font(NULL, NULL,
593 widget[id].color0 = gui_wht;
594 widget[id].color1 = gui_wht;
595 widget[id].value = 0;
596 widget[id].size = GUI_LRG;
597 widget[id].rect = GUI_ALL;
602 /*---------------------------------------------------------------------------*/
604 * Create a multi-line text box using a vertical array of labels.
605 * Parse the text for '\' characters and treat them as line-breaks.
606 * Preserve the rect specification across the entire array.
609 int gui_multi(int pd, const char *text, int size, int rect, const float *c0,
614 if (text && (id = gui_varray(pd)))
624 /* Copy each delimited string to a line buffer. */
626 for (p = text, j = 0; *p && j < 8; j++)
628 strncpy(s[j], p, (n = strcspn(p, "\\")));
632 if (*(p += n) == '\\') p++;
635 /* Set the curves for the first and last lines. */
639 r[0] |= rect & (GUI_NW | GUI_NE);
640 r[j - 1] |= rect & (GUI_SW | GUI_SE);
643 /* Create a label widget for each line. */
645 for (i = 0; i < j; i++)
646 gui_label(id, s[i], size, r[i], c0, c1);
651 /*---------------------------------------------------------------------------*/
653 * The bottom-up pass determines the area of all widgets. The minimum
654 * width and height of a leaf widget is given by the size of its
655 * contents. Array and stack widths and heights are computed
656 * recursively from these.
659 static void gui_widget_up(int id);
661 static void gui_harray_up(int id)
665 /* Find the widest child width and the highest child height. */
667 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
671 if (widget[id].h < widget[jd].h)
672 widget[id].h = widget[jd].h;
673 if (widget[id].w < widget[jd].w)
674 widget[id].w = widget[jd].w;
679 /* Total width is the widest child width times the child count. */
684 static void gui_varray_up(int id)
688 /* Find the widest child width and the highest child height. */
690 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
694 if (widget[id].h < widget[jd].h)
695 widget[id].h = widget[jd].h;
696 if (widget[id].w < widget[jd].w)
697 widget[id].w = widget[jd].w;
702 /* Total height is the highest child height times the child count. */
707 static void gui_hstack_up(int id)
711 /* Find the highest child height. Sum the child widths. */
713 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
717 if (widget[id].h < widget[jd].h)
718 widget[id].h = widget[jd].h;
720 widget[id].w += widget[jd].w;
724 static void gui_vstack_up(int id)
728 /* Find the widest child width. Sum the child heights. */
730 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
734 if (widget[id].w < widget[jd].w)
735 widget[id].w = widget[jd].w;
737 widget[id].h += widget[jd].h;
741 static void gui_paused_up(int id)
743 /* Store width and height for later use in text rendering. */
745 widget[id].x = widget[id].w;
746 widget[id].y = widget[id].h;
748 /* The pause widget fills the screen. */
750 widget[id].w = config_get_d(CONFIG_WIDTH);
751 widget[id].h = config_get_d(CONFIG_HEIGHT);
754 static void gui_button_up(int id)
756 /* Store width and height for later use in text rendering. */
758 widget[id].x = widget[id].w;
759 widget[id].y = widget[id].h;
761 if (widget[id].w < widget[id].h && widget[id].w > 0)
762 widget[id].w = widget[id].h;
765 /* Padded text elements look a little nicer. */
767 if (widget[id].w < config_get_d(CONFIG_WIDTH))
768 widget[id].w += radius;
769 if (widget[id].h < config_get_d(CONFIG_HEIGHT))
770 widget[id].h += radius;
773 static void gui_widget_up(int id)
776 switch (widget[id].type & GUI_TYPE)
778 case GUI_HARRAY: gui_harray_up(id); break;
779 case GUI_VARRAY: gui_varray_up(id); break;
780 case GUI_HSTACK: gui_hstack_up(id); break;
781 case GUI_VSTACK: gui_vstack_up(id); break;
782 case GUI_PAUSE: gui_paused_up(id); break;
783 default: gui_button_up(id); break;
787 /*---------------------------------------------------------------------------*/
789 * The top-down layout pass distributes available area as computed
790 * during the bottom-up pass. Widgets use their area and position to
791 * initialize rendering state.
794 static void gui_widget_dn(int id, int x, int y, int w, int h);
796 static void gui_harray_dn(int id, int x, int y, int w, int h)
798 int jd, i = 0, c = 0;
805 /* Count children. */
807 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
810 /* Distribute horizontal space evenly to all children. */
812 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
814 int x0 = x + i * w / c;
815 int x1 = x + (i + 1) * w / c;
817 gui_widget_dn(jd, x0, y, x1 - x0, h);
821 static void gui_varray_dn(int id, int x, int y, int w, int h)
823 int jd, i = 0, c = 0;
830 /* Count children. */
832 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
835 /* Distribute vertical space evenly to all children. */
837 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
839 int y0 = y + i * h / c;
840 int y1 = y + (i + 1) * h / c;
842 gui_widget_dn(jd, x, y0, w, y1 - y0);
846 static void gui_hstack_dn(int id, int x, int y, int w, int h)
848 int jd, jx = x, jw = 0, c = 0;
855 /* Measure the total width requested by non-filler children. */
857 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
858 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
863 /* Give non-filler children their requested space. */
864 /* Distribute the rest evenly among filler children. */
866 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
868 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
869 gui_widget_dn(jd, jx, y, (w - jw) / c, h);
871 gui_widget_dn(jd, jx, y, widget[jd].w, h);
877 static void gui_vstack_dn(int id, int x, int y, int w, int h)
879 int jd, jy = y, jh = 0, c = 0;
886 /* Measure the total height requested by non-filler children. */
888 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
889 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
894 /* Give non-filler children their requested space. */
895 /* Distribute the rest evenly among filler children. */
897 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
899 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
900 gui_widget_dn(jd, x, jy, w, (h - jh) / c);
902 gui_widget_dn(jd, x, jy, w, widget[jd].h);
908 static void gui_filler_dn(int id, int x, int y, int w, int h)
910 /* Filler expands to whatever size it is given. */
918 static void gui_button_dn(int id, int x, int y, int w, int h)
920 /* Recall stored width and height for text rendering. */
922 int W = widget[id].x;
923 int H = widget[id].y;
924 int R = widget[id].rect;
925 int r = ((widget[id].type & GUI_TYPE) == GUI_PAUSE ? radius * 4 : radius);
927 const float *c0 = widget[id].color0;
928 const float *c1 = widget[id].color1;
935 /* Create display lists for the text area and rounded rectangle. */
937 widget[id].text_obj = gui_list(-W / 2, -H / 2, W, H, c0, c1);
938 widget[id].rect_obj = gui_rect(-w / 2, -h / 2, w, h, R, r);
941 static void gui_widget_dn(int id, int x, int y, int w, int h)
944 switch (widget[id].type & GUI_TYPE)
946 case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
947 case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
948 case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
949 case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
950 case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
951 case GUI_SPACE: gui_filler_dn(id, x, y, w, h); break;
952 default: gui_button_dn(id, x, y, w, h); break;
956 /*---------------------------------------------------------------------------*/
958 * During GUI layout, we make a bottom-up pass to determine total area
959 * requirements for the widget tree. We position this area to the
960 * sides or center of the screen. Finally, we make a top-down pass to
961 * distribute this area to each widget.
964 void gui_layout(int id, int xd, int yd)
968 int w, W = config_get_d(CONFIG_WIDTH);
969 int h, H = config_get_d(CONFIG_HEIGHT);
977 else if (xd > 0) x = (W - w);
978 else x = (W - w) / 2;
981 else if (yd > 0) y = (H - h);
982 else y = (H - h) / 2;
984 gui_widget_dn(id, x, y, w, h);
986 /* Hilite the widget under the cursor, if any. */
988 gui_point(id, -1, -1);
991 int gui_search(int id, int x, int y)
995 /* Search the hierarchy for the widget containing the given point. */
997 if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
998 widget[id].y <= y && y < widget[id].y + widget[id].h))
1003 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1004 if ((kd = gui_search(jd, x, y)))
1011 * Activate a widget, allowing it to behave as a normal state widget.
1012 * This may be used to create image buttons, or cause an array of
1013 * widgets to behave as a single state widget.
1015 int gui_active(int id, int token, int value)
1017 widget[id].type |= GUI_STATE;
1018 widget[id].token = token;
1019 widget[id].value = value;
1024 int gui_delete(int id)
1028 /* Recursively delete all subwidgets. */
1030 gui_delete(widget[id].cdr);
1031 gui_delete(widget[id].car);
1033 /* Release any GL resources held by this widget. */
1035 if (glIsTexture(widget[id].text_img))
1036 glDeleteTextures(1, &widget[id].text_img);
1038 if (glIsList(widget[id].text_obj))
1039 glDeleteLists(widget[id].text_obj, 1);
1040 if (glIsList(widget[id].rect_obj))
1041 glDeleteLists(widget[id].rect_obj, 1);
1043 /* Mark this widget unused. */
1045 widget[id].type = GUI_FREE;
1046 widget[id].text_img = 0;
1047 widget[id].text_obj = 0;
1048 widget[id].rect_obj = 0;
1055 /*---------------------------------------------------------------------------*/
1057 static void gui_paint_rect(int id, int st)
1059 static const GLfloat back[4][4] = {
1060 { 0.1f, 0.1f, 0.1f, 0.5f }, /* off and inactive */
1061 { 0.5f, 0.5f, 0.5f, 0.8f }, /* off and active */
1062 { 1.0f, 0.7f, 0.3f, 0.5f }, /* on and inactive */
1063 { 1.0f, 0.7f, 0.3f, 0.8f }, /* on and active */
1068 /* Use the widget status to determine the background color. */
1071 i = st | (((widget[id].value) ? 2 : 0) |
1072 ((id == active) ? 1 : 0));
1074 switch (widget[id].type & GUI_TYPE)
1086 /* Recursively paint all subwidgets. */
1088 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1089 gui_paint_rect(jd, i);
1095 /* Draw a leaf's background, colored by widget state. */
1099 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1100 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1102 glColor4fv(back[i]);
1103 glCallList(widget[id].rect_obj);
1111 /*---------------------------------------------------------------------------*/
1113 static void gui_paint_text(int id);
1115 static void gui_paint_array(int id)
1121 GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1122 GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1123 GLfloat ck = widget[id].scale;
1125 glTranslatef(+cx, +cy, 0.0f);
1126 glScalef(ck, ck, ck);
1127 glTranslatef(-cx, -cy, 0.0f);
1129 /* Recursively paint all subwidgets. */
1131 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1137 static void gui_paint_image(int id)
1139 /* Draw the widget rect, textured using the image. */
1143 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1144 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1146 glScalef(widget[id].scale,
1150 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1151 glColor4fv(gui_wht);
1152 glCallList(widget[id].rect_obj);
1157 static void gui_paint_count(int id)
1159 int j, i = widget[id].size;
1163 glColor4fv(gui_wht);
1165 /* Translate to the widget center, and apply the pulse scale. */
1167 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1168 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1170 glScalef(widget[id].scale,
1174 if (widget[id].value > 0)
1176 /* Translate left by half the total width of the rendered value. */
1178 for (j = widget[id].value; j; j /= 10)
1179 glTranslatef((GLfloat) +digit_w[i][j % 10] / 2.0f, 0.0f, 0.0f);
1181 glTranslatef((GLfloat) -digit_w[i][0] / 2.0f, 0.0f, 0.0f);
1183 /* Render each digit, moving right after each. */
1185 for (j = widget[id].value; j; j /= 10)
1187 glBindTexture(GL_TEXTURE_2D, digit_text[i][j % 10]);
1188 glCallList(digit_list[i][j % 10]);
1189 glTranslatef((GLfloat) -digit_w[i][j % 10], 0.0f, 0.0f);
1192 else if (widget[id].value == 0)
1194 /* If the value is zero, just display a zero in place. */
1196 glBindTexture(GL_TEXTURE_2D, digit_text[i][0]);
1197 glCallList(digit_list[i][0]);
1203 static void gui_paint_clock(int id)
1205 int i = widget[id].size;
1206 int mt = (widget[id].value / 6000) / 10;
1207 int mo = (widget[id].value / 6000) % 10;
1208 int st = ((widget[id].value % 6000) / 100) / 10;
1209 int so = ((widget[id].value % 6000) / 100) % 10;
1210 int ht = ((widget[id].value % 6000) % 100) / 10;
1211 int ho = ((widget[id].value % 6000) % 100) % 10;
1213 GLfloat dx_large = (GLfloat) digit_w[i][0];
1214 GLfloat dx_small = (GLfloat) digit_w[i][0] * 0.75f;
1216 if (widget[id].value < 0)
1221 glColor4fv(gui_wht);
1223 /* Translate to the widget center, and apply the pulse scale. */
1225 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1226 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1228 glScalef(widget[id].scale,
1232 /* Translate left by half the total width of the rendered value. */
1235 glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1237 glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1239 /* Render the minutes counter. */
1243 glBindTexture(GL_TEXTURE_2D, digit_text[i][mt]);
1244 glCallList(digit_list[i][mt]);
1245 glTranslatef(dx_large, 0.0f, 0.0f);
1248 glBindTexture(GL_TEXTURE_2D, digit_text[i][mo]);
1249 glCallList(digit_list[i][mo]);
1250 glTranslatef(dx_small, 0.0f, 0.0f);
1252 /* Render the colon. */
1254 glBindTexture(GL_TEXTURE_2D, digit_text[i][10]);
1255 glCallList(digit_list[i][10]);
1256 glTranslatef(dx_small, 0.0f, 0.0f);
1258 /* Render the seconds counter. */
1260 glBindTexture(GL_TEXTURE_2D, digit_text[i][st]);
1261 glCallList(digit_list[i][st]);
1262 glTranslatef(dx_large, 0.0f, 0.0f);
1264 glBindTexture(GL_TEXTURE_2D, digit_text[i][so]);
1265 glCallList(digit_list[i][so]);
1266 glTranslatef(dx_small, 0.0f, 0.0f);
1268 /* Render hundredths counter half size. */
1270 glScalef(0.5f, 0.5f, 1.0f);
1272 glBindTexture(GL_TEXTURE_2D, digit_text[i][ht]);
1273 glCallList(digit_list[i][ht]);
1274 glTranslatef(dx_large, 0.0f, 0.0f);
1276 glBindTexture(GL_TEXTURE_2D, digit_text[i][ho]);
1277 glCallList(digit_list[i][ho]);
1282 static void gui_paint_label(int id)
1284 /* Draw the widget text box, textured using the glyph. */
1288 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1289 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1291 glScalef(widget[id].scale,
1295 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1296 glCallList(widget[id].text_obj);
1301 static void gui_paint_text(int id)
1303 switch (widget[id].type & GUI_TYPE)
1305 case GUI_SPACE: break;
1306 case GUI_FILLER: break;
1307 case GUI_HARRAY: gui_paint_array(id); break;
1308 case GUI_VARRAY: gui_paint_array(id); break;
1309 case GUI_HSTACK: gui_paint_array(id); break;
1310 case GUI_VSTACK: gui_paint_array(id); break;
1311 case GUI_IMAGE: gui_paint_image(id); break;
1312 case GUI_COUNT: gui_paint_count(id); break;
1313 case GUI_CLOCK: gui_paint_clock(id); break;
1314 default: gui_paint_label(id); break;
1318 void gui_paint(int id)
1322 glPushAttrib(GL_LIGHTING_BIT |
1323 GL_COLOR_BUFFER_BIT |
1324 GL_DEPTH_BUFFER_BIT);
1325 config_push_ortho();
1328 glEnable(GL_COLOR_MATERIAL);
1329 glDisable(GL_LIGHTING);
1330 glDisable(GL_DEPTH_TEST);
1332 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1334 glPushAttrib(GL_TEXTURE_BIT);
1336 glDisable(GL_TEXTURE_2D);
1337 gui_paint_rect(id, 0);
1343 config_pop_matrix();
1348 void gui_blank(void)
1350 gui_paint(pause_id);
1353 /*---------------------------------------------------------------------------*/
1355 void gui_dump(int id, int d)
1363 switch (widget[id].type & GUI_TYPE)
1365 case GUI_HARRAY: type = "harray"; break;
1366 case GUI_VARRAY: type = "varray"; break;
1367 case GUI_HSTACK: type = "hstack"; break;
1368 case GUI_VSTACK: type = "vstack"; break;
1369 case GUI_FILLER: type = "filler"; break;
1370 case GUI_IMAGE: type = "image"; break;
1371 case GUI_LABEL: type = "label"; break;
1372 case GUI_COUNT: type = "count"; break;
1373 case GUI_CLOCK: type = "clock"; break;
1376 for (i = 0; i < d; i++)
1379 printf("%04d %s\n", id, type);
1381 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1382 gui_dump(jd, d + 1);
1386 void gui_pulse(int id, float k)
1388 if (id) widget[id].scale = k;
1391 void gui_timer(int id, float dt)
1397 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1400 if (widget[id].scale - 1.0f < dt)
1401 widget[id].scale = 1.0f;
1403 widget[id].scale -= dt;
1407 int gui_point(int id, int x, int y)
1409 static int x_cache = 0;
1410 static int y_cache = 0;
1414 /* Reuse the last coordinates if (x,y) == (-1,-1) */
1417 return gui_point(id, x_cache, y_cache);
1422 /* Short-circuit check the current active widget. */
1424 jd = gui_search(active, x, y);
1426 /* If not still active, search the hierarchy for a new active widget. */
1429 jd = gui_search(id, x, y);
1431 /* If the active widget has changed, return the new active id. */
1433 if (jd == 0 || jd == active)
1439 void gui_focus(int i)
1449 int gui_token(int id)
1451 return id ? widget[id].token : 0;
1454 int gui_value(int id)
1456 return id ? widget[id].value : 0;
1459 void gui_toggle(int id)
1461 widget[id].value = widget[id].value ? 0 : 1;
1464 /*---------------------------------------------------------------------------*/
1466 static int gui_vert_test(int id, int jd)
1468 /* Determine whether widget id is in vertical contact with widget jd. */
1470 if (id && gui_hot(id) && jd && gui_hot(jd))
1472 int i0 = widget[id].x;
1473 int i1 = widget[id].x + widget[id].w;
1474 int j0 = widget[jd].x;
1475 int j1 = widget[jd].x + widget[jd].w;
1477 /* Is widget id's top edge is in contact with jd's bottom edge? */
1479 if (widget[id].y + widget[id].h == widget[jd].y)
1481 /* Do widgets id and jd overlap horizontally? */
1483 if (j0 <= i0 && i0 < j1) return 1;
1484 if (j0 < i1 && i1 <= j1) return 1;
1485 if (i0 <= j0 && j0 < i1) return 1;
1486 if (i0 < j1 && j1 <= i1) return 1;
1492 static int gui_horz_test(int id, int jd)
1494 /* Determine whether widget id is in horizontal contact with widget jd. */
1496 if (id && gui_hot(id) && jd && gui_hot(jd))
1498 int i0 = widget[id].y;
1499 int i1 = widget[id].y + widget[id].h;
1500 int j0 = widget[jd].y;
1501 int j1 = widget[jd].y + widget[jd].h;
1503 /* Is widget id's right edge in contact with jd's left edge? */
1505 if (widget[id].x + widget[id].w == widget[jd].x)
1507 /* Do widgets id and jd overlap vertically? */
1509 if (j0 <= i0 && i0 < j1) return 1;
1510 if (j0 < i1 && i1 <= j1) return 1;
1511 if (i0 <= j0 && j0 < i1) return 1;
1512 if (i0 < j1 && j1 <= i1) return 1;
1518 /*---------------------------------------------------------------------------*/
1520 static int gui_stick_L(int id, int dd)
1524 /* Find a widget to the left of widget dd. */
1526 if (gui_horz_test(id, dd))
1529 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1530 if ((kd = gui_stick_L(jd, dd)))
1536 static int gui_stick_R(int id, int dd)
1540 /* Find a widget to the right of widget dd. */
1542 if (gui_horz_test(dd, id))
1545 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1546 if ((kd = gui_stick_R(jd, dd)))
1552 static int gui_stick_D(int id, int dd)
1556 /* Find a widget below widget dd. */
1558 if (gui_vert_test(id, dd))
1561 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1562 if ((kd = gui_stick_D(jd, dd)))
1568 static int gui_stick_U(int id, int dd)
1572 /* Find a widget above widget dd. */
1574 if (gui_vert_test(dd, id))
1577 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1578 if ((kd = gui_stick_U(jd, dd)))
1584 /*---------------------------------------------------------------------------*/
1586 static int gui_wrap_L(int id, int dd)
1590 if ((jd = gui_stick_L(id, dd)) == 0)
1591 for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
1597 static int gui_wrap_R(int id, int dd)
1601 if ((jd = gui_stick_R(id, dd)) == 0)
1602 for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
1608 static int gui_wrap_U(int id, int dd)
1612 if ((jd = gui_stick_U(id, dd)) == 0)
1613 for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
1619 static int gui_wrap_D(int id, int dd)
1623 if ((jd = gui_stick_D(id, dd)) == 0)
1624 for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
1630 /*---------------------------------------------------------------------------*/
1632 int gui_stick(int id, int x, int y)
1634 /* Flag the axes to prevent uncontrolled scrolling. */
1636 static int xflag = 1;
1637 static int yflag = 1;
1641 /* Find a new active widget in the direction of joystick motion. */
1643 if (x && -JOY_MID <= x && x <= +JOY_MID)
1645 else if (x < -JOY_MID && xflag && (jd = gui_wrap_L(id, active)))
1647 else if (x > +JOY_MID && xflag && (jd = gui_wrap_R(id, active)))
1650 if (y && -JOY_MID <= y && y <= +JOY_MID)
1652 else if (y < -JOY_MID && yflag && (jd = gui_wrap_U(id, active)))
1654 else if (y > +JOY_MID && yflag && (jd = gui_wrap_D(id, active)))
1657 /* If the active widget has changed, return the new active id. */
1659 if (jd == 0 || jd == active)
1665 /*---------------------------------------------------------------------------*/