Feature and Fix Patch - Mouse Event Propagation
by Charlie Patterson · in Torque Game Builder · 02/22/2012 (10:48 am) · 5 replies
Feature and Fix Patch - Mouse Event Propagation
I'm hoping for some help testing this. I've been using it all week in the editor and in-game without any issues.
Code Base: TGB Pro 1.7.5 (should work for 1.7.6 also)
Problem:
T2D sends mouse events to every scene object under the mouse. Sometimes I just want the "first" thing under the mouse. For instance, I want to create a dialog "window" made of just scene objects (not the built-in GUI objects). While the dialog is up, I don't want mouse events meant for objects in the dialog to also run on the items "under" the dialog in the game.
So, I have added a flag
to only send mouse events (onMouseEnter/Leave/Down/Up, etc) to the highest object that accepts mouse events. Note that this means that if you have a window-sized grey box over everything, that box will not eat the mouse events unless you want it to and have turned on its useMouseEvents.
This feature should be completely backwards compatible. If T2D doesn't see that variable set, or you set it to false, you will still get the classic way of working with mouse events. But...
BUT
I was naughty and while I was playing with this code I made several other adjustments to remove bugs and hopefully speed the code up a bit.
The "mouseLock bug" has been removed. This is (was) where you unlock the mouse from within a mouse event on an object, and then you receive the event again for that object. My version may cost a little more time when you do unlock the mouse but this *is* a bug fix and I think I make up for it with other changes...
I removed a copy of a pick list that wasn't needed.
Early in the process, I cull out objects that don't want mouse events anyway. Before, the code did several bits of search over these objects only to not send them events anyway.
I stop "re-picking" objects under the mouse after mouse-event scripts were run. Removing this should be a *big* win in time, probably cutting any processing time in half. However the comments warned *"re-obtain the pickVector as some objects may have been deleted!"* But this re-picking was picking objects I had just added and sending them the mouse events, which was itself a bug, very confusing, and wouldn't happen if the code didn't re-pick.
So this last one may cause you some heartburn if you do %this.delete in your scripts. To fix this, you would need a %this.safeDelete() in those cases instead.
Patch:
Files Affected:
T2D/t2dSceneWindow.cc
T2D/t2dSceneWindow.h
Included:
included in-line. The original file name was 'tgb175_mouseEvent_depth_etc_2012-02-22.patch' and it was a "unified diff" meaning it has context in it so that line-numbers aren't as important. It should be patched from your "source/" directory.
Caveats:
There are enough subtleties in this code that I'd be interested for some torquers to try it and make sure they are satisfied.
A specific concern is that you may have some %this.delete() in your mouse scripts and these will probably need to become %this.safeDelete().
I'm hoping for some help testing this. I've been using it all week in the editor and in-game without any issues.
Code Base: TGB Pro 1.7.5 (should work for 1.7.6 also)
Problem:
T2D sends mouse events to every scene object under the mouse. Sometimes I just want the "first" thing under the mouse. For instance, I want to create a dialog "window" made of just scene objects (not the built-in GUI objects). While the dialog is up, I don't want mouse events meant for objects in the dialog to also run on the items "under" the dialog in the game.
So, I have added a flag
sceneWindow2D.mouseEventDepthOne = true;
to only send mouse events (onMouseEnter/Leave/Down/Up, etc) to the highest object that accepts mouse events. Note that this means that if you have a window-sized grey box over everything, that box will not eat the mouse events unless you want it to and have turned on its useMouseEvents.
This feature should be completely backwards compatible. If T2D doesn't see that variable set, or you set it to false, you will still get the classic way of working with mouse events. But...
BUT
I was naughty and while I was playing with this code I made several other adjustments to remove bugs and hopefully speed the code up a bit.
The "mouseLock bug" has been removed. This is (was) where you unlock the mouse from within a mouse event on an object, and then you receive the event again for that object. My version may cost a little more time when you do unlock the mouse but this *is* a bug fix and I think I make up for it with other changes...
I removed a copy of a pick list that wasn't needed.
Early in the process, I cull out objects that don't want mouse events anyway. Before, the code did several bits of search over these objects only to not send them events anyway.
I stop "re-picking" objects under the mouse after mouse-event scripts were run. Removing this should be a *big* win in time, probably cutting any processing time in half. However the comments warned *"re-obtain the pickVector as some objects may have been deleted!"* But this re-picking was picking objects I had just added and sending them the mouse events, which was itself a bug, very confusing, and wouldn't happen if the code didn't re-pick.
So this last one may cause you some heartburn if you do %this.delete in your scripts. To fix this, you would need a %this.safeDelete() in those cases instead.
Patch:
Files Affected:
T2D/t2dSceneWindow.cc
T2D/t2dSceneWindow.h
Included:
included in-line. The original file name was 'tgb175_mouseEvent_depth_etc_2012-02-22.patch' and it was a "unified diff" meaning it has context in it so that line-numbers aren't as important. It should be patched from your "source/" directory.
Index: T2D/t2dSceneWindow.cc
===================================================================
--- T2D/t2dSceneWindow.cc (revision 192)
+++ T2D/t2dSceneWindow.cc (working copy)
@@ -75,6 +75,7 @@
mViewLimitActive(false),
mUseWindowMouseEvents(true),
mUseObjectMouseEvents(false),
+ mMouseEventDepthOne(false),
mMouseEventGroupMaskFilter(T2D_MASK_ALL),
mMouseEventLayerMaskFilter(T2D_MASK_ALL),
mMouseEventInvisibleFilter( false )
@@ -143,6 +144,7 @@
addField( "lockMouse", TypeBool, Offset(mLockMouse, t2dSceneWindow) );
addField( "useWindowMouseEvents", TypeBool, Offset(mUseWindowMouseEvents, t2dSceneWindow) );
addField( "useObjectMouseEvents", TypeBool, Offset(mUseObjectMouseEvents, t2dSceneWindow) );
+ addField( "mouseEventDepthOne", TypeBool, Offset(mMouseEventDepthOne, t2dSceneWindow) );
// Set Event-Modifier Script Variables.
Con::setIntVariable("$EventModifier::LSHIFT", SI_LSHIFT);
@@ -1645,6 +1647,13 @@
mMouseEventGroupMaskFilter = groupMask;
}
+ConsoleMethod(t2dSceneWindow, setMouseEventDepthOne, void, 3, 3, "(bool depthStatus) Sets the mouse event depth to one object."
+ "@return No return value.")
+{
+ // Set Lock Mouse.
+ object->setMouseEventDepthOne( dAtob(argv[2]) );
+}
+
//-----------------------------------------------------------------------------
// Set Collision Layers.
//-----------------------------------------------------------------------------
@@ -2265,144 +2274,156 @@
t2dVector worldMousePoint;
windowToSceneCoord(t2dVector(globalToLocalCoord(event.mousePoint)), worldMousePoint);
+ // we're going to let objects that have a mouse lock run their scripts first.
+ // *before* that we want to pick everything under the mouse. this is because the scripts
+ // may create new objects and those new objects may be under the mouse. we don't want those new objects
+ // to count for this current mouse event.
+ getSceneGraph()->pickPoint( worldMousePoint, mMouseEventGroupMaskFilter, mMouseEventLayerMaskFilter, mMouseEventInvisibleFilter );
+ typeSceneObjectVector pickVector = getSceneGraph()->getPickList();
+
+ // keep track of objects that started here with a mouse lock, but that remove it while processing their scripts.
+ SimSet newlyUnlocked;
+
// If an object is locked, we just send it all the events regardless of where it is.
const SimSet& lockedObjects = getSceneGraph()->getMouseLockedObjectSet();
for (U32 i = 0; i < lockedObjects.size(); i++)
{
t2dSceneObject* lockedObject = static_cast<t2dSceneObject*>( lockedObjects.at(i) );
+ AssertFatal(lockedObject->getMouseLocked(), "t2dSceneWindow::sendObjectMouseEvent() - Object is not locked for mouse but is in the scenegraph's locked object set!");
+
lockedObject->onMouseEvent(name, event, worldMousePoint);
+
+ // did this object release its mouse lock as part of the script?
+ if (!lockedObject->isDeleted() && !lockedObject->getMouseLocked())
+ newlyUnlocked.addObject( lockedObject );
}
- // Finish if we've no objects at the mouse-event point.
- if ( getSceneGraph()->pickPoint( worldMousePoint, mMouseEventGroupMaskFilter, mMouseEventLayerMaskFilter, mMouseEventInvisibleFilter ) == 0 )
- {
- if( !mLastPickVector.empty() )
- {
- for( U32 i = 0; i < mLastPickVector.size(); i++ )
- {
- t2dSceneObject* lastObject = static_cast<t2dSceneObject*>( mLastPickVector[i] );
- if( !lastObject )
- continue;
+ // note: we're going to edit pickVector often, even though it is a constant reference to the scene
+ // graph's pick list! just a head's up.
- if( lastObject->isProperlyAdded() && lastObject->getUseMouseEvents() )
- lastObject->onMouseEvent( "onMouseLeave" , event, worldMousePoint );
- }
- while( mLastPickVector.size() )
- mLastPickVector.popObject();
+ // note: it's possible that some things that were picked, were then deleted from mouse scripts above.
+ // be vigilant about monitoring the deleted status.
+
+ if (!mMouseEventDepthOne) {
+ // no sense accounting for objects under the mouse that either don't use mouse events or
+ // that have been deleted since we began
+ U32 n = 0;
+ while( n < pickVector.size() ) {
+ t2dSceneObject* object = pickVector[n];
+ if( object->isDeleted() || !object->getUseMouseEvents() )
+ pickVector.erase( &pickVector[n] );
+ else
+ n++;
}
- return;
}
+ else { // mMouseEventDepthOne
+ // only want to send this event to the "closest" object.
+ // we still have to search for the closest object that also uses mouse events.
+ t2dSceneObject* keeper = NULL;
+ for ( U32 i = 0; i < pickVector.size(); i++ ) {
+ t2dSceneObject* object = pickVector[i];
+ if (object->isDeleted() || !object->getUseMouseEvents())
+ continue;
+ keeper = object;
+ break;
+ }
- // Fetch Pick Vector.
- typeSceneObjectVector pickVector = getSceneGraph()->getPickList();
- SimSet lastPickVector;
- for( S32 i = 0; i < mLastPickVector.size(); i++ )
- lastPickVector.addObject( mLastPickVector[i] );
+ pickVector.clear();
+ if (keeper)
+ pickVector.push_back(keeper);
+ }
- // We have to dance a bit to get onMouseLeave to work
- for( U32 i = 0; i < lastPickVector.size(); i++ )
+ // send onMouseLeave to items that were under the mouse last time around, but not this time
+ for( U32 i = 0; i < mLastPickVector.size(); i++ )
{
- t2dSceneObject* lastObject = static_cast<t2dSceneObject*>( lastPickVector[i] );
- if( !lastObject )
+ t2dSceneObject* lastObject = static_cast<t2dSceneObject*>( mLastPickVector[i] );
+ if( lastObject->isDeleted() || !lastObject->isProperlyAdded() || !lastObject->getUseMouseEvents() )
continue;
- bool bFoundNew = true;
+ bool lastTimeOnly = true;
for( U32 j = 0; j < pickVector.size(); j++ )
{
t2dSceneObject* object = pickVector[j];
- if(!object)
- continue;
-
if( object == lastObject )
{
- bFoundNew = false;
+ lastTimeOnly = false;
break;
}
}
- if( lastObject && lastObject->isProperlyAdded() && bFoundNew == true && lastObject->getUseMouseEvents() )
+ if( lastTimeOnly )
lastObject->onMouseEvent( "onMouseLeave" , event, worldMousePoint );
}
- // We have to dance a bit to get onMouseEnter to work
+ // send onMouseEnter to items that were under the mouse this time around, but not last time
for( U32 i = 0; i < pickVector.size(); i++ )
{
t2dSceneObject* object = pickVector[i];
- if(!object)
+ if(object->isDeleted() || !object->getUseMouseEvents() )
continue;
- bool bFoundLast = false;
- for( U32 j = 0; j < lastPickVector.size(); j++ )
+ bool thisTimeOnly = true;
+ for( U32 j = 0; j < mLastPickVector.size(); j++ )
{
- t2dSceneObject* lastObject = static_cast<t2dSceneObject*>( lastPickVector[j] );
- if(!lastObject)
- continue;
-
+ t2dSceneObject* lastObject = static_cast<t2dSceneObject*>( mLastPickVector[j] );
if( lastObject == object )
{
- bFoundLast = true;
+ thisTimeOnly = false;
break;
}
}
- if( bFoundLast == false && object->getUseMouseEvents() )
+ if( thisTimeOnly )
object->onMouseEvent( "onMouseEnter" , event, worldMousePoint );
}
-
-
- // Action Objects if they're monitoring mouse-events.
+ // Action objects under the mouse (if they're monitoring mouse-events)
for( U32 n = 0; n < pickVector.size(); n++ )
{
+ // if this object is locked, we've already handled it
t2dSceneObject* object = pickVector[n];
- if(!object)
+ if(object->isDeleted() || !object->getUseMouseEvents() || object->getMouseLocked())
continue;
- // Monitoring Mouse Events?
- if( object->getUseMouseEvents() && (!object->getMouseLocked()))
+ // but just because he's unlocked, doesn't mean we *haven't* handled it. test for "newly unlocked"
+ bool alreadyCalled = false;
+ for( U32 j = 0; j < newlyUnlocked.size(); j++ )
{
- // Yes, so do callback.
+ t2dSceneObject* unlockedObject = static_cast<t2dSceneObject*>( newlyUnlocked[j] );
+ if( object == unlockedObject )
+ {
+ alreadyCalled = true;
+ break;
+ }
+ }
+
+ if( !alreadyCalled )
object->onMouseEvent( name, event, worldMousePoint );
- }
}
//Luma: re-obtain the pickVector as some objects may have been deleted!
- getSceneGraph()->pickPoint( worldMousePoint, mMouseEventGroupMaskFilter, mMouseEventLayerMaskFilter, mMouseEventInvisibleFilter );
- pickVector = getSceneGraph()->getPickList();
+ // charliep: this will pick things that have been added as well though, so I'm turning it off!
+ // after all, I can handle things that have been deleted by ignoring them if isDeleted()
+ //getSceneGraph()->pickPoint( worldMousePoint, mMouseEventGroupMaskFilter, mMouseEventLayerMaskFilter, mMouseEventInvisibleFilter );
+ //pickVector = getSceneGraph()->getPickList();
- // Prune Pick Vector of non-mouse event controls
- //Luma: U32 will cause issues if 1st element is removed... so change to S32 instead
- for( S32 n = 0; n < pickVector.size(); n++ )
- {
- t2dSceneObject* object = pickVector[n];
- if(!object)
+ // Store last pick list (Necessary for onMouseEnter/Leave callbacks)
+ mLastPickVector.clear();
+ for( S32 i = 0; i < pickVector.size(); i++ ) {
+ // one last chance to prune objects that, through mouse scripts, have just been deleted or
+ // stopped using the mouse
+ t2dSceneObject* object = pickVector[i];
+ if(object->isDeleted() || !object->getUseMouseEvents() )
continue;
- // Monitoring Mouse Events?
- if( !object->getUseMouseEvents() )
- {
- pickVector.erase( &pickVector[n] );
- n--;
- }
+ mLastPickVector.addObject( object );
}
- // Store last pick list (Necessary for onMouseEnter/Leave callbacks)
- while( mLastPickVector.size() )
- mLastPickVector.popObject();
-
- while( lastPickVector.size() )
- lastPickVector.popObject();
-
- for( S32 i = 0; i < pickVector.size(); i++ )
- mLastPickVector.addObject( pickVector[i] );
-
- // Clear Pick-List.
- if(getSceneGraph())
- getSceneGraph()->clearPickList();
+ newlyUnlocked.clear();
+ getSceneGraph()->clearPickList();
}
-
//-----------------------------------------------------------------------------
// Mouse Event Handler.
//-----------------------------------------------------------------------------
Index: T2D/t2dSceneWindow.h
===================================================================
--- T2D/t2dSceneWindow.h (revision 192)
+++ T2D/t2dSceneWindow.h (working copy)
@@ -105,6 +105,7 @@
// Mouse Events.
bool mUseWindowMouseEvents;
bool mUseObjectMouseEvents;
+ bool mMouseEventDepthOne;
U32 mMouseEventGroupMaskFilter;
U32 mMouseEventLayerMaskFilter;
bool mMouseEventInvisibleFilter;
@@ -182,6 +183,7 @@
bool getLockMouse( void ) { return mLockMouse; };
void setObjectMouseEventInvisibleFilter(bool useInvisible);
void setObjectMouseEventGroupFilter(U32 groupMask);
+ void setMouseEventDepthOne( bool status ) { mMouseEventDepthOne = status; };
void setObjectMouseEventLayerFilter(U32 layerMask);
void setObjectMouseEventFilter( U32 groupMask, U32 layerMask, bool useInvisible = false );
void setUseWindowMouseEvents( bool windowMouseStatus ) { mUseWindowMouseEvents = windowMouseStatus; };Caveats:
There are enough subtleties in this code that I'd be interested for some torquers to try it and make sure they are satisfied.
A specific concern is that you may have some %this.delete() in your mouse scripts and these will probably need to become %this.safeDelete().
#2
02/29/2012 (6:39 am)
I am putting it in this weekend to test out. I had to recover most of my work due to an HD crash... Lesson learned ... BACK UP BACK UP BACK UP!!! oh and BACK UP!!!!!
#3
02/29/2012 (6:57 pm)
Gah! I hate when that happens! Sorry to hear it @Michael. Sounds like you have bigger fish to fry right now.
#4
Then in my project game.cs I added sceneWindow2D.mouseEventDepthOne = true; in function startGame(%level) have I done this wrong because it dopiest seem to work when I load another t2dSceneWindow as a hud the mouse events get eaten up still :'(
07/18/2012 (6:36 pm)
noobie question, how do I apply this patch? I created tgb175_mouseEvent_depth_etc_2012-02-22.patch and placed the code inside and then placed the file in C:\Program Files (x86)\Torque\Torque 2D Pro 1.7.6\engine\source .Then in my project game.cs I added sceneWindow2D.mouseEventDepthOne = true; in function startGame(%level) have I done this wrong because it dopiest seem to work when I load another t2dSceneWindow as a hud the mouse events get eaten up still :'(
#5
This is a source code patch. You have to be able to build Torque from the source code first, which requires using Visual Studio (or XCode for Mac), etc, etc. If you are this far, then you literally change the source code by applying this patch. Then you recompile.
If you are at that stage, I'm afraid I don't actually *know* the best tool for applying a patch. I have yet to apply one; but I've made a few around here. :P Someone on here mentioned that there is a windows program that will work. Maybe write a new thread and ask about best way to patch?
If I weren't extremely busy this week, I'd do a little research on it, but alas I cannot.
07/18/2012 (8:57 pm)
Oy! If it could only be that easy. :)This is a source code patch. You have to be able to build Torque from the source code first, which requires using Visual Studio (or XCode for Mac), etc, etc. If you are this far, then you literally change the source code by applying this patch. Then you recompile.
If you are at that stage, I'm afraid I don't actually *know* the best tool for applying a patch. I have yet to apply one; but I've made a few around here. :P Someone on here mentioned that there is a windows program that will work. Maybe write a new thread and ask about best way to patch?
If I weren't extremely busy this week, I'd do a little research on it, but alas I cannot.
Associate Charlie Patterson
Default Studio Name