MultiSlider


#1

I’ve been playing with a MultiSlider component which creates multiple Sliders which can be set so that they don’t hold on to the mouse after the mouse down. This allows you to have many sliders and drag across them (like the multislider in MaxMSP and velocity editing in Cubase).

I’m using an empty “interceptor” component which grabs the mouse events and simulates mouse events as the mouse is dragged across the multiple sliders.

It was interesting getting it to work with all the hackery below but perhaps there’s an easier way…?

#include <juce/juce.h>

class ScopedIgnoreMouse
{
public:
	ScopedIgnoreMouse(Component *c) : comp(c)
	{
		comp->setInterceptsMouseClicks(false, false);
	}
	
	~ScopedIgnoreMouse()
	{
		comp->setInterceptsMouseClicks(true, true);
	}
	
private:
	Component *comp;
};

class Interceptor : public Component
{
public:
	Interceptor(Component* _owner) 
	:	owner(_owner),
		mouseIsOver(0),
		mouseIsDownOn(0)
	{ 
		setInterceptsMouseClicks(true, true);
	}
	
	void mouseEnter (const MouseEvent& e)
	{
		ScopedIgnoreMouse m(this);
		mouseMove(e);
	}
	
	void mouseExit (const MouseEvent& e)
	{
		ScopedIgnoreMouse m(this);
		
		if(mouseIsDownOn)
			mouseIsDownOn->mouseUp(e.getEventRelativeTo(mouseIsDownOn));
		
		mouseIsDownOn = 0;
		
		if(mouseIsOver)
			mouseIsOver->mouseExit(e.getEventRelativeTo(mouseIsOver));
		
		mouseIsOver = 0;
	}
	
	void mouseMove (const MouseEvent& e)
	{
		ScopedIgnoreMouse m(this);
		
		Component *comp = owner->getComponentAt(e.x, e.y);
		
		if(comp == mouseIsOver)
		{
			if(mouseIsOver)
				mouseIsOver->mouseMove(e.getEventRelativeTo(mouseIsOver));
		}
		else
		{
			if(mouseIsOver)
				mouseIsOver->mouseExit(e.getEventRelativeTo(mouseIsOver));
			
			mouseIsOver = comp;
			
			if(mouseIsOver)
				mouseIsOver->mouseEnter(e.getEventRelativeTo(mouseIsOver));
		}
	}
	
	void mouseDown (const MouseEvent& e)
	{
		ScopedIgnoreMouse m(this);
		
		if(mouseIsOver)
		{
			mouseIsDownOn = mouseIsOver;
			mouseIsDownOn->mouseDown(e.getEventRelativeTo(mouseIsDownOn));
			
			lastDragX = e.x;
			lastDragY = e.y;
		}
	}
	
	void mouseUp (const MouseEvent& e)
	{
		ScopedIgnoreMouse m(this);
		
		if(mouseIsDownOn)
		{
			mouseIsDownOn->mouseUp(e.getEventRelativeTo(mouseIsDownOn));
			mouseIsDownOn = 0;
		}
	}
	
	void mouseDrag (const MouseEvent& e)
	{
		ScopedIgnoreMouse m(this);
		
		Component *comp = owner->getComponentAt(e.x, e.y);
		
		if(comp == mouseIsDownOn)
		{
			if(mouseIsDownOn)
				mouseIsDownOn->mouseDrag(e.getEventRelativeTo(mouseIsDownOn));
		}
		else
		{
			int deltaX = e.x - lastDragX;
			int deltaY = e.y - lastDragY;
			double incX, incY;
			int steps;
			
			if(deltaX == 0)
			{
				incX = 0;
				incY = e.y > lastDragY ? 1.0 : -1.0;
				steps = abs(deltaY);
			}
			else if(deltaY == 0)
			{
				incX = e.x > lastDragX ? 1.0 : -1.0;
				incY = 0;
				steps = abs(deltaX);
			}
			else if(abs(deltaX) > abs(deltaY))
			{
				incX = e.x > lastDragX ? 1.0 : -1.0;;
				incY = (double)deltaY / abs(deltaX);
				steps = abs(deltaX);
			}
			else
			{
				incX = (double)deltaX / abs(deltaY);
				incY = e.y > lastDragY ? 1.0 : -1.0;
				steps = abs(deltaY);
			}
			
			double x = lastDragX;
			double y = lastDragY;
			
			MouseEvent eventCopy = e;
			
			while(steps--)
			{
				eventCopy.x = (int)x;
				eventCopy.y = (int)y;
				
				Component *comp = owner->getComponentAt(eventCopy.x, eventCopy.y);
				if(comp == mouseIsDownOn)
				{
					if(mouseIsDownOn)
						mouseIsDownOn->mouseDrag(eventCopy.getEventRelativeTo(mouseIsDownOn));
				}
				else
				{
					if(mouseIsDownOn)
					{
						mouseIsDownOn->mouseUp(eventCopy.getEventRelativeTo(mouseIsDownOn));
						mouseIsDownOn->mouseExit(eventCopy.getEventRelativeTo(mouseIsDownOn));
					}
					
					mouseIsDownOn = comp;
					
					if(mouseIsDownOn)
					{
						mouseIsDownOn->mouseEnter(eventCopy.getEventRelativeTo(mouseIsDownOn));
						mouseIsDownOn->mouseDown(eventCopy.getEventRelativeTo(mouseIsDownOn));
					}
				}
				
				x += incX;
				y += incY;
			}
		}
		
		lastDragX = e.x;
		lastDragY = e.y;
	}	
	
private:
	Component* const owner;
	Component* mouseIsOver;
	Component* mouseIsDownOn;
	int lastDragX, lastDragY;
};


