Custom knobs with mouse-over and on-click?

Hello! I’d like to make some custom knobs that support on-click and on-hover, but I don’t know where to start. Could someone point me in the right direction? Thanks!

Hi,

probably start here - VCV Rack API: rack::app::Knob Struct Reference and then deriving your Knob variant from rack::app::Knob (or rack::app::SvgKnob) and implement the onHover and onAction methods? And also have a look into the Rack source code and look for knobs that are derived from the Knob base for implementation examples.

1 Like

Thanks for the friendly response! Deriving from RoundBlackKnob got me started:

Here’s how I added the knob to the panel:

addParam(createParamCentered<EngineKnob>(mm2px(Vec(50,80)), module, MyModule::ENGINE_SELECT_KNOB));`

And here’s the knob’s code:

struct EngineKnob : RoundBlackKnob
{
  void onButton(const event::Button &e) override
  {
    if(e.button == GLFW_MOUSE_BUTTON_LEFT && e.action == GLFW_PRESS)
    {
      DEBUG("I was clicked");
      e.consume(this);
    }
  }
};

This works so far. Next, I’d like to do something in the main module code when the click event happens.

I was thinking that the approach might be to send in a pointer to the module through the knob’s constructor, but I don’t know what that syntax would look like. I’m also worried about a circular dependency. Any hints?

The knob already has a reference to module. It exends ParamWidget which has one. You just need to cast it to your module.

1 Like

This worked so far! Thanks a ton!

1 Like

I literally learn that information 3 hours before you asked :slight_smile:

Hmm. I’m still having some trouble with the implementation.

struct ParameterKnob : RoundBlackKnob
{
  unsigned int lcd_focus = LCD_NO_FOCUS_CHANGE;
  unsigned int parameter_number = 0;
  MyModule *module = dynamic_cast<MyModule*>(this->module);

  void onButton(const event::Button &e) override
  {
    DEBUG("button clicked");

    if(module)
    {
      DEBUG("module is defined"); // this code is never reached

      if(e.button == GLFW_MOUSE_BUTTON_LEFT && e.action == GLFW_PRESS)
      {
        module->selectParameter(parameter_number);
        DEBUG("parameter selected");
      }
    }

    e.consume(this);  // DO I need this?
  }
};

Sometimes the code crashes when I touch a knob, but not always. if(module) is never true, either.

@Patheros Do you happen to have some working code that you could share?

move the cast into onButton, I don’t think it will work up top like that.

this->module isn’t initialized until after the knob is created, so I think its pointing at random memory which I think would cause the inconsistent crashes.

Try something like this…

struct ParameterKnob : RoundBlackKnob
{
  unsigned int lcd_focus = LCD_NO_FOCUS_CHANGE;
  unsigned int parameter_number = 0;

  void onButton(const event::Button &e) override
  {
    DEBUG("button clicked");
    MyModule *module = dynamic_cast<MyModule*>(this->module);
    DEBUG("module casted");
    if(module)
    {
      DEBUG("module is defined"); // this code is never reached

      if(e.button == GLFW_MOUSE_BUTTON_LEFT && e.action == GLFW_PRESS)
      {
        module->selectParameter(parameter_number);
        DEBUG("parameter selected");
      }
    }

    e.consume(this);  // DO I need this?
  }
};

This post has another example of working code

I also see your comment about e.consume(). I’ve not used that myself but my understanding is consuming an event like a button press prevents other elements from receiving it, like widgets overlapped. So I’m guessing you probably do not want to do e.consume() in your case.

There is also the notion of calling onButton on the parent. Which would be “RoundBlackKnob::onButton(e);” in your example. Call this allows the basic RoundBlackKnob to do its normal behavior onButton. Unless youre trying to prevent the knob from working, you probably want to call RoundBlackKnob::onButton(e); somewhere in there.

Ok! We’re getting somewhere! The code doesn’t crash now. However, I’m still having issues with with module pointer being null. In the following code, the if(module) conditional is always false. Any suggestions?


struct ParameterKnob : RoundBlackKnob
{
  unsigned int lcd_focus = LCD_NO_FOCUS_CHANGE;
  unsigned int parameter_number = 0;
  MyModule *module = dynamic_cast<MyModule*>(this->module);

  void onButton(const event::Button &e) override
  {
    DEBUG("button clicked");
    if(module)
    {
      DEBUG("module is defined");
      if(e.button == GLFW_MOUSE_BUTTON_LEFT && e.action == GLFW_PRESS)
      {
        module->selectParameter(parameter_number);
        DEBUG("parameter selected");
      }
    }
    RoundBlackKnob::onButton(e);
  }
};

I think moving this line

MyModule *module = dynamic_cast<MyModule*>(this->module);

inside onButton will fix that.

2 Likes

Wow, that worked! :smile: :trophy: Thanks a ton, Andrew!

1 Like

@Patheros This is a bit off-topic, but would you know how to pass data into the custom button’s constructor, or how to set member variables after instantiation?

Here’s how I’m adding the knob to the module’s widget:

addParam(createParamCentered<ParameterKnob>(mm2px(Vec(10,10)), module, MyModule::ENGINE_PARAMS + 1));

I have a variable called “parameter_number” in the custom button’s structure that I need to set:

struct ParameterKnob : RoundBlackKnob
{
  unsigned int lcd_focus = LCD_NO_FOCUS_CHANGE;
  unsigned int parameter_number = 0;  // <=============== I NEED TO SET THIS!
  etc...

…but I don’t know the syntax to use. Sorry for all of the questions!

		ParameterKnob* parameterKnob = createParamCentered<ParameterKnob >(mm2px(Vec(10,10)), module, MyModule::ENGINE_PARAMS + 1);
		parameterKnob->parameter_number = 10;
		addParam(parameterKnob);

Something like this I think should work

1 Like

It did!! That was a lot easier than I expected!

1 Like