MouseEnter for a Component and its children! SOURCE CODE

19 posts / 0 new
Last post
TheVinn
Offline
Last seen: 2 months 2 weeks ago
Joined: 29 Aug 2009 - 11:31
MouseEnter for a Component and its children! SOURCE CODE

If you want to get robust mouse enter and mouse exit for your parent and all of its children treated together as a group, this is the class for you! Just add this class in the list of bases for your Component-derived parent object and you will get onMouseEnterGroup() and onMouseEnterGroup() when the mouse goes in and out of your hierarchy (they are treated as a single object).

// Derive your Component-derived class from this object in order to receive
// mouse enter and mouse exit messages for your component and all of it's
// children.
//
class MouseEnterGroup
{
public:
  explicit MouseEnterGroup (Component* parentComponent)
    : m_parentComponent (parentComponent)
    , m_helper (this)
    , m_mouseInside (false)
    , m_mouseInsideNext (false)
  {
  }

  ~MouseEnterGroup ()
  {
  }

  bool isMouseInsideGroup () const
  {
    return m_mouseInside;
  }

  // Called when the mouse enters or exits the group.
  // The event will be relative to the parent Component.
  //
  virtual void onMouseEnterGroup (MouseEvent const& e) { }
  virtual void onMouseExitGroup (MouseEvent const& e) { }

private:
  void updateState ()
  {
    if (m_mouseInside != m_mouseInsideNext)
    {
      if (m_mouseInsideNext)
      {
        m_mouseInside = true;
        onMouseEnterGroup (getMouseEvent ());
      }
      else
      {
        m_mouseInside = false;
        onMouseExitGroup (getMouseEvent ());
      }
    }
  }

private:
   // HACK because of Juce
  inline void setMouseEvent (MouseEvent const& mouseEvent)
    { memcpy (m_mouseEvent, &mouseEvent, sizeof (mouseEvent)); }
  inline MouseEvent const& getMouseEvent () const
    { return *reinterpret_cast <MouseEvent const*> (m_mouseEvent); }

  class Helper
    : private MouseListener
    , private AsyncUpdater
  {
  public:
    explicit Helper (MouseEnterGroup* const owner)
      : m_owner (*owner)
    {
      m_owner.m_parentComponent->addMouseListener (this, true);
    }

    ~Helper ()
    {
      m_owner.m_parentComponent->removeMouseListener (this);
    }

    void mouseEnter (MouseEvent const& e)
    {
      m_owner.m_mouseInsideNext = true;
      m_owner.setMouseEvent (e.getEventRelativeTo (m_owner.m_parentComponent));
      triggerAsyncUpdate ();
    }

    void mouseExit (MouseEvent const& e)
    {
      m_owner.m_mouseInsideNext = false;
      m_owner.setMouseEvent (e.getEventRelativeTo (m_owner.m_parentComponent));
      triggerAsyncUpdate ();
    }

    void handleAsyncUpdate ()
    {
      m_owner.updateState ();
    }

  private:
    MouseEnterGroup& m_owner;
  };

private:
  Component* const m_parentComponent;
  Helper m_helper;
  bool m_mouseInside;
  bool m_mouseInsideNext;
  char m_mouseEvent [sizeof (MouseEvent)]; // HACK because of Juce!
};
X-Ryl669
Offline
Last seen: 7 months 1 week ago
Joined: 24 Apr 2005 - 17:30
Re: MouseEnter for a Component and its children! SOURCE CODE

Few remarks:
1) WTF man, you're still doing polish / hugarian naming for your variables ? We're no more in MFC era...
2) The hack is very ugly (and, worse, it's prone to break as soon as mouseevent will change to contain anything other than plain old data)
3) You know about Component::setInterceptsMouseClicks right ?

X-Ryl669

TheVinn
Offline
Last seen: 2 months 2 weeks ago
Joined: 29 Aug 2009 - 11:31
Re: MouseEnter for a Component and its children! SOURCE CODE

X-Ryl669 wrote:
1) WTF man, you're still doing polish / hugarian naming for your variables ? We're no more in MFC era...

You're free to rename the variables to whatever you'd like! But I'm not using Hungarian Notation, just the prefix to distinguish class members from local variables ("m_"). Sure, this was borrowed from MFC. I like it.

