Detect leaving userarea
-
On 15/02/2017 at 23:11, xxxxxxxx wrote:
User Information:
Cinema 4D Version: R18
Platform: Windows ;
Language(s) : C++ ;---------
I am working on a CommandData plugin with dialog and userarea. The userarea will draw items, and when hovering over these with the mouse I draw the items in a different color, suggesting the item being highlighted. I keep the hovered-over item as a member of my derived userarea class, and use this in the DrawMsg method to know what to draw in regular color and what to draw highlighted.
I have noticed that when the mouse cursor is outside of the userarea, no messages are captured in InputEvent or Message methods. Which means that when the user hovers over an item which is at the edge of the userarea and then "quickly" moves the mouse cursor outside of the userarea, no message is send to the userarea to inform that the item is not hovered over anymore. Nor is there any message send to trigger a redraw.If I remember correctly, I have read in the documentation (somewhere) that there is indeed no possibility to sent a message when mouse leaves userarea as this is not supported cross platform ... or something similar.
I have resolved this by using a timer, polling every 1000ms to check if the current mouse position is still inside of the userarea. When outside I then clear the hovered over item and trigger a redraw, stopping the timer. The timer is thus only active when the mouse is inside the userarea.
However, I am not to fond of using a timer as this means that the timer is continuously running and triggering an event when mouse is inside the userarea. It's only when the mouse is outside of the userarea and the timer has timed out, that the timer gets stopped.Wondering if there is a better way to detect the mouse leaving the userarea (with or without mousebuttons pressed or keyboard strokes)?
Thanks in advance.
-
On 15/02/2017 at 23:48, xxxxxxxx wrote:
Hi C4DS,
just a thought: you could try using the BFM_GETCURSORINFO in the dialog's Message() to see where the mouse is. Something like:
// psuedo code - will need to revise for R18! LONG Your_Dialog::Message(const BaseContainer &msg,BaseContainer &result) { switch(msg.GetId()) { case BFM_GETCURSORINFO: { BaseContainer bc; GetInputState(BFM_INPUT_MOUSE,BFM_INPUT_MOUSELEFT,bc); LONG Cur_X = bc.GetLong(BFM_INPUT_X); LONG Cur_Y = bc.GetLong(BFM_INPUT_Y); Global2Local(&Cur_X,&Cur_Y); LONG X,Y,W,H; GetItemDim(DLG_USER_AREA_ID,&X,&Y,&W,&H); if(Cur_X >= X && Cur_X <= (X+W) && Cur_Y >= Y && Cur_Y <= (Y+H)) { if(YourUserArea.GetMouse() != TRUE) // test flag's status { YourUserArea.CheckMouse(TRUE); // custom UA function to set flag YourUserArea.Redraw(FALSE); // redraw user area } } else { if(YourUserArea.GetMouse() != FALSE) // test flag's status { YourUserArea.CheckMouse(FALSE); // custom UA function to set flag YourUserArea.Redraw(FALSE); // redraw user area } } } } return GeDialog::Message(msg,result); }
Hold a class level variable (which you might already have?) in your user area to hold the mouse over status, and include a couple of custom functions in the user area to get and set the flag from when the mouse is beyond it's area. Might help?
WP.
-
On 16/02/2017 at 05:24, xxxxxxxx wrote:
Hi C4DS,
yep, as WickedP suggests handling this on a dialog level could be one option. And I wouldn't consider your Timer solution necessarily evil, either.
Maybe another GeUserArea-based option could be to make use of Fading (not saying it's any better or easier), see GUI and Interaction Messages manual and ActivateFading() / BFM_FADE. The issue here is, you will get the message once fading got activated, regardless of mouse pointer position. So you'd again need to come up with your own additional solutions to achieve the effect you want.
-
On 16/02/2017 at 13:09, xxxxxxxx wrote:
Thanks for the suggestion WickedP.
Didn't think about the Dialog messageloop. Unfortunately, in my case the userarea takes up all the room of the dialog, as such when the mouse is outside of the userarea it is also outside of the dialog. No messages do reach the dialog then. Still a good though to keep in mind.Big thanks Andreas,
The fading trick seems to be the solution.With following in UserArea::Message I can monitor the mouse being inside or outside, with less code than I needed to get it working via the timer.
switch (msg.GetId()) { case BFM_FADE: { BaseContainer state; GetInputState(BFM_INPUT_MOUSE, BFM_INPUT_MOUSELEFT, state); Int32 mx = state.GetInt32(BFM_INPUT_X); Int32 my = state.GetInt32(BFM_INPUT_Y); if (Global2Local(&mx, &my) && ((mx < 0) || (mx > GetWidth()) || (my < 0) || (my > GetHeight()))) { GePrint("Cursor outside userarea"); } return true; } case BFM_GETCURSORINFO: { GePrint("Cursor inside userarea"); ActivateFading(100); return true; } }
I now only need to clear the hovered-over item and trigger the redraw instead of doing a GePrint("Cursor outside userarea")
-
On 25/06/2017 at 08:29, xxxxxxxx wrote:
Just a note to mention that while using the fade seemed to be the solution, I now have encountered a situation where this doesn't work. I am currently resolving more important issues, so haven't looked into a solution, but I guess I will have to revert to my original solution using a timer.
When userarea gets the message BFM_GETCURSORINFO the ActivateFading() is called.
When BFM_FADE is received I print out the value (fading in from 0.0 to 1.0, fading out from 1.0 to 0.0).
However, sometimes no BFM_FADE message is received while the mouse is definitely hovering over the userarea. When the mouse is moved away from the userarea before any BFM_FADE message is received, the outside-userarea state is never detected.It seems in this scenario (randomly reproducible) the ActivateFading never triggered anything, as such no fade in, no fade out does occur. And no messages are sent.
-
On 26/06/2017 at 09:00, xxxxxxxx wrote:
Hi,
nice coincidence, one of our developers just made us aware of another option to detect the mouse pointer leaving a GeUserArea.
Here's his code snippet, which should be more or less self explanatory:
// Callback used by RemoveLastCursorInfo() (see MyUserArea::Message()) called when mouse leaves the area. MyUserArea* g_infoAreaPointer = nullptr; static void RemoveCursorInfoCallback() { if (!g_infoAreaPointer) return; BaseContainer dummy; g_infoAreaPointer->Message(BaseContainer(BFM_CURSORINFO_REMOVE), dummy); } class MyUserArea : public GeUserArea { public: ~MyUserArea(); virtual Int32 Message(const BaseContainer& msg, BaseContainer& result); private: Int32 _mouseX = NOTOK; Int32 _mouseY = NOTOK; }; MyUserArea::~MyUserArea() { // Don't forget to set it to nullptr on destruction g_infoAreaPointer = nullptr; } Int32 MyUserArea::Message(const BaseContainer& msg, BaseContainer& result) { switch (msg.GetId()) { case BFM_CURSORINFO_REMOVE: { // Mouse has left the user area: remove cross cursor _mouseX = NOTOK; _mouseY = NOTOK; Redraw(); break; } case BFM_GETCURSORINFO: { // Mouse over area: draw cross cursor Int32 x = msg.GetInt32(BFM_DRAG_SCREENX); Int32 y = msg.GetInt32(BFM_DRAG_SCREENY); if (!Screen2Local(&x, &y)) return true; _mouseX = x; _mouseY = y; // Set pointer and callback g_infoAreaPointer = this; RemoveLastCursorInfo(RemoveCursorInfoCallback); Redraw(); return true; } } return GeUserArea::Message(msg, result); }
Sorry, for not providing this solution in the first place.
-
On 26/06/2017 at 11:16, xxxxxxxx wrote:
Thanks Andreas.
Please pass on my gratitude towards the developer who brought up this idea.
A very nice solution indeed.To be honest, while it might be self explanatory for some, I still need to figure out the details.
EDIT:
OK, after reading it a few more times I get the whole idea, except for RemoveLastCursorInfo().
I understand the concept of the callback function to send the BFM_CURSORINNFO_REMOVE to the userarea. But when is that callback function actually called, what is the criteria or trigger to call it?
The SDK documentation is quite unclear about the purpose of RemoveLastCursorInfo.Not that I NEED to know how it works, but it would be nice to understand in order to know what it can be used for.
-
On 27/06/2017 at 08:55, xxxxxxxx wrote:
I like your very friendly description "The SDK documentation is quite unclear about the purpose of RemoveLastCursorInfo". We'll have to check with development, why this function has been marked private. So long as with everything being marked private, please to proper testing.
And despite its rather long name, I did not find any other use-case of RemoveLastCursorInfo() than to detect the mouse pointer leaving a area.