Game Development Community

T3D Custom Hardware Cursors

by Joshua Leeming · in Torque 3D Professional · 10/04/2011 (8:21 pm) · 3 replies

I've been working on implementing hardware cursors into T3D with *some* success, and I'll share what I've got so far.

What I want to be able to do is completely replace the default arrow cursor with a custom hardware cursor, loaded from a file, and push and pop other custom cursors as required...

Much thanks goes to this resource by Dave Young TGEA 1.7.1 Custom Hardware Cursors.

First off, my changes:

Add to guiCanvas.cpp

void GuiCanvas::pushCustomCursor( const char* fileName )
{  
   GuiCanvas *pRoot = this;  
   if( !pRoot )  
      return;  

   PlatformWindow *pWindow = pRoot->getPlatformWindow();  
   AssertFatal(pWindow != NULL,"GuiControl without owning platform window!  This should not be possible.");  
   PlatformCursorController *pController = pWindow->getCursorController();  
   AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!"); 

   pController->popCursor(); //replace the "current" cursor with the custom one
   pController->pushCursor((UTF8 *)fileName);
}

ConsoleMethod( GuiCanvas, pushCustomCursor, void, 3, 3, "(filename)")  
{  
   object->pushCustomCursor(argv[2]);  
}

GuiCanvas.h

S32 mCursorChanged;

   virtual void pushCustomCursor( const char* fileName ); //added

Now I've also added:

Win32CursorController.cpp

void Win32CursorController::setCursorShape( const UTF8 *fileName, bool reload )

...

mCustomCursor = true; //this line

and

void Win32CursorController::setCursorShape(U32 cursorID)

...
mCustomCursor = false; //this line

along with PlatformCursorController.h

private:
   bool mCustomCursor;
public:
   virtual bool isCustomCursor() { return mCustomCursor; }

which allows me to do checks like this in guiControl and other places where getCursor is called/overriden.

if(!pController->isCustomCursor())
		pController->popCursor();

Calling...

Canvas.pushCustomCursor("art/gui/cursors/hand.ani");

...now successfully loads the custom cursor from the .ani file, however, and this is my problem: When a mouse button is clicked, the custom cursor reverts back to the default OS cursor. But...as soon as the mouse is moved/dragged/released it then renders the custom cursor again...

I can't seem to track down if it's because the custom cursor has stopped being rendered - and so defaults to the default OS cursor, or if another cursor (in this case the arrow) has been pushed to the cursor stack. I think it's the probably the former because I couldn't find any related calls that set the cursor to the arrow...but I'm not sure where to look for the former...

Please reply if you know why or could think of where to look.

Cheers







#1
10/04/2011 (8:43 pm)
with the following function of GuiCanvas it performs the following:
void GuiCanvas::renderFrame(bool preRenderOnly, bool bufferSwap /* = true */)
...
   if(bool(mMouseCapturedControl))
      mMouseCapturedControl->getCursor(mouseCursor, cursorVisible, mLastEvent);
   else if(bool(mMouseControl))
      mMouseControl->getCursor(mouseCursor, cursorVisible, mLastEvent);
...

of which in turn calls GuiControl and other control types that inherited GuiControl and implemented getCursor() to eventually manipulate the window cursor control via PlatformCursorController instance through code such as the one in my GuiModernTextListCtrl control:
void GuiModernTextListCtrl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent)
{
	GuiCanvas *rootCtrl = getRoot();
	if(!rootCtrl)
		return;

	PlatformWindow *platformWindow = static_cast<GuiCanvas*>(getRoot())->getPlatformWindow();
	AssertFatal( platformWindow != NULL,"GuiControl without owning platform window!  This should not be possible." );

	PlatformCursorController *cusrorController = platformWindow->getCursorController();
	AssertFatal( cusrorController != NULL,"PlatformWindow without an owned CursorController!" );

	// check to see if mouse cursor is near any column header resizers
	checkColumnResizers(lastGuiEvent);

	// Check to see if we need one or just the default...
	if(mColumnDND.resize || (mColumnDND.active && mColumnDND.move))
	{
		S32 desiredCursor = (mColumnDND.resize)? PlatformCursorController::curResizeVert : PlatformCursorController::curResizeAll;

		// Do we need to change it or is it already set?
		if(rootCtrl->mCursorChanged != desiredCursor)
		{
			// We've already changed the cursor, so set it back
			if(rootCtrl->mCursorChanged != -1)
				cusrorController->popCursor();

			// Now change the cursor shape
			cusrorController->pushCursor(desiredCursor);
			rootCtrl->mCursorChanged = desiredCursor;
		}
	}
	else if(rootCtrl->mCursorChanged != -1)
	{
		// Just the default
		cusrorController->popCursor();
		rootCtrl->mCursorChanged = -1;
	}
}

So, what you'll need to do is override/modify the platform specific class that implements the PlatformCursorController abstraction class is what I'm thinking.
#2
10/04/2011 (9:10 pm)
wow, that was a quick reply! Thanks Nathan...

I just did a search on pController->refreshCursor(); and one of the places it's called is in winDispatch.cpp for the mouse move event case statement.

I added a refreshCursor() call to the mouse down events like so:

case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN: 
		{
		int index = (message - WM_LBUTTONDOWN) / 3;
		button[index] = true;

		// Capture the mouse on button down to allow dragging outside
		// of the window boundary.
		if (GetCapture() != hWnd)
			SetCapture(hWnd);

		if (window)
		{
			window->buttonEvent.trigger(devId,_ModifierKeys,IA_MAKE,index);

			PlatformCursorController *pController = window->getCursorController();

			// Let the cursor manager update the native cursor.
			pController->refreshCursor();
		}
		break;

	    }


and the custom cursor now renders on mouse down...fantastic...

Unfortunately for me, you've just pointed out a whole stack of controls that I haven't accounted for... so I might have to have a rethink.

Thanks

#3
10/04/2011 (9:56 pm)
I have not looked at this, really, but is there perhaps a "clicked arrow" image that goes with the main arrow image? Like when you click in Warcraft and the finger on the gauntlet moves.... This would account for the behavior described because the second image would have been missing. Perhaps the arrow image should have two frames.

Aw heck - who wrote the system?!? lol - make them explain it!