FR: Components can receive mouseEnter with pressed button

In some occasions it is necessary to receive mouseEnter and mouseExit events regardless if the button is pressed or not. So please add the following feature:

Add a flag to Component named similar: receivesMouseEnterExitWhileButtonPressed (bool enter, bool exit). Default should be false to keep current behaviour.

A usecase is given in this thread: Toggle buttons when swiping over a row

1 Like

I actually think the best way to implement this would be with the same trick that I’ve been meaning to add for a while, i.e. a new mouse method called mouseCancel(), and a mechanism by which you can steal the mouse from the currently-dragged component. The main motivator for adding this would be for gesture detection to be able to handle things like someone pressing a button, but then spotting that the movement is actually a swipe, and stealing the input from that button without it triggering a mouseUp. In your case, a group of buttons could watch for when the mouse is dragged outside themselves onto another button, and then relinquish the event, allowing the other one to take over.

4 Likes

Thanks @jules, that sounds like a sensible workflow.
But do I get that right, that it is not possible currently?

If I would do this:

void mouseMoved (const MouseEvent& e)
{
    if (!getLocalBounds().contains (e.getPosition())
        e.cancelDrag();
}

Would that automatically create a mouseExit event? And a mouseEnter to the new component?

What about the mouseUp and mouseDown? They would probably not occur then, but maybe that is a good thing?

Yeah, it’s not possible at the moment, it’s something that would need to be added to the MouseInputSource.

Is there any chance of something like this being added in the near future? I’m trying to make a row of sliders that can be all set at once by a long mouse drag.

1 Like

Well never mind I found a solution that seems to work for me. I created a container my sliders are added to which covers them with a component. This cover gets all the mouse events and then checks where to send them based on event position. If component borders are crossed, additional mouseDown and mouseUp calls are created. This works with the Juce Slider class.

 /*
==============================================================================

DragDistributionContainer.h
Created: 21 May 2019 9:55:15am
Author:  adrianpf

==============================================================================
*/

#pragma once

#include "JuceHeader.h"

class DragDistributionContainer : public Component {
public:
	DragDistributionContainer() {
		cover.addMouseListener(this, true);
		cover.setAlwaysOnTop(true);
		addAndMakeVisible(&cover);
	}

	virtual ~DragDistributionContainer() {}

	void resized() override {
		cover.setBounds(getLocalBounds());
	}

	void mouseMove(const MouseEvent& event) override {  
		if (auto* c = getComponentAtExcludingCover(event.getPosition())) {
			c->mouseMove(event.getEventRelativeTo(c));
		}
	}

	void mouseDown(const MouseEvent& event) override { 
		if (auto* c = getComponentAtExcludingCover(event.getPosition())) {
			c->mouseDown(event.getEventRelativeTo(c));
			dragComponent = c;
		}
	}

	void mouseDrag(const MouseEvent& event) override {  
		if (auto* c = getComponentAtExcludingCover(event.getPosition())) {
			if (c != dragComponent) {
				// switch drag components
				if (dragComponent) {
					dragComponent->mouseUp(event.getEventRelativeTo(dragComponent));
					dragComponent->mouseExit(event.getEventRelativeTo(dragComponent));
				}
				c->mouseEnter(event.getEventRelativeTo(c));
				c->mouseDown(event.getEventRelativeTo(c));
				dragComponent = c;
			}
		} 
		if (dragComponent) dragComponent->mouseDrag(event.getEventRelativeTo(dragComponent));
	}

	void mouseUp(const MouseEvent& event) override {  
		if (auto* c = getComponentAtExcludingCover(event.getPosition())) {
			c->mouseUp(event.getEventRelativeTo(c));
		}
		dragComponent = nullptr;
	}

	void mouseWheelMove(const MouseEvent& event, const MouseWheelDetails& wheel) override {
		if (auto* c = getComponentAtExcludingCover(event.getPosition())) {
			c->mouseWheelMove(event.getEventRelativeTo(c), wheel);
		}
	}

	void mouseDoubleClick(const MouseEvent& event) override {
		if (auto* c = getComponentAtExcludingCover(event.getPosition())) {
			c->mouseDoubleClick(event.getEventRelativeTo(c));
		}
	}

private:
	Component* getComponentAtExcludingCover(Point<int> pt) {
		for (auto* c : getChildren()) {
			if (c != &cover) {
				if (c->getBounds().contains(pt)) {
					return c;
				}
			}
		}
		return dragComponent; 
	}

	WeakReference<Component> dragComponent;
	Component cover;
};
1 Like

That’s a great solution, thanks for sharing!

I happy it was so easy to hack something together. This doesn’t account for z-Order and if the components need keyboard events, that would have to be added.

I fixed some issues the initial version had and also used it with Buttons. It only works if the Button is set to toggle on mouseDown using setTriggeredOnMouseDown(). The default Button logic prevents dragging onto the button from working.