Plugin Cafe Homepage
Forum Home Forum Home > Plugin Cafe > SDK Help
  New Posts New Posts
  FAQ FAQ  Forum Search   Register Register  Login Login

Detect leaving userarea

 Post Reply Post Reply
Author
Message
C4DS View Drop Down
Member
Member


Joined: 2015 Dec 01
Online Status: Offline
Posts: 125
Post Options Post Options   Quote C4DS Quote  Post ReplyReply Direct Link To This Post Topic: Detect leaving userarea
    Posted: 2017 Feb 15 at 11:11pm

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.




Edited by C4DS - 2017 Feb 15 at 11:12pm
Back to Top
WickedP View Drop Down
Member
Member
Avatar

Joined: 2011 Aug 21
Online Status: Offline
Posts: 458
Post Options Post Options   Quote WickedP Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Feb 15 at 11:48pm
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.


Edited by WickedP - 2017 Feb 16 at 12:12am
WickedP® Developer
http://www.wickedp.com
Back to Top
Andreas Block View Drop Down
Forum Moderator
Forum Moderator
Avatar

Joined: 2014 Oct 01
Location: Hannover
Online Status: Offline
Posts: 1540
Post Options Post Options   Quote Andreas Block Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Feb 16 at 5:24am
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.

Cheers,
Andreas
SDK Support Engineer
Back to Top
C4DS View Drop Down
Member
Member


Joined: 2015 Dec 01
Online Status: Offline
Posts: 125
Post Options Post Options   Quote C4DS Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Feb 16 at 1:09pm
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")



Edited by C4DS - 2017 Feb 16 at 1:11pm
Back to Top
C4DS View Drop Down
Member
Member


Joined: 2015 Dec 01
Online Status: Offline
Posts: 125
Post Options Post Options   Quote C4DS Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Jun 25 at 8:29am
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.




Edited by C4DS - 2017 Jun 25 at 8:31am
Back to Top
Andreas Block View Drop Down
Forum Moderator
Forum Moderator
Avatar

Joined: 2014 Oct 01
Location: Hannover
Online Status: Offline
Posts: 1540
Post Options Post Options   Quote Andreas Block Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Jun 26 at 9:00am
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.



Edited by Andreas Block - 2017 Jun 26 at 9:01am
Cheers,
Andreas
SDK Support Engineer
Back to Top
C4DS View Drop Down
Member
Member


Joined: 2015 Dec 01
Online Status: Offline
Posts: 125
Post Options Post Options   Quote C4DS Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Jun 26 at 11:16am
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.


Edited by C4DS - 2017 Jun 26 at 12:40pm
Back to Top
Andreas Block View Drop Down
Forum Moderator
Forum Moderator
Avatar

Joined: 2014 Oct 01
Location: Hannover
Online Status: Offline
Posts: 1540
Post Options Post Options   Quote Andreas Block Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Jun 27 at 8:55am
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.

Cheers,
Andreas
SDK Support Engineer
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down

Bulletin Board Software by Web Wiz Forums® version 9.61 [Free Express Edition]
Copyright ©2001-2009 Web Wiz

This page was generated in 0.109 seconds.