BUG: Canvas setCursorPos() does not work
by David Wyand · in Torque Game Engine · 07/09/2004 (11:32 am) · 43 replies
Greetings!
Introduction
It seems that people are having a problem with getting the Canvas.setCursorPos() method working. I was among those having this very problem. The cursor would move to the new position but would not stay there as soon as the user moved the mouse. I've managed to work around this problem for my DTS Viewing Tool and present my solution here.
Problem Statement
The current Canvas.setCursorPos() method modifies an internal cursorPt variable to change the cursor position. This variable is used in the drawing of the cursor. However, the hardware platform is not aware of the new mouse location. When the user moves the mouse after a Canvas.setCursorPos() call, the platform sends its current absolute mouse position and the TGE obliges by popping the mouse to this hardware position. What is needed is a method of notifying the hardware platform that the mouse position has changed.
Solution
My proposed solution involves a new Input method definition and a change to the Canvas setCursorPos() operation. First the new Input class method.
Open up platform/platformInput.h and go to the Input class definition. Just before the getManager() method definition, add the following (the new code is bolded):
That's it for this file. Next, open up platformWin32/wininput.cc and go to just after the void Input::process() and before the InputManager* Input::getManager() code. Add the following function:
This new setCursorPos() function will move the mouse at the hardware level to the new position (by using the Windows PDK setCursorPos() function). The coordinates passed to it are relative to the client area of the window (the part of the window the TGE draws into).
Finally, we'll need to change how the Canvas.setCursorPos() method works. Open gui/guiCanvas.h and add the following to the top of the file (new code is bolded):
This will make sure the Canvas has access to the new Input::setCursorPos() method through the platform layer. Next, find the Canvas' setCursorPos() method and replace it with the following:
This new function will make sure that the passed coordinates are relative to the Canvas' origin. So, by calling Canvas.setCursorPos(0,0); in script should now place the mouse pointer in the upper left corner of the Canvas' drawing area.
Multi-platform Notice
The presented solution will only work under Windows. All that is required to have it work on another platform is to write the appropriate platform specific code for the Input::setCursorPos() method. Hopefully someone will endeavor to do just that.
Enjoy!
- LightWave Dave
Introduction
It seems that people are having a problem with getting the Canvas.setCursorPos() method working. I was among those having this very problem. The cursor would move to the new position but would not stay there as soon as the user moved the mouse. I've managed to work around this problem for my DTS Viewing Tool and present my solution here.
Problem Statement
The current Canvas.setCursorPos() method modifies an internal cursorPt variable to change the cursor position. This variable is used in the drawing of the cursor. However, the hardware platform is not aware of the new mouse location. When the user moves the mouse after a Canvas.setCursorPos() call, the platform sends its current absolute mouse position and the TGE obliges by popping the mouse to this hardware position. What is needed is a method of notifying the hardware platform that the mouse position has changed.
Solution
My proposed solution involves a new Input method definition and a change to the Canvas setCursorPos() operation. First the new Input class method.
Open up platform/platformInput.h and go to the Input class definition. Just before the getManager() method definition, add the following (the new code is bolded):
static void process();
[b]static void setCursorPos(S32 x, S32 y); // DAW: Set the cursor position relative to the window's client space[/b]
static InputManager* getManager();That's it for this file. Next, open up platformWin32/wininput.cc and go to just after the void Input::process() and before the InputManager* Input::getManager() code. Add the following function:
//------------------------------------------------------------------------------
// DAW: Set the cursor position relative to the window's client space
void Input::setCursorPos(S32 x, S32 y)
{
POINT pt;
pt.x = x;
pt.y = y;
ClientToScreen(winState.appWindow, &pt);
SetCursorPos(pt.x, pt.y);
}This new setCursorPos() function will move the mouse at the hardware level to the new position (by using the Windows PDK setCursorPos() function). The coordinates passed to it are relative to the client area of the window (the part of the window the TGE draws into).
Finally, we'll need to change how the Canvas.setCursorPos() method works. Open gui/guiCanvas.h and add the following to the top of the file (new code is bolded):
#ifndef _EVENT_H_ #include "platform/event.h" #endif [b]#ifndef _PLATFORMINPUT_H_ #include "platform/platformInput.h" #endif[/b] #ifndef _GUICONTROL_H_ #include "gui/guiControl.h" #endif
This will make sure the Canvas has access to the new Input::setCursorPos() method through the platform layer. Next, find the Canvas' setCursorPos() method and replace it with the following:
void setCursorPos(const Point2I &pt) { Input::setCursorPos(mBounds.point.x+pt.x, mBounds.point.y+pt.y); } //DAW: Was: {cursorPt.x = F32(pt.x); cursorPt.y = F32(pt.y); }This new function will make sure that the passed coordinates are relative to the Canvas' origin. So, by calling Canvas.setCursorPos(0,0); in script should now place the mouse pointer in the upper left corner of the Canvas' drawing area.
Multi-platform Notice
The presented solution will only work under Windows. All that is required to have it work on another platform is to write the appropriate platform specific code for the Input::setCursorPos() method. Hopefully someone will endeavor to do just that.
Enjoy!
- LightWave Dave
About the author
A long time Associate of the GarageGames' community and author of the Torque 3D Game Development Cookbook. Buy it today from Packt Publishing!
#2
07/09/2004 (2:29 pm)
Good work, guys. Can someone step up to the plate with MacOS and Linux implementations of the relevant code so I can check this in to HEAD?
#3
Edit: I just noticed the platformWin32/wininput.cc, nevermind, hehe.
07/09/2004 (2:36 pm)
Ben, from what I can see those very code fixies should work fine in Linux and Mac OS X since I see nothing MS-Windows specific. :)Edit: I just noticed the platformWin32/wininput.cc, nevermind, hehe.
#4
This will need to be a no-op on linux, I don't believe X allows you to move the cursor around. At least directly. The closest I've seen in this is using one of the 'test' extensions (which is _not_ recommended royal pain to set up)
07/11/2004 (12:43 pm)
Quote:
Good work, guys. Can someone step up to the plate with MacOS and Linux implementations of the relevant code so I can check this in to HEAD?
This will need to be a no-op on linux, I don't believe X allows you to move the cursor around. At least directly. The closest I've seen in this is using one of the 'test' extensions (which is _not_ recommended royal pain to set up)
#5
07/11/2004 (11:15 pm)
@Gregory: It can be done, as a matter of fact SDL has a setCursor method. I was going to check this out today but forgot. I'll try to do it tomorrow if I remember.
#6
07/11/2004 (11:19 pm)
Hmm... How does X deal with letting you do drag and drop and such? Does the mouse never snap to a position?
#7
@Ben: All the drag and drop I've seen don't mess with the cursor's position, just the cursor's image. Then there is the typical Unix we have many ways to do something problem. I know of at least 4 different DND protocols that can be used on X. Most popular is XDnD. If you have the interest its documented here http://www.newplanetsoftware.com/xdnd/ (lots of X voodoo there so beware)
07/12/2004 (4:23 am)
@Xavier: I stand corrected X appears to have this function: "XWarpPointer - move pointer" with the corresponding SDL_WarpMouse wrapper.@Ben: All the drag and drop I've seen don't mess with the cursor's position, just the cursor's image. Then there is the typical Unix we have many ways to do something problem. I know of at least 4 different DND protocols that can be used on X. Most popular is XDnD. If you have the interest its documented here http://www.newplanetsoftware.com/xdnd/ (lots of X voodoo there so beware)
#8
I'll make a patch of both changes and put it here... now we need the mac version, any takers?
07/12/2004 (8:45 am)
Ok, got the changes. Here we go, platformX86UNIX/x86UNIXInput.cc, following David's way, before getManager add:void Input::setCursorPos(S32 x, S32 y)
{
SDL_WarpMouse(x, y);
}That's it folks! SDL uses coordinates relative to the window, not to the screen like windows, so this code is simpler in UNIX.I'll make a patch of both changes and put it here... now we need the mac version, any takers?
#9
07/12/2004 (7:30 pm)
Awesome. Can we get an OS X fix?
#10
Maybe I can figure it out.... yummy..
07/12/2004 (7:32 pm)
I would do it if I knew anything about Mac :)Maybe I can figure it out.... yummy..
#11
No clue how usefull this is as I only looked at the Mac Input platform code once...
07/12/2004 (7:43 pm)
After some searching on the net in the middle of a boring XML class.No clue how usefull this is as I only looked at the Mac Input platform code once...
CGDisplayMoveCursorToPoint -------------------------------------------------------------------------------- Moves the cursor to a specified point relative to the display origin (the upper left corner of the display). CGDisplayErr CGDisplayMoveCursorToPoint ( CGDirectDisplayID display, CGPoint point ); Availability Available in Mac OS X version 10.0 and later. Header: CGDirectDisplay.h
#12
07/12/2004 (7:45 pm)
Well that's nice Labby. I guess we need another function to convert from Screen to window coordinates though, it works like in Windows it seems.
#13
code fragments below:
from platform/macCarbWindow.cc starting around line 250
the above sets cgddTeleportMouse as an alias to CGDisplayMoveCursorToPoint
then starting around line 398
So this code gets the current window, finds the center, and moves the mouse to that point...
Looks like it should have all the important bits there.
EDIT: Added a function below
Again from the same file, starting around line 363
07/12/2004 (9:00 pm)
Looks like that function is already being used in Torque....code fragments below:
from platform/macCarbWindow.cc starting around line 250
static OSStatus LoadCGDDFunctions()
{
CFBundleRef cgBundle;
OSStatus err = cfragNoSymbolErr;
if (!platState.osX) return(err);
// err = LoadFrameworkBundle(CFSTR("CoreGraphics.framework"), &cgBundle);
err = LoadFrameworkBundle(CFSTR("ApplicationServices.framework"), &cgBundle);
if (err!=noErr) return(err);
cgddGetDisplay = (CGGDWPPtr) CFBundleGetFunctionPointerForName( cgBundle, CFSTR("CGGetDisplaysWithPoint") );
if (cgddGetDisplay == NULL)
err = cfragNoSymbolErr;
cgddBounds = (CGDBPtr) CFBundleGetFunctionPointerForName( cgBundle, CFSTR("CGDisplayBounds") );
if (cgddBounds == NULL)
err = cfragNoSymbolErr;
cgddTeleportMouse = (CGDMCTPPtr) CFBundleGetFunctionPointerForName( cgBundle, CFSTR("CGDisplayMoveCursorToPoint") );
if (cgddTeleportMouse == NULL)
err = cfragNoSymbolErr;
return(err);
}
#endif //TARG_MACCARBthe above sets cgddTeleportMouse as an alias to CGDisplayMoveCursorToPoint
then starting around line 398
static void RecenterCapturedMouse(void)
{
if (!platState.appWindow || !windowLocked)
return;
Rect r;
Point wndCenter;
GrafPtr savePort;
GetPort( &savePort );
SetPortWindowPort(platState.appWindow);
GetWindowPortBounds(platState.appWindow, &r);
wndCenter.h = r.left + (r.right-r.left)>>1;
wndCenter.v = r.top + (r.bottom-r.top)>>1;
LocalToGlobal( &wndCenter );
if (!platState.osX)
{
}
#if defined(TORQUE_OS_MAC_CARB)
else // on OSX, we use CGDD functions.
if (cgddGetDisplay!=NULL) // then we have the funcs we need.
{
CGDirectDisplayID cgdid;
CGDisplayCount cgdc;
CGPoint cgp;
cgp.x = wndCenter.h;
cgp.y = wndCenter.v;
cgddGetDisplay(cgp, 1, &cgdid, &cgdc);
if (cgdc) // had better be non-zero!!!
{
CGRect cgr = cgddBounds(cgdid);
cgp.x = wndCenter.h - cgr.origin.x;
cgp.y = wndCenter.v - cgr.origin.y;
cgddTeleportMouse(cgdid, cgp);
}
}
#endif
SetPort( savePort );
}So this code gets the current window, finds the center, and moves the mouse to that point...
Looks like it should have all the important bits there.
EDIT: Added a function below
Again from the same file, starting around line 363
static Point& ClientToScreen( WindowPtr wnd, Point pt )
{
static Point screen;
screen = pt;
GrafPtr savePort;
GetPort( &savePort );
SetPortWindowPort(platState.appWindow);
LocalToGlobal( &screen );
SetPort( savePort );
return screen;
}
#14
On a comment it says to use cggd methods in OSX, so I just used those, not sure what's the difference. Anyone can test this?
EDIT: Remove the stupid ' from the comments, site bug.
07/12/2004 (9:10 pm)
I can't compile to test this, I'm going to check now if all the defintions are availabe to it's headers.On a comment it says to use cggd methods in OSX, so I just used those, not sure what's the difference. Anyone can test this?
void Input::setCursorPos(S32 x, S32 y)
{
// Grab the window coordinates and conver them to global ones.
Point winPos;
winPos.x = x;
winPos.y = y;
LocalToGlobal(&winPos);
// Now fill in the CGPoint
CGPoint cPoint;
cPoint.x = x;
cPoint.y = y;
// Get the display at that point
CGDGDirectDisplayID dID;
CGDisplayCount dCount;
cgddGetDisplay(cPoint, 1, &dID, &dCount);
// And finally lets move there.
cgddTeleportMouse(dID, cPoint);
}EDIT: Remove the stupid ' from the comments, site bug.
#15
Hey, it's fun programming in a new PDK you can't test! :o) Based on Harold's and Xavier's code above, here's my take on it:
I found it interesting that the recentre mouse code made reference to the top left corner of the window (rather than just assuming 0,0) so I included that in the calculation of the new cursor point.
- LightWave Dave
Edit: Compiling in my head turned up an undefined variable. Corrected.
07/13/2004 (5:29 am)
Greetings!Hey, it's fun programming in a new PDK you can't test! :o) Based on Harold's and Xavier's code above, here's my take on it:
void Input::setCursorPos(S32 x, S32 y)
{
// Grab the window coordinates and convert them to global ones.
Rect r;
Point wndPnt;
GrafPtr savePort;
GetPort( &savePort );
SetPortWindowPort(platState.appWindow);
GetWindowPortBounds(platState.appWindow, &r);
wndPnt.h = r.left + x;
wndPnt.v = r.top + y;
LocalToGlobal( &wndPnt);
// Now fill in the CGPoint
CGPoint cPoint;
cPoint.x = wndPnt.h;
cPoint.y = wndPnt.v;
// Get the display at that point
CGDGDirectDisplayID dID;
CGDisplayCount dCount;
cgddGetDisplay(cPoint, 1, &dID, &dCount);
// And finally lets move there.
cgddTeleportMouse(dID, cPoint);
// Restore the port
SetPort( savePort );
}I found it interesting that the recentre mouse code made reference to the top left corner of the window (rather than just assuming 0,0) so I included that in the calculation of the new cursor point.
- LightWave Dave
Edit: Compiling in my head turned up an undefined variable. Corrected.
#16
Funny how the Mac seems the most complicated of the three :)
07/13/2004 (8:16 am)
Excellent Dave!!Funny how the Mac seems the most complicated of the three :)
#17
Does this mean we can all put OSX Programming Experience on our resumes?
:o)
- LightWave Dave
07/13/2004 (8:41 am)
Heh.Does this mean we can all put OSX Programming Experience on our resumes?
:o)
- LightWave Dave
#18
Doubt any employer would care anyway :) (rant).
07/13/2004 (8:52 am)
Hehehe. *deftly edits his resume*Doubt any employer would care anyway :) (rant).
#19
guiCanvas.h
I noticed some of my functions were returning bad mouse cursor positions, because they were accessing the mouse event which wasn't updated. Also, the object selection resource calculates the mouse vector off of the mouse event. So, if you wanted to change the cursor position, then do work with the updates within the same tick, you may want to do something like this to update the event. I just tossed these lines in there because those were the values I needed, not sure if this is the best way to update an event. Seems like it should work though, after looking through the various GuiCanvas functions.
You could also call one of the stock GuiCanvas function to reprocess the new mouse position as input, but then you'd be generating a new mousemove or mousedrag event, and it seems like SetCursorPos probably shouldn't generate a mousemove event (in the HEAD at least, I'd recommend), since the mouse actually isn't moving. But if someone wanted it to, it would be pretty easy to make it do that.
Edit: Made code posting clearer
07/13/2004 (10:23 am)
I added a few lines after Dave's Input::setCursorPos lines in GuiCanavs.setCursorPos() to update the event:guiCanvas.h
void setCursorPos(const Point2I &pt) { Input::setCursorPos(mBounds.point.x+pt.x, mBounds.point.y+pt.y);
cursorPt.x = F32(pt.x); cursorPt.y = F32(pt.y);
mLastEvent.mousePoint.x = S32(cursorPt.x);
mLastEvent.mousePoint.y = S32(cursorPt.y); }I noticed some of my functions were returning bad mouse cursor positions, because they were accessing the mouse event which wasn't updated. Also, the object selection resource calculates the mouse vector off of the mouse event. So, if you wanted to change the cursor position, then do work with the updates within the same tick, you may want to do something like this to update the event. I just tossed these lines in there because those were the values I needed, not sure if this is the best way to update an event. Seems like it should work though, after looking through the various GuiCanvas functions.
You could also call one of the stock GuiCanvas function to reprocess the new mouse position as input, but then you'd be generating a new mousemove or mousedrag event, and it seems like SetCursorPos probably shouldn't generate a mousemove event (in the HEAD at least, I'd recommend), since the mouse actually isn't moving. But if someone wanted it to, it would be pretty easy to make it do that.
Edit: Made code posting clearer
#20
ALONG with Drew's previous posts (which clears up an issue with the pointer not drawing in the new position once moved), AND the original set up by Dave for windows, make the following changes:
In macCarbInput.cc, add to the includes at the top of the file:
and further down, as per David's previous instructions:
I've made the calls directly on the system with the code rather than using the alias' as the above code does. I am not sure of the repercussions of this, as I am not familiar with why the CGDD calls are alias'd in the first place. This has been compiled and tested on OSX 10.3, XCode 1.2.
07/13/2004 (11:28 am)
Ok, I've been working with Drew on this, and finally got the mac OSX version to compile and work properly.ALONG with Drew's previous posts (which clears up an issue with the pointer not drawing in the new position once moved), AND the original set up by Dave for windows, make the following changes:
In macCarbInput.cc, add to the includes at the top of the file:
#include <Carbon/Carbon.h>
and further down, as per David's previous instructions:
void Input::setCursorPos(S32 x, S32 y)
{
Point windowPnt;
Rect r;
GrafPtr savePort;
CGDirectDisplayID cgdid;
CGDisplayCount cgdc;
CGPoint cgp;
//grab the port
GetPort( &savePort );
SetPortWindowPort( platState.appWindow );
GetWindowPortBounds(platState.appWindow, &r);
//calc and localize the xy coords
windowPnt.h = r.left + x;
windowPnt.v = r.top + y;
LocalToGlobal( &windowPnt);
//set up CGDID
CGGetDisplaysWithPoint(cgp, 1, &cgdid, &cgdc);
//load up the CGPoint with local screen coords
cgp.x = windowPnt.h;
cgp.y = windowPnt.v;
//move the little sucka
CGDisplayMoveCursorToPoint(cgdid, cgp);
//return the port
SetPort( savePort );
}I've made the calls directly on the system with the code rather than using the alias' as the above code does. I am not sure of the repercussions of this, as I am not familiar with why the CGDD calls are alias'd in the first place. This has been compiled and tested on OSX 10.3, XCode 1.2.
Torque 3D Owner Drew Parker
The old function:
GuiCanvas.cc
ConsoleMethod( GuiCanvas, setCursorPos, void, 3, 4, "(Point2I pos)") { Point2I pos(0,0); if(argc == 4) pos.set(dAtoi(argv[2]), dAtoi(argv[3])); else dSscanf(argv[3], "%d %d", &pos.x, &pos.y); Canvas->setCursorPos(pos); }First off, after stepping through the function, I found that the wrong value was being passed to the Canvas->setCursorPos. This function allows 3 or 4 arguments (take off 2 for class and function name), so it actually is taking 1 or 2 arguments. When you send one, the else statement will run, but that checks argv[3] for the arguments. But argv is the 4th argument (which means the second val use passed to the console method), and that doesn't exist since you only passed one. So it should actually check argv[2], like this...
ConsoleMethod( GuiCanvas, setCursorPos, void, 3, 4, "(Point2I pos)") { Point2I pos(0,0); if(argc == 4) pos.set(dAtoi(argv[2]), dAtoi(argv[3])); else [b]dSscanf(argv[2], "%d %d", &pos.x, &pos.y);[/b] // should be argv[2], not argv[3] Canvas->setCursorPos(pos); }