Custom Switch - How to?

I’m sure that this has been addressed somewhere, but my searching hasn’t come up with an answer yet. Could someone point me to how to implement a custom switch in VCV Rack? I have two .svg files for the two states of the switch, but I don’t have any examples or clear documentation on how to implement the switch. Any help would be much appreciated.

I wouldn’t really recommend it, but I have a two state toggle button that you give two SVG to.

I’m all ears. Why wouldn’t you recommend your code? Do you think that I’m approaching something incorrectly?

You could subclass SvgSwitch and call addFrame for each of your SVGs in the constructor. An example from the component library:

1 Like

Hi Michael,

Ah! That might be a step in the right direction. Why is there…

momentary = true;

I’m looking for a toggle behavoir, so would momentary = false? I really appreciate the code sample.

oh, you know, there’s always something bad… I suspect mine has too much code that’s not needed. But it works for me: SquinkyVCV/ToggleButton.h at main · squinkylabs/SquinkyVCV · GitHub

Indeed!

AH! NEVERMIND! I named my constructor incorrectly. I at least have the button showing up now. I should be good to go for a little while.

=-=-=-=-=-=-

@Squinky appreciate your code a lot. It’s clear that you’ve spent a lot on the foundations of your modules. Me… I’m still hacking things together! Ha ha ha.

I don’t have it working yet but I feel that I’m pretty close. At least it compiles. Here’s what I have. First, I define freezeToggle at the top of my widget file:

struct freezeToggle : app::SvgSwitch {
	void freeze_toggle() {
		addFrame(APP->window->loadSvg(asset::system("res/freeze-button-off.svg")));
    addFrame(APP->window->loadSvg(asset::system("res/freeze-button-on.svg")));
	};
};

Then I’m adding the widget to the front panel in the widget constructor like:

    freezeToggle *freeze_toggle = new freezeToggle();
    freeze_toggle->box.pos = mm2px(Vec(5, 5));
    addChild(freeze_toggle);

Here’s the entire code:

class TrimpotNoRandom : public Trimpot
{
public:
  void randomize() override {} // do nothing. base class would actually randomize
};

struct freezeToggle : app::SvgSwitch {
	void freeze_toggle() {
		addFrame(APP->window->loadSvg(asset::plugin(pluginInstance,"res/freeze-button-off.svg")));
    addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/freeze-button-on.svg")));
	};
};

struct DigitalSequencerWidget : ModuleWidget
{
  DigitalSequencer* module;
  int copy_sequencer_index = -1;

