Rack: Plugins feature request: I wish I could place widgets in SVG

Created on 19 Oct 2017  Â·  13Comments  Â·  Source: VCVRack/Rack

OS (if applicable): OS X
Version (or "dev" if compiling from source): v0.4.0

So, I can create graphics for my plugins in SVG. Neat. But, when I need to place widgets, I am stuck doing awkward stuff like addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 0)));.

I think it is silly to be placing widgets by pixel when I have an SVG editor right here. Here is my suggestion:

  • When I edit my svg in my svg editor, I set some sort of custom attribute on an item, such as setting vcv:placeholder="true" on a <rect />.
  • The SVG loader in VCV Rack knows that objects with the "placeholder" attribute should be silently deleted when loading in vcv.
  • SVGPanel gives me some way of getting the origin coordinates of placeholder objects by id. So for example if my <rect vcv:placeholder="true" /> had an id="knob1", I should be able to say

    SVGPanel *panel = new SVGPanel();
    addChild(panel);
    addChild(createScrew<ScrewSilver>(panel->placeholderOrigin("knob1")));
    

Or some such analogous API. It also might make sense to be able to get the size of a placeholder object.

This would make plugins easier both to write and to make modifications to.

feature request

Most helpful comment

That's essentially what I've put together tonight. Here's the documentation. I'll put it in a scripts/ directory when I battle-test it.

This hacky script converts an .svg panel file to a list of C++ commands to create VCV widget.

The panel must be set up in a very specific way using Inkscape.
1. Create a layer called "widgets". You may make it invisible when saving. This script will still read them.
2. In that layer, create a rectangle for every widget. Set the fill color accordingly.
    - params: red #ff0000
    - inputs: green #00ff00
    - outputs: blue #0000ff
3. The top-left position of the rectangle is what matters. Size doesn't, however you may want to match it to the size of the widget for alignment purposes.
4. Set the ID of the element with the enum the widget will refer to, excluding _PARAM, etc.
5. Run the script, and an entire C++ file will be generated on stdout which is a better starting point than nothing at all.

If you can think of an easier/quicker way of designating widget types in Inkscape, post your ideas.

All 13 comments

That'd be really nice, and your implementation is a good idea. I'll be working on about 100 module panels soon (yes, I know) so something like this would be required.

I had been thinking along similar lines, being able to design interfaces in a graphic programs and then load all of it from an svg rather than just the background would make gui building much easier.

Makes scaling the whole UI a lot easier I presume

nanosvg doesn't support loading metadata other than the id of the path. I'll either have to 1) hack it into the library or 2) find another way.

What about a Python script that generates the C++ lines required to add the widgets from special SVG objects, like all objects on the widgets layer? It could handle a lot of complexity, much more than a built-in C++ solution could do. It could generate lines like

    addParam(createParam<MyParam>(Vec(355, 274), module, MyModule::MY_ENUM, 0.0, 1.0, 0.0));

Another option would be, since SVG images are basically XML files, to use a XML parser library.
Then you could pre-parses the SVG, get the relevant information and pass a modified version (removing the vcv:placeholder tagged objects) to nanosvg.

That's essentially what I've put together tonight. Here's the documentation. I'll put it in a scripts/ directory when I battle-test it.

This hacky script converts an .svg panel file to a list of C++ commands to create VCV widget.

The panel must be set up in a very specific way using Inkscape.
1. Create a layer called "widgets". You may make it invisible when saving. This script will still read them.
2. In that layer, create a rectangle for every widget. Set the fill color accordingly.
    - params: red #ff0000
    - inputs: green #00ff00
    - outputs: blue #0000ff
3. The top-left position of the rectangle is what matters. Size doesn't, however you may want to match it to the size of the widget for alignment purposes.
4. Set the ID of the element with the enum the widget will refer to, excluding _PARAM, etc.
5. Run the script, and an entire C++ file will be generated on stdout which is a better starting point than nothing at all.

If you can think of an easier/quicker way of designating widget types in Inkscape, post your ideas.

Wow i just love how things are happening so quickly with this project.

