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 data, render all our windows, render the mouse cursor, then push the whole thing to the screen.
The Mouse
Popup : Source Listing 1
Pretty straightforward class definition. We've got three data pieces, m_button, m_absposition and m_relposition, abstracted by two functions, GetButton, GetAbsPosition(), and GetRelPosition(). 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.
Absolute vs. Relative Position, and DirectInput
Why am I using DirectInput? It's a matter of taste, actually. There are two ways to get mouse data in Windows – from DirectInput (the way I'm about to show), and via a Win32 API function called GetCursorPos(). The primary difference is that DirectInput will give you "relative" mouse information – that is, the cursor's current position relative to its last position – whereas GetCursorPos will give you the absolute screen coordinates. Absolute positioning is great for GUIs; relative positioning is good when the mouse is used without a cursor, i.e., to look around in a FPS game. You can however, calculate relative from absolute, and vice versa.
I used DirectInput. This decision was made for many reasons, all of which are outside the scope of this article (three words: multiple-mice systems). GetCursorPos() may be a better solution for you – if that's the case, it should be easy to flesh out the mouse class. DirectInput is more tricky (and more interesting), so the rest of this article will be in DirectInput.
Initializing DirectInput
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. Make sure you understand this distinction. Here's the code to initialize the master DirectInput interface pointer:
Popup : Source Listing 2
That code does three important things. First, it gets a valid DirectInput mouse device interface, and puts it in di_mouse. Next, it sets the data format and the cooperative level for the device, basically letting windows know that we want to query the device as a mouse, and that we don't want to take exclusive ownership of it. (Exclusive ownership means that we're the only app that can use the mouse – by specifying DISCL_NONEXCLUSIVE, we've told Windows that we're going to be sharing the mouse with other applications.)
Polling DirectInput for Mouse Status
Now let's flesh out CMouse::Refresh(), the function responsible for updating the CMouse's internal button state and position. Here's the code:
Popup : Source Listing 3
That code's doing a lot of things. First, it queries DirectInput for the new absolute mouse position (there's a while loop in there that will automatically retry the query if we've lost the interface). Next, it squirrels the absolute position data away in m_absposition, then it "applies" the relative position to come up with the new absolute position. The ConstrainPosToScreenSize() makes sure the point is within the bounds of the screen. Finally, it loops through and refreshes all the buttons.
|