Advanced DirectInput Mouse Handling

Most of the things that you will see here may look familar. That's because most of what I learned about using the mouse in DirectX7 came from Lucky's DirectInput Tutorial. I just suped it up a bit. In this tutorial you will see how to 'Clip' the cursor when it's halfway off the screen, how to add 'Hot Spots' to your cursors, as well as how to put a cursor on your DX surface. It may help you to read Lucky's DirectInput Tutorial first, if you haven't already, because I don't explain what you can learn there.

You will notice that when you move your mouse around in Windows (go ahead try it) you will see that you can move it all the way to the right and bottom until only 1 pixel is showing (depending on your cursor). If you try to draw your cursor in DX with any part of the cursor (even the invisible parts) off the screen/surface DX will seem to ignore that line. That is because you can't draw anything that falls off the surface, it just won't happen. So we need to clip the areas that we aren't going to see because they are off screen.

Actually, in Windows, your cursor stops moving right and/or down if doing so will hide your cursor's 'Hot Spot'. A Hot Spot is the actual pixel on your cursor that Windows cares about. Your cursor isn't actually 32x32 pixels to Windows, it's only a single pixel. There is just a drawing over this pixel so you can see it better, or just for looks. If your cursor was 32x32 pixels then you would be able to click on more than one button at the same time, if the buttons were close together. So, in other words, a Hot Spot is the single most important pixel of your cursor, usually the tip of an arrow or pen, or the center of a cross-hair.

Ok, let's get to it.

You will need these Variables and Declarations, if you don't have them already.

'Used for simple collision detection and clipping
Declare Function IntersectRect Lib "user32" (lpDestRect As RECT, lpSrc1Rect As RECT, lpSrc2Rect As RECT) As Long

'The DirectX7 object, DUH!
Global DXMouse As New DirectX7
'The DirectInput object! DOUBLE-DUH!
Global DXMouseInput As DirectInput
'Input Device object we'll use for the mouse
Global DXMouseInputDev As DirectInputDevice
'Mouse state type
Global DXMouseState As DIMOUSESTATE


'Speed of mouse cursor movement
Global Const MOUSE_SPEED = 1
'Rectange for mouse cursor
Global Mouse_Rect As RECT
'X Coordinate of the mouse cursor
Global DXMouseX As Integer
'Y Coordinate of the mouse cursor
Global DXMouseY As Integer
'Is the left mouse button being pressed?
Global DXLeftMouseButton As Boolean
'Is the right mouse button being pressed?
Global DXRightMouseButton As Boolean

'On Mouse Hot Spot Rectangle
Global HotSpot_Rect As RECT
'Mouse 'Hot Spot' X value (on the cursor)
Global DXMouseHotX As Integer
'Mouse 'Hot Spot' Y value (on the cursor)
Global DXMouseHotY
'This will tell us if we should show the mouse
Global DXMouseVisible As Boolean

Yes, those are Lucky's remarks that came from his Tutorial Source and No, I'm not trying to plagiarize Lucky's words. Like I said at the top: "...most of what I learned about using the mouse in DirectX7 came from Lucky's DirectInput Tutorial." I did however change some of the variable names because his names don't quite make sense to me.

Now, the 4 things new here are HotSpot_Rect, DXMouseHotX, DXMouseHotY, and DXMouseVisible.

HotSpot_Rect is our 'ever so changing' RECT that lets us know: Where the Hot Spot is on the screen. It will only be 1 pixel big.

DXMouseHotX and DXMouseHotY is the X and Y location of the Hot Spot on the cursor. The top left most corner is considered as (1, 1).

DXMouseVisible is just a Boolean variable to tell us if we are going to draw the cursor on the screen, because sometimes we don't want to.

Now comes the Initialization Sub. Again this is the exact same thing in Lucky's Source, but I changed the variable names so we can all understand them.

Sub Initialize()
'If we can't initialize properly, trap the error
On Error Resume Next

'Create the direct input object
Set DXMouseInput = DXMouse.DirectInputCreate()

'Aquire the mouse as the diMouse device
Set DXMouseInputDev = DXMouseInput.CreateDevice("GUID_SysMouse")

'Get mouse input exclusively, but only when in foreground mode
DXMouseInputDev.SetCommonDataFormat DIFORMAT_MOUSE
DXMouseInputDev.SetCooperativeLevel frmMain.hWnd, DISCL_FOREGROUND Or DISCL_EXCLUSIVE
DXMouseInputDev.Acquire

'Initialize the mouse variables
DXMouseX = 0
DXMouseY = 0
DXLeftMouseButton = False
DXRightMouseButton = False

End Sub

I won't explain anything there. You'll have to check out Lucky's Tutorial and Source for that.
Now for the some new stuff, including some old stuff.

