Convert all path-related (body, switch) logic to use integer milliseconds
[neverball] / share / gui.c
index 68a9b25..a942126 100644 (file)
 #include <stdio.h>
 
 #include "config.h"
+#include "video.h"
 #include "glext.h"
 #include "image.h"
 #include "vec3.h"
-#include "text.h"
 #include "gui.h"
+#include "common.h"
+
+#include "fs.h"
+#include "fs_rwops.h"
 
 /*---------------------------------------------------------------------------*/
 
@@ -42,6 +46,8 @@
 #define GUI_CLOCK  18
 #define GUI_SPACE  20
 
+#define GUI_LINES 8
+
 struct widget
 {
     int     type;
@@ -63,6 +69,11 @@ struct widget
     const GLfloat *color1;
 
     GLfloat  scale;
+
+    int text_obj_w;
+    int text_obj_h;
+
+    enum trunc trunc;
 };
 
 /*---------------------------------------------------------------------------*/
@@ -82,6 +93,10 @@ static int           active;
 static int           radius;
 static TTF_Font     *font[3] = { NULL, NULL, NULL };
 
+static void      *fontdata;
+static int        fontdatalen;
+static SDL_RWops *fontrwops;
+
 static GLuint digit_text[3][11];
 static GLuint digit_list[3][11];
 static int    digit_w[3][11];
@@ -177,10 +192,10 @@ static GLuint gui_rect(int x, int y, int w, int h, int f, int r)
                 float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
                 float Yb = y     + ((f & GUI_SW) ? (r - s) : 0);
 
-                glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
+                glTexCoord2f((X - x) / w, (Ya - y) / h);
                 glVertex2f(X, Ya);
 
-                glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
+                glTexCoord2f((X - x) / w, (Yb - y) / h);
                 glVertex2f(X, Yb);
             }
 
@@ -196,10 +211,10 @@ static GLuint gui_rect(int x, int y, int w, int h, int f, int r)
                 float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
                 float Yb = y     + ((f & GUI_SE) ? (r - c) : 0);
 
-                glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
+                glTexCoord2f((X - x) / w, (Ya - y) / h);
                 glVertex2f(X, Ya);
 
-                glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
+                glTexCoord2f((X - x) / w, (Yb - y) / h);
                 glVertex2f(X, Yb);
             }
         }
@@ -212,6 +227,23 @@ static GLuint gui_rect(int x, int y, int w, int h, int f, int r)
 
 /*---------------------------------------------------------------------------*/
 
