The Graphical User Interface (GUI)

Overview

The GUI library manages the user interface of Torque applications. Based very loosely on the NeXTSTEP interface class library, the GUI library is designed specifically for the needs of game UI development. Some important classes are:

GuiCanvas - The Canvas object, a singleton instance of the GuiCanvas class, is the root of the currently active GUI hierarchy. It is responsible for processing and dispatching mouse and keyboard events, managing update regions and cursor handling, and calling the GuiControl render methods when it is time to draw the next frame. The GuiCanvas keeps track of a stack of content controls - separate hierarchies of GuiControls that render from bottom to top. The main content control is a screen in the shell, and can be covered by any number of floating windows or dialogs. The root of each content control hierarchy is sized to cover the entire canvas, so generally dialogs are composed of a content control containing a dialog control.

GuiControlProfile - The GuiControlProfile class instances maintain common instance data across a set of controls. Common information such as font face, colors, bitmaps and sound data are all stored in instances of GuiControlProfile, so that they don't need to be replicated on each control.

GuiControl - The GuiControl class is the root class for all the GUI controls in the system. GuiControl is derived from SimGroup and can contain any number of child GUI controls. Each GuiControl maintains a bounding rectangle in the coordinate system of its parent control. GuiControl has virtual methods for processing input events (onMouseDown, onMouseUp, onMouseEnter, onMouseLeave, onMouseMove, onMouseDragged, onRightMouseDown, onKeyDown, onKeyUp,onKeyRepeat), methods called by the Canvas for rendering (onPreRender, onRender), methods to control the lock focus of the mouse (mouseLock, mouseUnlock), coordinate conversion methods (localToGlobalCoord, globalToLocalCoord), and automatic sizing behavior when its parent control is resized (for example, when the screen resolution changes). When a control is made visible, it (if not already loaded) loads the data associated with its GuiControlProfile.

Input Event Processing in the GUI system

Input events are first processed for dispatch in GuiCanvas::processInputEvent. Depending on the type of input event (keyboard or mouse), the Canvas processes the event in different ways.

For keyboard events, the Canvas maintains an instance variable call the first responder (mFirstResponder). The first responder is essentially the control that has keyboard focus - for example a text field that the user is typing into or a button that the user has tabbed to. For any keyboard event, the first responder has the first chance to process it. If it does not process the event it passes it up to its parent class and then in GuiControl finally to its parent control. If the keyboard event is not handled by any control in the responder chain (from first responder up to the root content control), the Canvas checks to see if it is a special key - tab and shift-tab set the first responder to the next and previous controls in the tab list respectively. If it is not tab or shift-tab, the Canvas checks to see if the key is in the accelerator map for any of the controls visible (for example an OK button mapped to the enter key). If it is not mapped, the Canvas passes back false, signifying that the event should be processed by the action map handler.

For mouse events (if the cursor is on), the event is handled by the GUI system. Each mouse event can potentially generate several virtual method calls to controls. For example, moving the mouse cursor from one control into another will cause the first control to receive an onMouseLeave method call, followed by an onMouseEnter method call to the new control and finally an onMouseMove to that control as well. GuiControls can lock the mouse focus on the canvas so that only that control receives mouse method invocations - for example the GuiButtonCtrl control locks the mouse on mouse down and unlocks it on mouse up so that even if the user moves the cursor outside the control, the button still maintains focus and can re-highlight when the cursor is moved back inside. The GuiControl virtual method pointInControl by default returns true if the cursor is inside the bounding rectangle of the control. Control subclasses with non-rectangular shapes can override this method to provide controls with irregular mouse boundaries.

Control Rendering in the GUI system

The Canvas object is the root of the control rendering hierarchy and is responsible for the rendering process. When the game receives a time event from the platform layer it advances the state of the simulation and then instructs the canvas to render itself.

Before actually doing any rendering the Canvas instructs each control on the screen to onPreRender() itself, allowing each control to determine what if anything needs to be rendered on that control. The canvas, in order to render more quickly, maintains a set of "dirty" bounding boxes signifying areas of the screen that need to be repainted - a button may change, for example, if the mouse has just been pressed inside it, causing it to glow in the depressed state. During or before the onPreRender, a control can call the setUpdate() method on itself signifying that it needs to be repainted. Because the Torque engine works with page flipping devices and there may be as many as three separate buffers with old screen data, the Canvas maintains three dirty rectangles representing the dirty state of each of the (up to) three buffers.

After onPreRender, the Canvas renders the hierarchy of controls by telling the root of each layer (content control) to onRender() itself, and passing in a rectangle of the visible area of that control, which is initially set to the dirty area of the screen. When a control renders it can optionally call renderChildControls with this visible rectangle which will clip the rectangle to the bounds of each of its child controls, and if there is any area visible, will call onRender() on the child with the clipped rectangle as the visible area.

The GUI system and the Console

The GUI system is designed to work tightly with the Console language. The GuiControl class has an instance variable called mConsoleVariable which is the name of a global console variable that will, if set, reflect the state of that control in a control-dependant way. For example, a variable that is mapped to a checkbox control will have the value of 1 when the control is checked and 0 when it is not. Each GuiControl also has an instance variable called mConsoleCommand and mAltConsoleCommand that get executed in different cases depending on the control - in the case of a GuiButton the mConsoleCommand script gets evaluated when the button is clicked (in GuiControl::onAction()).

Another way the GUI system interacts with the console is via Console namespaces - when you name a control (like MainMenuQuitButton) that control name is registered as a namespace whose parent is the control's class, and special console methods can then be invoked from code on that control - in the case of a button the buttons onAction console method will be called when the button is pressed. Custom controls can be built in this way to execute several different commands with custom arguments.