]> git.draconx.ca Git - rrace.git/blobdiff - src/xcounter.c
Reimplement xcounter based on pre-rendered pixmpas.
[rrace.git] / src / xcounter.c
index 8266107e11fb8f6c13703c8d0d06bf764af479c4..e8e5d17baa2ad76c532e0436dd6c8dc78990f320 100644 (file)
 #include <assert.h>
 #include <Xm/XmAll.h>
 
+/* Define to 1 to add highlights and delays for visually debugging redraws */
+#if X11_RENDER_DEBUG
+#  include <unistd.h>
+#endif
+
 #include "xcounter.h"
 
+#define MAX_STRINGS 20
 #define DIGIT_LIST "0123456789"
 #define MARGIN 2
 
-static XmString digits[10];
-
-#define XC_FLAG_STATIC  1u
-#define XC_FLAG_CHANGED 2u
-
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
 #define MAX(a, b) ((a) > (b) ? (a) : (b))
 
+/* render flags in the upper bits are ORed into render_mask */
+#define XC_TICK_INSTALLED (1ul << 31)
+#define XC_PROC_INSTALLED (1ul << 30)
+#define XC_RESIZING       (1ul << 29)
+
 struct xcounter {
-       XmRenderTable rt;
+       Widget widget;
        GC gc;
 
-       uint_least8_t num_static, num_segments, total_segments;
+       Pixmap pixmap;
 
-       /* Prior dimensions for resize handling. */
-       Dimension old_w, old_h;
+       uint_least32_t render_mask;
+       unsigned char seq[23], num_strings;
 
-       struct xcounter_segment {
-               XmString str;
-               uint_least16_t width;
-               uint_least8_t flags;
-       } *segments;
-
-       XmString static_strings[FLEXIBLE_ARRAY_MEMBER];
+       /*
+        * xpos[n] is the x position of the nth string image within the
+        * pixmap, plus the width of that image.
+        */
+       uint_least16_t xpos[FLEXIBLE_ARRAY_MEMBER];
 };
+#define XC_SIZE(n) offsetof(struct xcounter, xpos[n])
 
-static void init_digits(void)
+static struct xcounter *
+xc_alloc(Widget w, XmRenderTable rt, XmString *strings, int nstr)
 {
+       Display *display = XtDisplay(w);
+       Screen *screen = XtScreen(w);
+       unsigned long fg, bg;
+
+       Drawable root = RootWindowOfScreen(screen);
+       unsigned depth = DefaultDepthOfScreen(screen);
+       unsigned wsum = 0, height = 0;
+       struct xcounter *xc;
        int i;
 
-       if (digits[0])
-               return;
+       assert(nstr >= 0 && nstr < MAX_STRINGS);
+       xc = (void *)XtMalloc(XC_SIZE(nstr));
+       xc->gc = XtAllocateGC(w, 0, 0, 0, GCForeground, GCBackground);
+       memset(xc->seq, -1, sizeof xc->seq);
+       xc->num_strings = nstr;
+       xc->render_mask = 0;
+       xc->widget = w;
 
-       for (i = 0; i < 10; i++) {
-               char s[2] = {'0' + i};
-               digits[i] = XmStringCreateLocalized(s);
-       }
-}
+       for (i = 0; i < nstr; i++) {
+               unsigned w, h;
 
-static void alloc_segments(struct xcounter *xc, int num_digits)
-{
-       int count = num_digits + xc->num_static;
+               if ((h = XmStringHeight(rt, strings[i])) > height)
+                       height = h + 2*MARGIN;
 
-       assert(num_digits >= 0 && count <= (uint_least8_t)-1);
-       xc->segments = (void *)XtRealloc((void *)xc->segments,
-                                        count * sizeof xc->segments[0]);
+               wsum += XmStringWidth(rt, strings[i]);
+               xc->xpos[i] = wsum;
+       }
 
-       while (xc->total_segments < count) {
-               struct xcounter_segment new_seg = {0};
+       XtVaSetValues(w, XmNheight, height + 2*MARGIN, (char *)NULL);
+       XtVaGetValues(w, XmNforeground, &fg, XmNbackground, &bg, (char *)NULL);
 
-               new_seg.flags = XC_FLAG_CHANGED;
-               xc->segments[xc->total_segments++] = new_seg;
-       }
-       assert(xc->total_segments == count);
-}
+       xc->pixmap = XCreatePixmap(display, root, wsum, height, depth);
+       XSetForeground(display, xc->gc, bg);
+       XFillRectangle(display, xc->pixmap, xc->gc, 0, 0, wsum, height);
 
