]> git.draconx.ca Git - rrace.git/blob - src/xcounter.h
motif: Don't query widget width every timer update.
[rrace.git] / src / xcounter.h
1 /*
2  * Helpers for implementing a rapid-update counter display in Motif.
3  * Copyright © 2022-2023 Nick Bowler
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18
19 #ifndef XCOUNTER_H_
20 #define XCOUNTER_H_
21
22 /*
23  * A set of functions to implement a simple decimal counter display supporting
24  * rapid updates.  This is intended to avoid two specific practical problems
25  * with the Motif text widgets:
26  *
27  *  (1) Updating the text involves expensive(?) XmString operations, and
28  *  (2) Updating the text causes the label to be cleared and redrawn.
29  *
30  * Point #2 in particular results in very annoying flicker.
31  *
32  * These functions are intended for dealing with strings that consist of
33  * "static" text interspersed with digits.  For example, a string such as:
34  *
35  *    "The time is 12:30:57."
36  *
37  * On a call to xcounter_init, a string like this is provided as the template.
38  * The "static" elements are all the maximal nondigit substrings, which in this
39  * instance are "The time is ", ":" (twice) and ".".  These static elements
40  * are pre-rendered to an X pixmap along with all possible decimal digits.
41  *
42  * A subsequent call to xcounter_update will change the displayed string by
43  * combining the pre-rendered images in various ways.  This takes a strings in
44  * the same format the original template to modify the digit sequences.
45  *
46  * This solves the practical problems by significantly simplifying the actual
47  * X drawing process (just a single operation to update any particular pixel).
48  * This avoids the flicker inherent to a clear+redraw, and a simple change
49  * tracking scheme is employed to avoid redundant drawing for a smoother
50  * update.
51  *
52  * To simplify the implementation, some arbitrary limits are baked in:
53  *
54  *   - maximum number of static elements in a template:            10
55  *   - maximum number of digits plus static elements in an update: 23
56  *
57  * Which should be more than enough to handle the RRace displays.
58  */
59
60 /*
61  * Create xcounter based on template.
62  *
63  * The provided template must be writeable as it is internally modified by
64  * this function.  However, it is restored to its original value upon return.
65  */
66 struct xcounter *xcounter_init(Widget w, char *template);
67
68 /*
69  * Update xcounter segments according to str.
70  *
71  * Generally, the string should be in the same format as the original
72  * template, with only the digits changing between calls.  However, there
73  * are two allowed exceptions:
74  *
75  *   - It does not matter what exact text is placed in the static portions
76  *     between digits, provided they are nonempty.  These are used only to
77  *     separate groups of digits.  The pre-rendered text from the original
78  *     template is always used.
79  *
80  *   - The updated string need not include all the elements of the original
81  *     template.  Omitted elements from the update string are not drawn.
82  */
83 void xcounter_update(struct xcounter *xc, const char *str);
84
85 /*
86  * Redraw the counter in response to an expose event.
87  */
88 void xcounter_expose(struct xcounter *xc, XExposeEvent *e);
89
90 /*
91  * Resize and expose callbacks that can be registered on an XmDrawingArea.
92  */
93 void xcounter_resize_cb(Widget w, void *data, void *cb_data);
94 void xcounter_expose_cb(Widget w, void *data, void *cb_data);
95
96 /*
97  * Call after the widget is resized to update the internal dimensions,
98  * and clear the margins.
99  *
100  * Since the pre-rendered text never changes, this is all that is needed:
101  * existing portions of the window do not need to be redrawn (except when
102  * shrinking the margins over previously-drawn text), and newly-revealed
103  * areas will get expose events to trigger redraw.
104  */
105 static inline void xcounter_resize(struct xcounter *xc)
106 {
107         xcounter_resize_cb(0, xc, 0);
108 }
109
110 static inline struct xcounter *xcounter_simple_init(Widget w, char *template)
111 {
112         struct xcounter *xc = xcounter_init(w, template);
113
114         XtAddCallback(w, XmNresizeCallback, xcounter_resize_cb, xc);
115         XtAddCallback(w, XmNexposeCallback, xcounter_expose_cb, xc);
116
117         return xc;
118 }
119
120 #endif