+static const char *pick_font_path(void)
+{
+    const char *path;
+
+    path = _(GUI_FACE);
+
+    if (!fs_exists(path))
+    {
+        fprintf(stderr, L_("Font '%s' doesn't exist, trying default font.\n"),
+                path);
+
+        path = GUI_FACE;
+    }
+
+    return path;
+}
+
 void gui_init(void)
 {
     const float *c0 = gui_yel;
@@ -225,17 +257,43 @@ void gui_init(void)
 
     if (TTF_Init() == 0)
     {
+        const char *fontpath = pick_font_path();
+
         int s0 = s / 26;
         int s1 = s / 13;
         int s2 = s /  7;
 
         memset(widget, 0, sizeof (struct widget) * MAXWIDGET);
 
-        /* Load small, medium, and large typefaces. */
+        /* Load the font. */
+
+        if ((fontdata = fs_load(fontpath, &fontdatalen)))
+        {
+            fontrwops = SDL_RWFromConstMem(fontdata, fontdatalen);
+
+            /* Load small, medium, and large typefaces. */
+
+            font[GUI_SML] = TTF_OpenFontRW(fontrwops, 0, s0);
+
+            SDL_RWseek(fontrwops, 0, SEEK_SET);
+            font[GUI_MED] = TTF_OpenFontRW(fontrwops, 0, s1);
+
+            SDL_RWseek(fontrwops, 0, SEEK_SET);
+            font[GUI_LRG] = TTF_OpenFontRW(fontrwops, 0, s2);
+
+            /* fontrwops remains open. */
+        }
+        else
+        {
+            fontrwops = NULL;
+
+            font[GUI_SML] = NULL;
+            font[GUI_MED] = NULL;
+            font[GUI_LRG] = NULL;
+
+            fprintf(stderr, L_("Could not load font '%s'.\n"), fontpath);
+        }
 
-        font[GUI_SML] = TTF_OpenFont(config_data(GUI_FACE), s0);
-        font[GUI_MED] = TTF_OpenFont(config_data(GUI_FACE), s1);
-        font[GUI_LRG] = TTF_OpenFont(config_data(GUI_FACE), s2);
         radius = s / 60;
 
         /* Initialize digit glyphs and lists for counters and clocks. */
@@ -319,6 +377,9 @@ void gui_free(void)
     if (font[GUI_MED]) TTF_CloseFont(font[GUI_MED]);
     if (font[GUI_SML]) TTF_CloseFont(font[GUI_SML]);
 
+    if (fontrwops) SDL_RWclose(fontrwops);
+    if (fontdata)  free(fontdata);
+
     TTF_Quit();
 }
 
@@ -348,6 +409,10 @@ static int gui_widget(int pd, int type)
             widget[id].color0   = gui_wht;
             widget[id].color1   = gui_wht;
             widget[id].scale    = 1.0f;
+            widget[id].trunc    = TRUNC_NONE;
+
+            widget[id].text_obj_w = 0;
+            widget[id].text_obj_h = 0;
 
             /* Insert the new widget into the parent's widget list. */
 
@@ -379,6 +444,104 @@ int gui_filler(int pd) { return gui_widget(pd, GUI_FILLER); }
 
 /*---------------------------------------------------------------------------*/
 
+struct size
+{
+    int w, h;
+};
+
+
+static struct size gui_measure(const char *text, TTF_Font *font)
+{
+    struct size size = { 0, 0 };
+
+    if (font)
+        TTF_SizeUTF8(font, text, &size.w, &size.h);
+
+    return size;
+}
+
+static char *gui_trunc_head(const char *text,
+                            const int maxwidth,
+                            TTF_Font *font)
+{
+    int left, right, mid;
+    char *str = NULL;
+
+    left  = 0;
+    right = strlen(text);
+
+    while (right - left > 1)
+    {
+        mid = (left + right) / 2;
+
+        str = concat_string("...", text + mid, NULL);
+
+        if (gui_measure(str, font).w <= maxwidth)
+            right = mid;
+        else
+            left = mid;
+
+        free(str);
+    }
+
+    return concat_string("...", text + right, NULL);
+}
+
+static char *gui_trunc_tail(const char *text,
+                            const int maxwidth,
+                            TTF_Font *font)
+{
+    int left, right, mid;
+    char *str = NULL;
+
+    left  = 0;
+    right = strlen(text);
+
+    while (right - left > 1)
+    {
+        mid = (left + right) / 2;
+
+        str = malloc(mid + sizeof ("..."));
+
+        memcpy(str,       text,  mid);
+        memcpy(str + mid, "...", sizeof ("..."));
+
+        if (gui_measure(str, font).w <= maxwidth)
+            left = mid;
+        else
+            right = mid;
+
+        free(str);
+    }
+
+    str = malloc(left + sizeof ("..."));
+
+    memcpy(str,        text,  left);
+    memcpy(str + left, "...", sizeof ("..."));
+
+    return str;
+}
+
+static char *gui_truncate(const char *text,
+                          const int maxwidth,
+                          TTF_Font *font,
+                          enum trunc trunc)
+{
+    if (gui_measure(text, font).w <= maxwidth)
+        return strdup(text);
+
+    switch (trunc)
+    {
+    case TRUNC_NONE: return strdup(text);                         break;
+    case TRUNC_HEAD: return gui_trunc_head(text, maxwidth, font); break;
+    case TRUNC_TAIL: return gui_trunc_tail(text, maxwidth, font); break;
+    }
+
+    return NULL;
+}
+
+/*---------------------------------------------------------------------------*/
+
 void gui_set_image(int id, const char *file)
 {
     if (glIsTexture(widget[id].text_img))
@@ -396,10 +559,19 @@ void gui_set_label(int id, const char *text)
     if (glIsList(widget[id].text_obj))
         glDeleteLists(widget[id].text_obj, 1);
 
+    text = gui_truncate(text, widget[id].w - radius,
+                        font[widget[id].size],
+                        widget[id].trunc);
+
     widget[id].text_img = make_image_from_font(NULL, NULL, &w, &h,
                                                text, font[widget[id].size]);
     widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
                                    widget[id].color0, widget[id].color1);
+
+    widget[id].text_obj_w = w;
+    widget[id].text_obj_h = h;
+
+    free((void *) text);
 }
 
 void gui_set_count(int id, int value)
