Notes for theme-able SVGs with nanoSvg

I had an idea for how to do theming with nanoSVG, and spent some time doing basic research and writing it up. Here’s the detailed notes: SVG Theme Notes

If there is interest from others, I might try an implementation and make it available in a reusable form.

With this scheme, you operate on a loaded SVG, and apply a stylesheet written in JSON that matches SVG elements by id to change colors. Very simple and lean. The SVG remains editable in Inkscape. There would be helpers to add a Theme menu with options for each of the themes you define.

4 Likes

I’d personally be very interested in this as I’m (slowly) building two modues and have been thinking of themeing options. The first being a v2 implementation of an old v0.5/6 plugin that would have different themes. The other is an alternate firmware port of a well known module that would have completely different panels with maybe some additional colour options for each of the panels.

The original Rack version of Surge did similar to what you’re proposing but with an XML stylesheet.

<surge-rack-skin name="Classic">
  <colors>
    <color name="panelBackground" hex="#CDCED4"/>
    <color name="panelBackgroundOutline" hex="#ADB0B7"/>
    <color name="panelFooter" hex="#FF9000"/>
    <color name="panelFooterOutline" hex="#E37008"/>
    <color name="panelFooterSeparator" hex="#123463"/>
    <color name="panelLabel" hex="#123463"/>
    <color name="panelLabelRule" hex="#123463"/>
    <color name="panelTitle" hex="#123463"/>
    <color name="panelSeparator" hex="#ADB0B7"/>

[snipped]

</colors>
  <assets>
    <asset name="surgeKnobBG" path="res/vectors/surgeKnobRotateBG.svg"/>
    <asset name="surgeKnobOverlay" path="res/vectors/surgeKnobOverlay.svg"/>
    <asset name="surgeKnobFG" path="res/vectors/surgeKnobRotateFG.svg"/>a

    <asset name="surgeKnobRoosterBG" path="res/vectors/surgeKnobRoosterBG.svg"/>
    <asset name="surgeKnobRoosterFG" path="res/vectors/surgeKnobRoosterFG.svg"/>a
  </assets>
</surge-rack-skin>

new version does basically the same just i don’t serialize the color choices to xml but keep them in code.

I definitely used to change SVG colors though and it worked great for those assets. Basically you can just traverse the svg object and whack whatever color you find in the tree. With the new version we really minimized our SVG usage though. Just a few knobs and background panels and the rest we paint into FrameBuffers using a frame buffer lambda class

1 Like

That’s the idea. I’ve got a start on the implementation, so I’ll update when I have an MVP ready.

cool here’s the old surge svg code if that helps surge-rack/src/SurgeWidgets.hpp at 5fdec2a4521b7f85d054bbcf72cef17537da3f8b · surge-synthesizer/surge-rack · GitHub

I’ve finished the implementation, documentation, and a Demo module for this proposal.

See Paul-Dempsey/svg_theme: Lean in-memory SVG theming for VCV Rack plugins. (github.com).

I think it’s pretty much ready for production, but I’m sure that thought is a brief bit of hubris that won’t survive first contact with real developers. If you run into a problem, I’m listening, and if you find a bug, please open an issue on GitHub.

I ended up using two header files. One for Rack-independent nanosvg theming, and another for helpers dependent on VCV Rack.

The Demo is a complete working plugin implementing a single Blank panel, backed by an SVG using standard Rack SvgPanel class. The demo also includes an example of a theme-able widget. In this case, a theme-able version of the standard Rack screws. Applying the Dark theme gives you the look of the Rack ScrewBlack, and the Light theme is the look of the Rack ScrewSilver (with one Screw.svg).

The demo shows how to theme a stock widget, and also how to implement theme-able widgets that can all be updated to a new theme with one line of code.

The Theme menu in the module is created by calling another helper that does all the work for you. You just implement a trivially-implementable interface on your module widget.

6 Likes

Wow, I was hoping something like this existed… and it does! Thanks, I’ll be trying this out for some modules I’m working on.

