Merge branch 'gles'
[neverball] / share / gui.c
index 0daeb62..8ac72d5 100644 (file)
 #include <stdio.h>
 
 #include "config.h"
+#include "video.h"
 #include "glext.h"
 #include "image.h"
 #include "vec3.h"
 #include "gui.h"
+#include "common.h"
+
+#include "fs.h"
+#include "fs_rwops.h"
+
+/*---------------------------------------------------------------------------*/
+
+/* Very pure colors for the GUI. I was watching BANZAI when I designed this. */
+
+const GLubyte gui_wht[4] = { 0xFF, 0xFF, 0xFF, 0xFF };  /* White  */
+const GLubyte gui_yel[4] = { 0xFF, 0xFF, 0x00, 0xFF };  /* Yellow */
+const GLubyte gui_red[4] = { 0xFF, 0x00, 0x00, 0xFF };  /* Red    */
+const GLubyte gui_grn[4] = { 0x00, 0xFF, 0x00, 0xFF };  /* Green  */
+const GLubyte gui_blu[4] = { 0x00, 0x00, 0xFF, 0xFF };  /* Blue   */
+const GLubyte gui_blk[4] = { 0x00, 0x00, 0x00, 0xFF };  /* Black  */
+const GLubyte gui_gry[4] = { 0x55, 0x55, 0x55, 0xFF };  /* Gray   */
+const GLubyte gui_shd[4] = { 0x00, 0x00, 0x00, 0x80 };  /* Shadow */
 
 /*---------------------------------------------------------------------------*/
 
-#define MAXWIDGET 256
+#define WIDGET_MAX 256
 
-#define GUI_TYPE 0xFFFE
+#define GUI_TYPE 0xFFFC
 
-#define GUI_FREE   0
-#define GUI_STATE  1
-#define GUI_HARRAY 2
-#define GUI_VARRAY 4
-#define GUI_HSTACK 6
-#define GUI_VSTACK 8
-#define GUI_FILLER 10
-#define GUI_IMAGE  12
-#define GUI_LABEL  14
-#define GUI_COUNT  16
-#define GUI_CLOCK  18
-#define GUI_SPACE  20
-#define GUI_PAUSE  22
+#define GUI_FREE  0
+
+#define GUI_STATE 1
+#define GUI_FILL  2
+
+#define GUI_FLAGS 2
+
+#define GUI_HARRAY (1  << GUI_FLAGS)
+#define GUI_VARRAY (2  << GUI_FLAGS)
+#define GUI_HSTACK (3  << GUI_FLAGS)
+#define GUI_VSTACK (4  << GUI_FLAGS)
+#define GUI_FILLER (5  << GUI_FLAGS)
+#define GUI_IMAGE  (6  << GUI_FLAGS)
+#define GUI_LABEL  (7  << GUI_FLAGS)
+#define GUI_COUNT  (8  << GUI_FLAGS)
+#define GUI_CLOCK  (9  << GUI_FLAGS)
+#define GUI_SPACE  (10 << GUI_FLAGS)
+
+#define GUI_LINES 8
+
+/*---------------------------------------------------------------------------*/
 
 struct widget
 {
@@ -50,45 +76,41 @@ struct widget
     int     size;
     int     rect;
 
+    const GLubyte *color0;
+    const GLubyte *color1;
+
     int     x, y;
     int     w, h;
     int     car;
     int     cdr;
 
-    GLuint  text_img;
-    GLuint  text_obj;
-    GLuint  rect_obj;
+    GLuint  image;
+    GLfloat scale;
 
-    const GLfloat *color0;
-    const GLfloat *color1;
+    int     text_w;
+    int     text_h;
 
-    GLfloat  scale;
+    enum trunc trunc;
 };
 
 /*---------------------------------------------------------------------------*/
 
