]> git.draconx.ca Git - rrace.git/commitdiff
Reimplement xcounter based on pre-rendered pixmpas.
authorNick Bowler <nbowler@draconx.ca>
Sat, 24 Dec 2022 05:24:25 +0000 (00:24 -0500)
committerNick Bowler <nbowler@draconx.ca>
Sat, 31 Dec 2022 01:38:26 +0000 (20:38 -0500)
This alters the xcounter idea, instead of caching XmStrings and doing
full-blown font rendering with each update, we can pre-render all the
needed strings to a pixmap and stitch them together using XCopyArea.

This achieves basically the same visual result but clears are eliminated
completely which should further reduce the chances of visible flicker.

While we are rejigging this, tune the state structure to be much
more focused to the actual usage, preferring fixed limits and compact
data representation over dynamic allocation.

src/motif.c
src/motif.h
src/motif_ui.c
src/motifgui.dat
src/xcounter.c
src/xcounter.h

index 891fe471235f997260c222049c1c0b5fbeccf814..fe030799984fc3442aa4b35917cbdb5039eba160 100644 (file)
@@ -142,7 +142,7 @@ static void timer_tick(void *data, XtIntervalId *id)
                return;
        }
 
-       app = XtWidgetToApplicationContext(state->timer);
+       app = XtWidgetToApplicationContext(state->game);
        ui_timer_update(state, game_elapsed(&state->board));
        state->timer_tick = XtAppAddTimeOut(app, TIMER_UPDATE_MS,
                                            timer_tick, state);
index 361abd98b12690807dff01e59e1166e794625e41..cb07ec3d7c1dc9f5a2979b96e5861330706a14db 100644 (file)
@@ -26,7 +26,9 @@
 struct app_state {
        struct board board;
 
-       Widget game, goal, timer;
+       Widget game, goal;
+
+       struct xcounter *timer;
 
        /* Current window width/height for resize handling. */
        Dimension game_sz[2], goal_sz[2];
index 3658615d917dc0870c02ca5109bb7e992f928f05..6abdbc9d6db5dbe0aca21eab9cb6d28a358056bf 100644 (file)
@@ -116,21 +116,22 @@ void ui_timer_update(struct app_state *state, int_fast32_t ms)
        char buf[100];
 
        if (ms < 0) {
-               xcounter_simple_update(state->timer, "\n");
+               xcounter_update(state->timer, "\n");
                return;
        }
 
-       xcounter_simple_update(state->timer, timer_text(ms, buf));
+       xcounter_update(state->timer, timer_text(ms, buf));
 }
 
 static void configure_mainwin(struct app_state *state, Widget form)
 {
        Widget gamearea = XtNameToWidget(form, &tree_strtab[gameArea]);
        Widget goalarea = XtNameToWidget(form, &tree_strtab[goalArea]);
+       Widget timer = XtNameToWidget(form, &tree_strtab[timeDisplay]);
        XtActionsRec resize_rec;
        char xc_template[100];
 
-       assert(gamearea && goalarea);
+       assert(gamearea && goalarea && timer);
        XtVaSetValues(form, XmNfractionBase, SPLIT_DENOMINATOR, (char *)NULL);
 
        state->game = XtNameToWidget(gamearea, &tree_strtab[gameCanvas]);
@@ -145,15 +146,13 @@ static void configure_mainwin(struct app_state *state, Widget form)
                                XmNtopWidget, gamearea,
                                (char *)NULL);
 