Quote:
2) The hack is very ugly (and, worse, it's prone to break as soon as mouseevent will change to contain anything other than plain old data)

Well that's your opinion. I think it is an elegant hack. But it is still a hack. If MouseEvent changes I might just remove the parameter entirely.

Quote:
You know about Component::setInterceptsMouseClicks right ?

I sure do! And I wish it worked the way that I needed it to work (or that there was a suitable Juce function). The problem is that you get a mouseExit for the parent and you have no idea that the next mouseEnter is for a child (versus one of the parent's siblings).

This code does exactly what it advertises, it treats the Component and its children as a single object with respect to mouse enter and mouse exit. In other words, if the mouse goes from the parent to a child, it is still considered that the mouse is inside the parent, and no additional enter/exits are generated. It does this while preserving all other Component behaviors, such as clicks, and the traditional mouse enter/exit.

You can complain about it all you want, but this code WORKS, provides functionality that people NEED but is NOT AVAILABLE in the current Juce tip. If the implementation details are not to your liking, you can use the concept to create your own!

dave96
Online
Last seen: 6 min 49 sec ago
Joined: 27 Dec 2008 - 20:29
Re: MouseEnter for a Component and its children! SOURCE CODE

Why do you have to store the mouse event in the first place? Can't you just pass on the event to the listeners synchronously like juce does with normal mouse callbacks?

void updateState (MouseEvent const& e)
  {
    if (m_mouseInside != m_mouseInsideNext)
    {
      if (m_mouseInsideNext)
      {
        m_mouseInside = true;
        onMouseEnterGroup (e);
      }
      else
      {
        m_mouseInside = false;
        onMouseExitGroup (e);
      }
    }
  }

void mouseEnter (MouseEvent const& e)
    {
      m_owner.m_mouseInsideNext = true;
      m_owner.updateState(e.getEventRelativeTo (m_owner.m_parentComponent));
    }
etc.
TheVinn
Offline
Last seen: 2 months 2 weeks ago
Joined: 29 Aug 2009 - 11:31
Re: MouseEnter for a Component and its children! SOURCE CODE

dave96 wrote:
Can't you just pass on the event to the listeners synchronously like juce does with normal mouse callbacks?

Nope. Consider the case of component A, which contains component B. The mouse is inside A, and moves into B. This will be the series of events:

mouseExit (A)
mouseEnter (B)

If we call the listener synchronously, it will get a mouseExit for A. This is incorrect. We want A and B to be considered as a single Component for purposes of receiving this message. To do that, we need to see all of the events before we take action. Using the AsyncUpdater, we defer the processing of mouseEnter and mouseExit messages until all mouse actions have been sent.

Is it necessary to have the MouseEvent parameter at all? Well that is open to question. I suppose one could add additional callbacks that don't take the parameter. Me personally I don't need the MouseEvent but it does provide a certain symmetry to the existing Component:mouseEnter() and Component::mouseExit() calls.

I'm more than happy to hear constructive suggestions about how the hack could be eliminated. Perhaps I should store some basic information and then just re-create the MouseEvent fresh?

TheVinn
Offline
Last seen: 2 months 2 weeks ago
Joined: 29 Aug 2009 - 11:31
Re: MouseEnter for a Component and its children! SOURCE CODE

TheVinn wrote:
Perhaps I should store some basic information and then just re-create the MouseEvent fresh?

I think I can re-create the MouseEvent instead of caching it, but is it safe to remember a pointer to the MouseInputEvent in the MouseEvent, and then use the pointer later to construct a new MouseEvent?

X-Ryl669
Offline
Last seen: 7 months 1 week ago
Joined: 24 Apr 2005 - 17:30
Re: MouseEnter for a Component and its children! SOURCE CODE

TheVinn wrote:

This code does exactly what it advertises, it treats the Component and its children as a single object with respect to mouse enter and mouse exit. In other words, if the mouse goes from the parent to a child, it is still considered that the mouse is inside the parent, and no additional enter/exits are generated. It does this while preserving all other Component behaviors, such as clicks, and the traditional mouse enter/exit.

What I'm doing right now, is simply check if it's one of my child in my component's mouseEnter/Exit. Not that hard in fact.
I didn't want to complain for your valueable work, but, instead give you a few suggestion to improve it.
You can subclass a MouseEvent, and write a copy constructor in there - taking a MouseEvent as parameter-, not calling the base one, and avoid this hack completely.

X-Ryl669

TheVinn
Offline
Last seen: 2 months 2 weeks ago
Joined: 29 Aug 2009 - 11:31
Re: MouseEnter for a Component and its children! SOURCE CODE

X-Ryl669 wrote:
What I'm doing right now, is simply check if it's one of my child in my component's mouseEnter/Exit. Not that hard in fact.

But the parent still gets a mouseExit...how do you know at the time you receive a mouseExit that you are going to get a mouseEnter for a child in the future? If your mouseExit() override for the parent does complicated things (for example, release the focus on an editable text box) they will get triggered.

Quote:
You can subclass a MouseEvent, and write a copy constructor in there - taking a MouseEvent as parameter-, not calling the base one, and avoid this hack completely.

Actually I was thinking to save the fields of the MouseEvent and re-create the MouseEvent later. Jules gave the green light on taking the address of a MouseInputSource and using it later. This will eliminate the hack.

gekkie100
Offline
Last seen: 1 week 6 days ago
Joined: 12 Apr 2005 - 14:35
Re: MouseEnter for a Component and its children! SOURCE CODE

I check the mouse position to determine wether a mouseExit really exited the component in question instead of one it's childs.
You're probably able to do something similar for mouseEnter as well.

addMouseListener(this, true);

void Component::mouseExit (const MouseEvent& e)
{
        //check if mouse position is outside of this component
        //if so the mouse exited this component for real not just a child
 	if (!getScreenBounds().contains(e.getScreenPosition()))
	{
		dosomething();
		
	}
}

AaronLeese
AaronLeese's picture
Offline
Last seen: 1 week 3 days ago
Joined: 5 Feb 2009 - 17:52
Re: MouseEnter for a Component and its children! SOURCE CODE

I dig it.

I would say though, that for many purposes you can use isMouseOver(includeChildren) in the paint routine and get the same effect.

Of course, this only works if you are repainting often (really, you have to be painting on a timer for this to work since mouse Enter/Exits aren't always reliably received).

Aaron Leese
Stagecraft Software
-= new ways for DJs =-
Synths • Plugs • Turntablism / DJ Tech

TheVinn
Offline
Last seen: 2 months 2 weeks ago
Joined: 29 Aug 2009 - 11:31
Re: MouseEnter for a Component and its children! SOURCE CODE

aaronleese wrote:
...mouse Enter/Exits aren't always reliably received...

Hey now! That's only a partial truth. If you get the mouse enter, you will always get the mouse exit. But yes if the mouse moves very quickly through a component, the mouse enter might not get called.

Pages