Developing A GUI in C++ and DirectX
Developing A GUI in C++ and DirectX
Developing A GUI in C++ and DirectX
At first glance, it may seem like I’m reinventing the wheel; Windows
already comes with a very complex, very functional GUI. Unfortunately, while
the Windows GUI is great for office apps, quite frequently, it’s not suited for
many games. Games tend to want a more precise control over the GUI than
Windows can provide (for example, games may want to use alpha-blending to
implement partially transparent windows - easy if you’ve written your own GUI,
but next to impossible using the Windows GUI).
This article will walk you though how to create a GUI using C++ and
DirectX. The series is divided into several parts, each dealing with a specific
aspect of GUI programming:
Part I: The Basics, and the Mouse
Part II: Windows
Part III: Controls
Part IV: Resource Editors and Other Madness
NOTE: This document was originally four separate articles on
www.gamedev.net. I’ve concatenated all four into one for the XGDC, but they
remain otherwise unchanged. - Mason
Mason McCuskey
Part I: The Basics, and the Mouse
The Design
The Plan
The Implementation
Window Management Code
Coordinate Systems
Window Drawing Code
Window Messages
Wrapping It Up
Part III: Implementing Controls
GUI Controls We’ll Need
Breaking It Down: Complex Controls As Combinations of Simple
Ones
The Static Controls
Pushbutton Controls
Carets and The Textbox Control
Progress Bars
Sliders and Scrollbars
Saving Windows
Loading Windows
Resource Editors
Subclassing
Speeding up GUI Rendering
Conclusion
Mason McCuskey
Developing a GUI in C++ and DirectX
Part I: The Basics, and the Mouse
Before I get started, I want to throw out a disclaimer: I’m going to outline
the approach I used when I created the GUI system for my upcoming title,
Quaternion. Treat this text as one solution to a very intricate problem, nothing
more. I’m not saying that this way of making a GUI is the fastest or easiest way;
I’m simply outlining a solution that worked for me. Also, this text is not
complete. Like one of those Bob-Vila TV episodes, it skips over the easy stuff
and concentrates on the interesting. There is no attached source file; there are
code snippets in the text, and that’s it. In that code, I’ve stripped out a lot of
layers of indirection that aren’t relevant to what I’m trying to show (i.e. the
wrappers that you’d probably have for your DirectX functions, non-relevant
initialization and cleanup code, etc). Also, beware of bugs - I’ve done lots of bug
checking, but I’m only human. If you find a bug, please let me know about it by
emailing mason@spin-studios.com.
I’m making several assumptions about your knowledge. I’m assuming you
know the basics of how event-driven programming works (message queues, etc),
and I’m assuming you have a strong grasp of PDL (the commenting language - if
you don’t know what this is, read Code Complete, by Steve McConnell), and
C++. I used C++ to implement my GUI system, because I’m a card-carrying
member of the C++ fan club, and because the OOP of C++ work great for
implementing window and control types. Shameless plug for the C++ language:
Note the power of OOP in this solution, and ask yourself if you could do the
same thing as easily in C.
Let’s start by defining our scope. It’s important to realize up front that we’re
not remaking Windows 95, we’re just trying to get a simple GUI up for a game,
so we don’t have to implement every single control and GUI construct. We only
need a few parts for this simple GUI: a mouse pointer, a generic window, and
some dialog controls to place within that window. We’re also going to need a
resource editor, a program that will allow us to design dialogs by graphically
dropping controls at various places.
Start with the basics - The Rendering Loop
I’m going to start at the top, by defining a function that will calculate and
draw one frame of our GUI system. Let’s call this function RenderGUI(). In
PDL, RenderGUI does something like this:
void CApplication::RenderGUI(void) {
// get position and button status of mouse cursor
// calculate mouse cursor’s effects on windows / send messages
// render all windows
// render mouse
// flip to screen
}
Pretty straightforward for now. Basically, we grab the new position and
status of the mouse cursor, calculate any changes that are caused by the new
position, render all our windows, render the mouse cursor, then push the whole
thing to the screen.
The Mouse
Now that we’ve got a main function, we’re going to create a mouse class.
This mouse class will initialize the rodent, and will be responsible for querying
its position and storing the results. Here’s the definition:
class CMouse {
public:
CMouse(); // boring
~CMouse(); // boring
private:
LPDIRECTINPUTDEVICE m_mousedev;
char m_button[NUMMOUSEBUTTONS]; // state of buttons
CPoint m_position; // actual screen position
};
Pretty straightforward class definition. We’ve got two data pieces, m_button
and m_position, abstracted by two functions, GetButton and GetPosition(). Then
we’ve got Init and Refresh functions, which initialize the mouse and Refresh its
button and position information. The m_mousedev is an interface to our mouse
device; we get this interface during Init(), and use it in Refresh to communicate
with DirectInput.
So… before we go any further with CMouse, let’s look at the code to
initialize DirectInput. Note that this code doesn’t belong in our CMouse::Init()
routine; the DirectInput pointer is used by the entire game, not just the mouse, so
the code that inits DirectInput should go in your main init function - the same
time you init DirectDraw, DirectSound, etc. A DirectInput interface pointer is
different than a DirectInput device pointer; you use DirectInput pointers to get
DirectInputDevice pointers.
Here’s the code to initialize the master DirectInput interface pointer:
LPDIRECTINPUT di = NULL;
hr = DirectInputCreate(hinst, DIRECTINPUT_VERSION, &di, NULL);
if (FAILED(hr)) {
// error processing
}
That will put a valid DirectInput interface pointer into di. (Don’t forget to
Release() it when your game ends!)
Now that we’ve got a DirectInput interface, let’s begin fleshing out our
CMouse by implementing CMouse::Init().
bool CMouse::Init(LPDIRECTINPUT di) {
// Obtain an interface to the system mouse device.
hr = di-›CreateDevice(GUID_SysMouse,
(LPDIRECTINPUTDEVICE*)&di_mouse, NULL);
if (FAILED(hr)) {/* handle errors! */}
if (!m_di) return;
while (!done) {
hr = m_mousedev-›GetDeviceState(sizeof(DIMOUSESTATE), &dims);
if (FAILED(hr)) {
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) {
// device lost… reacquire
hr = m_mousedev-›Acquire();
if (FAILED(hr)) {
// houston, we have a problem… clear & bail
clear();
done=1;
}
} else {
// it’s some other error - clear and bail!
m_mousedev.clear();
done = 1;
}
} else // read mouse successfully!
{
done = 1;
}
} //while !done
m_position.z += dims.lZ;
if (m_vga-›isfullscreen()) {
// we're in fullscreen, so this is easy… just copy the coords
m_position.x += dims.lX;
m_position.y += dims.lY;
} else {
// we're in window mode, so this is not-so-easy…
// grab the relative mouse position
GetCursorPos(&p);
ScreenToClient((HWND)m_vga-›gethwnd(), &p);
if (p.x ‹ 0 || p.y ‹ 0) {
// the cursor is out of our window! "hide" it!
m_mousedev.setposition(KJM_AXES_X,
m_vga-›getscreendims().getwidth());
m_mousedev.setposition(KJM_AXES_Y,
m_vga-›getscreendims().getheight());
} else {
m_mousedev.setposition(KJM_AXES_X, p.x);
m_mousedev.setposition(KJM_AXES_Y, p.y);
}
}
m_mousedev.constrainpos(KJM_AXES_X, 0,
m_vga-›getscreendims().getwidth());
m_mousedev.constrainpos(KJM_AXES_Y, 0,
m_vga-›getscreendims().getheight());
/////////////
// section I: window management controls
/////////////
/////////////
// Section II: coordinates
/////////////
/////////////
// Section III: Drawing Code
/////////////
/////////////
// Messaging stuff to be discussed in later Parts
/////////////
int calcall(void);
protected:
gui_window *m_pParent;
uti_pointerarray m_subwins;
uti_rectangle m_position;
// window caption
uti_string m_caption;
};
First of all, notice the virtual destructor on the window class. This may not
seem like it’s needed just yet, but we’ll eventually be deriving controls from this
class, so it’s important that it have a virtual destructor.
As you peruse the functions we’ll be talking about, keep in mind that
recursion is everywhere. For example, our game will be drawing the entire GUI
system by making a call to the renderall() method of the root window, which will
in turn call the renderall() methods of its subwindows, which will call renderall()
for their subwindows, and so on. Most of the functions follow this recursive
pattern.
The whole GUI system will be contained within one global static variable -
the root window. To be on the safe side, I encapsulated this variable within a
global GetDesktop() function.
Also, notice that the class definition is rife with virtual keywords. This is
where C++’s polymorphism is working for us. Need to change how certain types
of windows (or controls - say, buttons) deal with a “left mouse button has just
been pushed down” event? Simple, derive a class from the base window and
override its wm_lbuttondown() method. The system will automatically call the
derived class’s method where appropriate; behold the power of C++.
Now that we’ve got the header, let’s start filling in some functions, starting
with the Window Management code…
Window Management Code
/**************************************************************************
addwindow: adds a window to this window's subwin array
***************************************************************************
int gui_window::addwindow(gui_window *w) {
if (!w) return(-1);
// only add it if it isn't already in our window list.
if (m_subwins.find(w) == -1) m_subwins.add(w);
w-›setparent(this);
return(0);
}
/**************************************************************************
removewindow: removes a window from this window's subwin array
***************************************************************************
int gui_window::removewindow(gui_window *w) {
w-›setparent(NULL);
return (m_subwins.findandremove(w));
}
/**************************************************************************
bringtotop: bring this window to the top of the z-order. the
top of the
z-order is the HIGHEST index in the subwin array.
***************************************************************************
void gui_window::bringtotop(void) {
if (m_parent) {
// we gotta save the old parent so we know who to add back to
gui_window *p = m_parent;
p-›removewindow(this);
p-›addwindow(this);
}
}
/**************************************************************************
isactive: returns true if this window is the active one (the
one with input focus).
***************************************************************************
bool gui_window::isactive(void) {
if (!m_parent) return(1);
if (!m_parent-›isactive()) return(0);
return(this ==
m_parent-›m_subwins.getat(m_parent-›m_subwins.getsize()-1));
}
This set of functions deals with what I call window management; adding
windows, deleting them, showing/hiding them, and changing their z-order. All of
these are really just array operations; this is where your array class gets a
workout.
The only thing interesting in the add / remove window functions is the
question, “who is responsible for the window pointer?” This is always a good
question to ask yourself in C++. Addwindow and removewindow both take
pointers to a window class. This means that to create a new window, your code
news it, then passes the pointer to the parent (desktop) window through
addwindow(). So who’s responsible for deleting the pointer you newed?
My answer was “the GUI doesn’t own the window pointers; the game itself
is responsible for adding them.” This is consistent with the C++ rule of thumb
that says “those who new things also delete them.”
The alternative to the method I chose was to say “the parent window is
responsible for the pointers of all his child windows.” That would mean that to
prevent memory leaks, each window must, in it’s (virtual) destructor (remember,
there’s derived classes), loop through its m_subwindows array and delete all of
the windows contained within it.
If you decide to implement a GUI-owns-pointer system, be aware of an
important trade-off - all of your windows must be dynamically allocated
(newed). A quick way to crash a system like that is to pass in the address of a
variable on the stack, i.e. say something like “addwindow(&mywindow)”, where
mywindow is declared as a local variable on the stack. Things will work until
mywindow goes out of scope, or until the destructor for the parent window is
called, whereupon it’ll try to delete that address and all hell will break loose. The
lesson is “be extra careful with pointers.”
That’s the main reason behind why I decided that my GUI would not own
the window pointer. If you’re passing a lot of complex window classes into and
out of your GUI (say, for example, you’re populating a tabbed property sheet),
you might prefer a system where the GUI doesn’t keep track of the pointers, and
where remove simply means “the pointer is now in my control; remove it from
your array, but don’t delete it.” This would also allow you to (carefully) use
addresses of local variables on the stack, provided you made sure that you
removewindow()’ed them before they went out of scope.
Moving on… Showing and hiding windows is accompished through a
boolean variable. Showwindow() and hidewindow() simply set or clear this
variable; the window drawing and message processing functions check this “is
window shown” flag before they do anything. Pretty easy stuff.
Z-ordering was also fairly easy. For those unfamiliar with the term, z-
ordering refers to the “stacking” of windows on top of each other. At first
thought, you may decide to implement z-ordering similar to how DirectDraw
does it for overlays - you might decide to give each window an integer that
describes its absolute z-order position, their place on the z axis - say, maybe, 0 is
the top of the screen, and negative -1000 is furthest back. I thought a bit about
implementing this type of z-ordering, but decided against it - absolute z-order
positions don’t concern me; I care more about relative z-order positions. That is,
I don’t really need to know “how far back” one window is from another; I
simply need to know whether a given window is behind another, or in front of it.
So, I decided to implement z-order like this: The window with the highest
index in the array, m_subwins, would be the window “on top.” The window at
[size-1] would be directly under it, followed by [size-2], etc. The window at
position [0] would be on the very bottom. In this way, processing z-ordering
became very easy. Also, killing two birds with one stone, I deemed that the
topmost window would always be the active window, or more technically, the
window with input focus. Although this restricted my GUI from making “always
on top” windows (for example: Windows NT’s task manager is always on top of
all other windows, regardless of who has the input focus), I felt it was worth it to
keep the code as simple as possible.
Also, I paid a small price for using array indices as z-orders was the array
shuffle that occurs when I tell a given window to move to the top of the z-order.
Say I tell window #2 to move to the top of a 50 window list; I’ve got to shift 48
windows down a slot to accommodate window #2’s new position at the end. The
good news is that moving a window to top of the z-order isn’t really a time-
critical function, and even if it were, there’s dozens of good, quick ways to
juggle array items like this - linked lists spring to mind.
Check out the cheap trick I used in the bringtotop() function. Since I know
that the window doesn’t own the pointers, I can just clobber the window and
then immediate re-add him, effectively repositioning him at the top of the array. I
did this solely because my pointer class, uti_pointerarray, already had code that
would delete an element and slide all higher elements backwards one slot.
So that’s window management. Now, onto the joy of coordinate systems…
Coordinate Systems
/**************************************************************************
virtual coordinate system to graphics card resolution
converters
***************************************************************************
const double GUI_SCALEX = 10000.0;
const double GUI_SCALEY = 10000.0;
/**************************************************************************
findchildatcoord: returns the top-most child window at coord
(x,y); recursive.
***************************************************************************
gui_window *gui_window::findchildatcoord(coord x, coord y, int
flags) {
for (int q = m_subwins.getsize()-1; q ›= 0; q--) {
gui_window *ww = (gui_window *)m_subwins.getat(q);
if (ww) {
gui_window *found = ww-›findchildatcoord(x-m_position.getx1(),
y-m_position.gety1(), flags);
if (found) return(found);
}
}
// render mouse
m_mouse.Render();
// flip to screen
GetBackBuffer()-›Flip();
}
Hopefully, seeing this code now will show you how things are starting to
come together.
Part III: Implementing Controls
This section doesn’t have as much code as the others - this is mainly
because we programmers are fairly picky when it comes to the appearance of our
GUI. We like to code things up so that our buttons, our textboxes, and our GUI
appear unique, and fit our own aesthetic tastes. Consequently, everyone’s control
code will be slightly (or maybe drastically different), and it wouldn’t make sense
to include my particular drawing code. Besides, writing code to draw all the GUI
elements is fun, in fact, in my opinion, it’s the most fun you can have
implementing a GUI. Go wild.
That being said, let’s start by determining which GUI controls we need.
GUI Controls We’ll Need
I didn’t want to spend a lot of time implementing controls for my game
GUI; I wanted to stick with the smallest set of controls that I could. So, I came
up with a list of controls that I consider the minimum set for game GUIs…
· Static Text, Icon, and Group Boxes - vital. These controls label and group
the other controls in a dialog box. The static control is crucial; the frame control
we could probably live without, but it’s fairly simple, and in some cases can go a
long way towards making a dialog box easy to navigate, so I’m including it. Icon
controls should be simple, but should be able to animate, providing cool
background animations in our dialogs and menus (ala Theif: The Dark Project).
· Buttons and Checkboxes - vital. Weird button types (flat buttons,
pushbutton-style radio buttons) we can do without, but most games can’t live
without a basic button and checkbox.
· List control - important. I’ve found list controls, especially multi-column
list controls, indispensable when creating game GUIs. They’re used
everywhere. You’re going to want a very intelligent, heavyweight list control, as
good or better than the Windows List Control. For me, the list control was the
most difficult control to implement.
· Sliders and scrollbars - Important. Famous for controlling sound and
music volume. The bad news is that we’ll probably need horizontal and vertical
flavors of these guys; the good news is that they’re so similar you can implement
them very easily.
· Textboxes - Vital. You have to be able to enter your mega-3l33t, super-
kewl player handle somewhere, right?
· Progress Bars - Essential for displaying hit points, “I’m almost done
loading!”, etc.
Noticeably absent from this list are the spin button controls (which aren’t
crucial, and irritate me to no end anyway), radio buttons (we can get by with a
single selection listbox instead), and the drop-down combo box (again, we can
just use a list box), and tree control. By making the listbox control smart enough
to indent certain items, we can incorporate the functionality of the tree control
Tab controls aren’t included simply because my game doesn’t have enough
of a GUI to warrant them, though your needs may differ.
Even with all the omissions, the “minimum” list might seem daunting at
first, but we can simplify it quite a bit…
Breaking It Down: Complex Controls As
Combinations of Simple Ones
The list becomes much more manageable when we realize that the more
complex controls are just clever combinations of other, more simple controls.
For example, a scrollbar is basically just two buttons and a slider control. A
checkbox is a static control and two buttons (one “off” button, and one “on”
button). A plain old button could be implemented using three static icon controls
(just show/hide the appropriate ones to get the button to “press”), so that you can
reuse your drawing code. If you were really strapped for time, you could even
implement a progress bar as a slider that’s moved by the computer, though I
prefer having a separate control for this.
There are, however, disadvantages to this - namely, your GUI controls are
going to take up more system resources than they really need. Think about it -
each control is a window. Let’s say you’ve gone with the reuse philosophy, and
have created a button control that’s really just three different statics. That’s three
windows per button. Now, you build a scrollbar control, using two button
controls. That’s six windows per scrollbar control. Build a List control using
horizontal and vertical scrollbars, and you’re sitting at twelve windows per list.
It adds up quickly.
So it’s really just another example of the classic tradeoff between “how fast
can I develop it” and “how little resources can I get it to use?” If you need a very
high performance, no-waste GUI, implement each control from the ground up. If
you would instead a quick implementation, and don’t mind the performance hit,
you might choose to implement your controls so that the only control that would
actually draw to the screen would be the static, and all other controls would be
made up of combinations of statics.
When building my GUI, I tried to create a good balance between these two
extremes.
Now, let’s dive into the actual implementation of each control, starting with
everyone’s favorite, the static label.
The Static Controls
There are three kinds of static controls we’ll be looking at: static text
controls, static icon controls, and frame controls. All three of these controls are
very easy, because they take no messages - all they do is draw themselves at
certain positions.
Static text controls are by far the easiest control you’ll ever implement - just
draw your window’s caption at the upper-left of your window, and you’re done.
If you’re especially through, you might want to add code to justify your text a
certain way - for example, to center your text in your client rect, you might
employ the classic centering algorithm - take the width of your window, subtract
the width of the text you’re going to draw, and divide by two, telling you how
many pixels “in” (that is, how many pixels right from the left window edge) to
start drawing.
Static icon controls are a little tougher. Actually, the term “static icon
control” is a bit of a misnomer, given that we want our icon controls to be able to
animate. Even so, implementation of these icon controls isn’t tough, provided
you’ve got a solid sprite library to handle all the details of implementing
animation: checking the millisecond delta between this frame and the one that’s
on the screen now, using this delta to determine how many frames your sprites
should advance by, etc.
Icon controls only become painful to implement if you’re not redrawing
your entire GUI system every frame. In this case, you’ve somehow got to deal
with clipping the icon control, so that even though it’s being drawn every frame,
it doesn’t accidentally overwrite pixels belonging to a window that’s sitting on
top of it (but wasn’t changed, so therefore wasn’t drawn). I didn’t implement this
- my GUI gets redrawn every frame - but if you’re faced with this problem, you
might want to try setting up a clip list for each icon, using it to draw the icon,
and re-evaluating it when any window is moved, closed, or opened. This may or
may not be a viable solution - I just dreamt it up while writing this - but it seems
to be at least a good jumping off point.
Frame controls are also pretty straightforward. I implemented my frame
control by drawing a border around m_position, then drawing the window
caption at about position (5,5), in client coordinates (that is, about five pixels
right and five pixels down from the upper-left of the frame control), but you may
decide you want something a little fancier.
The one complex thing you might want to do for your static controls is to
change the behavior of the findwindow function slightly so that it “skips” all
windows that are static controls. That way, if a static text control is sitting on top
of a pushbutton, the user will be able to push the button “through” the static
control.
Speaking of, let’s now take a look at how to implement that button.
Pushbutton Controls
Pushbuttons are only slightly more difficult than static controls. Your
pushbutton control needs to keep track of whether it’s “pressed” (pushed down)
or “unpressed.” It does this by implementing two virtual functions,
wm_mousedown() and wm_mouseup(), which your main calcall() function
needs to call when appropriate.
Basically, in wm_mousedown(), you set a boolean variable, which I call the
“depressed flag,” to true, and in wm_mouseup(), you set it back to false. Then,
in your drawing code, if the depressed flag is set, you draw the button “pressed,”
otherwise, you draw it “unpressed.”
Next, add an additional condition - say, “only draw the button depressed if
the depressed flag is set, AND, the mouse cursor is within my client coordinates,
otherwise, set the depressed flag back to false.” This will give you buttons that
“pop out” if you move your mouse cursor off of them, and is very important for
accurately determining when a button is clicked.
In normal GUIs, when a button is clicked, it fires off an event to its parent
window, which then does whatever the button represents - i.e., clicking the close
button will close the window, clicking the save button will save the file,
whatever. My GUI considers a button clicked if and only if, inside
wm_mouseup(), the depressed flag is set. The only way the depressed flag can
still be set inside mouseup() is if the user both pressed and released the mouse
button while the pointer was inside the button. This allows users to “bail out” at
the last minute by holding the button down and dragging the mouse pointer
somewhere outside of the button to cancel the button click, just like in any other
GUI system.
That’s pushbuttons. Now, let’s take a peek at text boxes.
private:
uti_string m_name;
int m_width;
};
private:
int m_isselected;
int m_indent; // # of pixels to indent this item
UL m_itemdata;
uti_pointerarray m_coltext;
};
int getnumitems(void);
int getnumcols(void);
void deselectall(void);
void selectitem(int item);
void selecttoggleitem(int item);
private:
int m_numdispobjsy;
int m_vertgutterwidth; // # of pixels between items vertically
gui_fancyscrollbar_horz m_hscroll;
gui_fancyscrollbar_vert m_vscroll;