Trying to implement a knob that snaps to the center

As the topic says, I’m trying to create a Slider subclass that “catches” the knob when reaching the center position.

I expected it to simply be a matter of subclassing Slider and overriding mouseDrag() or something like that, but it seems that I was mistaken… The original implementation is using a lot of states that are hidden privately inside the Pimpl class, for instance valueWhenLastDragged, mouseDragStartPos, etc… Does this mean I have to rewrite mouseDown() and mouseUp() as well, and add my own variables to keep track of everything? I would have preferred to override as few methods as possible.

Or is there another, smarter way to accomplish this in Juce?

Cheers!

Can you better explain how you want the end-user experience to behave? You can simply check the value in your Listener and when it’s within a certain range change the value??

Rail

What I want to do is pretty standard in many GUIs: When the user drags a center-snap-enabled knob across its center position, the knob will pause for a short while, and then start moving again once the user has dragged a few pixels more. The purpose is to make it easier for the user to set the center position of a bipolar knob (a pan pot, for instance), without having to use any modifier keys.

Implementing the function itself is not hard – I already have a working prototype. I guess what I’m after is some advice about “best practices” when extending the built-in components in Juce. There seems to be so many internal dependencies, so replacing one small piece often causes something else to break. For instance, the tooltip popup is not working 100% in my version yet.

What about creating a dead zone inside Slider::proportionOfLengthToValue() and Slider::valueToProportionOfLength() ?

Something like:

class CenterSlider : public Slider
{
public:
	CenterSlider(){}
	~CenterSlider(){}
	
	double proportionOfLengthToValue(double proportion) override
	{
		if(proportion >= 0.4 && proportion < 0.6)
			proportion = 0.5;
		return Slider::proportionOfLengthToValue(proportion);
	}
	
	double valueToProportionOfLength(double value) override
	{
		if(value >= 0.4 && value < 0.6)
			value = 0.5;
		return Slider::valueToProportionOfLength(value);
	}
};

Ah, very cool, thank you so much! :smile:

I had to modify it a little bit to make it behave more like I wanted:

const double kSnapDistance = 0.05;
const double kLowerSnap = 0.5 - kSnapDistance;
const double kUpperSnap = 0.5 + kSnapDistance;

double proportionOfLengthToValue(double proportion) override
{
	double value;
	
	if (proportion <= kLowerSnap) {
		value = proportion * 0.5 / kLowerSnap;
		
	} else if (proportion >= kUpperSnap) {
		value = 0.5 + (proportion - kUpperSnap) * 0.5 / kLowerSnap;
	} else {
		value = 0.5;
	}

	return value;
}

double valueToProportionOfLength(double value) override
{
	double proportion;
	
	if (value < 0.5) {
		proportion = value * kLowerSnap / 0.5;
		
	} else if (value > 0.5) {
		proportion = kUpperSnap + (value - kUpperSnap) * kLowerSnap / 0.5;
		
	} else {
		proportion = 0.5;
	}
	
	return proportion;
}

Now it behaves perfectly, since you can set values 0.0…0.5 by moving the knob from 0.0…0.45, and values 0.5…1.0 by moving the knob from 0.55…1.0, and the knob positions 0.45…0.55 will snap the value to 0.5!

By not calling the superclass I lose support for skew, but I wasn’t planning of using that anyway. :slight_smile:

Thanks again!

Tagging @jules here because this might be a case where having the possibility to specify the behavior with lambdas would be preferred over inheritance

Yes, it’d certainly be a good place for some lambdas!

1 Like