-const GLfloat gui_wht[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
-const GLfloat gui_yel[4] = { 1.0f, 1.0f, 0.0f, 1.0f };
-const GLfloat gui_red[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
-const GLfloat gui_grn[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
-const GLfloat gui_blu[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
-const GLfloat gui_blk[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
-const GLfloat gui_gry[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
+/* GUI widget state */
 
-/*---------------------------------------------------------------------------*/
-
-static struct widget widget[MAXWIDGET];
+static struct widget widget[WIDGET_MAX];
 static int           active;
 static int           radius;
 static TTF_Font     *font[3] = { NULL, NULL, NULL };
-static int           scale[3] = { 1, 1, 1 };
 
-static GLuint digit_text[3][11];
-static GLuint digit_list[3][11];
-static int    digit_w[3][11];
-static int    digit_h[3][11];
+/* Digit widgets for the HUD. */
+
+static int digit_id[3][11];
 
-static int pause_id;
+/* Font data access. */
+
+static void      *fontdata;
+static int        fontdatalen;
+static SDL_RWops *fontrwops;
 
 /*---------------------------------------------------------------------------*/
 
@@ -98,238 +120,306 @@ static int gui_hot(int id)
 }
 
 /*---------------------------------------------------------------------------*/
-/*
- * Initialize a  display list  containing a  rectangle (x, y, w, h) to
- * which a  rendered-font texture  may be applied.   Colors  c0 and c1
- * determine the top-to-bottom color gradiant of the text.
- */
 
-static GLuint gui_list(int x, int y,
-                       int w, int h, const float *c0, const float *c1)
+/* Vertex buffer definitions for widget rendering. */
+
+#define RECT_LEN 36
+#define TEXT_LEN 8
+#define WIDGET_LEN (RECT_LEN + TEXT_LEN)
+
+struct vert
 {
-    GLuint list = glGenLists(1);
+    GLubyte c[4];
+    GLfloat u[2];
+    GLshort p[2];
+};
 
-    GLfloat s0, t0;
-    GLfloat s1, t1;
+static struct vert vert_buf[WIDGET_MAX * WIDGET_LEN];
+static GLuint      vert_obj = 0;
 
-    int W, H, d = h / 16;
+/*---------------------------------------------------------------------------*/
 
-    /* Assume the applied texture size is rect size rounded to power-of-two. */
+static void set_vert(struct vert *v, int x, int y,
+                     GLfloat s, GLfloat t, const GLubyte *c)
+{
+    v->c[0] = c[0];
+    v->c[1] = c[1];
+    v->c[2] = c[2];
+    v->c[3] = c[3];
+    v->u[0] = s;
+    v->u[1] = t;
+    v->p[0] = x;
+    v->p[1] = y;
+}
 
-    image_size(&W, &H, w, h);
+/*---------------------------------------------------------------------------*/
 
-    s0 = 0.5f * (W - w) / W;
-    t0 = 0.5f * (H - h) / H;
-    s1 = 1.0f - s0;
-    t1 = 1.0f - t0;
+static void draw_enable(GLboolean c, GLboolean u, GLboolean p)
+{
+    glBindBuffer_(GL_ARRAY_BUFFER, vert_obj);
 
-    glNewList(list, GL_COMPILE);
+    if (c)
     {
-        glBegin(GL_QUADS);
-        {
-            glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
-            glTexCoord2f(s0, t1); glVertex2i(x     + d, y     - d);
-            glTexCoord2f(s1, t1); glVertex2i(x + w + d, y     - d);
-            glTexCoord2f(s1, t0); glVertex2i(x + w + d, y + h - d);
-            glTexCoord2f(s0, t0); glVertex2i(x     + d, y + h - d);
-
-            glColor4fv(c0);
-            glTexCoord2f(s0, t1); glVertex2i(x,     y);
-            glTexCoord2f(s1, t1); glVertex2i(x + w, y);
-
-            glColor4fv(c1);
-            glTexCoord2f(s1, t0); glVertex2i(x + w, y + h);
-            glTexCoord2f(s0, t0); glVertex2i(x,     y + h);
-        }
-        glEnd();
+        glEnableClientState(GL_COLOR_ARRAY);
+        glColorPointer   (4, GL_UNSIGNED_BYTE, sizeof (struct vert),
+                                  (GLvoid *) offsetof (struct vert, c));
     }
-    glEndList();
+    if (u)
+    {
+        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+        glTexCoordPointer(2, GL_FLOAT,         sizeof (struct vert),
+                                  (GLvoid *) offsetof (struct vert, u));
+    }
+    if (p)
+    {
+        glEnableClientState(GL_VERTEX_ARRAY);
+        glVertexPointer  (2, GL_SHORT,         sizeof (struct vert),
+                                  (GLvoid *) offsetof (struct vert, p));
+    }
+}
 
-    return list;
+static void draw_rect(int id)
+{
+    glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_LEN,  RECT_LEN);
 }
 
-/*
- * Initialize a display list containing a rounded-corner rectangle (x,
- * y, w, h).  Generate texture coordinates to properly apply a texture
- * map to the rectangle as though the corners were not rounded.
- */
+static void draw_text(int id)
+{
+    glDrawArrays(GL_TRIANGLE_STRIP, id * WIDGET_LEN + RECT_LEN, TEXT_LEN);
+}
+
+static void draw_disable(void)
+{
+    glBindBuffer_(GL_ARRAY_BUFFER, 0);
 
-static GLuint gui_rect(int x, int y, int w, int h, int f, int r)
+    glDisableClientState(GL_VERTEX_ARRAY);
+    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+    glDisableClientState(GL_COLOR_ARRAY);
+}
+
+/*---------------------------------------------------------------------------*/
+
+static void gui_rect(int id, int x, int y, int w, int h, int f, int r)
 {
-    GLuint list = glGenLists(1);
+    struct vert *v = vert_buf + id * WIDGET_LEN;
+    struct vert *p = v;
+
+    /* Generate vertex data for the widget's rounded rectangle. */
 
     int n = 8;
     int i;
 
-    glNewList(list, GL_COMPILE);
+    /* Left side... */
+
+    for (i = 0; i <= n; i++)
     {
-        glBegin(GL_QUAD_STRIP);
-        {
-            /* Left side... */
+        float a = 0.5f * V_PI * (float) i / (float) n;
+        float s = r * fsinf(a);
+        float c = r * fcosf(a);
 
-            for (i = 0; i <= n; i++)
-            {
-                float a = 0.5f * V_PI * (float) i / (float) n;
-                float s = r * fsinf(a);
-                float c = r * fcosf(a);
+        float X  = x     + r - c;
+        float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
+        float Yb = y     + ((f & GUI_SW) ? (r - s) : 0);
 
-                float X  = x     + r - c;
-                float Ya = y + h + ((f & GUI_NW) ? (s - r) : 0);
-                float Yb = y     + ((f & GUI_SW) ? (r - s) : 0);
+        set_vert(p++, X, Ya, (X - x) / w, (Ya - y) / h, gui_wht);
+        set_vert(p++, X, Yb, (X - x) / w, (Yb - y) / h, gui_wht);
+    }
 
-                glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
-                glVertex2f(X, Ya);
+    /* Right side... */
 
-                glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
-                glVertex2f(X, Yb);
-            }
+    for (i = 0; i <= n; i++)
+    {
+        float a = 0.5f * V_PI * (float) i / (float) n;
+        float s = r * fsinf(a);
+        float c = r * fcosf(a);
 
-            /* ... Right side. */
+        float X  = x + w - r + s;
+        float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
+        float Yb = y     + ((f & GUI_SE) ? (r - c) : 0);
 
-            for (i = 0; i <= n; i++)
-            {
-                float a = 0.5f * V_PI * (float) i / (float) n;
-                float s = r * fsinf(a);
-                float c = r * fcosf(a);
+        set_vert(p++, X, Ya, (X - x) / w, (Ya - y) / h, gui_wht);
+        set_vert(p++, X, Yb, (X - x) / w, (Yb - y) / h, gui_wht);
+    }
 
-                float X  = x + w - r + s;
-                float Ya = y + h + ((f & GUI_NE) ? (c - r) : 0);
-                float Yb = y     + ((f & GUI_SE) ? (r - c) : 0);
+    /* Copy this off to the VBO. */
 
-                glTexCoord2f((X - x) / w, 1 - (Ya - y) / h);
-                glVertex2f(X, Ya);
+    glBindBuffer_   (GL_ARRAY_BUFFER, vert_obj);
+    glBufferSubData_(GL_ARRAY_BUFFER,
+                     id * WIDGET_LEN * sizeof (struct vert),
+                            RECT_LEN * sizeof (struct vert), v);
+}
+
+static void gui_text(int id, int x, int y,
+                             int w, int h, const GLubyte *c0, const GLubyte *c1)
+{
+    struct vert *v = vert_buf + id * WIDGET_LEN + RECT_LEN;
+
+    /* Assume the applied texture size is rect size rounded to power-of-two. */
+
+    int W;
+    int H;
+
+    image_size(&W, &H, w, h);
+
+    if (w > 0 && h > 0 && W > 0 && H > 0)
+    {
+        const int d = h / 16;  /* Shadow offset */
+
+        const int ww = ((W - w) % 2) ? w + 1 : w;
+        const int hh = ((H - h) % 2) ? h + 1 : h;
+
+        const GLfloat s0 = 0.5f * (W - ww) / W;
+        const GLfloat t0 = 0.5f * (H - hh) / H;
+        const GLfloat s1 = 1.0f - s0;
+        const GLfloat t1 = 1.0f - t0;
+
+        /* Generate vertex data for the colored text and its shadow. */
+
+        set_vert(v + 0, x      + d, y + hh - d, s0, t0, gui_shd);
+        set_vert(v + 1, x      + d, y      - d, s0, t1, gui_shd);
+        set_vert(v + 2, x + ww + d, y + hh - d, s1, t0, gui_shd);
+        set_vert(v + 3, x + ww + d, y      - d, s1, t1, gui_shd);
+
+        set_vert(v + 4, x,          y + hh,     s0, t0, c1);
+        set_vert(v + 5, x,          y,          s0, t1, c0);
+        set_vert(v + 6, x + ww,     y + hh,     s1, t0, c1);
+        set_vert(v + 7, x + ww,     y,          s1, t1, c0);
 
-                glTexCoord2f((X - x) / w, 1 - (Yb - y) / h);
-                glVertex2f(X, Yb);
-            }
-        }
-        glEnd();
     }
-    glEndList();
+    else memset(v, 0, TEXT_LEN * sizeof (struct vert));
 
-    return list;
+    /* Copy this off to the VBO. */
+
+    glBindBuffer_   (GL_ARRAY_BUFFER, vert_obj);
+    glBufferSubData_(GL_ARRAY_BUFFER,
+                     (id * WIDGET_LEN + RECT_LEN) * sizeof (struct vert),
+                                        TEXT_LEN  * sizeof (struct vert), v);
 }
 
 /*---------------------------------------------------------------------------*/
 
-void gui_init(void)
+static const char *pick_font_path(void)
 {
-    const float *c0 = gui_yel;
-    const float *c1 = gui_red;
+    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)
+{
     int w = config_get_d(CONFIG_WIDTH);
     int h = config_get_d(CONFIG_HEIGHT);
-    int i, j, s = (h < w) ? h : w;
+    int s = (h < w) ? h : w;
+    int i, j;
 
     /* Initialize font rendering. */
 
     if (TTF_Init() == 0)
     {
+        const char *fontpath = pick_font_path();
+
         int s0 = s / 26;
         int s1 = s / 13;
         int s2 = s /  7;
-        int m;
 
-        /* Make sure text size doesn't exceed the maximum texture size. */
+        memset(widget, 0, sizeof (struct widget) * WIDGET_MAX);
 
-        glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m);
+        /* Load the font. */
 
-        scale[0] = 1;
-        scale[1] = 1;
-        scale[2] = 1;
+        if ((fontdata = fs_load(fontpath, &fontdatalen)))
+        {
+            fontrwops = SDL_RWFromConstMem(fontdata, fontdatalen);
 
-        while (s0 > m) { s0 /= 2; scale[0] *= 2; }
-        while (s1 > m) { s1 /= 2; scale[1] *= 2; }
-        while (s2 > m) { s2 /= 2; scale[2] *= 2; }
+            /* Load small, medium, and large typefaces. */
 
-        memset(widget, 0, sizeof (struct widget) * MAXWIDGET);
+            font[GUI_SML] = TTF_OpenFontRW(fontrwops, 0, s0);
 
-        /* Load small, medium, and large typefaces. */
+            SDL_RWseek(fontrwops, 0, SEEK_SET);
+            font[GUI_MED] = TTF_OpenFontRW(fontrwops, 0, s1);
 
-        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;
+            SDL_RWseek(fontrwops, 0, SEEK_SET);
+            font[GUI_LRG] = TTF_OpenFontRW(fontrwops, 0, s2);
 
-        /* Initialize the global pause GUI. */
+            /* fontrwops remains open. */
+        }
+        else
+        {
+            fontrwops = NULL;
 
-        if ((pause_id = gui_pause(0)))
-            gui_layout(pause_id, 0, 0);
+            font[GUI_SML] = NULL;
+            font[GUI_MED] = NULL;
+            font[GUI_LRG] = NULL;
 
-        /* Initialize digit glyphs and lists for counters and clocks. */
+            fprintf(stderr, L_("Could not load font '%s'.\n"), fontpath);
+        }
 
-        for (i = 0; i < 3; i++)
-        {
-            char text[2];
+        radius = s / 60;
+    }
 
-            /* Draw digits 0 throught 9. */
+    /* Initialize the VBOs. */
 
-            for (j = 0; j < 10; j++)
-            {
-                text[0] = '0' + (char) j;
-                text[1] =  0;
-
-                digit_text[i][j] = make_image_from_font(NULL, NULL,
-                                                        &digit_w[i][j],
-                                                        &digit_h[i][j],
-                                                        text, font[i], scale[i]);
-                digit_list[i][j] = gui_list(-digit_w[i][j] / 2,
-                                            -digit_h[i][j] / 2,
-                                            +digit_w[i][j],
-                                            +digit_h[i][j], c0, c1);
-            }
+    memset(vert_buf, 0, sizeof (vert_buf));
 
-            /* Draw the colon for the clock. */
+    glGenBuffers_(1,              &vert_obj);
+    glBindBuffer_(GL_ARRAY_BUFFER, vert_obj);
+    glBufferData_(GL_ARRAY_BUFFER, sizeof (vert_buf), vert_buf, GL_STATIC_DRAW);
+    glBindBuffer_(GL_ARRAY_BUFFER, 0);
 
-            digit_text[i][j] = make_image_from_font(NULL, NULL,
-                                                    &digit_w[i][10],
-                                                    &digit_h[i][10],
-                                                    ":", font[i], scale[i]);
-            digit_list[i][j] = gui_list(-digit_w[i][10] / 2,
-                                        -digit_h[i][10] / 2,
-                                        +digit_w[i][10],
-                                        +digit_h[i][10], c0, c1);
-        }
+    /* Cache digit glyphs for HUD rendering. */
+
+    for (i = 0; i < 3; i++)
+    {
+        digit_id[i][ 0] = gui_label(0, "0", i, 0, 0, 0);
+        digit_id[i][ 1] = gui_label(0, "1", i, 0, 0, 0);
+        digit_id[i][ 2] = gui_label(0, "2", i, 0, 0, 0);
+        digit_id[i][ 3] = gui_label(0, "3", i, 0, 0, 0);
+        digit_id[i][ 4] = gui_label(0, "4", i, 0, 0, 0);
+        digit_id[i][ 5] = gui_label(0, "5", i, 0, 0, 0);
+        digit_id[i][ 6] = gui_label(0, "6", i, 0, 0, 0);
+        digit_id[i][ 7] = gui_label(0, "7", i, 0, 0, 0);
+        digit_id[i][ 8] = gui_label(0, "8", i, 0, 0, 0);
+        digit_id[i][ 9] = gui_label(0, "9", i, 0, 0, 0);
+        digit_id[i][10] = gui_label(0, ":", i, 0, 0, 0);
     }
 
+    for (i = 0; i < 3; i++)
+        for (j = 0; j < 11; ++j)
+            gui_layout(digit_id[i][j], 0, 0);
+
     active = 0;
 }
 
 void gui_free(void)
 {
-    int i, j, id;
+    int id;
 
-    /* Release any remaining widget texture and display list indices. */
+    /* Release the VBOs. */
 
-    for (id = 1; id < MAXWIDGET; id++)
-    {
-        if (glIsTexture(widget[id].text_img))
-            glDeleteTextures(1, &widget[id].text_img);
-
-        if (glIsList(widget[id].text_obj))
-            glDeleteLists(widget[id].text_obj, 1);
-        if (glIsList(widget[id].rect_obj))
-            glDeleteLists(widget[id].rect_obj, 1);
-
-        widget[id].type     = GUI_FREE;
-        widget[id].text_img = 0;
-        widget[id].text_obj = 0;
-        widget[id].rect_obj = 0;
-        widget[id].cdr      = 0;
-        widget[id].car      = 0;
-    }
+    if (glIsBuffer_(vert_obj))
+        glDeleteBuffers_(1, &vert_obj);
 
-    /* Release all digit textures and display lists. */
+    /* Release any remaining widget texture and display list indices. */
 
-    for (i = 0; i < 3; i++)
-        for (j = 0; j < 11; j++)
-        {
-            if (glIsTexture(digit_text[i][j]))
-                glDeleteTextures(1, &digit_text[i][j]);
+    for (id = 1; id < WIDGET_MAX; id++)
+    {
+        if (glIsTexture(widget[id].image))
+            glDeleteTextures(1, &widget[id].image);
 
-            if (glIsList(digit_list[i][j]))
-                glDeleteLists(digit_list[i][j], 1);
-        }
+        widget[id].type  = GUI_FREE;
+        widget[id].image = 0;
+        widget[id].cdr   = 0;
+        widget[id].car   = 0;
+    }
 
     /* Release all loaded fonts and finalize font rendering. */
 
@@ -337,6 +427,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,26 +441,27 @@ static int gui_widget(int pd, int type)
 
     /* Find an unused entry in the widget table. */
 
-    for (id = 1; id < MAXWIDGET; id++)
+    for (id = 1; id < WIDGET_MAX; id++)
         if (widget[id].type == GUI_FREE)
         {
             /* Set the type and default properties. */
 
-            widget[id].type     = type;
-            widget[id].token    = 0;
-            widget[id].value    = 0;
-            widget[id].size     = 0;
-            widget[id].rect     = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
-            widget[id].w        = 0;
-            widget[id].h        = 0;
-            widget[id].text_img = 0;
-            widget[id].text_obj = 0;
-            widget[id].rect_obj = 0;
-            widget[id].color0   = gui_wht;
-            widget[id].color1   = gui_wht;
-            widget[id].scale    = 1.0f;
-
-            /* Insert the new widget into the parents's widget list. */
+            widget[id].type   = type;
+            widget[id].token  = 0;
+            widget[id].value  = 0;
+            widget[id].size   = 0;
+            widget[id].rect   = GUI_NW | GUI_SW | GUI_NE | GUI_SE;
+            widget[id].w      = 0;
+            widget[id].h      = 0;
+            widget[id].image  = 0;
+            widget[id].color0 = gui_wht;
+            widget[id].color1 = gui_wht;
+            widget[id].scale  = 1.0f;
+            widget[id].trunc  = TRUNC_NONE;
+            widget[id].text_w = 0;
+            widget[id].text_h = 0;
+
+            /* Insert the new widget into the parent's widget list. */
 
             if (pd)
             {
@@ -384,7 +478,7 @@ static int gui_widget(int pd, int type)
             return id;
         }
 
-    fprintf(stderr, _("Out of widget IDs\n"));
+    fprintf(stderr, "Out of widget IDs\n");
 
     return 0;
 }
@@ -397,28 +491,131 @@ 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))
-        glDeleteTextures(1, &widget[id].text_img);
+    if (glIsTexture(widget[id].image))
+        glDeleteTextures(1, &widget[id].image);
 
-    widget[id].text_img = make_image_from_file(NULL, NULL, NULL, NULL, file);
+    widget[id].image = make_image_from_file(file);
 }
 
 void gui_set_label(int id, const char *text)
 {
-    int w, h;
+    int w = 0;
+    int h = 0;
+
+    if (glIsTexture(widget[id].image))
+        glDeleteTextures(1, &widget[id].image);
+
+    text = gui_truncate(text, widget[id].w - radius,
+                        font[widget[id].size],
+                        widget[id].trunc);
 
-    if (glIsTexture(widget[id].text_img))
-        glDeleteTextures(1, &widget[id].text_img);
-    if (glIsList(widget[id].text_obj))
-        glDeleteLists(widget[id].text_obj, 1);
+    widget[id].image = make_image_from_font(NULL, NULL, &w, &h,
+                                            text, font[widget[id].size]);
+    widget[id].text_w = w;
+    widget[id].text_h = h;
 
-    widget[id].text_img = make_image_from_font(NULL, NULL, &w, &h,
-                                               text, font[widget[id].size],
-                                                    scale[widget[id].size]);
-    widget[id].text_obj = gui_list(-w / 2, -h / 2, w, h,
-                                   widget[id].color0, widget[id].color1);
+    gui_text(id, -w / 2, -h / 2, w, h, widget[id].color0, widget[id].color1);
+
+    free((void *) text); /* Really? */
 }
 
 void gui_set_count(int id, int value)
@@ -431,36 +628,64 @@ void gui_set_clock(int id, int value)
     widget[id].value = value;
 }
 
