2003.03.18 ~10 OES UI The last three (four?) days I worked on overhauling the ui system for Orbital Eunuchs Sniper (http://www.icculus.org/oes/). For one thing, I was annoyed at the existing menus not working. Secondly, I wanted a platform to prototype a ui for my q3 mod (the one done in Scheme). Mainly because of the Scheme slant, I used S-expressions for storage format. For quite a while, I knew of the existence of sfsexp (http://sexpr.sourceforge.net/), but didn't have much practice actually using it. To cut my teeth on sexpr, I started on something smaller, the preferences file in OES. The original preference format was binary, a memory dump of a C struct. I'm a strong believer in the unixian way of plain-text configuration files. As an added bonus, the preference file only held four settings, so the preferences could be simplified into a four-member association list. Reading would involve a simple one-level traversal of the list, and then picking the pairs out of each member of the list. This little exercise gave me a sufficiently half-clued idea of how to use the sexpr data structs. Afterwards, I took on the menus system. The initial idea was to parallel the menu system in Q3TA, involving text files that describe the initial state of the gui, then letting the system rip loose with a C core. The first goal was to have the buttons react to the mouse, focus and clicking. The first incarnation checked the mouse location every time the screen was redrawn, checking if a button was in focus or not. The ui system walked down the widgets heirarchy to propagate click events, checking each widget's clickability, mouse location, and name to react to mouse clicks. This method was really nasty, since button actions were hard-coded according to their name, embedded in the ui functions (broken encapsulation). I think I went through four rewrites of the data structures for the widgets. What I wanted in the data structure was a simplified query/modify interface to widget properties. Initially, I wanted to directly modify the sexp structs describing the widgets, in the form of alists, but I couldn't quite figure out how to modify the structs decently, either to add a key/value pair or to modify one. The data structures I finally settled on involves a singly-linked list of structs holding a key/value pair, which I found to be more easily modified than the sexp structs. A couple of support functions translates the sexp structs into the linked list. After the buttons started reacting to clicks properly (starting game, quitting, etc.), I worked on purifying the encapsulation of the ui system. Namely, to separate the OES-specific data and actions from ui.cpp. The goal was to make the ui system capable of being a drop-in component into another project. One interesting progress I notice in retrospect is my gravitation towards a signals system as used in gtk+ and SWING, away from the "cascading events" style. Initially I couldn't comprehend the signals system, and partially as a result had a general distaste for it at first. For one thing, I didn't like, and couldn't handle very well, the notion of multiple paths to a widget (one via parenting heirarchy, and one via signals, etc.). OTOH, what I found out is that using a signals system greatly expanded the capabilty of the ui system. I don't know if it's something intrinsic within the signals system or if it's a "perspective" thing, like taking a different slice of The Whole General Mishmash of widgets. As a hack, the extent of signal-handling within the ui system itself is emitting yet another signal. There is C instruction to handle signals not understood by the widgets in the ui system. So, for example, handling of the Escape key (which was added fairly late in development) to backtrack through menus was done by connecting the toplevel (menu) widget to the "esc" signal; the "esc" signal is generated by pressing the Escape key (from the ui input handler); the toplevel widget re-emits the signal "go-main" upon receiving "esc"; no widgets understand "go-main", so this signal is handed off to the C handler, which does a series a string checks to figure out what the signal is, then rearranges menus accordingly (in the case of "go-main", bringing the menu named "main" to the foreground). This signals system is still better than recursively stepping through child widgets and repeating events to all of them. For one thing, signalling doesn't need to worry that some widget that doesn't want/like/know an event won't stop the entire event-propagation loop. For another, CPU cycles aren't wasted on widgets that don't care about a particular signal. The advantages of a signals system are probably common knowledge to developers of ui systems, but I'm scribbling all this down for my own future reference.