CU_MDX_GUI.zip (63.8 KiB, 7,590 hits)
CUnitFramework.zip (101.1 KiB, 20,421 hits)
inside the project (not solution) directory.
This is the first tutorial of a 4-part series designed to create_ a GUI system. A GUI system, or graphical user interface, is one of the most user-friendly methods of letting users interact with your programs. During this tutorial series we will learn how to make a GUI system as shown below:

The GUI system will support buttons, sliders, checkboxes, radio buttons, drop down menus, list boxes, text labels, and edit boxes. All of these controls have the option of being placed in moveable windows as shown in the above screenshot.
Every graphical user interface ever made that uses a mouse needs to know at least 2 things: where the mouse is located and whether it is clicked. Once we know the position and state of the mouse, it is only a matter of determining whether the mouse is clicked over a control and responding to the click. Luckily the C-Unit Framework already supplies all this information in the OnMouse method.
All the controls that we will implement will be formed with a sort of jigsaw puzzle of Quads. If you don’t remember the Quad class from earlier tutorials, here it is again:
{
{
public Quad()

///
/// Top left vertex.
/// Top right vertex.
/// Bottom left vertex.
/// Bottom right vertex.
public Quad( TransformedColoredTextured topLeft, TransformedColoredTextured topRight,

{
m_vertices[0] = topLeft;
m_vertices[1] = bottomRight;
m_vertices[2] = bottomLeft;
m_vertices[3] = topLeft;
m_vertices[4] = topRight;
m_vertices[5] = bottomRight;
///
public TransformedColoredTextured[] Vertices

set { value.CopyTo( m_vertices, 0 ); }
///
public TransformedColoredTextured TopLeft

///
public TransformedColoredTextured TopRight

///
public TransformedColoredTextured BottomLeft

///
public TransformedColoredTextured BottomRight

///
public float X

set
{
m_vertices[0].X = value;
m_vertices[1].X = value + width;
m_vertices[2].X = value;
m_vertices[3].X = value;
m_vertices[4].X = value + width;
m_vertices[5].X = value + width;
///
public float Y

set
{
m_vertices[0].Y = value;
m_vertices[1].Y = value + height;
m_vertices[2].Y = value + height;
m_vertices[3].Y = value;
m_vertices[4].Y = value;
m_vertices[5].Y = value + height;
///
public float Width

set
{
m_vertices[4].X = m_vertices[0].X + value;
m_vertices[5].X = m_vertices[0].X + value;
///
public float Height

set
{
m_vertices[2].Y = m_vertices[0].Y + value;
m_vertices[5].Y = m_vertices[0].Y + value;
///
public float Right

///
public float Bottom

///
public int Color

set
{
{
///
///
public override string ToString()

result += “nY = “ + Y.ToString();
result += “nWidth = “ + Width.ToString();
result += “nHeight = “ + Height.ToString();
return result;
///
///
public object Clone()

A Quad is simply a rectangle of polygons in screen coordinates. As I stated before, each control will be formed out of a jigsaw puzzle of these Quads. This allows us to scale the controls without any loss of image quality. For example, some of the controls are composed of 9 Quads to form a scaleable rectangle: the 4 corners, the 4 sides, and the middle. The moveable Panel in the screenshot above is an example of a 9 piece control. In this case, the top and bottom sides scale horizontally, the left and right sides scale vertically, the middle would be scaled to cover the size of the control, and the corners don’t scale at all. Below is the texture that holds all the pieces for the screenshot you see above.

Keeping this scaling in mind, we should create_ the images so that the portions that are scaled look the same when they’re both scaled big and when they’re scaled small. Of course, if you’re never going to change the size of your controls, you can make them look however you want.
Since all the controls are made out of this jigsaw puzzle of Quads, it should be fairly easy to create_ different GUI stylesheets. We just need to create_ a texture image and define the locations and dimensions of all the different pieces. A great way to do this is with XML. For each new GUI style, we’ll create_ a texture and then write up an XML file that contains the location, width, and height of each piece. Here is the XML file for the displayed style.
In the XML file, each control is made of several Quads. Each Quad has a name, rectangle and color. To store this information, we’ll create_ a couple of simple classes:
public class ControlNode
{
public List
///
public class ImageNode
{
public Color Color;
public RectangleF Rectangle;
These classes simply store the information found in the XML file.
Since all the controls share the same basic functionality, we’ll create_ an abstract Control class:
{
public abstract class Control
{
public enum TextAlign { Left, Right, Top, Bottom, Center };
protected int m_id;
protected List
protected List
protected Rectangle m_hotspot;
protected ControlState m_state;
protected TextAlign m_textAlign;
protected RectangleF m_textRect;
protected SizeF m_size;
protected bool m_disabled;
protected bool m_hasFocus;
protected bool m_mouseDown;
protected object m_data;
protected int m_zDepth;
protected string m_text = string.Empty;
protected bool m_hasTouchDownPoint;
protected Point m_touchDownPoint;
protected PointF m_position;
protected BitmapFont m_bFont;
protected float m_fontSize;
protected ColorValue m_textColor;
protected float m_fontPadding;
private int m_startVertex;
private int m_fontStartVertex;
private int m_panelID;
// Events
public event GuiManager.ControlDelegate OnControl;
///
public Control()

m_fontPadding = 5f;
m_state = ControlState.Normal;
m_textRect = new RectangleF();
m_quads = new List
m_fontQuads = new List
m_disabled = false;
m_hasFocus = false;
m_hasTouchDownPoint = false;
m_mouseDown = false;
m_data = null;
///
/// Mouse position
/// Mouse buttons
/// Mouse wheel delta
///
public bool MouseHandler( Point cursor, bool[] buttons, float zDelta )

if ( !m_hasTouchDownPoint && buttons[0] )
{
m_hasTouchDownPoint = true;
else if ( m_hasTouchDownPoint && !buttons[0] )
{
if ( ( !( this is EditBox ) && ( Contains( cursor ) || m_hasFocus ) ) || ( ( this is EditBox ) && Contains( cursor ) ) )
{
{
// In order to send message, mouse must have been pressed and
// released over the hotspot.
if ( !buttons[0] )
{
{
if ( this is EditBox )
{
m_hasFocus = !m_hasFocus;
else
{
if ( Contains( cursor ) )
{
if ( OnControl != null && !(this is Panel) && ( !(this is ListBox) || ( ( this is ListBox ) && ( this as ListBox ).HasNewData ) ) )
{
bool result = true;
// ListBoxes need to continuously check mouse since it has sub parts that change on mouse over
// Mouseover on Panels should be able to reset over state on back controls.
if ( State == ControlState.Over && !( this is ListBox ) && !( this is Panel ) )
{
result = false;
State = ControlState.Over;
OnMouseOver( cursor, buttons );
return result;
else if ( buttons[0] && m_hasTouchDownPoint &&
Contains( m_touchDownPoint ) || m_hasFocus )
{
OnMouseDown( cursor, buttons );
// Slider sends data while mouse is down
if ( ( this is Slider ) && OnControl != null )
{
m_mouseDown = true;
if ( !( this is EditBox ) )
{
return true;
else if ( !Contains( cursor ) && State != ControlState.Normal )
{
{
if ( !m_hasFocus )
{
return true;
return false;
///
/// List of pressed keys
/// Pressed character
/// Pressed key from Form used for repeatable keys
///
public virtual bool KeyboardHandler( List

{
{
return true;
return false;
///
/// List of pressed keys
/// Pressed character
/// Pressed key from Form used for repeatable keys
///
public virtual bool OnKeyDown( List

///
/// Mouse position
///
public virtual bool Contains( Point cursor )

///
/// Mouse position
/// Mouse buttons
protected virtual void OnMouseOver( Point cursor, bool[] buttons )

///
/// Mouse position
/// Mouse buttons
protected virtual void OnMouseDown( Point cursor, bool[] buttons )

///
/// Mouse position
protected virtual void OnMouseRelease( Point cursor )

///
/// Mouse wheel delta
protected virtual void OnZDelta( float zDelta )

///
protected virtual void BuildText()

///
public virtual int ID

///
public virtual List

///
public virtual List

///
public virtual object Data

///
public virtual ControlState State

set { m_state = value; }
///
public virtual bool Disabled

set
{
{
else
{
///
public virtual int ZDepth

set { m_zDepth = value; }
///
public virtual int StartVertex

set { m_startVertex = value; }
///
public virtual int FontStartVertex

set { m_fontStartVertex = value; }
///
public virtual string Text

set { m_text = value; BuildText(); }
///
public virtual PointF Position

set { m_position = value; }
///
public virtual float FontPadding

set { m_fontPadding = value; }
///
public virtual int PanelID

set { m_panelID = value; }
///
public virtual bool HasFocus

set { m_hasFocus = value; }
The Control class determines what action to take based on user input. Probably the most important method is MouseHandler. MouseHandler takes in user input and determines what state the Control should be in based on the user input. For example, when the mouse is over the Control’s hotspot, the Control will enter the over state. The Control will return a different set of Quads depending on what state it is in. In order for the Control to send its data through the OnControl event, the mouse must be pressed and released over the Control’s hotspot. Most of the methods are empty, such as OnMouseDown, OnMouseOver, and OnMouseRelease, because they will be overridden in the inherited controls.
Each Control has its own hotspot(s). These hotspots are Rectangles that we can use to check when the user wants to interact with the control. By using the Rectangle.Contains method, we can determine if the mouse is over the Control’s hotspot(s) and take the appropriate action.
Since our GUI is displayed with Quads, and it will be updated frequently, we are going to render it with a dynamic VertexBuffer. If you recall from the dynamic buffer tutorial, dynamic vertex buffers are THE way to render geometry that is updated frequently. Also, since the text rendered in the GUI is created using my BitmapFont class, which prints text on Quads, we’ll be able to put the text in the same VertexBuffer.
The class that manages the VertexBuffer and all of the controls is GuiManager:
{
private List
private List
private List
private VertexBuffer m_vb = null;
private Texture m_texture = null;
private ImageInformation m_imageInfo;
private string m_textureFileName;
private string m_fntFile;
private string m_fontImage;
private string m_searchName;
private bool m_inCallback;
private bool m_dirtyBuffer;
private bool m_panelDragging;
private int m_openComboBox;
private int m_activeEditBox;
private BitmapFont m_bFont = null;
private ControlDelegate m_onControl;
public delegate void ControlDelegate( int controlID, object data );
private const int MaxVertices = 4096;
public GuiManager( string xmlStyleSheet, ControlDelegate onControl )

m_panelDragging = false;
m_dirtyBuffer = true;
m_quads = new List
m_fontQuads = new List
m_controlNodes = new List
m_controls = new List
m_onControl = onControl;
m_openComboBox = –1;
m_activeEditBox = –1;
ParseXML( xmlStyleSheet );
m_bFont = new BitmapFont( m_fntFile, m_fontImage );
///
/// XML file name
private void ParseXML( string xmlFile )

reader.WhitespaceHandling = WhitespaceHandling.None;
while ( reader.Read() )
{
{
{
for ( int i = 0; i < reader.AttributeCount; i++ )
{
if ( reader.Name == “ImageFile” )
{
m_imageInfo = Texture.GetImageInformationFromFile( m_textureFileName );
else if ( reader.Name == “FntFile” )
{
else if ( reader.Name == “FontImage” )
{
else if ( reader.Name == “Control” )
{
for ( int i = 0; i < reader.AttributeCount; i++ )
{
if ( reader.Name == “Name” )
{
// Read the Image elements of this Control
while ( reader.NodeType != XmlNodeType.EndElement )
{
if ( ( reader.NodeType == XmlNodeType.Element ) && ( reader.Name == “Image” ) )
{
for ( int i = 0; i < reader.AttributeCount; i++ )
{
if ( reader.Name == “Name” )
{
else if ( reader.Name == “X” )
{
else if ( reader.Name == “Y” )
{
else if ( reader.Name == “Width” )
{
else if ( reader.Name == “Height” )
{
else if ( reader.Name == “Color” )
{
controlNode.Images.Add( imageNode );
m_controlNodes.Add( controlNode );
///
/// Hex stream of form 0x00000000 or 00000000
///
private Color StringToColor( string hexString )

{
System.Globalization.NumberStyles style =
System.Globalization.NumberStyles.AllowHexSpecifier;
int alpha = int.Parse( hexString.Substring( 0, 2 ), style );
int red = int.Parse( hexString.Substring( 2, 2 ), style );
int green = int.Parse( hexString.Substring( 4, 2 ), style );
int blue = int.Parse( hexString.Substring( 6, 2 ), style );
return Color.FromArgb( alpha, red, green, blue );
///
/// D3D Device
public void OnCreateDevice( Device device )

if ( m_bFont != null )
{
///
public void OnDestroyDevice()

{
m_texture = null;
if ( m_bFont != null )
{
///
public void OnLostDevice()

{
m_vb = null;
if ( m_bFont != null )
{
///
/// D3D Device
public void OnResetDevice( Device device )

Usage.Dynamic | Usage.WriteOnly, TransformedColoredTextured.Format, Pool.Default, null );
BuildQuadList();
UpdateBuffer();
if ( m_bFont != null )
{
///
public void Clear()

m_fontQuads.Clear();
m_controlNodes.Clear();
m_controls.Clear();
///
/// Control id
/// Panel position
/// Panel size
public void CreatePanel( int id, PointF position, SizeF size )

m_searchName = “Panel”;
ControlNode node = m_controlNodes.Find( HasSearchName );
if ( node == null )
{
Panel p = new Panel( id, new RectangleF( position, size ), node, m_imageInfo );
AddControl( 0, p );
///
/// Control id
/// ID of Panel to associate Control with or 0 to make independant Control
/// Button position
/// Button size
/// Button text
/// Font size
/// Text color
/// Text alignment
public void CreateLabel( int id, int panelID, PointF position, SizeF size, string text, float fontSize, ColorValue textColor, BitmapFont.Align alignment )

Label c = new Label( id, new RectangleF( position, size ), text, fontSize, textColor, m_bFont, alignment );
AddControl( panelID, c );
///
/// Control id
/// ID of Panel to associate Control with or 0 to make independant Control
/// Button position
/// Button size
/// Button text
/// Font size
/// Text color
public void CreateButton( int id, int panelID, PointF position, SizeF size, string text, float fontSize, ColorValue textColor )

m_searchName = “Button”;
ControlNode node = m_controlNodes.Find( HasSearchName );
if ( node == null )
{
Button c = new Button( id, new RectangleF( position, size ), text, fontSize, textColor, m_bFont, node, m_imageInfo );
AddControl( panelID, c );
///
/// Control id
/// ID of Panel to associate Control with or 0 to make independant Control
/// Button position
/// Button size
/// Button text
/// Font size
/// Text color
/// Which side of the control to place the text on.
/// Whether the CheckBox is initially checked or not.
public void CreateCheckBox( int id, int panelID, PointF position, SizeF size, string text, float fontSize, ColorValue textColor, Control.TextAlign textAlignment, bool isChecked )

m_searchName = “CheckBox”;
ControlNode node = m_controlNodes.Find( HasSearchName );
if ( node == null )
{
CheckBox c = new CheckBox( id, new RectangleF( position, size ), text, fontSize, textColor, textAlignment, isChecked, m_bFont, node, m_imageInfo );
AddControl( panelID, c );
///
/// Control id
/// ID of Panel to associate Control with or 0 to make independant Control
/// Radio button groupID. Only one button per group may be selected at once.
/// Button position
/// Button size
/// Button text
/// Font size
/// Text color
/// Which side of the control to place the text on.
/// Whether the RadioButton is initially selected.
public void CreateRadioButton( int id, int panelID, int groupID, PointF position, SizeF size, string text, float fontSize, ColorValue textColor, Control.TextAlign textAlignment, bool isSelected )

m_searchName = “RadioButton”;
ControlNode node = m_controlNodes.Find( HasSearchName );
if ( node == null )
{
RadioButton c = new RadioButton( id, groupID, new RectangleF( position, size ), text, fontSize, textColor, textAlignment, isSelected, m_bFont, node, m_imageInfo );
// If RadioButton is selected, deselect RadioButtons of same groupID
if ( isSelected )
{
for ( int i = 0; i < m_controls.Count; i++ )
{
( ( m_controls[i] as RadioButton ).GroupID == c.GroupID ) )
{
AddControl( panelID, c );
///
/// Control id
/// ID of Panel to associate Control with or 0 to make independant Control
/// Slider position
/// Slider width
/// Minimum slider value;
/// Maximum slider value;
/// Current slider value;
/// Slider text
/// Font size
/// Text color
/// Which side of the control to place the text on.
public void CreateSlider( int id, int panelID, PointF position, float width, float min, float max, float current, string text, float fontSize, ColorValue textColor, Control.TextAlign textAlignment )

m_searchName = “Slider”;
ControlNode node = m_controlNodes.Find( HasSearchName );
if ( node == null )
{
Slider c = new Slider( id, position, width, min, max, current, text, fontSize, textColor, textAlignment, m_bFont, node, m_imageInfo );
AddControl( panelID, c );
///
/// Control id
/// ID of Panel to associate Control with or 0 to make independant Control
/// Whether single or multiple items can be selected
/// Button position
/// Button size
/// Button text
/// Font size
/// Text color
public void CreateListBox( int id, int panelID, bool singleItemSelect, PointF position, SizeF size, float fontSize, ColorValue textColor )

m_searchName = “ListBox”;
ControlNode node = m_controlNodes.Find( HasSearchName );
if ( node == null )
{
ListBox c = new ListBox( id, singleItemSelect, new RectangleF( position, size ), fontSize, textColor, m_bFont, node, m_imageInfo );
AddControl( panelID, c );
///
/// Control id
/// ID of Panel to associate Control with or 0 to make independant Control
/// true if only one item can be selected at a time, false to allow multiple selection
/// Button position
/// Button size
/// Button text
/// Font size
/// Text color
public void CreateComboBox( int id, int panelID, PointF position, SizeF size, float openHeight, float fontSize, ColorValue textColor )

m_searchName = “ComboBox”;
ControlNode node = m_controlNodes.Find( HasSearchName );
if ( node == null )
{
ComboBox c = new ComboBox( id, new RectangleF( position, size ), openHeight, fontSize, textColor, m_bFont, node, m_imageInfo );
AddControl( panelID, c );
///
/// Control id
/// ID of Panel to associate Control with or 0 to make independant Control
/// Whether single or multiple items can be selected
/// Button position
/// Button size
/// Button text
/// Font size
/// Max number of characters allowed in the edit box.
/// Text color
public void CreateEditBox( int id, int panelID, PointF position, SizeF size, string text, float fontSize, int maxLength, ColorValue textColor )

m_searchName = “EditBox”;
ControlNode node = m_controlNodes.Find( HasSearchName );
if ( node == null )
{
EditBox c = new EditBox( id, new RectangleF( position, size ),text, fontSize, maxLength, textColor, m_bFont, node, m_imageInfo );
AddControl( panelID, c );
///
/// ID of Control to add Listable item.
/// Displayed text of item.
/// Data of item.
public void AddListableItem( int controlID, string text, object data )

if ( !( c is ListBox ) )
{
( c as ListBox ).AddItem( text, data );
///
/// PanelID to add Control to, or 0 for independant Control
/// New Control
private void AddControl( int panelID, Control c )

{
// Find highest zDepth
if ( m_controls.Count > 0 )
{
else
{
else
{
c.OnControl += new ControlDelegate( m_onControl );
m_controls.Add( c );
SortControls();
BuildQuadList();
///
/// Panel’s ID
/// Control
private void AttachControlToPanel( int panelID, Control c )

Control p = this[panelID];
if ( !( p is Panel ) )
{
c.PanelID = panelID;
c.ZDepth = p.ZDepth;
( p as Panel ).NumControls++;
c.Position += new SizeF( p.Position.X, p.Position.Y );
///
/// Control ID
public void DeleteControl( int id )

{
{
BuildQuadList();
UpdateBuffer();
break;
///
private void BuildQuadList()

m_fontQuads.Clear();
for ( int i = 0; i < m_controls.Count; i++ )
{
m_controls[i].StartVertex = m_quads.Count * 6;
m_quads.AddRange( m_controls[i].Quads );
m_controls[i].FontStartVertex = m_quads.Count * 6;
m_quads.AddRange( m_controls[i].FontQuads );
///
private void UpdateBuffer()

{
GraphicsBuffer
m_vb.Lock
for ( int i = 0; i < m_quads.Count; i++ )
{
m_vb.Unlock();
m_dirtyBuffer = false;
///
/// D3D Device
public void Render( Device device )

{
UpdateBuffer();
// Set render states
device.SetRenderState( RenderStates.ZEnable, false );
device.SetRenderState( RenderStates.FillMode, (int)FillMode.Solid );
device.SetRenderState( RenderStates.ZBufferWriteEnable, false );
device.SetRenderState( RenderStates.FogEnable, false );
device.SetRenderState( RenderStates.AlphaTestEnable, false );
device.SetRenderState( RenderStates.AlphaBlendEnable, true );
device.SetRenderState( RenderStates.SourceBlend, (int)Blend.SourceAlpha );
device.SetRenderState( RenderStates.DestinationBlend, (int)Blend.InvSourceAlpha );
// Blend alphas
device.SetTextureState( 0, TextureStates.ColorArgument1, (int)TextureArgument.Texture );
device.SetTextureState( 0, TextureStates.AlphaArgument1, (int)TextureArgument.Texture );
device.SetTextureState( 0, TextureStates.AlphaArgument2, (int)TextureArgument.Diffuse );
device.SetTextureState( 0, TextureStates.AlphaOperation, (int)TextureOperation.Modulate );
// Set sampler states
device.SetSamplerState( 0, SamplerStates.MinFilter, (int)Filter.Linear );
device.SetSamplerState( 0, SamplerStates.MagFilter, (int)Filter.Linear );
device.SetSamplerState( 0, SamplerStates.MipFilter, (int)Filter.Linear );
// Render
device.VertexFormat = TransformedColoredTextured.Format;
device.SetTexture( 0, m_texture );
device.SetStreamSource( 0, m_vb, 0, TransformedColoredTextured.StrideSize );
foreach ( Control c in m_controls )
{
if ( c.Text != string.Empty && c.Text != “” )
{
device.DrawPrimitives( PrimitiveType.TriangleList, c.FontStartVertex, 2 * c.FontQuads.Count );
device.SetTexture( 0, m_texture );
///
/// List of pressed keys
/// Pressed character
/// Pressed key from Form used for repeatable keys
///
public bool KeyBoardHandler( List

for ( int i = m_controls.Count – 1; i >= 0; i– )
{
{
continue;
if ( m_controls[i].KeyboardHandler( pressedKeys, pressedChar, pressedKey ) )
{
UpdateBuffer();
return true;
return false;
///
/// Mouse position
/// Mouse buttons
/// Mouse wheel delta
///
public bool MouseHandler( Point cursor, bool[] buttons, float zDelta )

m_inCallback = true;
// Go front to back
for ( int i = m_controls.Count – 1; i >= 0; i– )
{
{
continue;
if ( m_panelDragging && !buttons[0] )
{
int numControls = m_controls.Count;
if ( ( !m_panelDragging || ( m_controls[i] is Panel ) ) && m_controls[i].MouseHandler( cursor, buttons, zDelta ) )
{
{
// the index will be missing. Return to prevent IndexOutOfBounds
return true;
if ( ( m_controls[i] is Panel ) && ( m_controls[i].State == Control.ControlState.Down ) )
{
if ( ( m_controls[i] is ComboBox ) && ( m_controls[i] as ComboBox ).IsOpen )
{
{
for ( int j = 0; j < m_controls.Count; j++ )
{
{
break;
m_openComboBox = m_controls[i].ID;
if ( ( m_controls[i] is EditBox ) && m_controls[i].HasFocus )
{
m_dirtyBuffer = true;
// For Controls in a Panel, mouse may have just been released from another
// Control’s focus, so we need to make sure we reset that Control’s state
if ( ( m_controls[i].State == Control.ControlState.Over ) && ( m_controls[i].PanelID != 0 )
&& !( m_controls[i] is Panel ) )
{
{
{
break;
// If new RadioButton was selected, deselect RadioButtons of same groupID
if ( ( m_controls[i] is RadioButton ) && ( ( m_controls[i] as RadioButton ).NeedToDelectOthers ) )
{
for ( int j = 0; j < m_controls.Count; j++ )
{
{
if ( ( m_controls[j] is RadioButton ) &&
( ( m_controls[j] as RadioButton ).GroupID == ( m_controls[i] as RadioButton ).GroupID ) )
{
// We may have moved from a back Control to a
// front control so reset over states
for ( int j = 0; j < i; j++ )
{
{
break;
if ( m_controls[i].State == Control.ControlState.Down )
{
// Mouse is down over another control so close any open ComboBox
if ( ( m_openComboBox != –1 ) && ( m_openComboBox != m_controls[i].ID ) )
{
for ( int j = 0; j < m_controls.Count; j++ )
{
{
m_openComboBox = –1;
break;
if ( ( m_activeEditBox != –1 ) && ( m_activeEditBox != i ) )
{
( m_controls[m_activeEditBox] as EditBox ).ReleaseFocus();
m_activeEditBox = –1;
Control c = m_controls[i];
// Adjust Z Depths
if ( m_controls[i].ZDepth != 1 )
{
{
// Move Panel and its Controls to the front
for ( int j = 0; j < m_controls.Count; j++ )
{
{
else if ( m_controls[j].ZDepth < m_controls[i].ZDepth )
{
m_controls[i].ZDepth = 1;
else if ( m_controls[i].PanelID != 0 )
{
// Move Panel and its Controls to the front
for ( int j = 0; j < m_controls.Count; j++ )
{
( m_controls[j].ID == m_controls[i].PanelID ) ) && ( i != j ) )
{
else if ( m_controls[j].ZDepth < m_controls[i].ZDepth )
{
m_controls[i].ZDepth = 1;
else
{
for ( int j = 0; j < m_controls.Count; j++ )
{
{
m_controls[i].ZDepth = 1;
// Resort the Controls
SortControls();
if ( ( c is Panel ) && ( c as Panel ).NumControls > 0 && !( c as Panel ).Locked )
{
for ( int j = m_controls.Count – 1; j >= 0; j– )
{
position.X += ( c as Panel ).XOffset;
position.Y += ( c as Panel ).YOffset;
m_controls[j].Position = position;
if ( m_controls[j].ID == c.ID )
{
break;
break;
else if ( buttons[0] )
{
if ( m_openComboBox != –1 )
{
// Close the open ComboBox
if ( !openComboBox.Contains( cursor ) )
{
m_openComboBox = –1;
if ( ( m_activeEditBox != –1 ) && ( m_activeEditBox != i ) )
{
( m_controls[m_activeEditBox] as EditBox ).ReleaseFocus();
m_activeEditBox = –1;
m_inCallback = false;
return result;
///
private void SortControls()

m_controls.Sort( sorter );
///
/// Control ID
/// New position
public void SetPosition( int id, PointF position )

if ( c == null )
{
if ( c is Panel && ( c as Panel ).NumControls > 0 )
{
float yOffset = c.Position.Y – position.Y;
// Move Panel
c.Position = position;
// Move Controls of Panel
for ( int i = 0; i < m_controls.Count; i++ )
{
{
newPosition.X -= xOffset;
newPosition.Y -= yOffset;
m_controls[i].Position = newPosition;
else
{
UpdateBuffer();
///
/// Control ID
/// true or false
///
/// disabled of enabled.
public void DisableControl( int id, bool disabled )

if ( c == null )
{
if ( c.PanelID > 0 )
{
if ( panel.Disabled && !disabled )
{
return;
c.Disabled = disabled;
if ( ( c is Panel ) && ( ( c as Panel ).NumControls > 0 ) )
{
for ( int j = 0; j < m_controls.Count; j++ )
{
{
BuildQuadList();
UpdateBuffer();
///
/// ID to check
private void CheckUniqueID( int id )

{
{
///
public Control this[int i]

{
{
{
return null;
///
public bool DirtyBuffer

set { m_dirtyBuffer = value; }
///
/// Current node.
///
private bool HasSearchName( ControlNode node )

GuiManager is the mothership of the GUI system. Starting at the top, we have the initialization methods. When GuiManager is instanciated, it parses through the XML file and saves all the data in a List of ControlNodes. Since the GuiManager uses resources like a Texture and VertexBuffer, it has the normal resource On*Device methods that we’ve seen in all the other tutorials.
Next up are all the Control creation methods. When a Control is created, GuiManager finds the ControlNode for the desired Control, creates the Control, and adds the Control to its List of Controls. Remember, a ControlNode holds all the Quad definitions read from the XML file. Controls must also have a unique ID in order to respond to it, so this is enforced through the CheckUniqueID method.
A new Control is added to the List in the AddControl method. If the Control is supposed to be attached to a Panel, the AddControl method calls AttachControlToPanel. Otherwise, AddControl simply adds the Control to the List.
The BuildQuadList and UpdateBuffer methods are used to fill up the dynamic VertexBuffer. The BuildQuadList method iterates through the List of Controls and builds a List of the Quads of all the Controls. Notice in this method that we also assign a start vertex to each Control. This start vertex is used as an offset when rendering the Controls. Since both the image Quads and the text Quads are all stored in the same VertexBuffer, we need to be able to switch Textures from the gui texture to the font texture in order to maintain the proper display. If we just rendered all the text last, all the text would be displayed on top of the graphics, even if the text belonged to a Control that was obscured by another Control. The UpdateBuffer method simply updates the VertexBuffer with the vertices found in the List of Quads generated by BuildQuadList. Since the List of Controls is sorted by z-depth, the List of Quads and thus the VertexBuffer will also be sorted by z-depth.
All the Controls have an assigned z-depth. This z-depth is used to determine the display order of the Controls. For example, if you click on a Control, it will be brought to the front of the screen all other Controls will be shifted back one space. The Controls with the lower z-depth (front Controls) should be rendered after Controls with higher z-depths (back Controls) in order for them to appear on top. To implement this z-sorting, the List of Controls must be sorted by z-depth. To do this, the SortControls method sorts the List with the use of the List search predicate shown below:
public class ControlSorter : IComparer
{
/// Control 1
/// Control 2
public int Compare( Control x, Control y )
{
{
if ( x.ZDepth == y.ZDepth )
{
if ( ( x is Panel ) && !( y is Panel ) )
{
else if ( ( y is Panel ) && !( x is Panel ) )
{
// For Controls on same Panel, sort bottom to top
if ( x.Position.Y < y.Position.Y )
{
if ( x.Position.Y > y.Position.Y )
{
return 0;
return –1;
The ControlSorter class sorts Controls by z-depth. When Controls are attached to a Panel, the Panel should be rendered first, followed by its Controls. When Controls are on the same Panel, they have the same z-depth, so they are sorted bottom to top. We want the higher Control rendered last because Controls like the ComboBox have a drop-down component and we want that component to be displayed on top.
Back in GuiManager, when the GUI is rendered, we must first set a few render states in order for the GUI to be displayed correctly. We need disable ZEnable and ZBufferWriteEnable since our Quads are made of transformed vertices. We also enable alpha blending in case the GUI has any transparency. We also enable some sampler state filters which increase the quality of the displayed images. Notice when the VertexBuffer is being rendered, we switch Textures if the Control has any text. This is where Control.StartVertex comes into play, which is assigned in BuildQuadList.
Another important method of GuiManager is the MouseHandler method. This method performs a lot of calculations to keep the GUI running smoothly. The MouseHandler method iterates through the List of Controls from front to back and processes any Control actions. The reason why we go front to back is that if a front Control is processed, we can break out of the loop to prevent us from processing any back Controls. The MouseHandler method contains a lot of logic that would take a long time to describe so I’ll just summarize some of the key points that we have to keep track of:
- When a Panel is being dragged, we also have to move all of its attached Controls. We also don’t want to process any other Control while a Panel is being dragged.
- Whenever we open a ComboBox and then click anywhere else, we should close the ComboBox.
- When a mouse is pressed over one Control and then released over another, we have to reset the down state of the first control to the normal state.
- When a RadioButton is selected, we have to deselect the other RadioButtons of the same group.
- When a Control is in the over state and is partially obscured by another Control (for example, a Panel is moved over half a Button), the back Control’s over state should be returned to normal when the mouse moves over the front Control.
- When a text EditBox has focus and we click anywhere else, release the focus from the EditBox.
- When we click on a Control that is not in front, we have to recalculate all the z-depths. We move the Control to the front. If we click on a Panel or a Control in a Panel, we have to move the Panel and all of its Controls to the front.
All of the other methods are pretty much self-explanatory or explained in the comments so I won’t go over them here. I’d say this tutorial is long enough, so in the next tutorial we’ll start implementing some of the Controls.