-void gui_set_color(int id, const float *c0,
-                           const float *c1)
+void gui_set_color(int id, const GLubyte *c0,
+                           const GLubyte *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)
+        {
+            int w = widget[id].text_w;
+            int h = widget[id].text_h;
+
+            widget[id].color0 = c0;
+            widget[id].color1 = c1;
+
+            gui_text(id, -w / 2, -h / 2, w, h, c0, c1);
+        }
+    }
 }
 
 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;
+}
+
+void gui_set_fill(int id)
+{
+    widget[id].type |= GUI_FILL;
 }
 
 /*---------------------------------------------------------------------------*/
@@ -471,10 +696,9 @@ int gui_image(int pd, const char *file, int w, int h)
 
     if ((id = gui_widget(pd, GUI_IMAGE)))
     {
-        widget[id].text_img = make_image_from_file(NULL, NULL,
-                                                   NULL, NULL, file);
-        widget[id].w     = w;
-        widget[id].h     = h;
+        widget[id].image = make_image_from_file(file);
+        widget[id].w = w;
+        widget[id].h = h;
     }
     return id;
 }
@@ -495,11 +719,10 @@ int gui_state(int pd, const char *text, int size, int token, int value)
 
     if ((id = gui_widget(pd, GUI_STATE)))
     {
-        widget[id].text_img = make_image_from_font(NULL, NULL,
+        widget[id].image = make_image_from_font(NULL, NULL,
                                                    &widget[id].w,
                                                    &widget[id].h,
-                                                   text, font[size],
-                                                        scale[size]);
+                                                   text, font[size]);
         widget[id].size  = size;
         widget[id].token = token;
         widget[id].value = value;
@@ -507,18 +730,17 @@ int gui_state(int pd, const char *text, int size, int token, int value)
     return id;
 }
 
