X-Git-Url: https://git.draconx.ca/gitweb/rrace.git/blobdiff_plain/5a4a23e5e8032dfa1f7b73facb32f0c9ab200765..bbe659b000e2c281340953c5fc96bd7ac3b94eaa:/src/xcounter.c diff --git a/src/xcounter.c b/src/xcounter.c index 8266107..e8e5d17 100644 --- a/src/xcounter.c +++ b/src/xcounter.c @@ -22,320 +22,324 @@ #include #include +/* Define to 1 to add highlights and delays for visually debugging redraws */ +#if X11_RENDER_DEBUG +# include +#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); }