Sub DXMouseRefresh()
'Refresh the mouse variables
'Get the current state of the mouse
DXMouseInputDev.GetDeviceStateMouse DXMouseState

'If we've been forced to unaquire, try to reaquire
If Err.Number <> 0 Then DXMouseInputDev.Acquire

'If this fails, exit sub
If Err.Number <> 0 Then Exit Sub
On Error GoTo 0

'Adjust the mouse cursor x coordinate with Hot Spot support
DXMouseX = DXMouseX + DXMouseState.x * MOUSE_SPEED
If DXMouseX < 0 Then DXMouseX = 0
If DXMouseX > 640 - DXMouseHotX Then DXMouseX = 640 - DXMouseHotX

'Adjust the mouse cursor y coordinate with Hot Spot support
DXMouseY = DXMouseY + DXMouseState.y * MOUSE_SPEED
If DXMouseY < 0 Then DXMouseY = 0
If DXMouseY > 480 - DXMouseHotY Then DXMouseY = 480 - DXMouseHotY

'Check the left mouse button state
If DXMouseState.buttons(0) <> 0 Then DXLeftMouseButton = True
If DXMouseState.buttons(0) = 0 Then DXLeftMouseButton = False

'Check the right mouse button state
If DXMouseState.buttons(1) <> 0 Then DXRightMouseButton = True
If DXMouseState.buttons(1) = 0 Then DXRightMouseButton = False

'If the mouse image falls off the surface it will dissapear,
'so here we trim the mouse when it moves close to the edge.
If DXMouseX <= 608 Then
Let Mouse_Rect.Right = 32
Else
Let Mouse_Rect.Right = 640 - DXMouseX
End If

If DXMouseY <= 448 Then
Let Mouse_Rect.Bottom = 32
Else
Let Mouse_Rect.Bottom = 480 - DXMouseY
End If

HotSpot_Rect.Left = DXMouseX
HotSpot_Rect.Top = DXMouseY
HotSpot_Rect.Right = DXMouseX + 1
HotSpot_Rect.Bottom = DXMouseY + 1

'If mouse is visible then show it.
If DXMouseVisible = True Then
DrawMouse
End If

End Sub

Don't forget to set the DXMouseHotX, DXMouseHotY, and DXMouseVisible variables before you call that sub.

Wow, that's a lot of code. You will notice most of the top half resembles Lucky's RefreshMouseState Sub. That part has only been changed to support screen sizes and Hot Spots. In Lucky's Tutorial Source you can't move your drawing cursor off the screen (no offence Lucky). By subtracting DXMouseHotX from the screen width and DXMouseHotY from the screen height you can allow parts of the cursor to be located off the screen. The cursor will stop moving right when the DXMouseHotX is against the right side of the screen and the cursor will stop moving down when DXMouseHotY is against the bottom of the screen.

But that's not all. We still have to 'Clip' the cursor so that it won't totally disappear when a part of it falls off the screen. That's what the If...Then...Else... Statements are for. They will change the Mouse_Rect.Right and Mouse_Rect.Bottom to the right size so that if the cursor falls off the screen it will be resized to rest against that area.

You should have noticed the numbers 640 and 480 as well as the number 32. 640 and 480 are the screen measurements (in pixels) used in my example and you may need to change them. 32 is the size of my cursor (standard size) depending on your cursor size you may need to change that also. You may also notice, in the If...Then...Else... Statements, the numbers 608 and 448 when checking the mouse for overlap. These numbers are figured out by subtracting your cursor width from the screen width and your cursor height from the screen height. Also, if your cursor isn't at the top left-hand corner of your source surface then you'll need to modify those lines to locate it.

After the cursor is clipped we move HotSpot_Rect to its right position by changing the Left, Top, Right, and Bottom.

The last couple of lines checks to see if you want the mouse to be visible, and if so; draw it.

This is a little Sub and all it does is uses BltFast to draw the mouse on the screen using the correct 'clipped' Mouse_Rect size. I won't go into any detail about how BltFast is done, you'll just have to check out Lucky's Tutorials on that.

Public Sub DrawMouse()
SurfBack.BltFast DXMouseX, DXMouseY, SurfMain, Mouse_Rect, DDBLTFAST_SRCCOLORKEY Or DDBLTFAST_WAIT
End Sub

Oh, SurfBack is my back buffer and SurfMain is the unseen surface that holds my mouse cursor image.

With all said and done, You'll have to read Lucky's Basic Collision Detection Tutorial to learn how to use the HotSpot_Rect variable and his DirectInput Tutorial to learn what to do with the DXMouseState or mdiMState as he would call it.

Special thanks to Lucky for his great DirectX Tutorials.

Happy Programming,



GBeebe