I don’t actually use it in my current modules, but I learned good stuff making it. Others have copied the source and built on it to do different things.

Happy this was useful for you!

I’ve run into some difficulties / confusion that I’m hoping you can clear up for me. I opened an issue on your github so as not to clog this thread, but it basically boils down to the display of different instances of the Demo module being affected by the theme chosen in another one’s menu.

How does that happen?

I don’t know yet. I didn’t see anything obvious in the code, which is quite simple. My hunch is something surprising about how modules are duplicated, or an interaction with Rack SVG cacheing. Probably the SVG cache, which is seeing the same filename, not knowing that this module is modifying the SVG at runtime.

Thanks for opening the issue on github!

Hmmm, the SVG cache applying the latest rendering to all the modules (thinking they’re the same) would be consistent with what I’ve been seeing.

1 Like

Here’s a link to the github issue for convenience:

https://github.com/Paul-Dempsey/svg_theme/issues/3

We understand the problem now – all a combination of Rack’s SVG cache, and that we modify the parsed (shared) SVG in the cache. This will require a new approach (basically create foo-theme.svg so that each themed variation gets cached separately), but it’s doable. Details in the issue thread.

@ landgrvi’s PR has been approved and merged.

I’m going to use this in a new plugin I’ve started.

[Source build Rack v2.6.0 on Windows]

I’ve rebuilt the demo following PR Cache by theme (#5) · Paul-Dempsey/svg_theme@4b17896 · GitHub

The light version “DEMO” text doesn’t revert back after changing a light panel to dark and back again. e.g.

image

From L to R:

light panel 1 is a Ctrl+D dupe of dark panel 2 switched to light

light panel 3 switched to dark then back to light

light panel 4 is a dupe of light panel 3

Restarting Rack sets the text colour correctly as per theme i.e. light panel 4 in screenshot.

Thanks for those notes.

I’ve started working on an update that will be making further changes in the logistics and caching, which should be more efficient and avoid some of the issues the current scheme has.

I am adding a second module to the demo with themed ports, knobs, and switches in addition to the module panel. Some of the entities move from module to plugin scope, for sharing themed svgs among modules in a plugin.

If all works out, themed svgs won’t exist in the global rack svg cache, or maybe only the default un-themed ones. This should prevent the odd runtime mutations that have been observed.

Unfortunately this is all breaking changes, so I’ll need to write some migration docs.

Yes, I noticed that as well, and came to the conclusion that it’s because Demo.svg doesn’t match either theme.

If you bring it up in Inkscape, you’ll see “DEMO” in black. However, you can’t get back to that state by applying the demo themes.

In detail: When the demo module loads, the theme code does nothing if it thinks it has the default theme (Light). Since nothing has happened, this shows “DEMO” in black as in Demo.svg.

You apply Dark, and Dark logo-text is applied to “DEMO”:

            "logo-text": {
                "fill": "#e8971d",
                "stroke": "#754e12"
            },

You apply Light, and Light logo-text is applied to “DEMO”:

            "logo-text": {
                "fill": "#272727",
                "stroke": "#d7b813"
            },

And so on and so forth; the theming code is doing what is asked of it, which doesn’t match the state at load.

Next version could prevent this effect by always applying and not being so “smart” by assuming the base svg is identical to “Light”, which by accident isn’t the case in the Demo. Since this prevents a class of issue, it’s either always apply or have a debug check for consistency. What do you think?

I’ve opened this PR for anyone interested to review: Add Widgets, Clip module. Rework SVG caching, widget creation, etc. by Paul-Dempsey · Pull Request #6 · Paul-Dempsey/svg_theme.

  • I expect current uses are broken and will require work to update.
  • Clarify naming (distinguish “theme” from a theme class and the theme name).
  • Move theme engine to plugin scope to support multi-module plugins (and call it “theme_engine”).
  • Simplify cache implementation from @landgrvi’s PR.
  • Add second working demo module “Clip” with a knob, a switch, and I/O Ports.
  • Add theme-able-Widget creation templates
  • Implement a Knob, Switch, and Port.
  • Update documentation