Change knob's bounding box to square?

Hello!

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?

If not, what other approach should I take?

Thanks!!

Don’t derive from a class with “knob” in the name ;-).

I think there’s an SvgWidget (typing this on phone, so can’t check.). Or drop down to OpaqueWidget and copypasta the svg logic.

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).

Thanks for your feedback. I decided to dig deeper and found an easy solution using SvgKnob.

Here’s the code in SvgKnob that limits the bounding box to a circle:

The bounding box itself is still a rectangle, but the additional code rejects any events outside of the circular shape.

It was easy as pie to override both onHover and onClick to get the desired behavior, like so:

    void onButton(const ButtonEvent& e) override 
    {
        ParamWidget::onButton(e);
    }

    void onHover(const HoverEvent& e) override
    {
        ParamWidget::onHover(e);
    }

Here’s the final code for my NumberChooser.

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.

Anyhow, thanks everyone!!

3 Likes