-int gui_label(int pd, const char *text, int size, int rect, const float *c0,
-                                                            const float *c1)
+int gui_label(int pd, const char *text, int size, int rect, const GLubyte *c0,
+                                                            const GLubyte *c1)
 {
     int id;
 
     if ((id = gui_widget(pd, GUI_LABEL)))
     {
-        widget[id].text_img = make_image_from_font(NULL, NULL,
-                                                   &widget[id].w,
-                                                   &widget[id].h,
-                                                   text, font[size],
-                                                        scale[size]);
+        widget[id].image = make_image_from_font(NULL, NULL,
+                                                &widget[id].w,
+                                                &widget[id].h,
+                                                text, font[size]);
         widget[id].size   = size;
         widget[id].color0 = c0 ? c0 : gui_yel;
         widget[id].color1 = c1 ? c1 : gui_red;
@@ -534,9 +756,9 @@ int gui_count(int pd, int value, int size, int rect)
     if ((id = gui_widget(pd, GUI_COUNT)))
     {
         for (i = value; i; i /= 10)
-            widget[id].w += digit_w[size][0];
+            widget[id].w += widget[digit_id[size][0]].text_w;
 
-        widget[id].h      = digit_h[size][0];
+        widget[id].h      = widget[digit_id[size][0]].text_h;
         widget[id].value  = value;
         widget[id].size   = size;
         widget[id].color0 = gui_yel;
@@ -552,8 +774,8 @@ int gui_clock(int pd, int value, int size, int rect)
 
     if ((id = gui_widget(pd, GUI_CLOCK)))
     {
-        widget[id].w      = digit_w[size][0] * 6;
-        widget[id].h      = digit_h[size][0];
+        widget[id].w      = widget[digit_id[size][0]].text_w * 6;
+        widget[id].h      = widget[digit_id[size][0]].text_h;
         widget[id].value  = value;
         widget[id].size   = size;
         widget[id].color0 = gui_yel;
@@ -575,36 +797,16 @@ int gui_space(int pd)
     return id;
 }
 
-int gui_pause(int pd)
-{
-    const char *text = _("Paused");
-    int id;
-
-    if ((id = gui_widget(pd, GUI_PAUSE)))
-    {
-        widget[id].text_img = make_image_from_font(NULL, NULL,
-                                                   &widget[id].w,
-                                                   &widget[id].h,
-                                                   text, font[GUI_LRG],
-                                                        scale[GUI_LRG]);
-        widget[id].color0 = gui_wht;
-        widget[id].color1 = gui_wht;
-        widget[id].value  = 0;
-        widget[id].size   = GUI_LRG;
-        widget[id].rect   = GUI_ALL;
-    }
-    return id;
-}
-
 /*---------------------------------------------------------------------------*/
+
 /*
  * Create  a multi-line  text box  using a  vertical array  of labels.
  * Parse the  text for '\'  characters and treat them  as line-breaks.
- * Preserve the rect specifation across the entire array.
+ * Preserve the rect specification across the entire array.
  */
 
-int gui_multi(int pd, const char *text, int size, int rect, const float *c0,
-                                                            const float *c1)
+int gui_multi(int pd, const char *text, int size, int rect, const GLubyte *c0,
+                                                            const GLubyte *c1)
 {
     int id = 0;
 
@@ -612,15 +814,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;
@@ -735,36 +937,29 @@ static void gui_vstack_up(int id)
     }
 }
 
-static void gui_paused_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;
-
-    /* The pause widget fills the screen. */
-
-    widget[id].w = config_get_d(CONFIG_WIDTH);
-    widget[id].h = config_get_d(CONFIG_HEIGHT);
-}
-
 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_w = widget[id].w;
+    widget[id].text_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)
@@ -776,7 +971,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_PAUSE:  gui_paused_up(id); break;
+        case GUI_FILLER:                    break;
         default:         gui_button_up(id); break;
         }
 }