great work andrew.

Cool! The C++ generation sounds like a good start.

It does seem to me the generator solution will be long term awkward as it can only work for initial setup and not for later interface adjustment (unless one starts doing something strange like mixing generated and handwritten C++ code). One thing that would be very interesting about loading metrics at runtime from the SVG file is it would potentially enable adjustments to the interface without even having to recompile the C++ code…

What I wonder about is whether there is some way to either (1) overload some sort of data NanoSVG already tracks to establish what items are "placeholders", or (2) find something appropriate which NanoSVG does not currently support but which NanoSVG would be likely to accept an upstream patch for. It seems to me that modifying NanoSVG is really not a problem as long as NanoSVG adopts the changes, it's just that maintaining a fork of NanoSVG sounds very unpleasant from the VCV project's perspective (?).

Looking at NanoSVG, nothing they currently track seems relevant, and it seems like it would be very difficult to add custom attribute support while keeping NanoSVG's minimal idiom. Layers on the other hand sound both simple to implement and like something NanoSVG might accept a patch for, and this would allow compatibility with the C++ generator script. Possibly NSVGimage could have a "layers" list and then there could be an int layerId; in NSVGshape, for example. (I don't know enough about how Rack uses NanoSVG to know how you would implement something like "hide layer 'widgets' when rendering", though.)

Maybe someone should approach NanoSVG's issues section to see if there's a version of the layers change they'd be interested in upstreaming. (I think I might be volunteering if it would be helpful.)

Right now with NanoSVG, the only way this could be pulled off is if you

  1. Use Inkscape or an SVG editor to create rectangles, similar to my script.
  2. Set the ID to *_PARAM, *_INPUT, *_OUTPUT, or *_LIGHT. If Rack sees this extension, it will not render the SVG but add it to a database. Note that SVGWidget is generic and not related to Rack-stuff, so this would be the first time they are mixed.
  3. Add a getParam(SVG*), etc. function to SVGPanel which iterates the NanoSVG shapes and returns the top-left of the bounding box.

The only thing it would get your is the position.

addInput(createInput<PJ301MPort>(panel->getParamPos("FM1"), module, LFO::FM1_INPUT));

Seems like a lot of typing compared to having this automatically generated with my script. Is it really that difficult to copy/paste a piece out of the generated output of my script to change positions? For my workflow, the panel is rarely changed after Grayscale gives me a panel design. And my script generates the needed enums as well, which would need to be explicitly stated with your method.

I think I'll try the generator script once it's available and then see if I have any further recommendations after that. Thanks!

Best thing since sliced bread since Rack itself.

But I had to set the stroke paint color before it recognized my rects. Have to go see Brit Floyd just now, I will look into this a bit - in the meantime, just set both? If you aren't having immediate joy.

Oh, Inkscape and OSX.

I have an updated version of Andrew's script. Here is my most recent version

https://github.com/mcclure/Tutorial/blob/ba4f3a3e9885416b7cc8ae89c16c841c01fdcb1c/panel2source.py

The changes are 1. Updated for Rack 0.6 API 2. Switches are supported

I do not like editing script-generated code, so what I have been doing is using the SVG and the script to generate "Base" module and widget classes in a hpp file, then having my actual module and widget inherit from the generated module and widget (an example: https://github.com/mcclure/Tutorial/blob/ba4f3a3e9885416b7cc8ae89c16c841c01fdcb1c/src/Hold.cpp ). This works very well!

The links above go to the most recent commits in my work repo, the plugins in the repo are unfinished. One big problem I have not resolved is the coordinates of the object positions seem to be wrong, like they are scaled by some factor. I think I might be using mm2vec wrong or something might be wrong with my svgs, I describe this in #897

Was this page helpful?
0 / 5 - 0 ratings

Related issues

vogelscheiss picture vogelscheiss  Â·  5Comments

PixelBulb picture PixelBulb  Â·  4Comments

oblivionratula picture oblivionratula  Â·  7Comments

dilom picture dilom  Â·  7Comments

antoniotuzzi picture antoniotuzzi  Â·  5Comments