Make a SvgSwitch widget glow in the dark

The Rhythm Explorer module in my soon to be released Venom plugin has square SvgSwitch widgets (buttons) that I want to glow in the dark. Each frame has a grey background with black text.

I had all but given up hope solving this, but then I saw How to use custom lights - #16 by Ahornberg. That was enough for me to get the following solution.

  struct ModeSwitch : app::SvgSwitch {
    ModeSwitch() {
      shadow->opacity = 0.0;
      addFrame(Svg::load(asset::plugin(pluginInstance,"res/mode_0.svg")));
      addFrame(Svg::load(asset::plugin(pluginInstance,"res/mode_1.svg")));
      addFrame(Svg::load(asset::plugin(pluginInstance,"res/mode_2.svg")));
      addFrame(Svg::load(asset::plugin(pluginInstance,"res/mode_3.svg")));
    }

    void drawLayer(const DrawArgs& args, int layer) override {
      if (layer==1) {
        if (module && !module->isBypassed()) {
          std::shared_ptr<window::Svg> svg = frames[static_cast<int>(getParamQuantity()->getValue())];
          if (svg)
            window::svgDraw(args.vg, svg->handle);
        }
      }
    }
  };

It seems to be working perfectly. But I’d like some experts to point out if I got anything wrong or incomplete.

Also, I tried to do some defensive programming, checking to make sure the svg was loaded before trying to access the handle. If I didn’t do this, then I assume my plugin could crash VCV if the svg file was missing or inaccessible for some reason - correct?

1 Like

Should you be calling the widget::drawLayer base method?

That is to ensure that any children are drawn, yes?

I had initially had the call to Widget::drawLayer(args, layer), but then reasoned that my widget would never have any children so I took it out. I couldn’t see any change in behavior. Is that a bad assumption on my part? I can always put it back.

Maybe it also marks dirty in some widgets?

I don’t know if it might help, but I did a similar thing with a limiter warning light in Elastika. The output level knob starts “glowing” red, meaning you can see it even if you turn the VCV room lights all the way to zero:

See the ElastikaWarningLightWidget source code. Like your solution, it also overrides drawLayer.

This class is a “light” that gets added as a child to the output level knob.

2 Likes

Very interesting - thanks. I probably would have added a red LED below the OUT knob, between the L and R - that seems more skeumorphic. But I can see how your design would be effective.

Your example code helps demonstrate some concepts for me, such as adding child widgets, and semi transparent widgets - concepts I had thought about, but hadn’t crossed that bridge.

I’m surprised your red “light” overlay is circular and not square. Does levelKnob->box.size specify geometry? If not, then I don’t understand where the circle comes from. I assumed a box would be a rectangle.

That was my first idea also, but I didn’t like it for purely cosmetic reasons. It would have broken the symmetry of the panel design, which I wanted to keep.

It’s circular because I’m inheriting from VCV Rack’s LightWidget class, and I’m chaining to its drawLayer method, which already draws a circular light, plus a diffuse circular halo around the light.

LightWidget is used by some of the VCV Fundamental modules like Mutes for its circular push-button switches that light up. It’s a little weird, but I took the same circular light and put it on top of a circular knob, and it actually works.

All I do is change the color member inside LightWidget. I set bgColor and borderColor to invisible so the LightWidget doesn’t completely obscure the knob beneath.

1 Like

Ah - that explains the circle. Thanks.

I had already noted the background and border colors.

1 Like

My ‘Should’ was genuinely a question. It sounds as though you don’t need to, but it probably is best practice.

1 Like

Ok, this is totally without me verifying anything, but my understanding of this is that drawLayer is an override, which means this method gets called instead of the drawLayer method in the SvgSwitch that is your base struct.

If you look at the api docs you will see the chain of structs that makes up the SvgSwitch as <- rack::app::Switch <- rack::app::ParamWidget <- rack::widget::OpaqueWidget <- rack::widget::Widget <- rack::WeakBase