@@ -415,33 +587,65 @@ void gui_set_clock(int id, int value)
 void gui_set_color(int id, const float *c0,
                            const float *c1)
 {
-    widget[id].color0 = c0 ? c0 : gui_yel;
-    widget[id].color1 = c1 ? c1 : gui_red;
+    if (id)
+    {
+        c0 = c0 ? c0 : gui_yel;
+        c1 = c1 ? c1 : gui_red;
+
+        if (widget[id].color0 != c0 || widget[id].color1 != c1)
+        {
+            widget[id].color0 = c0;
+            widget[id].color1 = c1;
+
+            if (glIsList(widget[id].text_obj))
+            {
+                int w, h;
+
+                glDeleteLists(widget[id].text_obj, 1);
+
+                w = widget[id].text_obj_w;
+                h = widget[id].text_obj_h;
+
+                widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
+                                               widget[id].color0,
+                                               widget[id].color1);
+            }
+        }
+    }
 }
 
 void gui_set_multi(int id, const char *text)
 {
     const char *p;
 
-    char s[8][MAXSTR];
-    int  i, j, jd;
+    char s[GUI_LINES][MAXSTR];
+    int i, sc, lc, jd;
 
     size_t n = 0;
 
+    /* Count available labels. */
+
+    for (lc = 0, jd = widget[id].car; jd; lc++, jd = widget[jd].cdr);
+
     /* Copy each delimited string to a line buffer. */
 
-    for (p = text, j = 0; *p && j < 8; j++)
+    for (p = text, sc = 0; *p && sc < lc; sc++)
     {
-        strncpy(s[j], p, (n = strcspn(p, "\\")));
-        s[j][n] = 0;
+        strncpy(s[sc], p, (n = strcspn(p, "\\")));
+        s[sc][n] = 0;
 
         if (*(p += n) == '\\') p++;
     }
 
     /* Set the label value for each line. */
 
-    for (i = j - 1, jd = widget[id].car; i >= 0 && jd; i--, jd = widget[jd].cdr)
-        gui_set_label(jd, s[i]);
+    for (i = lc - 1, jd = widget[id].car; i >= 0; i--, jd = widget[jd].cdr)
+        gui_set_label(jd, i < sc ? s[i] : "");
+}
+
+void gui_set_trunc(int id, enum trunc trunc)
+{
+    widget[id].trunc = trunc;
 }
 
 /*---------------------------------------------------------------------------*/
@@ -554,6 +758,7 @@ int gui_space(int pd)
 }
 
 /*---------------------------------------------------------------------------*/
+
 /*
  * Create  a multi-line  text box  using a  vertical array  of labels.
  * Parse the  text for '\'  characters and treat them  as line-breaks.
@@ -569,15 +774,15 @@ int gui_multi(int pd, const char *text, int size, int rect, const float *c0,
     {
         const char *p;
 
-        char s[8][MAXSTR];
-        int  r[8];
+        char s[GUI_LINES][MAXSTR];
+        int  r[GUI_LINES];
         int  i, j;
 
         size_t n = 0;
 
         /* Copy each delimited string to a line buffer. */
 
