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.
26 /*---------------------------------------------------------------------------*/
30 #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 };
85 static GLuint digit_text[3][11];
86 static GLuint digit_list[3][11];
87 static int digit_w[3][11];
88 static int digit_h[3][11];
90 /*---------------------------------------------------------------------------*/
92 static int gui_hot(int id)
94 return (widget[id].type & GUI_STATE);
97 /*---------------------------------------------------------------------------*/
99 * Initialize a display list containing a rectangle (x, y, w, h) to
100 * which a rendered-font texture may be applied. Colors c0 and c1
101 * determine the top-to-bottom color gradient of the text.
104 static GLuint gui_list(int x, int y,
105 int w, int h, const float *c0, const float *c1)
107 GLuint list = glGenLists(1);
112 int W, H, ww, hh, d = h / 16;
114 /* Assume the applied texture size is rect size rounded to power-of-two. */
116 image_size(&W, &H, w, h);
118 ww = ((W - w) % 2) ? w + 1 : w;
119 hh = ((H - h) % 2) ? h + 1 : h;
121 s0 = 0.5f * (W - ww) / W;
122 t0 = 0.5f * (H - hh) / H;
126 glNewList(list, GL_COMPILE);
130 glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
131 glTexCoord2f(s0, t1); glVertex2i(x + d, y - d);
132 glTexCoord2f(s1, t1); glVertex2i(x + ww + d, y - d);
133 glTexCoord2f(s1, t0); glVertex2i(x + ww + d, y + hh - d);
134 glTexCoord2f(s0, t0); glVertex2i(x + d, y + hh - d);
137 glTexCoord2f(s0, t1); glVertex2i(x, y);
138 glTexCoord2f(s1, t1); glVertex2i(x + ww, y);
141 glTexCoord2f(s1, t0); glVertex2i(x + ww, y + hh);
142 glTexCoord2f(s0, t0); glVertex2i(x, y + hh);
152 * Initialize a display list containing a rounded-corner rectangle (x,
153 * y, w, h). Generate texture coordinates to properly apply a texture
154 * map to the rectangle as though the corners were not rounded.
157 static GLuint gui_rect(int x, int y, int w, int h, int f, int r)
159 GLuint list = glGenLists(1);
164 glNewList(list, GL_COMPILE);
166 glBegin(GL_QUAD_STRIP);
170 for (i = 0; i <= n; i++)
172 float a = 0.5f * V_PI * (float) i / (float) n;
173 float s = r * fsinf(a);
174 float c = r * fcosf(a);
177 float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
178 float Yb = y + ((f & GUI_SW) ? (r - s) : 0);
180 glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
183 glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
187 /* ... Right side. */
189 for (i = 0; i <= n; i++)
191 float a = 0.5f * V_PI * (float) i / (float) n;
192 float s = r * fsinf(a);
193 float c = r * fcosf(a);
195 float X = x + w - r + s;
196 float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
197 float Yb = y + ((f & GUI_SE) ? (r - c) : 0);
199 glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
202 glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
213 /*---------------------------------------------------------------------------*/
217 const float *c0 = gui_yel;
218 const float *c1 = gui_red;
220 int w = config_get_d(CONFIG_WIDTH);
221 int h = config_get_d(CONFIG_HEIGHT);
222 int i, j, s = (h < w) ? h : w;
224 /* Initialize font rendering. */
232 memset(widget, 0, sizeof (struct widget) * MAXWIDGET);
234 /* Load small, medium, and large typefaces. */
236 font[GUI_SML] = TTF_OpenFont(config_data(GUI_FACE), s0);
237 font[GUI_MED] = TTF_OpenFont(config_data(GUI_FACE), s1);
238 font[GUI_LRG] = TTF_OpenFont(config_data(GUI_FACE), s2);
241 /* Initialize digit glyphs and lists for counters and clocks. */
243 for (i = 0; i < 3; i++)
247 /* Draw digits 0 through 9. */
249 for (j = 0; j < 10; j++)
251 text[0] = '0' + (char) j;
254 digit_text[i][j] = make_image_from_font(NULL, NULL,
258 digit_list[i][j] = gui_list(-digit_w[i][j] / 2,
261 +digit_h[i][j], c0, c1);
264 /* Draw the colon for the clock. */
266 digit_text[i][j] = make_image_from_font(NULL, NULL,
270 digit_list[i][j] = gui_list(-digit_w[i][10] / 2,
273 +digit_h[i][10], c0, c1);
284 /* Release any remaining widget texture and display list indices. */
286 for (id = 1; id < MAXWIDGET; id++)
288 if (glIsTexture(widget[id].text_img))
289 glDeleteTextures(1, &widget[id].text_img);
291 if (glIsList(widget[id].text_obj))
292 glDeleteLists(widget[id].text_obj, 1);
293 if (glIsList(widget[id].rect_obj))
294 glDeleteLists(widget[id].rect_obj, 1);
296 widget[id].type = GUI_FREE;
297 widget[id].text_img = 0;
298 widget[id].text_obj = 0;
299 widget[id].rect_obj = 0;
304 /* Release all digit textures and display lists. */
306 for (i = 0; i < 3; i++)
307 for (j = 0; j < 11; j++)
309 if (glIsTexture(digit_text[i][j]))
310 glDeleteTextures(1, &digit_text[i][j]);
312 if (glIsList(digit_list[i][j]))
313 glDeleteLists(digit_list[i][j], 1);
316 /* Release all loaded fonts and finalize font rendering. */
318 if (font[GUI_LRG]) TTF_CloseFont(font[GUI_LRG]);
319 if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
320 if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
325 /*---------------------------------------------------------------------------*/
327 static int gui_widget(int pd, int type)
331 /* Find an unused entry in the widget table. */
333 for (id = 1; id < MAXWIDGET; id++)
334 if (widget[id].type == GUI_FREE)
336 /* Set the type and default properties. */
338 widget[id].type = type;
339 widget[id].token = 0;
340 widget[id].value = 0;
342 widget[id].rect = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
345 widget[id].text_img = 0;
346 widget[id].text_obj = 0;
347 widget[id].rect_obj = 0;
348 widget[id].color0 = gui_wht;
349 widget[id].color1 = gui_wht;
350 widget[id].scale = 1.0f;
352 /* Insert the new widget into the parent's widget list. */
357 widget[id].cdr = widget[pd].car;
369 fprintf(stderr, "Out of widget IDs\n");
374 int gui_harray(int pd) { return gui_widget(pd, GUI_HARRAY); }
375 int gui_varray(int pd) { return gui_widget(pd, GUI_VARRAY); }
376 int gui_hstack(int pd) { return gui_widget(pd, GUI_HSTACK); }
377 int gui_vstack(int pd) { return gui_widget(pd, GUI_VSTACK); }
378 int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
380 /*---------------------------------------------------------------------------*/
382 void gui_set_image(int id, const char *file)
384 if (glIsTexture(widget[id].text_img))
385 glDeleteTextures(1, &widget[id].text_img);
387 widget[id].text_img = make_image_from_file(file);
390 void gui_set_label(int id, const char *text)
394 if (glIsTexture(widget[id].text_img))
395 glDeleteTextures(1, &widget[id].text_img);
396 if (glIsList(widget[id].text_obj))
397 glDeleteLists(widget[id].text_obj, 1);
399 widget[id].text_img = make_image_from_font(NULL, NULL, &w, &h,
400 text, font[widget[id].size]);
401 widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
402 widget[id].color0, widget[id].color1);
405 void gui_set_count(int id, int value)
407 widget[id].value = value;
410 void gui_set_clock(int id, int value)
412 widget[id].value = value;
415 void gui_set_color(int id, const float *c0,
418 widget[id].color0 = c0 ? c0 : gui_yel;
419 widget[id].color1 = c1 ? c1 : gui_red;
422 void gui_set_multi(int id, const char *text)
431 /* Copy each delimited string to a line buffer. */
433 for (p = text, j = 0; *p && j < 8; j++)
435 strncpy(s[j], p, (n = strcspn(p, "\\")));
438 if (*(p += n) == '\\') p++;
441 /* Set the label value for each line. */
443 for (i = j - 1, jd = widget[id].car; i >= 0 && jd; i--, jd = widget[jd].cdr)
444 gui_set_label(jd, s[i]);
447 /*---------------------------------------------------------------------------*/
449 int gui_image(int pd, const char *file, int w, int h)
453 if ((id = gui_widget(pd, GUI_IMAGE)))
455 widget[id].text_img = make_image_from_file(file);
462 int gui_start(int pd, const char *text, int size, int token, int value)
466 if ((id = gui_state(pd, text, size, token, value)))
472 int gui_state(int pd, const char *text, int size, int token, int value)
476 if ((id = gui_widget(pd, GUI_STATE)))
478 widget[id].text_img = make_image_from_font(NULL, NULL,
482 widget[id].size = size;
483 widget[id].token = token;
484 widget[id].value = value;
489 int gui_label(int pd, const char *text, int size, int rect, const float *c0,
494 if ((id = gui_widget(pd, GUI_LABEL)))
496 widget[id].text_img = make_image_from_font(NULL, NULL,
500 widget[id].size = size;
501 widget[id].color0 = c0 ? c0 : gui_yel;
502 widget[id].color1 = c1 ? c1 : gui_red;
503 widget[id].rect = rect;
508 int gui_count(int pd, int value, int size, int rect)
512 if ((id = gui_widget(pd, GUI_COUNT)))
514 for (i = value; i; i /= 10)
515 widget[id].w += digit_w[size][0];
517 widget[id].h = digit_h[size][0];
518 widget[id].value = value;
519 widget[id].size = size;
520 widget[id].color0 = gui_yel;
521 widget[id].color1 = gui_red;
522 widget[id].rect = rect;
527 int gui_clock(int pd, int value, int size, int rect)
531 if ((id = gui_widget(pd, GUI_CLOCK)))
533 widget[id].w = digit_w[size][0] * 6;
534 widget[id].h = digit_h[size][0];
535 widget[id].value = value;
536 widget[id].size = size;
537 widget[id].color0 = gui_yel;
538 widget[id].color1 = gui_red;
539 widget[id].rect = rect;
544 int gui_space(int pd)
548 if ((id = gui_widget(pd, GUI_SPACE)))
556 /*---------------------------------------------------------------------------*/
558 * Create a multi-line text box using a vertical array of labels.
559 * Parse the text for '\' characters and treat them as line-breaks.
560 * Preserve the rect specification across the entire array.
563 int gui_multi(int pd, const char *text, int size, int rect, const float *c0,
568 if (text && (id = gui_varray(pd)))
578 /* Copy each delimited string to a line buffer. */
580 for (p = text, j = 0; *p && j < 8; j++)
582 strncpy(s[j], p, (n = strcspn(p, "\\")));
586 if (*(p += n) == '\\') p++;
589 /* Set the curves for the first and last lines. */
593 r[0] |= rect & (GUI_NW | GUI_NE);
594 r[j - 1] |= rect & (GUI_SW | GUI_SE);
597 /* Create a label widget for each line. */
599 for (i = 0; i < j; i++)
600 gui_label(id, s[i], size, r[i], c0, c1);
605 /*---------------------------------------------------------------------------*/
607 * The bottom-up pass determines the area of all widgets. The minimum
608 * width and height of a leaf widget is given by the size of its
609 * contents. Array and stack widths and heights are computed
610 * recursively from these.
613 static void gui_widget_up(int id);
615 static void gui_harray_up(int id)
619 /* Find the widest child width and the highest child height. */
621 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
625 if (widget[id].h < widget[jd].h)
626 widget[id].h = widget[jd].h;
627 if (widget[id].w < widget[jd].w)
628 widget[id].w = widget[jd].w;
633 /* Total width is the widest child width times the child count. */
638 static void gui_varray_up(int id)
642 /* Find the widest child width and the highest child height. */
644 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
648 if (widget[id].h < widget[jd].h)
649 widget[id].h = widget[jd].h;
650 if (widget[id].w < widget[jd].w)
651 widget[id].w = widget[jd].w;
656 /* Total height is the highest child height times the child count. */
661 static void gui_hstack_up(int id)
665 /* Find the highest child height. Sum the child widths. */
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;
674 widget[id].w += widget[jd].w;
678 static void gui_vstack_up(int id)
682 /* Find the widest child width. Sum the child heights. */
684 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
688 if (widget[id].w < widget[jd].w)
689 widget[id].w = widget[jd].w;
691 widget[id].h += widget[jd].h;
695 static void gui_button_up(int id)
697 /* Store width and height for later use in text rendering. */
699 widget[id].x = widget[id].w;
700 widget[id].y = widget[id].h;
702 if (widget[id].w < widget[id].h && widget[id].w > 0)
703 widget[id].w = widget[id].h;
705 /* Padded text elements look a little nicer. */
707 if (widget[id].w < config_get_d(CONFIG_WIDTH))
708 widget[id].w += radius;
709 if (widget[id].h < config_get_d(CONFIG_HEIGHT))
710 widget[id].h += radius;
712 /* A button should be at least wide enough to accomodate the rounding. */
714 if (widget[id].w < 2 * radius)
715 widget[id].w = 2 * radius;
716 if (widget[id].h < 2 * radius)
717 widget[id].h = 2 * radius;
720 static void gui_widget_up(int id)
723 switch (widget[id].type & GUI_TYPE)
725 case GUI_HARRAY: gui_harray_up(id); break;
726 case GUI_VARRAY: gui_varray_up(id); break;
727 case GUI_HSTACK: gui_hstack_up(id); break;
728 case GUI_VSTACK: gui_vstack_up(id); break;
729 default: gui_button_up(id); break;
733 /*---------------------------------------------------------------------------*/
735 * The top-down layout pass distributes available area as computed
736 * during the bottom-up pass. Widgets use their area and position to
737 * initialize rendering state.
740 static void gui_widget_dn(int id, int x, int y, int w, int h);
742 static void gui_harray_dn(int id, int x, int y, int w, int h)
744 int jd, i = 0, c = 0;
751 /* Count children. */
753 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
756 /* Distribute horizontal space evenly to all children. */
758 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
760 int x0 = x + i * w / c;
761 int x1 = x + (i + 1) * w / c;
763 gui_widget_dn(jd, x0, y, x1 - x0, h);
767 static void gui_varray_dn(int id, int x, int y, int w, int h)
769 int jd, i = 0, c = 0;
776 /* Count children. */
778 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
781 /* Distribute vertical space evenly to all children. */
783 for (jd = widget[id].car; jd; jd = widget[jd].cdr, i++)
785 int y0 = y + i * h / c;
786 int y1 = y + (i + 1) * h / c;
788 gui_widget_dn(jd, x, y0, w, y1 - y0);
792 static void gui_hstack_dn(int id, int x, int y, int w, int h)
794 int jd, jx = x, jw = 0, c = 0;
801 /* Measure the total width requested by non-filler children. */
803 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
804 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
809 /* Give non-filler children their requested space. */
810 /* Distribute the rest evenly among filler children. */
812 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
814 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
815 gui_widget_dn(jd, jx, y, (w - jw) / c, h);
817 gui_widget_dn(jd, jx, y, widget[jd].w, h);
823 static void gui_vstack_dn(int id, int x, int y, int w, int h)
825 int jd, jy = y, jh = 0, c = 0;
832 /* Measure the total height requested by non-filler children. */
834 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
835 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
840 /* Give non-filler children their requested space. */
841 /* Distribute the rest evenly among filler children. */
843 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
845 if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
846 gui_widget_dn(jd, x, jy, w, (h - jh) / c);
848 gui_widget_dn(jd, x, jy, w, widget[jd].h);
854 static void gui_filler_dn(int id, int x, int y, int w, int h)
856 /* Filler expands to whatever size it is given. */
864 static void gui_button_dn(int id, int x, int y, int w, int h)
866 /* Recall stored width and height for text rendering. */
868 int W = widget[id].x;
869 int H = widget[id].y;
870 int R = widget[id].rect;
872 const float *c0 = widget[id].color0;
873 const float *c1 = widget[id].color1;
880 /* Create display lists for the text area and rounded rectangle. */
882 widget[id].text_obj = gui_list(-W / 2, -H / 2, W, H, c0, c1);
883 widget[id].rect_obj = gui_rect(-w / 2, -h / 2, w, h, R, radius);
886 static void gui_widget_dn(int id, int x, int y, int w, int h)
889 switch (widget[id].type & GUI_TYPE)
891 case GUI_HARRAY: gui_harray_dn(id, x, y, w, h); break;
892 case GUI_VARRAY: gui_varray_dn(id, x, y, w, h); break;
893 case GUI_HSTACK: gui_hstack_dn(id, x, y, w, h); break;
894 case GUI_VSTACK: gui_vstack_dn(id, x, y, w, h); break;
895 case GUI_FILLER: gui_filler_dn(id, x, y, w, h); break;
896 case GUI_SPACE: gui_filler_dn(id, x, y, w, h); break;
897 default: gui_button_dn(id, x, y, w, h); break;
901 /*---------------------------------------------------------------------------*/
903 * During GUI layout, we make a bottom-up pass to determine total area
904 * requirements for the widget tree. We position this area to the
905 * sides or center of the screen. Finally, we make a top-down pass to
906 * distribute this area to each widget.
909 void gui_layout(int id, int xd, int yd)
913 int w, W = config_get_d(CONFIG_WIDTH);
914 int h, H = config_get_d(CONFIG_HEIGHT);
922 else if (xd > 0) x = (W - w);
923 else x = (W - w) / 2;
926 else if (yd > 0) y = (H - h);
927 else y = (H - h) / 2;
929 gui_widget_dn(id, x, y, w, h);
931 /* Hilite the widget under the cursor, if any. */
933 gui_point(id, -1, -1);
936 int gui_search(int id, int x, int y)
940 /* Search the hierarchy for the widget containing the given point. */
942 if (id && (widget[id].x <= x && x < widget[id].x + widget[id].w &&
943 widget[id].y <= y && y < widget[id].y + widget[id].h))
948 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
949 if ((kd = gui_search(jd, x, y)))
956 * Activate a widget, allowing it to behave as a normal state widget.
957 * This may be used to create image buttons, or cause an array of
958 * widgets to behave as a single state widget.
960 int gui_active(int id, int token, int value)
962 widget[id].type |= GUI_STATE;
963 widget[id].token = token;
964 widget[id].value = value;
969 int gui_delete(int id)
973 /* Recursively delete all subwidgets. */
975 gui_delete(widget[id].cdr);
976 gui_delete(widget[id].car);
978 /* Release any GL resources held by this widget. */
980 if (glIsTexture(widget[id].text_img))
981 glDeleteTextures(1, &widget[id].text_img);
983 if (glIsList(widget[id].text_obj))
984 glDeleteLists(widget[id].text_obj, 1);
985 if (glIsList(widget[id].rect_obj))
986 glDeleteLists(widget[id].rect_obj, 1);
988 /* Mark this widget unused. */
990 widget[id].type = GUI_FREE;
991 widget[id].text_img = 0;
992 widget[id].text_obj = 0;
993 widget[id].rect_obj = 0;
1000 /*---------------------------------------------------------------------------*/
1002 static void gui_paint_rect(int id, int st)
1004 static const GLfloat back[4][4] = {
1005 { 0.1f, 0.1f, 0.1f, 0.5f }, /* off and inactive */
1006 { 0.5f, 0.5f, 0.5f, 0.8f }, /* off and active */
1007 { 1.0f, 0.7f, 0.3f, 0.5f }, /* on and inactive */
1008 { 1.0f, 0.7f, 0.3f, 0.8f }, /* on and active */
1013 /* Use the widget status to determine the background color. */
1016 i = st | (((widget[id].value) ? 2 : 0) |
1017 ((id == active) ? 1 : 0));
1019 switch (widget[id].type & GUI_TYPE)
1031 /* Recursively paint all subwidgets. */
1033 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1034 gui_paint_rect(jd, i);
1040 /* Draw a leaf's background, colored by widget state. */
1044 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1045 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1047 glColor4fv(back[i]);
1048 glCallList(widget[id].rect_obj);
1056 /*---------------------------------------------------------------------------*/
1058 static void gui_paint_text(int id);
1060 static void gui_paint_array(int id)
1066 GLfloat cx = widget[id].x + widget[id].w / 2.0f;
1067 GLfloat cy = widget[id].y + widget[id].h / 2.0f;
1068 GLfloat ck = widget[id].scale;
1070 glTranslatef(+cx, +cy, 0.0f);
1071 glScalef(ck, ck, ck);
1072 glTranslatef(-cx, -cy, 0.0f);
1074 /* Recursively paint all subwidgets. */
1076 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1082 static void gui_paint_image(int id)
1084 /* Draw the widget rect, textured using the image. */
1088 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1089 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1091 glScalef(widget[id].scale,
1095 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1096 glColor4fv(gui_wht);
1097 glCallList(widget[id].rect_obj);
1102 static void gui_paint_count(int id)
1104 int j, i = widget[id].size;
1108 glColor4fv(gui_wht);
1110 /* Translate to the widget center, and apply the pulse scale. */
1112 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1113 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1115 glScalef(widget[id].scale,
1119 if (widget[id].value > 0)
1121 /* Translate left by half the total width of the rendered value. */
1123 for (j = widget[id].value; j; j /= 10)
1124 glTranslatef((GLfloat) +digit_w[i][j % 10] / 2.0f, 0.0f, 0.0f);
1126 glTranslatef((GLfloat) -digit_w[i][0] / 2.0f, 0.0f, 0.0f);
1128 /* Render each digit, moving right after each. */
1130 for (j = widget[id].value; j; j /= 10)
1132 glBindTexture(GL_TEXTURE_2D, digit_text[i][j % 10]);
1133 glCallList(digit_list[i][j % 10]);
1134 glTranslatef((GLfloat) -digit_w[i][j % 10], 0.0f, 0.0f);
1137 else if (widget[id].value == 0)
1139 /* If the value is zero, just display a zero in place. */
1141 glBindTexture(GL_TEXTURE_2D, digit_text[i][0]);
1142 glCallList(digit_list[i][0]);
1148 static void gui_paint_clock(int id)
1150 int i = widget[id].size;
1151 int mt = (widget[id].value / 6000) / 10;
1152 int mo = (widget[id].value / 6000) % 10;
1153 int st = ((widget[id].value % 6000) / 100) / 10;
1154 int so = ((widget[id].value % 6000) / 100) % 10;
1155 int ht = ((widget[id].value % 6000) % 100) / 10;
1156 int ho = ((widget[id].value % 6000) % 100) % 10;
1158 GLfloat dx_large = (GLfloat) digit_w[i][0];
1159 GLfloat dx_small = (GLfloat) digit_w[i][0] * 0.75f;
1161 if (widget[id].value < 0)
1166 glColor4fv(gui_wht);
1168 /* Translate to the widget center, and apply the pulse scale. */
1170 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1171 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1173 glScalef(widget[id].scale,
1177 /* Translate left by half the total width of the rendered value. */
1180 glTranslatef(-2.25f * dx_large, 0.0f, 0.0f);
1182 glTranslatef(-1.75f * dx_large, 0.0f, 0.0f);
1184 /* Render the minutes counter. */
1188 glBindTexture(GL_TEXTURE_2D, digit_text[i][mt]);
1189 glCallList(digit_list[i][mt]);
1190 glTranslatef(dx_large, 0.0f, 0.0f);
1193 glBindTexture(GL_TEXTURE_2D, digit_text[i][mo]);
1194 glCallList(digit_list[i][mo]);
1195 glTranslatef(dx_small, 0.0f, 0.0f);
1197 /* Render the colon. */
1199 glBindTexture(GL_TEXTURE_2D, digit_text[i][10]);
1200 glCallList(digit_list[i][10]);
1201 glTranslatef(dx_small, 0.0f, 0.0f);
1203 /* Render the seconds counter. */
1205 glBindTexture(GL_TEXTURE_2D, digit_text[i][st]);
1206 glCallList(digit_list[i][st]);
1207 glTranslatef(dx_large, 0.0f, 0.0f);
1209 glBindTexture(GL_TEXTURE_2D, digit_text[i][so]);
1210 glCallList(digit_list[i][so]);
1211 glTranslatef(dx_small, 0.0f, 0.0f);
1213 /* Render hundredths counter half size. */
1215 glScalef(0.5f, 0.5f, 1.0f);
1217 glBindTexture(GL_TEXTURE_2D, digit_text[i][ht]);
1218 glCallList(digit_list[i][ht]);
1219 glTranslatef(dx_large, 0.0f, 0.0f);
1221 glBindTexture(GL_TEXTURE_2D, digit_text[i][ho]);
1222 glCallList(digit_list[i][ho]);
1227 static void gui_paint_label(int id)
1229 /* Draw the widget text box, textured using the glyph. */
1233 glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
1234 (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
1236 glScalef(widget[id].scale,
1240 glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
1241 glCallList(widget[id].text_obj);
1246 static void gui_paint_text(int id)
1248 switch (widget[id].type & GUI_TYPE)
1250 case GUI_SPACE: break;
1251 case GUI_FILLER: break;
1252 case GUI_HARRAY: gui_paint_array(id); break;
1253 case GUI_VARRAY: gui_paint_array(id); break;
1254 case GUI_HSTACK: gui_paint_array(id); break;
1255 case GUI_VSTACK: gui_paint_array(id); break;
1256 case GUI_IMAGE: gui_paint_image(id); break;
1257 case GUI_COUNT: gui_paint_count(id); break;
1258 case GUI_CLOCK: gui_paint_clock(id); break;
1259 default: gui_paint_label(id); break;
1263 void gui_paint(int id)
1267 config_push_ortho();
1269 glEnable(GL_COLOR_MATERIAL);
1270 glDisable(GL_LIGHTING);
1271 glDisable(GL_DEPTH_TEST);
1273 glDisable(GL_TEXTURE_2D);
1274 gui_paint_rect(id, 0);
1276 glEnable(GL_TEXTURE_2D);
1279 glColor4fv(gui_wht);
1281 glEnable(GL_DEPTH_TEST);
1282 glEnable(GL_LIGHTING);
1283 glDisable(GL_COLOR_MATERIAL);
1285 config_pop_matrix();
1289 /*---------------------------------------------------------------------------*/
1291 void gui_dump(int id, int d)
1299 switch (widget[id].type & GUI_TYPE)
1301 case GUI_HARRAY: type = "harray"; break;
1302 case GUI_VARRAY: type = "varray"; break;
1303 case GUI_HSTACK: type = "hstack"; break;
1304 case GUI_VSTACK: type = "vstack"; break;
1305 case GUI_FILLER: type = "filler"; break;
1306 case GUI_IMAGE: type = "image"; break;
1307 case GUI_LABEL: type = "label"; break;
1308 case GUI_COUNT: type = "count"; break;
1309 case GUI_CLOCK: type = "clock"; break;
1312 for (i = 0; i < d; i++)
1315 printf("%04d %s\n", id, type);
1317 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1318 gui_dump(jd, d + 1);
1322 void gui_pulse(int id, float k)
1324 if (id) widget[id].scale = k;
1327 void gui_timer(int id, float dt)
1333 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1336 if (widget[id].scale - 1.0f < dt)
1337 widget[id].scale = 1.0f;
1339 widget[id].scale -= dt;
1343 int gui_point(int id, int x, int y)
1345 static int x_cache = 0;
1346 static int y_cache = 0;
1350 /* Reuse the last coordinates if (x,y) == (-1,-1) */
1353 return gui_point(id, x_cache, y_cache);
1358 /* Short-circuit check the current active widget. */
1360 jd = gui_search(active, x, y);
1362 /* If not still active, search the hierarchy for a new active widget. */
1365 jd = gui_search(id, x, y);
1367 /* If the active widget has changed, return the new active id. */
1369 if (jd == 0 || jd == active)
1375 void gui_focus(int i)
1385 int gui_token(int id)
1387 return id ? widget[id].token : 0;
1390 int gui_value(int id)
1392 return id ? widget[id].value : 0;
1395 void gui_toggle(int id)
1397 widget[id].value = widget[id].value ? 0 : 1;
1400 /*---------------------------------------------------------------------------*/
1402 static int gui_vert_offset(int id, int jd)
1404 /* Vertical offset between bottom of id and top of jd */
1406 return widget[id].y - (widget[jd].y + widget[jd].h);
1409 static int gui_horz_offset(int id, int jd)
1411 /* Horizontal offset between left of id and right of jd */
1413 return widget[id].x - (widget[jd].x + widget[jd].w);
1416 static int gui_vert_dist(int id, int jd)
1418 /* Vertical distance between the tops of id and jd */
1420 return abs((widget[id].y + widget[id].h) - (widget[jd].y + widget[jd].h));
1423 static int gui_horz_dist(int id, int jd)
1425 /* Horizontal distance between the left sides of id and jd */
1427 return abs(widget[id].x - widget[jd].x);
1430 /*---------------------------------------------------------------------------*/
1432 static int gui_stick_L(int id, int dd)
1435 int o, omin, d, dmin;
1437 /* Find the closest "hot" widget to the left of dd (and inside id) */
1439 if (id && gui_hot(id))
1443 omin = widget[dd].x - widget[id].x + 1;
1444 dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1446 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1448 kd = gui_stick_L(jd, dd);
1452 o = gui_horz_offset(dd, kd);
1453 d = gui_vert_dist(dd, kd);
1455 if (0 <= o && o <= omin && d <= dmin)
1467 static int gui_stick_R(int id, int dd)
1470 int o, omin, d, dmin;
1472 /* Find the closest "hot" widget to the right of dd (and inside id) */
1474 if (id && gui_hot(id))
1478 omin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
1479 dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
1481 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1483 kd = gui_stick_R(jd, dd);
1487 o = gui_horz_offset(kd, dd);
1488 d = gui_vert_dist(dd, kd);
1490 if (0 <= o && o <= omin && d <= dmin)
1502 static int gui_stick_D(int id, int dd)
1505 int o, omin, d, dmin;
1507 /* Find the closest "hot" widget below dd (and inside id) */
1509 if (id && gui_hot(id))
1513 omin = widget[dd].y - widget[id].y + 1;
1514 dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1516 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1518 kd = gui_stick_D(jd, dd);
1522 o = gui_vert_offset(dd, kd);
1523 d = gui_horz_dist(dd, kd);
1525 if (0 <= o && o <= omin && d <= dmin)
1537 static int gui_stick_U(int id, int dd)
1540 int o, omin, d, dmin;
1542 /* Find the closest "hot" widget above dd (and inside id) */
1544 if (id && gui_hot(id))
1548 omin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
1549 dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
1551 for (jd = widget[id].car; jd; jd = widget[jd].cdr)
1553 kd = gui_stick_U(jd, dd);
1557 o = gui_vert_offset(kd, dd);
1558 d = gui_horz_dist(dd, kd);
1560 if (0 <= o && o <= omin && d <= dmin)
1572 /*---------------------------------------------------------------------------*/
1574 static int gui_wrap_L(int id, int dd)
1578 if ((jd = gui_stick_L(id, dd)) == 0)
1579 for (jd = dd; (kd = gui_stick_R(id, jd)); jd = kd)
1585 static int gui_wrap_R(int id, int dd)
1589 if ((jd = gui_stick_R(id, dd)) == 0)
1590 for (jd = dd; (kd = gui_stick_L(id, jd)); jd = kd)
1596 static int gui_wrap_U(int id, int dd)
1600 if ((jd = gui_stick_U(id, dd)) == 0)
1601 for (jd = dd; (kd = gui_stick_D(id, jd)); jd = kd)
1607 static int gui_wrap_D(int id, int dd)
1611 if ((jd = gui_stick_D(id, dd)) == 0)
1612 for (jd = dd; (kd = gui_stick_U(id, jd)); jd = kd)
1618 /*---------------------------------------------------------------------------*/
1620 /* Flag the axes to prevent uncontrolled scrolling. */
1622 static int xflag = 1;
1623 static int yflag = 1;
1627 /* Force the user to recenter the joystick before the next GUI action. */
1633 int gui_stick(int id, int x, int y)
1637 /* Find a new active widget in the direction of joystick motion. */
1639 if (x && -JOY_MID <= x && x <= +JOY_MID)
1641 else if (x < -JOY_MID && xflag && (jd = gui_wrap_L(id, active)))
1643 else if (x > +JOY_MID && xflag && (jd = gui_wrap_R(id, active)))
1646 if (y && -JOY_MID <= y && y <= +JOY_MID)
1648 else if (y < -JOY_MID && yflag && (jd = gui_wrap_U(id, active)))
1650 else if (y > +JOY_MID && yflag && (jd = gui_wrap_D(id, active)))
1653 /* If the active widget has changed, return the new active id. */
1655 if (jd == 0 || jd == active)
1661 /*---------------------------------------------------------------------------*/