Now, if any of these base structs wanted to do something in the drawLayer method, when you override that method in a child struct and do not call through to the struct you have extended, you are breaking that chain.

Since your module works, maybe thats not an issue.

But, there is potential for the VCV code to change and have one of these structs do something in their methods, which you would then not inherit in your module if you do not call through.

So personally in my modules, I always call the methods on the base struct for overrides, so in your drawLayer I would add app::SvgSwitch::drawLayer(args, layer);

Now, whether you do this before or after your own code is another question…

1 Like

Thanks to all that responded. I have decided to restore my call to Widget::drawLayer(args, layer) for now. I still suspect it isn’t needed, but it seems that it cannot hurt. But it all may be a moot point - I am just beginning to investigate LedDisplayChoice, thinking that may be a better option than SvgSwitch. :thinking:

Everything below has nothing really to do with my glowing SvgSwitch, but rather about my general understanding (or lack thereof) of overriding draw and/or drawLayer

I think in general I am good with the concept of overriding a function. In your override you may want to completely replace default behavior, in which case you would not call the superclass function. But if you want to augment existing behavior, then you would call the superclass function. And within your override you have the choice as to when to call the superclass function - it could be at the beginning, end, or even in the middle.

But the widgets have the possible complication of having children that are recursively called. And that is when my mind begins reeling trying to trace the possibilities. The documentation for draw and drawLayer both expressly mention calling the superclass to recurse children. I can’t wrap my head around where the actual recursion is taking place, and how it might be controlled. Maybe I want to override and replace the behavior of the parent widget, but keep the default behavior of the child recursion. Or maybe I want the default parent behavior to remain, but I want to change the behavior of the child recursion. I can’t fathom how either would be done - it is all a big muddle.

perhaps you are over thinking this?

your widget would have children if you used addChild in its widget constructor, this would create a tree structure of widgets (exactly like an html dom if you know anything about that)

that tree structure is recursively drawn to the screen (or visual buffer or whatever…) when Rack renders

you dont really need to worry about this apart from knowing where in the tree your widget is, so that you can calculate what would be drawn on top of it and what would be drawn underneath it

the actual overriding of the methods is not recursive, it is hierarchical, that is; each widget when it gets drawn would normally have its draw method called, and then it would call its parent struct draw method (unless that chain had been broken)

Note that this is not the draw method of the parent widget in the draw tree, this is just the method in the parent struct to the current struct that is being drawn

I hope I have not confused you even more…

By the way, if you make a call directly to Widget::drawLayer(args, layer) then I think you are removing the calls to SvgSwitch::drawLayer, Switch::drawLayer, ParamWidget::drawLayer and OpaqueWidget::drawLayer that would normally happen in-between

again, this might not be an issue, but you should only do that if you intentionally want to use the Widget::drawLayer method and not those in-between, and since they don’t seem to do anything, there doesn’t seem to be a reason to intentionally miss them out?

Thanks - According to the docs, none of those actually reimplement drawLayer. But then if something changed in the rack implementation such that SvgSwitch or something else in the hierarchy also reimplemented, then I would be bypassing that. So yeah, I can see how the safest thing to do is to call the class that you inherit directly from, and it will cascade up until it finds the first instance of drawLayer. I’ll make the change.

Sure, but when the doc explicitly says -

When overriding, call the superclass’s drawLayer(args, layer) to recurse to children.

It implies to me that somewhere in that drawLayer hierarchy there is recursion taking place, in which the drawLayer implementation calls drawLayer (or maybe draw?) for its children.

I assume that the Rack render engine has some sort of list of objects to render, (are they all widgets?). I also assume that the render engine only directly calls the parent widgets, and the parent(s) are responsible for making the call to render the children. Or do I have this wrong?