-        for (p = text, j = 0; *p && j < 8; j++)
+        for (p = text, j = 0; *p && j < GUI_LINES; j++)
         {
             strncpy(s[j], p, (n = strcspn(p, "\\")));
             s[j][n] = 0;
@@ -696,19 +901,25 @@ static void gui_button_up(int id)
 {
     /* Store width and height for later use in text rendering. */
 
-    widget[id].x = widget[id].w;
-    widget[id].y = widget[id].h;
+    widget[id].text_obj_w = widget[id].w;
+    widget[id].text_obj_h = widget[id].h;
 
     if (widget[id].w < widget[id].h && widget[id].w > 0)
         widget[id].w = widget[id].h;
 
-
     /* Padded text elements look a little nicer. */
 
     if (widget[id].w < config_get_d(CONFIG_WIDTH))
         widget[id].w += radius;
     if (widget[id].h < config_get_d(CONFIG_HEIGHT))
         widget[id].h += radius;
+
+    /* A button should be at least wide enough to accomodate the rounding. */
+
+    if (widget[id].w < 2 * radius)
+        widget[id].w = 2 * radius;
+    if (widget[id].h < 2 * radius)
+        widget[id].h = 2 * radius;
 }
 
 static void gui_widget_up(int id)
@@ -720,6 +931,7 @@ static void gui_widget_up(int id)
         case GUI_VARRAY: gui_varray_up(id); break;
         case GUI_HSTACK: gui_hstack_up(id); break;
         case GUI_VSTACK: gui_vstack_up(id); break;
+        case GUI_FILLER:                    break;
         default:         gui_button_up(id); break;
         }
 }
@@ -859,8 +1071,8 @@ static void gui_button_dn(int id, int x, int y, int w, int h)
 {
     /* Recall stored width and height for text rendering. */
 
-    int W = widget[id].x;
-    int H = widget[id].y;
+    int W = widget[id].text_obj_w;
+    int H = widget[id].text_obj_h;
     int R = widget[id].rect;
 
     const float *c0 = widget[id].color0;
@@ -1258,7 +1470,7 @@ void gui_paint(int id)
 {
     if (id)
     {
-        config_push_ortho();
+        video_push_ortho();
         {
             glEnable(GL_COLOR_MATERIAL);
             glDisable(GL_LIGHTING);
@@ -1269,12 +1481,14 @@ void gui_paint(int id)
 
                 glEnable(GL_TEXTURE_2D);
                 gui_paint_text(id);
+
+                glColor4fv(gui_wht);
             }
             glEnable(GL_DEPTH_TEST);
             glEnable(GL_LIGHTING);
             glDisable(GL_COLOR_MATERIAL);
         }
-        config_pop_matrix();
+        video_pop_matrix();
     }
 }
 
@@ -1609,38 +1823,25 @@ static int gui_wrap_D(int id, int dd)
 
 /*---------------------------------------------------------------------------*/
 
-/* Flag the axes to prevent uncontrolled scrolling. */
-
-static int xflag = 1;
-static int yflag = 1;
-
-void gui_stuck()
-{
-    /* Force the user to recenter the joystick before the next GUI action. */
-
-    xflag = 0;
-    yflag = 0;
-}
-
-int gui_stick(int id, int x, int y)
+int gui_stick(int id, int a, float v, int bump)
 {
     int jd = 0;
 
+    if (!bump)
+        return 0;
+
     /* Find a new active widget in the direction of joystick motion. */
 
-    if (x && -JOY_MID <= x && x <= +JOY_MID)
-        xflag = 1;
-    else if (x < -JOY_MID && xflag && (jd = gui_wrap_L(id, active)))
-        xflag = 0;
-    else if (x > +JOY_MID && xflag && (jd = gui_wrap_R(id, active)))
-        xflag = 0;
-
-    if (y && -JOY_MID <= y && y <= +JOY_MID)
-        yflag = 1;
-    else if (y < -JOY_MID && yflag && (jd = gui_wrap_U(id, active)))
-        yflag = 0;
-    else if (y > +JOY_MID && yflag && (jd = gui_wrap_D(id, active)))
-        yflag = 0;
+    if (config_tst_d(CONFIG_JOYSTICK_AXIS_X, a))
+    {
+        if (v < 0) jd = gui_wrap_L(id, active);
+        if (v > 0) jd = gui_wrap_R(id, active);
+    }
+    else if (config_tst_d(CONFIG_JOYSTICK_AXIS_Y, a))
+    {
+        if (v < 0) jd = gui_wrap_U(id, active);
+        if (v > 0) jd = gui_wrap_D(id, active);
+    }
 
     /* If the active widget has changed, return the new active id. */