@@ -854,6 +1049,11 @@ static void gui_hstack_dn(int id, int x, int y, int w, int h)
     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
             c += 1;
+        else if (widget[jd].type & GUI_FILL)
+        {
+            c  += 1;
+            jw += widget[jd].w;
+        }
         else
             jw += widget[jd].w;
 
@@ -864,6 +1064,8 @@ static void gui_hstack_dn(int id, int x, int y, int w, int h)
     {
         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
             gui_widget_dn(jd, jx, y, (w - jw) / c, h);
+        else if (widget[jd].type & GUI_FILL)
+            gui_widget_dn(jd, jx, y, widget[jd].w + (w - jw) / c, h);
         else
             gui_widget_dn(jd, jx, y, widget[jd].w, h);
 
@@ -885,6 +1087,11 @@ static void gui_vstack_dn(int id, int x, int y, int w, int h)
     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
             c += 1;
+        else if (widget[jd].type & GUI_FILL)
+        {
+            c  += 1;
+            jh += widget[jd].h;
+        }
         else
             jh += widget[jd].h;
 
@@ -895,6 +1102,8 @@ static void gui_vstack_dn(int id, int x, int y, int w, int h)
     {
         if ((widget[jd].type & GUI_TYPE) == GUI_FILLER)
             gui_widget_dn(jd, x, jy, w, (h - jh) / c);
+        else if (widget[jd].type & GUI_FILL)
+            gui_widget_dn(jd, x, jy, w, widget[jd].h + (h - jh) / c);
         else
             gui_widget_dn(jd, x, jy, w, widget[jd].h);
 
@@ -916,23 +1125,22 @@ 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_w;
+    int H = widget[id].text_h;
     int R = widget[id].rect;
-    int r = ((widget[id].type & GUI_TYPE) == GUI_PAUSE ? radius * 4 : radius);
 
-    const float *c0 = widget[id].color0;
-    const float *c1 = widget[id].color1;
+    const GLubyte *c0 = widget[id].color0;
+    const GLubyte *c1 = widget[id].color1;
 
     widget[id].x = x;
     widget[id].y = y;
     widget[id].w = w;
     widget[id].h = h;
 
-    /* Create display lists for the text area and rounded rectangle. */
+    /* Create vertex array data for the text area and rounded rectangle. */
 
-    widget[id].text_obj = gui_list(-W / 2, -H / 2, W, H, c0, c1);
-    widget[id].rect_obj = gui_rect(-w / 2, -h / 2, w, h, R, r);
+    gui_rect(id, -w / 2, -h / 2, w, h, R, radius);
+    gui_text(id, -W / 2, -H / 2, W, H, c0, c1);
 }
 
 static void gui_widget_dn(int id, int x, int y, int w, int h)
@@ -982,7 +1190,7 @@ void gui_layout(int id, int xd, int yd)
 
     /* Hilite the widget under the cursor, if any. */
 
-    /* gui_point(id, -1, -1); */
+    gui_point(id, -1, -1);
 }
 
 int gui_search(int id, int x, int y)