-       state->timer = XtNameToWidget(form, &tree_strtab[timeDisplay]);
-       XtVaSetValues(state->timer, XmNleftAttachment, XmATTACH_WIDGET,
-                                   XmNleftWidget, gamearea,
-                                   XmNtopAttachment, XmATTACH_WIDGET,
-                                   XmNtopWidget, goalarea,
-                                   XmNrightAttachment, XmATTACH_FORM,
-                                   (char *)NULL);
-
-       xcounter_simple_setup(state->timer, timer_text(20000, xc_template));
+       XtVaSetValues(timer, XmNleftAttachment, XmATTACH_WIDGET,
+                            XmNleftWidget, gamearea,
+                            XmNtopAttachment, XmATTACH_WIDGET,
+                            XmNtopWidget, goalarea,
+                            XmNrightAttachment, XmATTACH_FORM,
+                            (char *)NULL);
+       state->timer = xcounter_simple_init(timer, timer_text(0, xc_template));
        ui_timer_update(state, -1);
 
        resize_rec.string = "ResizeGameArea";
index b22900759e99b2db74976542aece1f02a9b602d2..a562e06f257bae46f59be5d57a569a8bda072e70 100644 (file)
@@ -2,11 +2,11 @@ MAINWIN
  main, main_OFFSET, widgetMainWindow
   menuBar, 0, widgetMenuBar
   game, game_OFFSET, widgetForm
+   timeDisplay, 0, widgetDrawingArea
    gameArea, gameArea_OFFSET, widgetFrame
     gameCanvas, 0, widgetDrawingArea
    goalArea, goalArea_OFFSET, widgetFrame
     goalCanvas, 0, widgetDrawingArea
-   timeDisplay, 0, widgetDrawingArea
 
 ABOUTDIALOG
  aboutDialog, aboutDialog_OFFSET, widgetUnmanaged|widgetMessageDialog
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);
 }
index 6109b0594a9a9636db2510fb3c53f0f0f734f7a1..e08b8a8a6333fb9b1bc9d9302abfb6182328c0b1 100644 (file)
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+#ifndef XCOUNTER_H_
+#define XCOUNTER_H_
+
 /*
- * A set of functions to implement a simple counter display with better support
- * for rapid updates than regular XmLabel (and other Motif text display widgets).
+ * A set of functions to implement a simple decimal counter display supporting
+ * rapid updates.  This is intended to avoid two specific practical problems
+ * with the Motif text widgets:
  *
- * This is intended to avoid two specific practical problems with these widgets:
+ *  (1) Updating the text involves expensive(?) XmString operations, and
+ *  (2) Updating the text causes the label to be cleared and redrawn.
  *
- *  (1) Any update to the text involves dynamic allocation of XmStrings
- *  (2) Updating the text causes the entire label to be redrawn.
+ * Point #2 in particular results in very annoying flicker.
  *
- * Point #2 can lead to annoying flicker on static portions of the label when
- * updates are occurring continuously.
+ * These functions are intended for dealing with strings that consist of
+ * "static" text interspersed with digits.  For example, a string such as:
  *
- * These functions are intended for displaying strings that consist of non-
- * changing static text with digits interspersed.  Specifically, strings such
- * as "The time is 12:30:57."
+ *    "The time is 12:30:57."
  *
- * Passing this string as the template argument to xcounter_init will pre-
- * allocate an XmString values for each of the four maximal nondigit
- * substrings.  These nondigit parts cannot be changed.
+ * On a call to xcounter_init, a string like this is provided as the template.
+ * The "static" elements are all the maximal nondigit substrings, which in this
+ * instance are "The time is ", ":" (twice) and ".".  These static elements
+ * are pre-rendered to an X pixmap along with all possible decimal digits.
  *
- * On a subsequent call to xcounter_update, a different string may be passed.
- * The digits in this string are used to construct a sequence of XmString
- * values that includes the precomputed static portions interspersed with
- * digits.  This new string is expected to be in the same format as the
- * template although there are two possible exceptions:
+ * A subsequent call to xcounter_update will change the displayed string by
+ * combining the pre-rendered images in various ways.  This takes a strings in
+ * the same format the original template to modify the digit sequences.
  *
- *   - The nondigit substrings do not need to match the template; they are
- *     used only to separate different groups of digits and otherwise have
- *     no effect on the output.
+ * This solves the practical problems by significantly simplifying the actual
+ * X drawing process (just a single operation to update any particular pixel).
+ * This avoids the flicker inherent to a clear+redraw, and a simple change
+ * tracking scheme is employed to avoid redundant drawing for a smoother
+ * update.
  *
- *   - If the new string may be a prefix of the template, only that portion
- *     will be output.  However it is still not possible to alter the static
- *     portions: they are either present in their entirety or omitted.
+ * To simplify the implementation, some arbitrary limits are baked in:
  *
- * The xcounter_redraw function can then be used to redraw only the portions of
- * the string that have changed since the last redraw.
+ *   - maximum number of static elements in a template:            10
+ *   - maximum number of digits plus static elements in an update: 23
+ *
+ * Which should be more than enough to handle the RRace displays.
  */
 
