Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Detect leaving userarea

    SDK Help
    0
    8
    1.1k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • H
      Helper
      last edited by

      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.

      1 Reply Last reply Reply Quote 0
      • H
        Helper
        last edited by

        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.

        1 Reply Last reply Reply Quote 0
        • H
          Helper
          last edited by

          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.

          1 Reply Last reply Reply Quote 0
          • H
            Helper
            last edited by

            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")

            1 Reply Last reply Reply Quote 0
            • H
              Helper
              last edited by

              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.

              1 Reply Last reply Reply Quote 0
              • H
                Helper
                last edited by

                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.

                1 Reply Last reply Reply Quote 0
                • H
                  Helper
                  last edited by

                  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.

                  1 Reply Last reply Reply Quote 0
                  • H
                    Helper
                    last edited by

                    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.

                    1 Reply Last reply Reply Quote 0
                    • First post
                      Last post