@@ -1029,22 +1237,15 @@ int gui_delete(int id)
 
         /* Release any GL resources held by this widget. */
 
-        if (glIsTexture(widget[id].text_img))
-            glDeleteTextures(1, &widget[id].text_img);
-
-        if (glIsList(widget[id].text_obj))
-            glDeleteLists(widget[id].text_obj, 1);
-        if (glIsList(widget[id].rect_obj))
-            glDeleteLists(widget[id].rect_obj, 1);
+        if (glIsTexture(widget[id].image))
+            glDeleteTextures(1, &widget[id].image);
 
         /* Mark this widget unused. */
 
-        widget[id].type     = GUI_FREE;
-        widget[id].text_img = 0;
-        widget[id].text_obj = 0;
-        widget[id].rect_obj = 0;
-        widget[id].cdr      = 0;
-        widget[id].car      = 0;
+        widget[id].type  = GUI_FREE;
+        widget[id].image = 0;
+        widget[id].cdr   = 0;
+        widget[id].car   = 0;
     }
     return 0;
 }
@@ -1096,8 +1297,8 @@ static void gui_paint_rect(int id, int st)
             glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
                          (GLfloat) (widget[id].y + widget[id].h / 2), 0.f);
 
-            glColor4fv(back[i]);
-            glCallList(widget[id].rect_obj);
+            glColor4f(back[i][0], back[i][1], back[i][2], back[i][3]);
+            draw_rect(id);
         }
         glPopMatrix();
 
@@ -1119,9 +1320,12 @@ static void gui_paint_array(int id)
         GLfloat cy = widget[id].y + widget[id].h / 2.0f;
         GLfloat ck = widget[id].scale;
 
-        glTranslatef(+cx, +cy, 0.0f);
-        glScalef(ck, ck, ck);
-        glTranslatef(-cx, -cy, 0.0f);
+        if (1.0 < ck || ck < 1.0)
+        {
+            glTranslatef(+cx, +cy, 0.0f);
+            glScalef(ck, ck, ck);
+            glTranslatef(-cx, -cy, 0.0f);
+        }
 
         /* Recursively paint all subwidgets. */
 
@@ -1144,9 +1348,9 @@ static void gui_paint_image(int id)
                  widget[id].scale,
                  widget[id].scale);
 
-        glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
-        glColor4fv(gui_wht);
-        glCallList(widget[id].rect_obj);
+        glBindTexture(GL_TEXTURE_2D, widget[id].image);
+        glColor4ub(gui_wht[0], gui_wht[1], gui_wht[2], gui_wht[3]);
+        draw_rect(id);
     }
     glPopMatrix();
 }
@@ -1157,8 +1361,6 @@ static void gui_paint_count(int id)
 
     glPushMatrix();
     {
-        glColor4fv(gui_wht);
-
         /* Translate to the widget center, and apply the pulse scale. */
 
         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
@@ -1170,28 +1372,32 @@ static void gui_paint_count(int id)
 
         if (widget[id].value > 0)
         {
-            /* Translate left by half the total width of the rendered value. */
+            /* Translate right by half the total width of the rendered value. */
+
+            GLfloat w = -widget[digit_id[i][0]].text_w * 0.5f;
 
             for (j = widget[id].value; j; j /= 10)
-                glTranslatef((GLfloat) +digit_w[i][j % 10] / 2.0f, 0.0f, 0.0f);
+                w += widget[digit_id[i][j % 10]].text_w * 0.5f;
 
-            glTranslatef((GLfloat) -digit_w[i][0] / 2.0f, 0.0f, 0.0f);
+            glTranslatef(w, 0.0f, 0.0f);
 
-            /* Render each digit, moving right after each. */
+            /* Render each digit, moving left after each. */
 
             for (j = widget[id].value; j; j /= 10)
             {
-                glBindTexture(GL_TEXTURE_2D, digit_text[i][j % 10]);
-                glCallList(digit_list[i][j % 10]);
-                glTranslatef((GLfloat) -digit_w[i][j % 10], 0.0f, 0.0f);
+                int id = digit_id[i][j % 10];
+
+                glBindTexture(GL_TEXTURE_2D, widget[id].image);
+                draw_text(id);
+                glTranslatef((GLfloat) -widget[id].text_w, 0.0f, 0.0f);
             }
         }
         else if (widget[id].value == 0)
         {
             /* If the value is zero, just display a zero in place. */
 
-            glBindTexture(GL_TEXTURE_2D, digit_text[i][0]);
-            glCallList(digit_list[i][0]);
+            glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][0]].image);
+            draw_text(digit_id[i][0]);
         }
     }
     glPopMatrix();
