Death to helper.py, Long live SvgHelper

don’t get involved with technologies like this until you know what your are doing. Just make some modules - it’s easy.

Not sure what you are asking. Do you mean:

setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/vco1_panel.svg")));

So all the modules are the same colour panel. Instead of multiple svg files for each HP, panel HP sized of colour.

EDIT: It’s my ZX81 based non-graphical arty design skills getting in the way of code to sound.

EDIT2: An auto AI themed svg from colour quad would be really nice.

setPanel(APP->window->loadAutoSvg(uint8_t hp, NVGcolor foreground, NVGcolor background, NVGcolor tintHint, NVGcolor contrast, long /* * */id, int slugSaltedChaos = 0)) ?

long id = gimmeAutoKnobVec(uint8_t hp, uint8_t count /*, int id*/) ?

std is a real namespace. Not sure why it stops there and not ::optional<?> for a template generic. std::optional - cppreference.com seems legit.

** (since C++17) **

https://www.google.com/search?q=beatles+just+17&rlz=1CAXAMW_enGB1031GB1031&oq=beatles+just+17&aqs=chrome.0.0i355i512j46i512j0i512j0i22i30l3j0i390l3.6455j0j7&sourceid=chrome&ie=UTF-8#fpstate=ive&vld=cid:4bb14677,vid:mwBdWVTR-o8

Rack defaults to c++11 and modules need to opt into 17. Optional requires that opt in

1 Like

Hello everyone!

I was inspired by this posting to convert all of my module front panels to use a variant of @ldlework’s SvgHelper. My version is called PanelHelper…

  • It’s made for me, so it’s not complete nor as good as Idlework’s original version.
  • Works with C++ 11.
  • Supports the light/dark theme capabilities that are now part of rack

In case anyone finds it useful, here’s the code:

// Modified from https://community.vcvrack.com/t/death-to-helper-py-long-live-svghelper/19427
// Assumes that panel control placement is on the light (default) SVG.

#pragma once

#include <functional>
#include <string>
#include <regex>
#include <rack.hpp>

struct PanelHelper
{
    rack::ModuleWidget* m_moduleWidget;
    std::shared_ptr<rack::Svg> m_svg;

    struct NamedPosition {
        std::string name;
        Vec position;
    };

    PanelHelper(rack::ModuleWidget* moduleWidget)
        : m_moduleWidget(moduleWidget), m_svg(nullptr) {}

    // Loads the panel from the given filename
    // Use this if you are using only the light version of the panel
    
    void loadPanel(const std::string& filename)
    {
        if (!m_svg) {
            auto panel = rack::createPanel(filename);
            m_svg = panel->svg;
            m_moduleWidget->setPanel(panel);
        }
    }

    // Loads the panel from the given filenames
    // Use this if you are using both the light and dark versions of the panel

    void loadPanel(const std::string& filename1, const std::string& filename2)
    {
        if (!m_svg) {
            ThemedSvgPanel *panel = rack::createPanel(filename1, filename2);
            m_svg = panel->lightSvg;
            m_moduleWidget->setPanel(panel);
        }
    }

    // Finds the position of a named control
    // Returns the center of the control's bounding box
    // If the control isn't found, returns Vec(0, 0)

    Vec findNamed(const std::string& name)
    {
        Vec result;
        forEachShape([&](NSVGshape* shape) {
            if (std::string(shape->id) == name) {
                result = getBoundsCenter(shape->bounds);
            }
        });
        return result;
    }

    std::vector<NamedPosition> findPrefixed(const std::string& prefix)
    {
        std::vector<NamedPosition> result;
        forEachShape([&](NSVGshape* shape) {
            std::string id(shape->id);
            if (id.compare(0, prefix.length(), prefix) == 0) {
                result.push_back({id, getBoundsCenter(shape->bounds)});
            }
        });
        return result;
    }

    std::vector<NamedPosition> findMatched(const std::string& pattern)
    {
        std::vector<NamedPosition> result;
        std::regex regex(pattern);
        forEachShape([&](NSVGshape* shape) {
            std::string id(shape->id);
            std::smatch match;
            if (std::regex_search(id, match, regex)) {
                result.push_back({id, getBoundsCenter(shape->bounds)});
            }
        });
        return result;
    }

    void forEachPrefixed(const std::string& prefix, const std::function<void(unsigned int, const Vec&)>& callback)
    {
        auto positions = findPrefixed(prefix);
        for (unsigned int i = 0; i < positions.size(); ++i) {
            callback(i, positions[i].position);
        }
    }

    void forEachMatched(const std::string& regex, const std::function<void(const std::vector<std::string>&, const Vec&)>& callback)
    {
        std::regex re(regex);
        forEachShape([&](NSVGshape* shape) {
            std::string id(shape->id);
            std::smatch match;
            if (std::regex_search(id, match, re)) {
                std::vector<std::string> captures;
                for (size_t i = 1; i < match.size(); ++i) {
                    captures.push_back(match[i].str());
                }
                callback(captures, getBoundsCenter(shape->bounds));
            }
        });
    }

private:
    void forEachShape(const std::function<void(NSVGshape*)>& callback)
    {
        if (!m_svg || !m_svg->handle) return;

        for (NSVGshape* shape = m_svg->handle->shapes; shape != nullptr; shape = shape->next) {
            callback(shape);
        }
    }

    Vec getBoundsCenter(float* bounds)
    {
        return Vec((bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2);
    }
};

Here’s an example of how I use it:

struct HazumiWidget : ModuleWidget
{
    HazumiWidget(Hazumi *module)
    {
        setModule(module);

        PanelHelper panelHelper(this);
        panelHelper.loadPanel(
            asset::plugin(pluginInstance, "res/hazumi/hazumi_panel.svg"),
            asset::plugin(pluginInstance, "res/hazumi/hazumi_panel-dark.svg")
        );

        addInput(createInputCentered<VoxglitchInputPort>(panelHelper.findNamed("clock_input"), module, Hazumi::STEP_INPUT));
        addInput(createInputCentered<VoxglitchInputPort>(panelHelper.findNamed("reset_input"), module, Hazumi::RESET_INPUT));

Cheers,
Bret

1 Like

Hello, posting to let you know that I’ve updated SvgHelper.

I’ve cleaned up its API, improved support for live-reload, and added dark mode support.

Check the updated original post or the readme for more info.

Thanks to @cosinekitty and @clone45 for the help.

6 Likes