change param from code: how to disable gui?

Hi,

I’m changing Knob from DSP code this way, as suggested by @Vortico time ago:

paramQuantity->setValue(newParamValue);

It seems work pretty well, except I could “move” the knob from GUI (even if that has not effect, since the next process() will overwrite the value changed from GUI).

So basically its a weird behaviour on UX side; I’d like to totally avoid draggable knob while its piloted by dsp code. Is there a way to do this? ParamWidget seems doesn’t have a “isDragging” bool which I can atomically set to false.

Should I work on dirty/fb?

Thanks

When I get back to my workstation to verify, I"ll follow up.

If I recall correctly, the standard Rack widgets have no defined mechanism to enable/disable the ui.

I have a need for something similar (disable controls until an external device is ready), and I built my own widgets to do that.

A solution maybe could be to add a Widget Layer over the Param?

Tried with a TransparentWidget, but it seems I can move/use the Knob as well, even if it should not passing event to what is “below”:

paramWidget->addChild(new UnclickableLayer(paramWidget->box.size));

struct UnclickableLayer : TransparentWidget {
	UnclickableLayer(Vec size, Vec pos = {0.0f, 0.0f}) {
		box.size = size;
		box.pos = pos;
	}

	void draw(const DrawArgs &args) override {
		// background
		nvgBeginPath(args.vg);
		nvgRect(args.vg, 0, 0, box.size.x, box.size.y);
		nvgFillColor(args.vg, nvgRGBA(255, 0, 0, 128));
		nvgFill(args.vg);
		nvgClosePath(args.vg);
	};
};

Which is very weird :thinking:

I think for that to work the knob would need to be a child of the transparent widget

I haven’t tried this, but perhaps if you set the knobs speed to 0 that would freeze it in place?

https://vcvrack.com/docs-v2/structrack_1_1app_1_1Knob#ae929a0da4fe36db0d9ec0bd66a461d25

If I place the knob as child of that UnclickableLayer, I can’t cover the knob with some layer/color on top… (such as opacity layer which grey out the knob color below it)

The TransparentWidget says:

A Widget that does not respond to events and does not pass events to children

So it seems correct that just covering another node does not prevent it from getting events.

But once an event has been prevented from propagating, no other children will receive it.

So, could you create a tree that has TransparentWidget > custom grey widget > knob widget

This way the knob is still not getting the events but the grey layer is still on top of it?

It can be “ok” for this case: I don’t want any action over the knob.

Probably I need somethings like this instead TransparentWidget > knob widget > custom grey widget, where grey is on top of knob. Like adding a “text” over a knob… But as said above, not sure why covering a TransparentWidget won’t do the task :frowning:

Because covering a widget does not make that widget a child, it just means that the two widgets draw themselves in the same place.

Normally when an event occurs on a widget, it will optionally do something with it, then it will propagate that event on to its children, and then each child widget will do that same thing. Therefore the event travels down the tree. (or I guess up the tree depending on which way you look at it)

The TransparentWidget only blocks events that occur upon it, from being propagated to its own children.

So to make use of this you must do TransparentWidget->addChild(OtherWidget), and then OtherWidget will not get events that occur on TransparentWidget

That child widget does not have to be the Knob itself, it could be any other widget that has the Knob as one of its children. Because the child does not get the event, it has no way to pass it on. So all widgets on the tree below the TransparentWidget will not get events from the TransparentWidget parent…

Does that make sense (or have I made it worse :face_with_spiral_eyes:)

IOW You have to create a derived class. It cannot be acheived by simply positioning existing widgets.

The derived class can layer a transparent widget, but then it must switch that child in and out or do sizing trickery for the enabled/disabled states. The alternative is to override input handling to filter propagation based on the enabled state.

It makes sense to provide a visual indication of disabled state, otherwise the knob is just mysteriously dead.

Also consider providing tip text to indicate when it’s disabled and why.

1 Like

I just tried this and it works for me

    Knob *disabledKnob = createParamCentered<Knob>(Vec(10.0f, 10.0f)), module, Module::PARAM);
    disabledKnob->speed = 0.0f;
    addParam(disabledKnob);

And what’s the speed to set when you want to re-enable it?

1.f apparently :wink:

image

Feels a little hacky.

Does this value change when ctrl and shift is held when dragging?

You also need to consider value changes by right clicking and editing.

Also, what if I want to disable a button/switch? There is no speed param there…

Maybe better to simply not use a param’s value in yor DSP code. If you want to freeze a value, then just reset it to that value whenever you’re processing control input.

Well, if you want something that you can use for any control, then like Paul said, you are going to have to roll your own. I would think that most devs would end up doing this eventually…

There are any number of ways to achieve what you want, and probably most will have both Pros and Cons.

The reason I suggested using the speed parameter is, when I want to create something complex, I follow a simple process:

  • actually start making it
  • make it do the thing that you want
  • test it and see if it fulfills your initial requirements
  • only then, clean up and optimize

Otherwise I typically find myself wasting time on something that didn’t need to block me.

In my plugin, the original modules all have a context menu function that switches the knobs from being attenuvertors to simply following the CV input, which makes the knobs move by themselves. I think is is similar to your issue? But I don’t disable the knobs from user input, I can imagine many a UXer telling me that is bad design…

Anyway, you probably need to write your own custom Knob struct (or possibly some sort of decorator style struct that you can compose with any GUI control) to achieve what you want, but maybe just use the speed param for now to see if it actually fulfills your requirements?

Tried your suggestion about TransparentWidget “before” the param. Here’s my attempt:

ParamWidget *myParam = getParam(16); // pointer to the knob
myParam->addChildBottom(new UnclickableLayer(myParam->box.size));

I can clearly see UnclickableLayer before the param, but I can still move knob and such, so it seems is passing event as well even if the parent is a TransparentWidget.

Am I missing somethings? :thinking:

That is the wrong way around, it is the parent widget that sends or does not send the event to the child.

You should add the Param widget as a child of the TransparentWidget, this makes the UnclickableLayer the parent and the myParam the child.

I don’t know the exact draw order off the top of my head (and possibly you can affect this by calling ModuleWidget::draw(args); at different times?)

But if the parent does not draw “on top” of the child widget, then you might need to add another widget to draw the grey layer

Surge has deactivatble knobs. (Hook up an audio input to the input of the ring modulator in 2.2.2 and the frequency and detune knobs draw no handle, no ring, and respond to no mouse events).

There’s the code I used to do it.

1 Like