I’m trying to implement a custom control that appears as a number in a box. When you drag up and down, the number increments and decrements.
It’s generally working:
struct NumberChooser : app::SvgKnob
{
std::vector<std::shared_ptr<Svg>> framesSvg; // Vector to hold Svg pointers
NumberChooser()
{
// Reserve space for 16 frames
framesSvg.reserve(16);
// Load SVGs
for (int i = 0; i < 16; ++i)
{
// Use string manipulation to create the file path dynamically
std::string filePath = "res/" + std::to_string(i + 1) + ".svg";
framesSvg.push_back(APP->window->loadSvg(asset::plugin(pluginInstance, filePath)));
}
// Set initial SVG
setSvg(framesSvg[0]);
shadow->box.pos = Vec(0.0, 1.0);
this->tw = new TransformWidget();
this->shadow->opacity = 0.0;
}
void step() override
{
SvgKnob::step(); // Call parent step function
if (auto *paramQuantity = getParamQuantity())
{
// Calculate the index based on paramQuantity value.
int index = static_cast<int>(paramQuantity->getValue() - 1);
if (index > 15) index = 15; // Ensure index does not exceed the vector size.
setSvg(framesSvg[index]);
}
}
};
However, mouse interaction is a bit iffy because VCV Rack thinks that it’s a round knob, so mouse events at the corners of the box are not registered. Is there any way to change the bounding box to, for example, square?
SvgWidget (per @patchde) would be a good base choice.
Seems like you basically want the mouse interaction of a Slider and the display output associated with (say) a SvgSwitch, so mashing those two up might be a starting point.
For a different approach you could take a look at DigitalDisplay and its subclass ChannelDisplay from the Fundamental header file (https://github.com/VCVRack/Fundamental/blob/v2/src/plugin.hpp)–that’s the two-digit readout you see in Fundamental Split and Fundamental Merge. Custom mouse logic on top of that should be pretty straightforward and it would let you easily go past 16 without juggling lots of SVGs (though you might know now that there would never be a reason to).
struct NumberChooser : app::SvgKnob
{
std::vector<std::shared_ptr<Svg>> framesSvg; // Vector to hold Svg pointers
NumberChooser()
{
// Reserve space for 16 frames
framesSvg.reserve(16);
// Load SVGs
for (int i = 0; i < 16; ++i)
{
// Use string manipulation to create the file path dynamically
std::string filePath = "res/" + std::to_string(i + 1) + ".svg";
framesSvg.push_back(APP->window->loadSvg(asset::plugin(pluginInstance, filePath)));
}
// Set initial SVG
setSvg(framesSvg[0]);
shadow->box.pos = Vec(0.0, 1.0);
this->tw = new TransformWidget();
this->shadow->opacity = 0.0;
}
void step() override
{
SvgKnob::step(); // Call parent step function
if (auto *paramQuantity = getParamQuantity())
{
// Calculate the index based on paramQuantity value.
int index = static_cast<int>(paramQuantity->getValue() - 1);
if (index > 15) index = 15; // Ensure index does not exceed the vector size.
setSvg(framesSvg[index]);
}
}
void onButton(const ButtonEvent& e) override
{
ParamWidget::onButton(e);
}
void onHover(const HoverEvent& e) override
{
ParamWidget::onHover(e);
}
};
It’s true that I had to make 16 different SVGs for the different numbers. On the bright side, that granted me a lot of control over the design. On the downside, it was a bit tedious to create them.
On the topic of multi-digit readouts, I created my own class for that a while back, but in this case I wanted something a bit different.