class MultiSlider : public Component
{
public:
	MultiSlider(const int numSliders = 1, 
				const bool canDragAcross = true, 
				const bool slidersAreHorizontal = true) throw()
	:	interceptor(0), horizontal(true)
	{
		setNumSliders(numSliders, canDragAcross, slidersAreHorizontal);
	}
	
	~MultiSlider()
	{
		deleteAllChildren();
	}
	
	int getNumSliders() const throw() { return sliders.size(); }
	
	void setNumSliders(const int numSliders, 
					   const bool canDragAcross = true,
					   const bool slidersAreHorizontal = true) throw()
	{
		if(numSliders != sliders.size())
		{
			deleteAllChildren();
			sliders.clear();
			interceptor = 0;
			
			horizontal = slidersAreHorizontal;
			
			for(int i = 0; i < numSliders; i++)
			{
				Slider *slider = new Slider(String("Multislider")<<i);
				slider->setSliderStyle(horizontal ? Slider::LinearHorizontal : Slider::LinearVertical);
				slider->setTextBoxStyle(Slider::NoTextBox, 0,0,0);
				slider->setRange(0.0, 1.0, 0.0);
				addAndMakeVisible(slider);
				sliders.add(slider);
			}
			
			if(canDragAcross)
				addAndMakeVisible(interceptor = new Interceptor(this));
			
			return;
		}
		
		if(canDragAcross == true && interceptor == 0)
		{
			addAndMakeVisible(interceptor = new Interceptor(this));
		}
		else if(canDragAcross == false && interceptor != 0)
		{
			removeChildComponent(interceptor);
			deleteAndZero(interceptor);
		}
		
		if(slidersAreHorizontal != horizontal)
		{
			horizontal = slidersAreHorizontal;
			
			for(int i = 0; i < sliders.size(); i++)
			{
				sliders[i]->setSliderStyle(horizontal ? Slider::LinearHorizontal : Slider::LinearVertical);
			}
		}
		
	}
	
	void resized()
	{
		if(interceptor)
			interceptor->setBounds(0, 0, getWidth(), getHeight());
		
		if(horizontal)
		{
			const float sliderHeight = (float)getHeight() / sliders.size();
			float y = 0.f;
			for(int i = 0; i < sliders.size(); i++)
			{
				sliders[i]->setBounds(0, roundFloatToInt(y), getWidth(), sliderHeight);
				y += sliderHeight;
			}
		}
		else
		{
			const float sliderWidth = (float)getWidth() / sliders.size();
			float x = 0.f;
			for(int i = 0; i < sliders.size(); i++)
			{
				sliders[i]->setBounds(roundFloatToInt(x), 0, sliderWidth, getHeight());
				x += sliderWidth;
			}
		}
	}
	
	Slider* getSlider(const int index) 
	{
		return sliders[index];
	}
		
private:
	Array<Slider*> sliders;
	Interceptor *interceptor;
	bool horizontal;
};


class MainComponent  : public Component      	
{	
public:
	MainComponent ()
	{			
		addAndMakeVisible(sliders = new MultiSlider(30, true, false));
	}
	
	~MainComponent ()
	{
		deleteAllChildren();
	}
	
	void resized()
	{
		sliders->setBounds(10, 10, getWidth()-20, getHeight()-20);
	}
	
private:
	MultiSlider* sliders;
};