MouseEnter for a Component and its children! SOURCE CODE


#1

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!
};

#2

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 ?

#3

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.

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.

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!


#4

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.


#5

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?


#6

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?


#7

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.


#8

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.

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.


#9

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();
		
	}
}

#10

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


#11

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.


#12

Hi.

[quote=“gekkie100”]check the mouse position to determine wether a mouseExit really exited the component in question instead of one it’s childs.
[/quote]

you are right and This is getting right things but making bad thing… It makes twice call every mouse events …
Because mouseListener is twice registered with any component by this method… As Component is already registered once with mouseListener…

[quote=“X-Ryl669”]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.
[/quote]

Can you please elaborate how can we get it implemented, for one parent which has 3-5 child component inside and each child has some button kinda components also inside… If i want to get my each 3-5 child want only their Enter and Exit, how can we get that ? Thanks

@ TheVinn
Thanks for your work, it is giving me right result with avoiding multiple mouse related events. Robust mouse Enter and Exit i can get, If any improvement you provided in this please also update us.


#13

@acn: Please elaborate about what you exactly want to achieve.
If you want to know when the mouse enters a child component, there is nothing to do except using mouseEnter/Exit on that child component.
If you need to know whether the mouse is still inside a parent component, you need to register a mouse listener for all your child component, and check in your parent::mouseExit if the mouse position is still inside your active area. If it’s the case, you don’t react, else you act like if the mouse just leaved the parent component area.


#14

What here implementing is:
One parent component have some child components inside it, and every child component has other button like component which is painted in every corner of that child on that child’s mouseEnter and mouseExit.

I got you that when you saying check in parent mouseExit whether particular child is exited out of mouse or not ?
But i can not use setMouseIntercept(false, false) for child as i need some thing to do on mouse move and other mouse events.
And childComponent’s edge is exact edge of parentComponent. So when mouse goes very fast out of child(and same time at parent’s exit too), none(either child or parent’s) of mouseExit is called. child has also problem that when mouse go on such buttons which are in corners of that child, it can also call mouseExit for that child.

So here need is: getting robust mouseExit for that each child with some buttons in it and those children are in one parent.


#15

Currently, you’ll get a mouseExit for every mouseEnter.
If you think you are “loosing” mouseExit, then there are probably other issues in your design.
What I was saying, is not to use setMouseIntercept, but instead register children’s mouseListener inside parent’s listener.
This means that for every mouse operation, the parent will be called in addition to the children.
It’s then you responsibility to deal with such (bad) mouse events, you will probably ignore most of them.
But you’ll have to take mouseExit into account in all case, by doing something special like this (pseudo code):

parent::mouseExit(...)
{
   // Check if the mouse cursor is inside our area (we don't actually care about the MouseEvent itself)
   if (hitText(Mouse::getCurrentPosition()))
   {
      // Inside our area, so ignore it
   }
   else
   {
      // Mouse got out, let do something
      hideYourChildComponentsOrWhatever();
   }
}

#16

register children’s mouseListener inside parent’s listener means i should forward my mouseEvent from child to parent, right ? Yeah i tried that, but that involves lot of child things to bring in parent. I tried above thing but in the case i am explaining it’s not working.

I simply need that, when
A component’s mouseExit is called when exactly mouse goes out of that component not when it goes on button inside it, So problem here is it get mouseExit even if mouse goes over the button inside that component itself.
And that button is exactly in bottom right corner of that component, So that makes mouseExit invisible of component’s exit.

Thanks X-Ryl669 for your help and listening to me :wink:


#17

No.
Every mouse event (in fact, every pointer event) goes through a filtering system. The system asks this:

  1. Is the current component concerned in the event (usually through the hitTest() method) ?
  2. Does it want it (by looking if any mouse listener is registered for this component) ?

Currently, the component registers themselves as mouse’s Listener, so you don’t see this. This doesn’t mean you can’t register any other component’s Listener and this is what I’m talking about here.
The code should read:

parent::parent()
{
     child1 = new WhateverComponent();
     addAndMakeVisible(child1);

     // Add the child mouse listener to our parent:
    addMouseListener(child1, true);
}

What happens from now, is that in parent class you’ll receive event for the parent class BUT also those of the child1. AND the child1 will still receive its events too.


#18

All of the concepts and code I wrote about regarding treating a Component and its children as a single entity for the purposes of detecting mouse enter and mouse exit, are now in the class MouseEnterGroup in VFLib:


#19

In YourParentView.h

void mouseEnter (const MouseEvent&);
In YourParentView.cpp (find the custom constructor place)

//[Constructor] You can add your own custom stuff here.. addMouseListener(this, true);
Then add the mouseEnter implementation

void YourParentView::mouseEnter (const MouseEvent&e) { if (e.eventComponent == yourChildComponent) { /* do your stuff, the mouse just entered yourChildComponent*/ } }