  DigitalSequencerWidget(DigitalSequencer* module)
  {
    this->module = module;
    setModule(module);
    setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/digital_sequencer_front_panel.svg")));

    // Cosmetic rack screws
    // addChild(createWidget<ScrewSilver>(Vec(15, 0)));
    addChild(createWidget<ScrewSilver>(Vec(15, 365)));
    addChild(createWidget<ScrewSilver>(mm2px(Vec(171.5, 0))));

    // Main voltage sequencer display
    VoltageSequencerDisplay *voltage_sequencer_display = new VoltageSequencerDisplay();
    voltage_sequencer_display->box.pos = mm2px(Vec(DRAW_AREA_POSITION_X, DRAW_AREA_POSITION_Y));
    voltage_sequencer_display->module = module;
    addChild(voltage_sequencer_display);

    GateSequencerDisplay *gates_display = new GateSequencerDisplay();
    gates_display->box.pos = mm2px(Vec(GATES_DRAW_AREA_POSITION_X, GATES_DRAW_AREA_POSITION_Y));
    gates_display->module = module;
    addChild(gates_display);

    double button_spacing = 9.6; // 9.1
    double button_group_x = 48.0;
    double button_group_y = 103.0;
    // Sequence 1 button
    addParam(createParamCentered<LEDButton>(mm2px(Vec(button_group_x, button_group_y)), module, DigitalSequencer::SEQUENCER_1_BUTTON));
    addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(button_group_x, button_group_y)), module, DigitalSequencer::SEQUENCER_1_LIGHT));
    // Sequence 2 button
    addParam(createParamCentered<LEDButton>(mm2px(Vec(button_group_x + (button_spacing * 1.0), button_group_y)), module, DigitalSequencer::SEQUENCER_2_BUTTON));
    addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(button_group_x + (button_spacing * 1.0), button_group_y)), module, DigitalSequencer::SEQUENCER_2_LIGHT));
    // Sequence 3 button
    addParam(createParamCentered<LEDButton>(mm2px(Vec(button_group_x + (button_spacing * 2.0), button_group_y)), module, DigitalSequencer::SEQUENCER_3_BUTTON));
    addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(button_group_x + (button_spacing * 2.0), button_group_y)), module, DigitalSequencer::SEQUENCER_3_LIGHT));
    // Sequence 4 button
    addParam(createParamCentered<LEDButton>(mm2px(Vec(button_group_x + (button_spacing * 3.0), button_group_y)), module, DigitalSequencer::SEQUENCER_4_BUTTON));
    addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(button_group_x + (button_spacing * 3.0), button_group_y)), module, DigitalSequencer::SEQUENCER_4_LIGHT));
    // Sequence 5 button
    addParam(createParamCentered<LEDButton>(mm2px(Vec(button_group_x + (button_spacing * 4.0), button_group_y)), module, DigitalSequencer::SEQUENCER_5_BUTTON));
    addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(button_group_x + (button_spacing * 4.0), button_group_y)), module, DigitalSequencer::SEQUENCER_5_LIGHT));
    // Sequence 6 button
    addParam(createParamCentered<LEDButton>(mm2px(Vec(button_group_x + (button_spacing * 5.0), button_group_y)), module, DigitalSequencer::SEQUENCER_6_BUTTON));
    addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(button_group_x + (button_spacing * 5.0), button_group_y)), module, DigitalSequencer::SEQUENCER_6_LIGHT));

    // addParam(createParamCentered<Trimpot>(mm2px(Vec(button_group_x, button_group_y + 8.6)), module, DigitalSequencer::SEQUENCER_1_LENGTH_KNOB));

    auto L1 = createParamCentered<TrimpotNoRandom>(mm2px(Vec(button_group_x, button_group_y + 8.6)), module, DigitalSequencer::SEQUENCER_1_LENGTH_KNOB); dynamic_cast<Knob*>(L1)->snap = true; addParam(L1);
    auto L2 = createParamCentered<TrimpotNoRandom>(mm2px(Vec(button_group_x + (button_spacing * 1.0), button_group_y + 8.6)), module, DigitalSequencer::SEQUENCER_2_LENGTH_KNOB); dynamic_cast<Knob*>(L2)->snap = true; addParam(L2);
    auto L3 = createParamCentered<TrimpotNoRandom>(mm2px(Vec(button_group_x + (button_spacing * 2.0), button_group_y + 8.6)), module, DigitalSequencer::SEQUENCER_3_LENGTH_KNOB); dynamic_cast<Knob*>(L3)->snap = true; addParam(L3);
    auto L4 = createParamCentered<TrimpotNoRandom>(mm2px(Vec(button_group_x + (button_spacing * 3.0), button_group_y + 8.6)), module, DigitalSequencer::SEQUENCER_4_LENGTH_KNOB); dynamic_cast<Knob*>(L4)->snap = true; addParam(L4);
    auto L5 = createParamCentered<TrimpotNoRandom>(mm2px(Vec(button_group_x + (button_spacing * 4.0), button_group_y + 8.6)), module, DigitalSequencer::SEQUENCER_5_LENGTH_KNOB); dynamic_cast<Knob*>(L5)->snap = true; addParam(L5);
    auto L6 = createParamCentered<TrimpotNoRandom>(mm2px(Vec(button_group_x + (button_spacing * 5.0), button_group_y + 8.6)), module, DigitalSequencer::SEQUENCER_6_LENGTH_KNOB); dynamic_cast<Knob*>(L6)->snap = true; addParam(L6);

    // 6 step inputs
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(button_group_x, button_group_y + 18.0)), module, DigitalSequencer::SEQUENCER_1_STEP_INPUT));
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(button_group_x + (button_spacing * 1.0), button_group_y + 18.0)), module, DigitalSequencer::SEQUENCER_2_STEP_INPUT));
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(button_group_x + (button_spacing * 2.0), button_group_y + 18.0)), module, DigitalSequencer::SEQUENCER_3_STEP_INPUT));
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(button_group_x + (button_spacing * 3.0), button_group_y + 18.0)), module, DigitalSequencer::SEQUENCER_4_STEP_INPUT));
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(button_group_x + (button_spacing * 4.0), button_group_y + 18.0)), module, DigitalSequencer::SEQUENCER_5_STEP_INPUT));
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(button_group_x + (button_spacing * 5.0), button_group_y + 18.0)), module, DigitalSequencer::SEQUENCER_6_STEP_INPUT));

    // Step
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(10, 114.893)), module, DigitalSequencer::STEP_INPUT));

    // Reset
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(10 + 14.544, 114.893)), module, DigitalSequencer::RESET_INPUT));



    // 6 sequencer outputs
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(118, 108.224)), module, DigitalSequencer::SEQ1_CV_OUTPUT));
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(129, 108.224)), module, DigitalSequencer::SEQ2_CV_OUTPUT));
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(140, 108.224)), module, DigitalSequencer::SEQ3_CV_OUTPUT));
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(151, 108.224)), module, DigitalSequencer::SEQ4_CV_OUTPUT));
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(162, 108.224)), module, DigitalSequencer::SEQ5_CV_OUTPUT));
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(173, 108.224)), module, DigitalSequencer::SEQ6_CV_OUTPUT));

    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(118, 119.309)), module, DigitalSequencer::SEQ1_GATE_OUTPUT));
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(129, 119.309)), module, DigitalSequencer::SEQ2_GATE_OUTPUT));
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(140, 119.309)), module, DigitalSequencer::SEQ3_GATE_OUTPUT));
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(151, 119.309)), module, DigitalSequencer::SEQ4_GATE_OUTPUT));
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(162, 119.309)), module, DigitalSequencer::SEQ5_GATE_OUTPUT));
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(173, 119.309)), module, DigitalSequencer::SEQ6_GATE_OUTPUT));

    // addChild(createWidget<freezeToggle>(Vec(15, 5)));

    freezeToggle *freeze_toggle = new freezeToggle();
    freeze_toggle->box.pos = mm2px(Vec(5, 5));
    addChild(freeze_toggle);
  }


  // Sample and Hold values
  struct SampleAndHoldItem : MenuItem {
    DigitalSequencer *module;
    int sequencer_number = 0;

    void onAction(const event::Action &e) override {
      module->voltage_sequencers[sequencer_number].sample_and_hold ^= true; // flip the value
    }
  };

  //
  // INPUT SNAP MENUS
  //

  struct InputSnapValueItem : MenuItem {
    DigitalSequencer *module;
    int snap_division_index = 0;
    int sequencer_number = 0;

    void onAction(const event::Action &e) override {
      module->voltage_sequencers[sequencer_number].snap_division_index = snap_division_index;
    }
  };

  struct InputSnapItem : MenuItem {
    DigitalSequencer *module;
    int sequencer_number = 0;

    Menu *createChildMenu() override {
      Menu *menu = new Menu;

      for (unsigned int i=0; i < NUMBER_OF_SNAP_DIVISIONS; i++)
      {
        InputSnapValueItem *input_snap_value_item = createMenuItem<InputSnapValueItem>(module->snap_division_names[i], CHECKMARK(module->voltage_sequencers[sequencer_number].snap_division_index == i));
        input_snap_value_item->module = module;
        input_snap_value_item->snap_division_index = i;
        input_snap_value_item->sequencer_number = this->sequencer_number;
        menu->addChild(input_snap_value_item);
      }

      return menu;
    }
  };

  //
  // OUTPUT RANGE MENUS
  //

  struct OutputRangeValueItem : MenuItem {
    DigitalSequencer *module;
    int range_index = 0;
    int sequencer_number = 0;

    void onAction(const event::Action &e) override {
      module->voltage_sequencers[sequencer_number].voltage_range_index = range_index;
    }
  };

  struct OutputRangeItem : MenuItem {
    DigitalSequencer *module;
    int sequencer_number = 0;

    Menu *createChildMenu() override {
      Menu *menu = new Menu;

      for (unsigned int i=0; i < NUMBER_OF_VOLTAGE_RANGES; i++)
      {
        OutputRangeValueItem *output_range_value_menu_item = createMenuItem<OutputRangeValueItem>(module->voltage_range_names[i], CHECKMARK(module->voltage_sequencers[sequencer_number].voltage_range_index == i));
        output_range_value_menu_item->module = module;
        output_range_value_menu_item->range_index = i;
        output_range_value_menu_item->sequencer_number = this->sequencer_number;
        menu->addChild(output_range_value_menu_item);
      }

      return menu;
    }
  };

  struct SequencerItem : MenuItem {
    DigitalSequencer *module;
    unsigned int sequencer_number = 0;

    Menu *createChildMenu() override {
      Menu *menu = new Menu;

      OutputRangeItem *output_range_item = createMenuItem<OutputRangeItem>("Output Range", RIGHT_ARROW);
      output_range_item->sequencer_number = this->sequencer_number;
      output_range_item->module = module;
      menu->addChild(output_range_item);

      InputSnapItem *input_snap_item = createMenuItem<InputSnapItem>("Snap", RIGHT_ARROW);
      input_snap_item->sequencer_number = this->sequencer_number;
      input_snap_item->module = module;
      menu->addChild(input_snap_item);

      SampleAndHoldItem *sample_and_hold_item = createMenuItem<SampleAndHoldItem>("Sample & Hold", CHECKMARK(module->voltage_sequencers[sequencer_number].sample_and_hold));
      sample_and_hold_item->sequencer_number = this->sequencer_number;
      sample_and_hold_item->module = module;
      menu->addChild(sample_and_hold_item);

      return menu;
    }
  };

  struct ResetOnNextOption : MenuItem {
    DigitalSequencer *module;

    void onAction(const event::Action &e) override {
      module->legacy_reset = false;
    }
  };

  struct ResetInstantOption : MenuItem {
    DigitalSequencer *module;

    void onAction(const event::Action &e) override {
      module->legacy_reset = true;
    }
  };

  struct ResetModeItem : MenuItem {
    DigitalSequencer *module;

    Menu *createChildMenu() override {
      Menu *menu = new Menu;

      ResetOnNextOption *reset_on_next = createMenuItem<ResetOnNextOption>("Next clock input.", CHECKMARK(! module->legacy_reset));
      reset_on_next->module = module;
      menu->addChild(reset_on_next);

      ResetInstantOption *reset_instant = createMenuItem<ResetInstantOption>("Instant", CHECKMARK(module->legacy_reset));
      reset_instant->module = module;
      menu->addChild(reset_instant);

      return menu;
    }
  };

  struct AllSequencersItem : MenuItem {

  };

  void appendContextMenu(Menu *menu) override
  {
    DigitalSequencer *module = dynamic_cast<DigitalSequencer*>(this->module);
    assert(module);

    // Menu in development
    menu->addChild(new MenuEntry); // For spacing only
    menu->addChild(createMenuLabel("Sequencer Settings"));

    // Add "all" sequencer settings
    /*
    AllSequencersItem *all_sequencer_items;
    all_sequencer_items = createMenuItem<AllSequencersItem>("All Sequencers", RIGHT_ARROW);
    menu->addChild(all_sequencer_items);
    */

    // Add individual sequencer settings
    SequencerItem *sequencer_items[6];

    for(unsigned int i=0; i < NUMBER_OF_SEQUENCERS; i++)
    {
      sequencer_items[i] = createMenuItem<SequencerItem>("Sequencer #" + std::to_string(i + 1), RIGHT_ARROW);
      sequencer_items[i]->module = module;
      sequencer_items[i]->sequencer_number = i;
      menu->addChild(sequencer_items[i]);
    }

    // Reset behavior
    /*
    LegacyResetOption *legacy_reset_option = createMenuItem<LegacyResetOption>("Legacy Reset", CHECKMARK(module->legacy_reset));
    legacy_reset_option->module = module;
    menu->addChild(legacy_reset_option);
    */

    ResetModeItem *reset_mode_item = createMenuItem<ResetModeItem>("Reset Mode", RIGHT_ARROW);
    reset_mode_item->module = module;
    menu->addChild(reset_mode_item);
  }

  void step() override {
    ModuleWidget::step();
  }

  //
  // Handler for keypresses that affect the entire module
  //
  void onHoverKey(const event::HoverKey &e) override
  {
      // Switch between seuences using the number keys 1-6
      if (e.key >= GLFW_KEY_1 && e.key <= GLFW_KEY_6)
      {

        if(e.action == GLFW_PRESS)
        {
          unsigned int sequencer_number = e.key - 49;

          // DEBUG(std::to_string(sequencer_number).c_str());
          sequencer_number = clamp(sequencer_number,0,NUMBER_OF_SEQUENCERS-1);
          module->selected_sequencer_index = sequencer_number;
          e.consume(this);
        }

      }

      if ((e.key == GLFW_KEY_F) && ((e.mods & RACK_MOD_MASK) != GLFW_MOD_CONTROL)) // F (no ctrl)
      {
        if(e.action == GLFW_PRESS)
        {
          module->frozen = ! module->frozen;
          e.consume(this);
        }
      }

      if ((e.key == GLFW_KEY_C) && ((e.mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL)) // Control-C
      {
        if(e.action == GLFW_PRESS)
        {
          copy_sequencer_index = module->selected_sequencer_index;
          e.consume(this);
        }
      }

      if ((e.key == GLFW_KEY_V) && ((e.mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL)) // Control-V
      {
        if(e.action == GLFW_PRESS)
        {
          if(copy_sequencer_index > -1)
          {
            module->copy(copy_sequencer_index, module->selected_sequencer_index);
            e.consume(this);
          }
        }
      }

      ModuleWidget::onHoverKey(e);

      // module->selected_voltage_sequencer->shiftRight();
      // if((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) module->selected_gate_sequencer->shiftRight();
  }
};

I’m sure that I’m making some trivial mistake. Any suggestions? (Once again, it turned out to be because my constructor used underscores instead of camel case!)

haha - it may look that way, but it’s pretty much the same for you as for me. “I need a switch! Why won’t it work? hack… hack… hack…”

The freezeToggle constructor should have the same name as the class.

2 Likes

Thanks @Dewb! That was it!

1 Like

Sorry everyone, I’m suck! I have the SVG switch showing up on my front panel, but now I don’t know how to make it function properly. If I click on it, nothing happens.

I’m not sure if I’m supposed to override the onChange method or perhaps override the onButton method. I’m not sure if I’m supposed to step() it myself. I’m totally lost at this point. I searched the community for “SvgSwitch”, and googled for “SvgSwitch VCV Rack”, but I can’t seem to find out what to do next.

Any help would be much appreciated!

Here’s how I’m defining it:

struct FreezeToggle : app::SvgSwitch {
	FreezeToggle() {
		addFrame(APP->window->loadSvg(asset::plugin(pluginInstance,"res/freeze-button-off.svg")));
    addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/freeze-button-on.svg")));
	};
};

And here’s where I’m adding it to the front panel:

    FreezeToggle *freeze_toggle = new FreezeToggle();
    freeze_toggle->box.pos = mm2px(Vec(1, 100));
    addChild(freeze_toggle);

Thanks!

they one I sent you a link to is only 30 lines of code and doesn’t override anything. I’ve used it in a lot of modules.

Thanks @Squinky. Your code is like a mean Kung Fu Master, but it taught me the ways.

The way that I was adding my widget to the front panel was the problem. Here’s how I got it working:

    addParam(createParamCentered<FreezeToggle>(mm2px(Vec(20,100)), module, DigitalSequencer::FREEZE_TOGGLE));

oh, yep, I do that all the time…

Hi everyone,

It’s been a while, but I’m back on the topic of custom switches. I have my custom switch showing up, but now I need to learn how to make it work. Here’s my code to display the switch:

struct squareToggle : app::SvgSwitch {
  squareToggle() {
    momentary = false;
    addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/PushButton_0.svg")));
    addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/PushButton_1.svg")));
  }
};

struct MyModuleWidget : ModuleWidget
{
  MyModuleWidget (MyModule* module)
  {
    setModule(module);
    setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/my_module_front_panel.svg")));

    // Add custom switch
    squareToggle *square_toggle = new squareToggle();
    square_toggle->box.pos = mm2px(Vec(5, 5));
    addChild(square_toggle);
  }
};

What are my next steps to implement that switch? At the moment, clicking on it does nothing. I suppose that’s no big surprise. That’s where I am right now.

Thanks!

Add an ID to the ParamIDs enum in the module struct

use config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); in your module constructor use configSwitch(…) in the module constructor use the createParam helper in the module widget.