@@ -1207,16 +1413,14 @@ static void gui_paint_clock(int id)
     int ht = ((widget[id].value % 6000) % 100) / 10;
     int ho = ((widget[id].value % 6000) % 100) % 10;
 
-    GLfloat dx_large = (GLfloat) digit_w[i][0];
-    GLfloat dx_small = (GLfloat) digit_w[i][0] * 0.75f;
-    
+    GLfloat dx_large = (GLfloat) widget[digit_id[i][0]].text_w;
+    GLfloat dx_small = (GLfloat) widget[digit_id[i][0]].text_w * 0.75f;
+
     if (widget[id].value < 0)
         return;
 
     glPushMatrix();
     {
-        glColor4fv(gui_wht);
-
         /* Translate to the widget center, and apply the pulse scale. */
 
         glTranslatef((GLfloat) (widget[id].x + widget[id].w / 2),
@@ -1237,41 +1441,41 @@ static void gui_paint_clock(int id)
 
         if (mt > 0)
         {
-            glBindTexture(GL_TEXTURE_2D, digit_text[i][mt]);
-            glCallList(digit_list[i][mt]);
+            glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][mt]].image);
+            draw_text(digit_id[i][mt]);
             glTranslatef(dx_large, 0.0f, 0.0f);
         }
 
-        glBindTexture(GL_TEXTURE_2D, digit_text[i][mo]);
-        glCallList(digit_list[i][mo]);
+        glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][mo]].image);
+        draw_text(digit_id[i][mo]);
         glTranslatef(dx_small, 0.0f, 0.0f);
 
         /* Render the colon. */
 
-        glBindTexture(GL_TEXTURE_2D, digit_text[i][10]);
-        glCallList(digit_list[i][10]);
+        glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][10]].image);
+        draw_text(digit_id[i][10]);
         glTranslatef(dx_small, 0.0f, 0.0f);
 
         /* Render the seconds counter. */
 
-        glBindTexture(GL_TEXTURE_2D, digit_text[i][st]);
-        glCallList(digit_list[i][st]);
+        glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][st]].image);
+        draw_text(digit_id[i][st]);
         glTranslatef(dx_large, 0.0f, 0.0f);
 
-        glBindTexture(GL_TEXTURE_2D, digit_text[i][so]);
-        glCallList(digit_list[i][so]);
+        glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][so]].image);
+        draw_text(digit_id[i][so]);
         glTranslatef(dx_small, 0.0f, 0.0f);
 
         /* Render hundredths counter half size. */
 
         glScalef(0.5f, 0.5f, 1.0f);
 
-        glBindTexture(GL_TEXTURE_2D, digit_text[i][ht]);
-        glCallList(digit_list[i][ht]);
+        glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][ht]].image);
+        draw_text(digit_id[i][ht]);
         glTranslatef(dx_large, 0.0f, 0.0f);
 
-        glBindTexture(GL_TEXTURE_2D, digit_text[i][ho]);
-        glCallList(digit_list[i][ho]);
+        glBindTexture(GL_TEXTURE_2D, widget[digit_id[i][ho]].image);
+        draw_text(digit_id[i][ho]);
     }
     glPopMatrix();
 }
@@ -1289,8 +1493,8 @@ static void gui_paint_label(int id)
                  widget[id].scale,
                  widget[id].scale);
 
-        glBindTexture(GL_TEXTURE_2D, widget[id].text_img);
-        glCallList(widget[id].text_obj);
+        glBindTexture(GL_TEXTURE_2D, widget[id].image);
+        draw_text(id);
     }
     glPopMatrix();
 }
@@ -1316,37 +1520,31 @@ void gui_paint(int id)
 {
     if (id)
     {
-        glPushAttrib(GL_LIGHTING_BIT     |
-                     GL_COLOR_BUFFER_BIT |
-                     GL_DEPTH_BUFFER_BIT);
-        config_push_ortho();
+        video_push_ortho();
         {
-            glEnable(GL_BLEND);
             glEnable(GL_COLOR_MATERIAL);
             glDisable(GL_LIGHTING);
             glDisable(GL_DEPTH_TEST);
-
-            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
-            glPushAttrib(GL_TEXTURE_BIT);
             {
+                draw_enable(GL_FALSE, GL_FALSE, GL_TRUE);
                 glDisable(GL_TEXTURE_2D);
                 gui_paint_rect(id, 0);
-            }
-            glPopAttrib();
 
-            gui_paint_text(id);
+                draw_enable(GL_TRUE, GL_TRUE, GL_TRUE);
+                glEnable(GL_TEXTURE_2D);
+                gui_paint_text(id);
+
+                draw_disable();
+                glColor4ub(gui_wht[0], gui_wht[1], gui_wht[2], gui_wht[3]);
+            }
+            glEnable(GL_DEPTH_TEST);
+            glEnable(GL_LIGHTING);
+            glDisable(GL_COLOR_MATERIAL);
         }
-        config_pop_matrix();
-        glPopAttrib();
+        video_pop_matrix();
     }
 }
 
-void gui_blank(void)
-{
-    gui_paint(pause_id);
-}
-
 /*---------------------------------------------------------------------------*/
 
 void gui_dump(int id, int d)
@@ -1433,6 +1631,11 @@ int gui_point(int id, int x, int y)
         return active = jd;
 }
 