I suppose maybe I should start a new topic. But the immediate thing that has me thinking about all this is that I would like to change how some knobs are drawn. The ones I am thinking about have a background child widget that does not rotate, and a foreground widget (perhaps a circle with a radial line visually indicating the current rotation) that does rotate. I would like to figure out a way to modify the rendering so that the foreground only gets rendered to layer 1 so that it glows in the dark. I don’t know if this is possible without reimplementing a knob from the ground up. But ideally I would like to create a class that inherits from knob and then do some type of override that just deals with the foreground.

Yeah something along those lines, but I haven’t really looked, perhaps AB will see this during some down time and comment. It makes sense that a module widget would be responsible for drawing its child widgets.

As you say, for safety, lets just say you should call the draw and drawLayer methods of the struct you are extending, that satisfies the VCV docs and future proofs your code, and in this instance has no detriment.

The order of rendering or where each widget is rendered from could be a number of different ways, ultimately it doesn’t make a difference to your ModeSwitch because it is always at the tip of the tree (doesn’t have any children, so you wouldn’t see if not calling the parent draw method chain, caused the children not to be drawn)

My comment about the override, now that I read it back to myself, was really a way of pointing out that the structure of the widgets definition (ie what struct extends what other struct), should not be confused with the structure of the widget instances (the draw tree)

If you were to override the draw or drawLayer method in a widget higher up the widget definition structure, not sure why you would, but for example, if you extend Widget::draw itself and use your own custom widgets, then ensuring you call the original widget draw method would probably be much more important

This is already how the RoundKnob widget works, you just have to provide your own SVGs, no draw method override is required.

You don’t need to stop the foreground svg from being drawn normally because layer 1 is drawn on top of the normal draw layer

So all you need to do is extend RoundKnob, set your own svgs then override drawLayer and redraw the forground svg on layer 1 (or use the nvg methods to draw shapes or pngs)

I suppose for efficiency you might want to stop the svg from being rendered by the regular draw method if it is also being rendered exactly the same by drawLayer, but initially you shouldn’t worry about this, just get what you want working first

There are probably a few different ways to do it though

possibly setting the foreground svg to null or empty string

possibly overriding draw, drawing the background svg then bypassing the struct that draws the foreground and calling its parent struct draw method

possibly extending just widget, draw exactly what you want, but then copying the knob specific methods from a knob struct

Yes, everything you say is in line with what I have been thinking. I guess my problem is figuring out the framebuffer. As I understand it, the knob handles all the rotation business and caches renderings in the framebuffer so it doesn’t have to repeat the same operations unnecessarily. But it appears the framebuffer has both the foreground and the background in it, and that is about as far as I have gotten. So I don’t know what it is exactly within the framebuffer that needs to be drawn in layer 1.

i dont think you need to worry about the frame buffer, that is possibly an optimisation to make later if required

i don’t use a framebuffer widget for any of my components, i just override the thing i want to change and implement the changes i want in the draw methods

for example

these are just RoundKnobs where I have done some extra NVG drawing in the overridden draw method

because I am extending RoundKnob and calling its draw method after I have done my own drawing, I assume I am getting the benefits of the FrameBufferWidget that the RoundKnob has already, there doesnt seem to be any rendering or performance issues

I could have easily done this in the drawLayer method instead of the draw method

if you want to draw the foreground svg on layer 1 in the drawLayer method, just load and use the svg file directly there like you would any other arbitrary svg

Yes, but I don’t want to get into the whole rotation business. The existing knob already has rotated the foreground and placed that info somewhere, and I want to tap into that “something” to then also render it in layer 1. I’m just not sure what precisely that “something” is. I was assuming it is in the framebuffer somewhere. Or maybe the foreground svg itself? or its handle?. I believe the framebuffer is a list or vector or whatever of svgs.

If what I want is in the framebuffer, I’m assuming that by the time layer 1 is called, it should already be refreshed as needed, so I don’t have to worry about the framebuffer being dirty.

Alas, I cannot share any code because my plugins are closed source

However, I do know that @carbon14 has lights on knobs!

1 Like

Thanks!