Then you can use params[ID].getValue() in your process.

This is an example of how I do it: https://github.com/countmodula/VCVRackPlugins/blob/v2.0.0/src/modules/CVSpreader.cpp

OK, what you recommended worked! Thanks a ton! Let me walk through it in detail, just in case someone else is following along:

Here’s how I did that:

struct MyModule : Module
{
  enum ParamIds {
        SWITCH_TEST, // added ID
		MODE_SWITCH
	};
	enum InputIds {
		NUM_INPUTS
	};
	enum OutputIds {
		NUM_OUTPUTS
	};

	MyModule() // constructor
	{
        config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); // Made sure that this exists
	}

Let’s continue…

Looking at your example, I might write the configSwitch something like…

configSwitch(MODE_PARAM, 0.0f, 1.0f, 1.0f, "Spread", {"Even", "Odd"});

In my case, it is:

configSwitch(SWITCH_TEST, 0.0f, 1.0f, 1.0f, "Something", {"Value", "Other Value"});

Or, more specifically,

MyModule()
{
    config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);

    // It's very important that this line of code comes after and not before the previous 'config' statement
    configSwitch(SWITCH_TEST, 0.0f, 1.0f, 1.0f, "Something", {"Value", "Other Value"});
}

I’m not exactly sure what the strings do, but I can vaguely understand by looking at the VCV Rack API documentation:

TSwitchQuantity * rack::engine::Module::configSwitch (
    int paramId,
    float minValue,
    float maxValue,
    float defaultValue,
    std::string name = "",
    std::vector< std::string > labels = {} 
)

I’m assuming that the name and labels are for display on mouse-over and stuff?

Moving on…

use the createParam helper in the module widget.

Ok, I’m seeing this line in your example:

addParam(createParamCentered<CountModulaToggle2P>(Vec(x, y), module, CVSpreader::MODE_PARAM));

In my widget, I have this so far:

    squareToggle *square_toggle = new squareToggle();
    square_toggle->box.pos = mm2px(Vec(5, 5));
    addChild(square_toggle);

Instead, I’m supposed to do something like…

addParam(createParamCentered<squareToggle>(Vec(x, y), module, MyModule::MODE_PARAM));

Here are the changes that I made to my code:

/* Remove this !!
    squareToggle *square_toggle = new squareToggle();
    square_toggle->box.pos = mm2px(Vec(5, 5));
    addChild(square_toggle);
*/
/* Replace with:*/
addParam(createParamCentered<squareToggle>(Vec(10, 10), module, MyModule::MODE_PARAM));

That did it! Getting there!!

You’ve got it.

The {“Value”, “Other Value”} values are displayed on mouse over and offered as selections when you right-click on the switch. They correspond with values 0, 1, 2 etc when you call getValue() for the switch parameter.