-#ifndef XCOUNTER_H_
-#define XCOUNTER_H_
-
 /*
- * Create xcounter based on template, which should be representative of
- * the number of digits expected to preallocate a typical number of digits.
+ * Create xcounter based on template.
  *
- * Note that the provided template string will be modified by this function,
- * but restored to its original value before returning.
+ * The provided template must be writeable as it is internally modified by
+ * this function.  However, it is restored to its original value upon return.
  */
 struct xcounter *xcounter_init(Widget w, char *template);
 
 /*
  * Update xcounter segments according to str.
  *
- * The nondigit sequences are assumed to match those from the original
- * template.
+ * Generally, the string should be in the same format as the original
+ * template, with only the digits changing between calls.  However, there
+ * are two allowed exceptions:
+ *
+ *   - It does not matter what exact text is placed in the static portions
+ *     between digits, provided they are nonempty.  These are used only to
+ *     separate groups of digits.  The pre-rendered text from the original
+ *     template is always used.
+ *
+ *   - The updated string need not include all the elements of the original
+ *     template.  Omitted elements from the update string are not drawn.
  */
-void xcounter_update(Widget w, struct xcounter *xc, const char *str);
+void xcounter_update(struct xcounter *xc, const char *str);
 
 /*
  * Redraw the counter in response to an expose event.
  */
-void xcounter_expose(Widget w, struct xcounter *xc, XExposeEvent *e);
+void xcounter_expose(struct xcounter *xc, XExposeEvent *e);
 
 /*
- * Resize and expose callbacks that can be registered on a drawing area widget.
+ * Resize and expose callbacks that can be registered on an XmDrawingArea.
  */
 void xcounter_resize_cb(Widget w, void *data, void *cb_data);
 void xcounter_expose_cb(Widget w, void *data, void *cb_data);
 
-static inline void xcounter_simple_setup(Widget w, char *template)
+/*
+ * Clear the margins of the counter after resizing the widget.
+ *
+ * Since the pre-rendered text never changes, this is all that is needed:
+ * existing portions of the window do not need to be redrawn (except when
+ * shrinking the margins over previously-drawn text), and newly-revealed
+ * areas will get expose events to trigger redraw.
+ */
+static inline void xcounter_resize(struct xcounter *xc)
+{
+       xcounter_resize_cb(0, xc, 0);
+}
+
+static inline struct xcounter *xcounter_simple_init(Widget w, char *template)
 {
        struct xcounter *xc = xcounter_init(w, template);
 
-       XtVaSetValues(w, XmNuserData, (void *)xc, (char *)NULL);
        XtAddCallback(w, XmNresizeCallback, xcounter_resize_cb, xc);
        XtAddCallback(w, XmNexposeCallback, xcounter_expose_cb, xc);
-}
-
-static inline void xcounter_simple_update(Widget w, const char *str)
-{
-       void *xc;
 
-       XtVaGetValues(w, XmNuserData, &xc, (char *)NULL);
-       xcounter_update(w, xc, str);
+       return xc;
 }
 
 #endif