The UI System in Command & Conquer Generals
The interface in Command & Conquer: Generals is not a collection of sprites painted directly by each game screen. It is a retained window system. Menus, buttons, text fields, list boxes, the command bar, and even the rectangular area that contains the radar exist as persistent GameWindow objects with parents, children, state, callbacks, and drawing data.
That design is easy to miss because the source uses the old Westwood vocabulary of WND files, gadgets, system procedures, and window messages. Under those names is a fairly recognizable UI architecture. WND files describe trees. The window manager owns those trees. Gadgets translate low-level input into semantic events. Screen and HUD code reacts to those events by mutating retained state or by appending commands to the game message stream.
I traced this article through the current Generals64 source. The important point is not merely that Generals has a custom GUI toolkit. It is that the front end and the in-game HUD use the same retained machinery, while the HUD adds a second layer that binds those windows to simulation data.
GameWindow is the retained object
Every visible control begins with GameWindow. The class comment is unusually direct:
/** Class definition for a game window. These are the basic elements of the
* whole windowing system, all windows are GameWindows, as are all GUI controls
* etc. */
class GameWindow : public MemoryPoolObject
A window keeps its rectangle, parent and child links, sibling order, identifier, status bits, style bits, user data, and instance data. It also carries function pointers for four separate jobs:
typedef void (*GameWinDrawFunc)(GameWindow *, WinInstanceData *);
typedef WindowMsgHandledType (*GameWinInputFunc)(GameWindow *, UnsignedInt,
WindowMsgData, WindowMsgData);
typedef WindowMsgHandledType (*GameWinSystemFunc)(GameWindow *, UnsignedInt,
WindowMsgData, WindowMsgData);
There is also a tooltip callback. The split matters. The input function implements the behavior of the control itself. The system function is normally owned by the containing screen or panel. The draw function presents the current retained state.
A push button, for example, does not call the main-menu code directly. Its input procedure tracks whether it is highlighted or pressed and sends a higher-level GBM_SELECTED message to its owner. The owner's system callback decides what that selection means.
The status bits show how much behavior lives in retained state. A window can be enabled, hidden, above or below its siblings, image-backed, focusable, check-like, right-click aware, flashing, or configured to trigger on mouse-down. Hiding a parent naturally removes its subtree from drawing and input without requiring the application to reconstruct that subtree later.
This is not an immediate-mode UI. The application does not emit a button every frame and ask whether it was clicked. It creates a button once, retains it, and changes its state until the layout is destroyed.
WND files are serialized window trees
The layout language is plain text. A checked-in MainMenu.wnd contains a top-level WINDOW followed by nested CHILD blocks. Each node records fields such as:
WINDOWTYPESCREENRECTNAMESTATUSSTYLESYSTEMCALLBACKINPUTCALLBACKTOOLTIPCALLBACKDRAWCALLBACKTEXT- per-state colors and images
A simplified entry looks like this:
WINDOWTYPE = PUSHBUTTON;
SCREENRECT = UPPERLEFT: 12 9,
BOTTOMRIGHT: 100 35,
CREATIONRESOLUTION: 800 600;
NAME = "MainMenu.wnd:ButtonSinglePlayer";
STATUS = ENABLED+IMAGE;
STYLE = PUSHBUTTON+MOUSETRACK;
INPUTCALLBACK = GadgetPushButtonInput;
DRAWCALLBACK = W3DGameWinDefaultDraw;
The file is more than a skin. It serializes hierarchy, geometry, behavior bindings, initial text, status, and presentation. In other words, it is a compact retained UI object graph.
Names include the layout name, such as ControlBar.wnd:ButtonCommand01. The engine hashes those strings through NameKeyGenerator and later retrieves windows with winGetWindowFromId(). This gives gameplay code stable handles without storing raw parser-time pointers everywhere.
The public Generals64 tree includes representative WND files, but not every shipped game-data layout. A bare name is prefixed with Window\, then opened through TheFileSystem, so layouts can come from mounted BIG archives as well as loose files. ControlBar.wnd itself is expected through that game-data filesystem. Its structure is nevertheless visible through the many named children that the runtime resolves.
Loading a layout
There are two related loading APIs.
GameWindowManager::winCreateFromScript() reads a WND file and returns the first created window. winCreateLayout() wraps the result in a WindowLayout, which also retains layout-level init, update, and shutdown callbacks. Menus commonly use layouts because they need a managed screen lifetime. The control bar's main tree is created directly, while auxiliary panels such as the general-points screen and build tooltip use WindowLayout objects.
The parser reads one window block at a time. It resolves the window type, chooses gadget callbacks, constructs a WinInstanceData, and calls the manager to allocate and link the GameWindow. A stack tracks the current parent while nested CHILD blocks are parsed.
The window tree therefore exists before screen-specific initialization code starts querying it. That ordering is visible in the HUD startup:
createControlBar();
createReplayControl();
TheControlBar = NEW ControlBar;
TheControlBar->init();
createControlBar() calls:
TheWindowManager->winCreateFromScript("ControlBar.wnd");
HideControlBar();
Only after that does ControlBar::init() resolve ControlBarParent, command slots, radar, portrait, queue, observer, and utility controls.
Callback names become function pointers
WND files store callback names as strings. Those strings are not interpreted by a scripting language. FunctionLexicon loads tables that map names to compiled C++ functions.
The generic table includes entries such as:
{ NAMEKEY_INVALID, "ControlBarSystem", (void*)ControlBarSystem },
{ NAMEKEY_INVALID, "GadgetPushButtonInput", (void*)GadgetPushButtonInput },
{ NAMEKEY_INVALID, "LeftHUDInput", (void*)LeftHUDInput },
The W3D-specific lexicon adds renderer callbacks such as W3DLeftHUDDraw and W3DGameWinDefaultDraw.
This arrangement gave the WND editor and layout files a symbolic vocabulary while keeping execution in native code. It also establishes a useful portability boundary. Layout and control behavior can remain unchanged while a platform-specific function table provides draw implementations.
There is a cost. Renaming a callback in C++ without updating the WND data breaks the binding at runtime. The names are an ABI between data and code, even though no compiler checks them together.
Resolution scaling happens during parsing
WND rectangles contain the resolution at which the layout was authored. parseScreenRect() reads the upper-left point, lower-right point, and CREATIONRESOLUTION, then computes:
Real xScale = (Real)TheDisplay->getWidth() / (Real)createRes.x;
Real yScale = (Real)TheDisplay->getHeight() / (Real)createRes.y;
It multiplies all four rectangle coordinates by those independent scales. If the window has a parent, the parser converts the scaled screen coordinates back into a position relative to the parent's client origin.
This explains two properties of the Generals UI. First, layout authors work in one reference resolution. Second, the original system stretches separately in X and Y rather than using modern anchors, constraints, or aspect-preserving layout. The hierarchy is retained, but the layout model is authored rectangles plus global scaling.
Input is routed through the window tree
Raw mouse and keyboard records first pass through WindowTranslator. Mouse input then reaches GameWindowManager::winProcessMouseEvent(). The manager tracks capture, modal state, the window under the cursor, and entering or leaving transitions. It ignores hidden or disabled windows and sends the resulting GWM_LEFT_DOWN, GWM_LEFT_UP, drag, wheel, and movement messages to the deepest eligible window. If that callback does not handle the message, dispatch bubbles toward its parents. Input consumed by the WND system is removed before downstream gameplay translators see it.
Keyboard input follows focus and tab-stop state. Modal windows limit the eligible subtree. This is conventional retained-mode routing, though it is implemented with explicit linked lists and function pointers rather than event objects.
The push-button gadget shows the next layer. On GWM_LEFT_DOWN it sets WIN_STATE_SELECTED, plays the GUI click sound, and may trigger immediately when WIN_STATUS_ON_MOUSE_DOWN is present. Otherwise GWM_LEFT_UP sends the semantic event:
TheWindowManager->winSendSystemMsg(
instData->getOwner(),
GBM_SELECTED,
(WindowMsgData)window,
mData1);
The owner receives both the event and the child control pointer. A menu callback can compare the child's ID and open another layout. The control-bar callback can retrieve the CommandButton attached to that control and turn it into a game command.
This distinction keeps reusable gadget mechanics out of screen policy. GadgetPushButtonInput knows how a button behaves. ControlBarSystem knows what a command-bar button means.
WindowLayout supplies screen lifetime
WindowLayout is a small coordinator around one or more root windows. It can hide, show, bring forward, and destroy the group. It also stores optional init, update, and shutdown callbacks. Those callbacks are not automatic. The owning screen must explicitly call runInit(), runUpdate(), and runShutdown() at the appropriate points in its lifetime.
That is how shell screens behave like retained scenes. Opening a menu does not enter a hand-written paint loop. The game loads its WND tree, calls its layout initializer, and lets the normal window manager route input and repaint it. Closing it hides or destroys the tree and runs the matching shutdown procedure.
Generals also uses small layouts inside the HUD. GeneralsExpPoints.wnd is loaded separately for science purchases. ControlBarPopupDescription.wnd is a tooltip panel with its own update function. These are not special renderer features. They are more retained window trees composed into the in-game interface.
The HUD is WND plus live game binding
The in-game HUD is not one class. It is a collaboration among several layers:
InGameUIowns selection state, pending cursor commands, floating text, messages, placement feedback, subtitles, and the tactical view.ControlBarbinds selected objects and player state to the windows created fromControlBar.wnd.Radarowns map sampling, radar objects, events, coordinate conversion, and visibility policy.- device-specific callbacks draw custom regions such as the radar and power meter.
- message translators turn UI intent into game messages.
InGameUI::init() creates the tactical view, creates the control-bar and replay WND trees, then constructs and initializes ControlBar. During each client update, InGameUI::update() maintains transient UI state, updates money and power visibility, updates floating text, calls ControlBar::update(), and maintains idle-worker state.
The money display illustrates the binding style. InGameUI finds ControlBar.wnd:MoneyDisplay, reads the currently viewed player's Money, formats localized text only when the amount changes, and calls GadgetStaticTextSetText(). The WND node remains the same object. Only its retained text changes.
The power display uses a custom draw callback. It reads the viewed player's production and consumption, selects green, yellow, or red art, and draws repeated image segments and a slider in the rectangle assigned to PowerWindow.
The command bar is a context machine
ControlBar::init() loads two independent kinds of data:
ini.loadFileDirectory("Data\\INI\\Default\\CommandButton", ...);
ini.loadFileDirectory("Data\\INI\\CommandButton", ...);
ini.loadFileDirectory("Data\\INI\\CommandSet", ...);
The WND file supplies physical slots. The INI files supply command semantics. ControlBar connects them at runtime.
It first resolves parent groups such as CommandWindow, ProductionQueueWindow, UnderConstructionWindow, BeaconWindow, observer windows, and the master ControlBarParent. It then looks up numbered slots from ButtonCommand01 onward and stores them in m_commandWindows.
When selection changes, onDrawableSelected() and onDrawableDeselected() mark the control bar dirty. The next update calls evaluateContextUI(). That function clears the old context and examines the current selection:
- no selection produces the empty context;
- several ordinary units produce multi-select;
- an unfinished object produces under-construction;
- a garrisonable structure can produce an inventory context;
- an object with an OCL update can produce a timer context;
- an object with a command-set name produces the command context;
- beacons and observers have their own paths.
Switching context hides and reveals persistent parent windows. It also updates the portrait, resets hotkeys, cancels radius-cursor state, and records the drawable that drives the panel.
For a command context, populateCommand() looks up the selected object's CommandSet. For each fixed WND slot it obtains the corresponding CommandButton, hides empty or script-only entries, and attaches command data with setControlCommand(). Science requirements, affordability, production queues, upgrade state, cooldowns, and availability are reflected by hiding, disabling, overlaying, or changing the retained button.
This is why the same physical grid can represent a dozer, tank, airfield, transport, or command center. The layout is stable. Context and data change.
Multi-selection is computed rather than authored
A multi-selection cannot simply display one object's command set. ControlBar compares the selected objects and presents commands that are valid for the group. It also has special handling for nexus-style selections such as an Angry Mob, where several drawables should appear as one logical unit in the interface.
The result is another example of the retained architecture. The command windows are not recreated. The control bar computes a common command model and repopulates the same slots.
Selection itself belongs to InGameUI and the message translators, not to WND. Dragging in the world can append area-selection messages. Once the resulting drawable list changes, ControlBar is notified and rebuilds its presentation on the next update.
A button click is not necessarily a simulation command yet
The complete path from a visible command icon to game logic has several stages.
For the normal command bar:
- The window manager sends mouse messages to
GadgetPushButtonInput. - The gadget sends
GBM_SELECTEDto its owner. ControlBarSystemforwards ordinary command controls toprocessContextSensitiveButtonClick().- That function calls
processCommandUI(). processCommandUI()reads the attachedCommandButtonand validates the current selection and command state.
Commands that need no further input can append a GameMessage immediately. Producing a unit, for example, appends MSG_QUEUE_UNIT_CREATE with the thing-template ID and a unique production ID. Stop appends MSG_DO_STOP. Selling appends MSG_SELL.
Commands that need a position or object target take another route. processCommandUI() stores the CommandButton in InGameUI as the pending GUI command. Cursor and radius feedback can then reflect that mode. On hover and on the second click, the client translator chain evaluates that pending command against the world. CommandTranslator::evaluateContextCommand() handles the general target-validation path, while GUICommandTranslator handles several specialized GUI-command cases. Together they convert screen coordinates through the tactical view, validate object-versus-location rules, and append concrete messages such as MSG_DO_WEAPON_AT_LOCATION, MSG_DO_GUARD_OBJECT, or MSG_SET_RALLY_POINT.
The important boundary is this: WND and gadgets create user-interface intent. They do not directly mutate deterministic game objects. Simulation-affecting actions enter the message stream, where the rest of the command and multiplayer machinery can order and execute them.
Some interface-only actions stay local. Opening the options panel, changing the control-bar stage, selecting the next idle worker, or showing the generals screen can update client state without becoming a synchronized game command.
The radar is a custom canvas inside a window
ControlBar.wnd:LeftHUD is a retained window, but the minimap pixels are not a set of child gadgets. Radar::newMap() stores that window and computes sampling intervals from the terrain extents. Radar objects are kept in priority-grouped lists, with local objects separated from other objects. The radar also tracks transient events such as attack and beacon pulses.
The W3D draw callback bridges the systems:
void W3DLeftHUDDraw(GameWindow *window, WinInstanceData *instData)
{
if (TheInGameUI->videoBuffer()) {
// draw video into the window rectangle
} else if (rts::localPlayerHasRadar()) {
TheRadar->draw(pos.x + 1, pos.y + 1,
size.x - 2, size.y - 2);
}
}
The WND window supplies placement, visibility, ownership, and input routing. The concrete radar supplies terrain, icons, events, shroud-aware content, and the camera view box. In the current D3D11 build, that implementation lives in D3D11Shims.cpp: it composites CPU terrain, object, and shroud pixels, uploads a dynamic texture, then draws radar events and the camera view box. The older standalone W3DRadar.cpp remains useful reference code, but it is not selected by the current CMake source list.
Input follows the same composition. LeftHUDInput converts a local mouse pixel to radar coordinates and then to world coordinates. Depending on the active command and mouse button, it can move the camera, select an object under the radar pixel, or append a move or attack-move command.
This is a useful pattern throughout the HUD: use a retained window as the layout and interaction boundary, then let a specialized subsystem render dynamic content inside it.
Rendering crosses a narrow device boundary
The generic window manager traverses visible windows and calls their draw callbacks. Default callbacks render the retained text, images, colors, borders, and gadget states. Specialized callbacks can ask TheDisplay or TheWindowManager to draw images, rectangles, strings, video, radar data, or meters.
The current Generals64 display path makes the boundary explicit. After 3D scene rendering and in-world overlays, W3DDisplay draws 3D UI feedback such as placement indicators, begins a 2D pass, repaints the window manager, draws the remaining InGameUI overlays, and finally draws the mouse cursor.
The active W3DInGameUI methods are also provided by D3D11Shims.cpp. They handle the drag-selection rectangle and screen-space overlays, while draw3DOverlays() submits move hints, attack hints, and placement-angle feedback before the renderer enters 2D mode. This is why the HUD should not be reduced to WND alone. WND owns the retained screen-space controls, while InGameUI also owns feedback that belongs in the tactical world or overlays the whole screen.
The older standalone W3DInGameUI.cpp still contains its own winRepaint() call, but that file is not in the current CMake source list. The active D3D11 shim explicitly leaves repainting to W3DDisplay, so the current path performs one retained window traversal from that display entry point.
Why the design works
The Generals interface divides responsibilities cleanly enough to survive a renderer port:
- WND data defines persistent hierarchy and authored geometry.
GameWindowManagerowns lifetime, stacking, focus, capture, modality, routing, and traversal.- gadgets implement reusable control behavior.
- callback lexicons bind data names to compiled code.
WindowLayoutmanages screen lifetime.InGameUIowns tactical and selection-oriented client state.ControlBarprojects game state into a context-sensitive retained panel.Radarand other specialized systems draw dynamic content inside assigned windows.- the message stream separates UI intent from deterministic simulation changes.
The system is old-fashioned in its implementation. It uses linked trees, status bitfields, hashed names, C-style callbacks, fixed command slots, and resolution-scaled rectangles. It lacks modern constraint layout, declarative binding, and type-safe signals.
But it is not primitive. The retained tree gives Generals reusable controls, modal screens, focus, hierarchy, stateful gadgets, data-defined layouts, and renderer abstraction. The HUD then builds a reactive layer on top by marking contexts dirty and updating only the state that changed.
That combination explains why the same foundation can run the main menu, options screens, command buttons, production queues, science purchases, observer panels, portraits, radar, power, and money display. WND provides the skeleton. Native systems supply behavior and live data. The message pipeline keeps the visual client separate from the game that all players must simulate identically.