-/* Add as many entries to the segments array as there are digits in s. */
-static void realloc_segments(struct xcounter *xc, const char *s)
-{
-       int new_digits = 0;
-       int l;
+       XSetForeground(display, xc->gc, fg);
+       for (i = 0; i < nstr; i++) {
+               unsigned xpos = i ? xc->xpos[i-1] : 0;
+               unsigned w = xc->xpos[i] - xpos;
 
-       for (; s[0]; s += strcspn(s, DIGIT_LIST)) {
-               l = strspn(s, DIGIT_LIST);
-               new_digits += l;
-               s += l;
+               XmStringDraw(display, xc->pixmap, rt, strings[i], xc->gc,
+                            xpos, 0, w, XmALIGNMENT_BEGINNING,
+                            XmSTRING_DIRECTION_L_TO_R, NULL);
        }
 
-       alloc_segments(xc, xc->total_segments - xc->num_static + new_digits);
+       return xc;
 }
 
-struct xcounter *xcounter_init(Widget w, char *template)
+static struct xcounter *xc_configure(Widget w, XmRenderTable rt, char *tpl)
 {
-       int l, num_static = 0, num_digits = 0;
+       XmString strings[MAX_STRINGS];
        struct xcounter *xc;
-       XmString *static_tab;
-       XmRenderTable rt;
-       Widget label;
+       int i, l;
        char *s;
 
-       init_digits();
+       for (i = 0; i < 10; i++) {
+               char s[2] = { '0' + i };
 
-       /* Count static segments and digits from template */
-       for (s = template; s[0];) {
-               if ((l = strcspn(s, DIGIT_LIST))) {
-                       num_static++;
-                       s += l;
-               }
+               strings[i] = XmStringCreateLocalized(s);
+       }
 
+       for (s = tpl; s[0];) {
                if ((l = strspn(s, DIGIT_LIST))) {
-                       num_digits += l;
                        s += l;
                }
-       }
-
-       assert(num_static >= 0 && num_static < (uint_least8_t)-1);
-       xc = (void *)XtMalloc(sizeof *xc
-                           + num_static * sizeof xc->static_strings[0]);
-       xc->num_static = num_static;
-       xc->gc = XtAllocateGC(w, 0, 0, 0, 0, 0);
-       xc->num_segments = xc->total_segments = 0;
-       xc->old_w = xc->old_h = 0;
-       xc->segments = NULL;
 
-       /* Use a dummy label widget's render table */
-       label = XmCreateLabelGadget(w, "text", NULL, 0);
-       XtVaGetValues(label, XmNrenderTable, &rt, (char *)NULL);
-       xc->rt = XmRenderTableCopy(rt, NULL, 0);
-       XtDestroyWidget(label);
-
-       /* Initialize static string table */
-       static_tab = xc->static_strings;
-       for (s = template; s[0]; s += strspn(s, DIGIT_LIST)) {
                if ((l = strcspn(s, DIGIT_LIST))) {
-                       char tmp;
+                       char tmp = s[l];
 
-                       tmp = s[l];
-                       s[l] = '\0';
-                       *static_tab++ = XmStringCreateLocalized(s);
-                       s[l] = tmp;
-                       s += l;
+                       s[l] = 0;
+                       strings[i++] = XmStringCreateLocalized(s);
+                       *(s += l) = tmp;
                }
        }
 
-       alloc_segments(xc, num_digits);
+       xc = xc_alloc(w, rt, strings, i);
+
+       for (i--; i >= 0; i--) {
+               XmStringFree(strings[i]);
+       }
+
        return xc;
 }
 
-static void update_static(struct xcounter *xc, int seg_index,
-                          int str_index, unsigned *new_xpos)
+struct xcounter *xcounter_init(Widget w, char *template)
 {
-       struct xcounter_segment *seg = &xc->segments[seg_index];
+       struct xcounter *xc;
+       XmRenderTable rt;
+       Widget dummy;
 
-       assert(str_index < xc->num_static);
+       /* Use a dummy label widget's render table */
+       dummy = XmCreateLabelGadget(w, "text", NULL, 0);
+       XtVaGetValues(dummy, XmNrenderTable, &rt, (char *)NULL);
 
-       if (seg->str != xc->static_strings[str_index]) {
-               seg->flags |= XC_FLAG_CHANGED;
-               seg->flags |= XC_FLAG_STATIC;
+       xc = xc_configure(w, rt, template);
 
-               seg->str = xc->static_strings[str_index];
-               seg->width = XmStringWidth(xc->rt, seg->str);
-       }
-       *new_xpos += seg->width;
+       XtDestroyWidget(dummy);
+
+       return xc;
 }
 
-static void update_digit(struct xcounter *xc, int seg_index,
-                         unsigned digit, unsigned *new_xpos)
+/*
+ * Internal helper to imlpement the xc_foreach_seq macro.
+ */
+static int xc_seq_iter(struct xcounter *xc, int i, unsigned *src_x,
+                                            unsigned *w, uint_fast32_t *mask)
 {
-       struct xcounter_segment *seg = &xc->segments[seg_index];
-
-       assert(digit >= '0' && digit <= '9');
-       digit -= '0';
+       int seq_i;
 
-       if (seg->str != digits[digit]) {
-               seg->flags |= XC_FLAG_CHANGED;
-               seg->flags &= ~XC_FLAG_STATIC;
+       if ((seq_i = xc->seq[i]) == (unsigned char)-1)
+               return 0;
 
-               seg->str = digits[digit];
-               seg->width = XmStringWidth(xc->rt, seg->str);
-       }
-       *new_xpos += seg->width;
+       *src_x = seq_i ? xc->xpos[seq_i-1] : 0;
+       *w = xc->xpos[seq_i] - *src_x;
+       *mask <<= 1;
+       return 1;
 }
 
-static void check_position(struct xcounter *xc, int i, unsigned *prev_xpos,
-                                                       unsigned new_xpos)
+/*
+ * xc_foreach_seq(xc, int, unsigned, unsigned, unsigned, uint_fast32_t)
+ *
+ * Helper macro to iterate through the display sequence.  The first argument is
+ * a pointer to the state structure.  The remaining 5 arguments are lvalues of
+ * the given type designating objects that are updated on each iteration.
+ *
+ *   i     - index into the sequence
+ *   out_x - x position for drawing of this element
+ *   src_x - x position in the pixmap of this element
+ *   w     - width of this element
+ *   mask  - shifted left by 1 each iteration
+ */
+#define xc_foreach_seq(xc, i, out_x, src_x, w, mask) for \
+       ( (out_x) = MARGIN, (i) = 0 \
+       ; (i) < sizeof (xc)->seq \
+         && xc_seq_iter(xc, i, &(src_x), &(w), &(mask)) \
+       ; (i)++, (out_x) += (w) )
+
+/*
+ * Redraw the changed portion of the text.  The mask bitmap indicates which
+ * elements of the sequence to redraw, with bit 0 corresponding to the last
+ * possible element, bit 1 the second last, and so on.
+ */
+static void xc_redraw(struct xcounter *xc, uint_fast32_t mask)
 {
-       struct xcounter_segment *seg = &xc->segments[i];
+       Display *display = XtDisplay(xc->widget);
+       Drawable d = XtWindow(xc->widget);
 
-       if (i >= xc->num_segments) {
-               seg->flags |= XC_FLAG_CHANGED;
-               return;
+       unsigned out_x = MARGIN, src_x, w;
+       Dimension max_w, h;
+       int i;
+
+       XtVaGetValues(xc->widget, XmNwidth, &max_w,
+                                 (char *)NULL);
+       max_w = MAX(MARGIN, max_w) - MARGIN;
+
+       xc_foreach_seq(xc, i, out_x, src_x, w, mask) {
+               if (out_x + w > max_w) {
+                       w = max_w - out_x;
+               }
+
+               if (mask & (1ul << sizeof xc->seq)) {
+#if X11_RENDER_DEBUG
+                       XRectangle r = { out_x, MARGIN, w, -1 };
+
+                       XSetForeground(display, xc->gc, 0xff0000);
+                       XFillRectangles(display, d, xc->gc, &r, 1);
+                       XFlush(display);
+                       usleep(18000);
+#endif
+                       /*
+                        * NB: we want to copy the entire vertical contents
+                        * of the pixmap; using any large height value will
+                        * work but not too large otherwise there are visual
+                        * problems on Intel DDX (<=821722853 seems OK).
+                        */
+                       XCopyArea(display, xc->pixmap, d, xc->gc,
+                                 src_x, 0, w, 4096,
+                                 out_x, MARGIN);
+               }
        }
 
-       if (*prev_xpos != new_xpos)
-               seg->flags |= XC_FLAG_CHANGED;
-       *prev_xpos += seg->width;
+       assert(out_x <= max_w);
+       XClearArea(display, d, out_x, 0, -1, -1, 0);
 }
 
-static void resize(Widget w, struct xcounter *xc, Dimension width)
+static Boolean xc_do_render(void *data)
 {
-       Dimension line_height;
+       struct xcounter *xc = data;
 
-       line_height = XmStringHeight(xc->rt, digits[0]);
-       XtVaSetValues(w, XmNwidth, width+4,
-                        XmNheight, line_height+4,
-                        (char *)NULL);
+       xc_redraw(xc, xc->render_mask);
+       xc->render_mask = 0;
+       return True;
 }
 
-static int in_rect(XRectangle *r, int x, int y, int width, int height)
+static void xc_start_render(void *data, XtIntervalId *id)
 {
-       int i_x1, i_y1, i_x2, i_y2;
-
-       if (!r)
-               return 0;
+       struct xcounter *xc = data;
+       XtAppContext app;
 
-       i_x1 = MAX(r->x, x);
-       i_y1 = MAX(r->y, y);
-       i_x2 = MIN(r->x + r->width, x + width);
-       i_y2 = MIN(r->y + r->height, y + height);
+       if (xc->render_mask & XC_PROC_INSTALLED)
+               return;
 
-       return i_x2 > i_x1 && i_y2 > i_y1;
+       xc->render_mask |= XC_PROC_INSTALLED;
+       app = XtWidgetToApplicationContext(xc->widget);
+       XtAppAddWorkProc(app, xc_do_render, xc);
 }
 
-/*
- * Redraw the changed portion of the text.  There are two types of changes:
- *
- *   - The text is explicitly updated via xcounter_update, or
- *   - A region needs to be redrawn due to expose events.
- */
-static void redraw(Widget w, struct xcounter *xc, XRectangle *expose)
+static void xc_queue_render(struct xcounter *xc, uint_fast32_t mask)
 {
-       Display *display = XtDisplay(w);
-       Window window = XtWindow(w);
-
-       Dimension line_height, width, xpos = MARGIN, ypos = MARGIN;
-       unsigned i;
-
-       XtVaGetValues(w, XmNwidth, &width, (char *)NULL);
-       line_height = XmStringHeight(xc->rt, digits[0]);
+       uint_fast32_t changed = xc->render_mask |= mask;
+       XtAppContext app;
 
-       for (i = 0; i < xc->num_segments && xpos < width; i++) {
-               struct xcounter_segment *seg = &xc->segments[i];
-               int exposed, changed;
+       if (changed & XC_TICK_INSTALLED || !changed)
+               return;
 
-               exposed = in_rect(expose, xpos, ypos, seg->width, line_height);
-               changed = seg->flags & XC_FLAG_CHANGED;
+       xc->render_mask |= XC_TICK_INSTALLED;
+       app = XtWidgetToApplicationContext(xc->widget);
+       XtAppAddTimeOut(app, 3, xc_start_render, xc);
+}
 
-               if (changed)
-                       XClearArea(display, window, xpos, ypos, seg->width, line_height, 0);
-               if (changed || exposed) {
-                       XmStringDraw(display, window, xc->rt, seg->str, xc->gc, xpos,
-                               ypos, seg->width, XmALIGNMENT_BEGINNING,
-                               XmSTRING_DIRECTION_L_TO_R, expose);
-                       seg->flags &= ~XC_FLAG_CHANGED;
-               }
+static uint_fast32_t xc_add_seq(struct xcounter *xc, int i, unsigned char val)
+{
+       uint_fast32_t ret;
 
-               xpos += seg->width;
-       }
+       ret = xc->seq[i] != val;
+       xc->seq[i] = val;
 
-       XClearArea(display, window, MIN(xpos, width-MARGIN), MARGIN,
-                                   -1, line_height, 0);
+       return ret;
 }
 
-void xcounter_update(Widget w, struct xcounter *xc, const char *str)
+void xcounter_update(struct xcounter *xc, const char *str)
 {
-       int i, l, num_static = 0;
-       unsigned prev_xpos = 0, new_xpos = 0;
+       int static_seq = 10, i = 0, l;
+       uint_fast32_t mask = 0;
        const char *s;
 
-       for (i = 0, s = str; s[0];) {
+       for (s = str; s[0];) {
                if ((l = strcspn(s, DIGIT_LIST))) {
-                       if (i >= xc->total_segments) {
-                               assert(s > str && s[-1] >= '0' && s[-1] <= '9');
-                               realloc_segments(xc, s-1);
-                       }
-                       check_position(xc, i, &prev_xpos, new_xpos);
-                       update_static(xc, i++, num_static++, &new_xpos);
+
+                       mask <<= 1;
+                       mask |= xc_add_seq(xc, i++, static_seq++);
                        s += l;
                }
 
-               for (l = strspn(s, DIGIT_LIST); l; l--) {
-                       if (i >= xc->total_segments)
-                               realloc_segments(xc, s);
-                       check_position(xc, i, &prev_xpos, new_xpos);
-                       update_digit(xc, i++, *s, &new_xpos);
+               while (s[0] >= '0' && s[0] <= '9') {
+                       mask <<= 1;
+                       mask |= xc_add_seq(xc, i++, s[0] - '0');
                        s++;
                }
        }
-       xc->num_segments = i;
 
-       resize(w, xc, new_xpos);
-       if (XtIsRealized(w))
-               redraw(w, xc, NULL);
-}
+       assert(static_seq <= MAX_STRINGS);
+       assert(i <= sizeof xc->seq);
 
-void xcounter_expose(Widget w, struct xcounter *xc, XExposeEvent *e)
-{
-       XRectangle rect = { e->x, e->y, e->width, e->height };
+       mask <<= sizeof xc->seq - i;
+       memset(xc->seq+i, -1, sizeof xc->seq - i);
 
-       XClearArea(XtDisplay(w), XtWindow(w),
-                  e->x, e->y, e->width, e->height, 0);
-       redraw(w, xc, &rect);
+       if (XtIsRealized(xc->widget))
+               xc_queue_render(xc, mask);
 }
 
-void xcounter_resize(Widget w, struct xcounter *xc,
-                     Dimension width, Dimension height)
+void xcounter_expose(struct xcounter *xc, XExposeEvent *e)
 {
-       if (XtIsRealized(w)) {
-               if (width > xc->old_w) {
-                       XClearArea(XtDisplay(w), XtWindow(w),
-                                  xc->old_w - MARGIN, 0, -1, height, 1);
-               }
+       unsigned left = e->x, right = left + e->width;
+       unsigned out_x = MARGIN, src_x, w;
+       uint_fast32_t mask = 0;
+       int i;
 
-               if (height > xc->old_h) {
-                       XClearArea(XtDisplay(w), XtWindow(w),
-                                  0, xc->old_h, xc->old_w, -1, 1);
-               }
+       xc_foreach_seq(xc, i, out_x, src_x, w, mask) {
+               mask |= out_x + w >= left && out_x < right;
        }
+       mask <<= sizeof xc->seq - i;
 
-       xc->old_w = MAX(width, MARGIN)-MARGIN;
-       xc->old_h = MAX(height, MARGIN)-MARGIN;
+       xc_queue_render(xc, mask);
 }
 
 void xcounter_resize_cb(Widget w, void *data, void *cb_data)
 {
-       Dimension width, height;
-
-       XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, (char *)NULL);
-       xcounter_resize(w, data, width, height);
+       xc_queue_render(data, XC_RESIZING);
 }
 
 void xcounter_expose_cb(Widget w, void *data, void *cb_data)
@@ -343,5 +347,5 @@ void xcounter_expose_cb(Widget w, void *data, void *cb_data)
        XmDrawingAreaCallbackStruct *cbs = cb_data;
 
        if (cbs->reason == XmCR_EXPOSE)
-               xcounter_expose(w, data, &cbs->event->xexpose);
+               xcounter_expose(data, &cbs->event->xexpose);
 }