+void gui_focus(int i)
+{
+    active = i;
+}
+
 int gui_click(void)
 {
     return active;
@@ -1455,122 +1658,174 @@ void gui_toggle(int id)
 
 /*---------------------------------------------------------------------------*/
 
-static int gui_vert_test(int id, int jd)
+static int gui_vert_offset(int id, int jd)
 {
-    /* Determine whether widget id is in vertical contact with widget jd. */
+    /* Vertical offset between bottom of id and top of jd */
 
-    if (id && gui_hot(id) && jd && gui_hot(jd))
-    {
-        int i0 = widget[id].x;
-        int i1 = widget[id].x + widget[id].w;
-        int j0 = widget[jd].x;
-        int j1 = widget[jd].x + widget[jd].w;
-
-        /* Is widget id's top edge is in contact with jd's bottom edge? */
+    return  widget[id].y - (widget[jd].y + widget[jd].h);
+}
 
-        if (widget[id].y + widget[id].h == widget[jd].y)
-        {
-            /* Do widgets id and jd overlap horizontally? */
+static int gui_horz_offset(int id, int jd)
+{
+    /* Horizontal offset between left of id and right of jd */
 
-            if (j0 <= i0 && i0 <  j1) return 1;
-            if (j0 <  i1 && i1 <= j1) return 1;
-            if (i0 <= j0 && j0 <  i1) return 1;
-            if (i0 <  j1 && j1 <= i1) return 1;
-        }
-    }
-    return 0;
+    return  widget[id].x - (widget[jd].x + widget[jd].w);
 }
 
-static int gui_horz_test(int id, int jd)
+static int gui_vert_dist(int id, int jd)
 {
-    /* Determine whether widget id is in horizontal contact with widget jd. */
-
-    if (id && gui_hot(id) && jd && gui_hot(jd))
-    {
-        int i0 = widget[id].y;
-        int i1 = widget[id].y + widget[id].h;
-        int j0 = widget[jd].y;
-        int j1 = widget[jd].y + widget[jd].h;
+    /* Vertical distance between the tops of id and jd */
 
-        /* Is widget id's right edge in contact with jd's left edge? */
+    return abs((widget[id].y + widget[id].h) - (widget[jd].y + widget[jd].h));
+}
 
-        if (widget[id].x + widget[id].w == widget[jd].x)
-        {
-            /* Do widgets id and jd overlap vertically? */
+static int gui_horz_dist(int id, int jd)
+{
+    /* Horizontal distance between the left sides of id and jd */
 
-            if (j0 <= i0 && i0 <  j1) return 1;
-            if (j0 <  i1 && i1 <= j1) return 1;
-            if (i0 <= j0 && j0 <  i1) return 1;
-            if (i0 <  j1 && j1 <= i1) return 1;
-        }
-    }
-    return 0;
+    return abs(widget[id].x - widget[jd].x);
 }
 
 /*---------------------------------------------------------------------------*/
 
 static int gui_stick_L(int id, int dd)
 {
-    int jd, kd;
+    int jd, kd, hd;
+    int o, omin, d, dmin;
 
-    /* Find a widget to the left of widget dd. */
+    /* Find the closest "hot" widget to the left of dd (and inside id) */
 
-    if (gui_horz_test(id, dd))
+    if (id && gui_hot(id))
         return id;
 
+    hd = 0;
+    omin = widget[dd].x - widget[id].x + 1;
+    dmin = widget[dd].y + widget[dd].h + widget[id].y + widget[id].h;
+
     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
-        if ((kd = gui_stick_L(jd, dd)))
-            return kd;
+    {
+        kd = gui_stick_L(jd, dd);
 
-    return 0;
+        if (kd && kd != dd)
+        {
+            o = gui_horz_offset(dd, kd);
+            d = gui_vert_dist(dd, kd);
+
+            if (0 <= o && o <= omin && d <= dmin)
+            {
+                hd = kd;
+                omin = o;
+                dmin = d;
+            }
+        }
+    }
+
+    return hd;
 }
 
 static int gui_stick_R(int id, int dd)
 {
-    int jd, kd;
+    int jd, kd, hd;
+    int o, omin, d, dmin;
 
-    /* Find a widget to the right of widget dd. */
+    /* Find the closest "hot" widget to the right of dd (and inside id) */
 
-    if (gui_horz_test(dd, id))
+    if (id && gui_hot(id))
         return id;
 
+    hd = 0;
+    omin = (widget[id].x + widget[id].w) - (widget[dd].x + widget[dd].w) + 1;
+    dmin = (widget[dd].y + widget[dd].h) + (widget[id].y + widget[id].h);
+
     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
-        if ((kd = gui_stick_R(jd, dd)))
-            return kd;
+    {
+        kd = gui_stick_R(jd, dd);
 
-    return 0;
+        if (kd && kd != dd)
+        {
+            o = gui_horz_offset(kd, dd);
+            d = gui_vert_dist(dd, kd);
+
+            if (0 <= o && o <= omin && d <= dmin)
+            {
+                hd = kd;
+                omin = o;
+                dmin = d;
+            }
+        }
+    }
+
+    return hd;
 }
 
 static int gui_stick_D(int id, int dd)
 {
-    int jd, kd;
+    int jd, kd, hd;
+    int o, omin, d, dmin;
 
-    /* Find a widget below widget dd. */
+    /* Find the closest "hot" widget below dd (and inside id) */
 
-    if (gui_vert_test(id, dd))
+    if (id && gui_hot(id))
         return id;
 
+    hd = 0;
+    omin = widget[dd].y - widget[id].y + 1;
+    dmin = widget[dd].x + widget[dd].w + widget[id].x + widget[id].w;
+
     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
-        if ((kd = gui_stick_D(jd, dd)))
-            return kd;
+    {
+        kd = gui_stick_D(jd, dd);
 
-    return 0;
+        if (kd && kd != dd)
+        {
+            o = gui_vert_offset(dd, kd);
+            d = gui_horz_dist(dd, kd);
+
+            if (0 <= o && o <= omin && d <= dmin)
+            {
+                hd = kd;
+                omin = o;
+                dmin = d;
+            }
+        }
+    }
+
+    return hd;
 }
 
 static int gui_stick_U(int id, int dd)
 {
-    int jd, kd;
+    int jd, kd, hd;
+    int o, omin, d, dmin;
 
-    /* Find a widget above widget dd. */
+    /* Find the closest "hot" widget above dd (and inside id) */
 
-    if (gui_vert_test(dd, id))
+    if (id && gui_hot(id))
         return id;
 
+    hd = 0;
+    omin = (widget[id].y + widget[id].h) - (widget[dd].y + widget[dd].h) + 1;
+    dmin = (widget[dd].x + widget[dd].w) + (widget[id].x + widget[id].w);
+
     for (jd = widget[id].car; jd; jd = widget[jd].cdr)
-        if ((kd = gui_stick_U(jd, dd)))
-            return kd;
+    {
+        kd = gui_stick_U(jd, dd);
 
-    return 0;
+        if (kd && kd != dd)
+        {
+            o = gui_vert_offset(kd, dd);
+            d = gui_horz_dist(dd, kd);
+
+            if (0 <= o && o <= omin && d <= dmin)
+            {
+                hd = kd;
+                omin = o;
+                dmin = d;
+            }
+        }
+    }
+
+    return hd;
 }
 
 /*---------------------------------------------------------------------------*/
@@ -1621,30 +1876,25 @@ static int gui_wrap_D(int id, int dd)
 
 /*---------------------------------------------------------------------------*/
 
-int gui_stick(int id, int x, int y)
+int gui_stick(int id, int a, float v, int bump)
 {
-    /* Flag the axes to prevent uncontrolled scrolling. */
-
-    static int xflag = 1;
-    static int yflag = 1;
-
     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. */