What's the best way to implement a pushbutton with three visual states, but only two user-controllable states?

So, this looks like a custom widget, but these are pretty standard SvgSwitch buttons with three frames (unlit, yellow, pink). By drawing the white keys first, I ensure the bounding box of the black ones overlaps and hijacks their visually inactive clickable zone. Please do not get distracted by the name of the module (which is not a placeholder).

What would be the least dirty way to make it so the user can toggle between the first two states, while the module could push the third visual frame to the buttons - without breaking the button’s functionality for the user?

To be clear about what I want to do: when the user presses a button, it toggles from unlit to yellow. When the module is set to visualize processing data, it can make the buttons pink, but the buttons should continue to operate normally for users. And when the user presses a key, the visualization mode is disabled for a few seconds for the sake of usability, but this part is beyond the scope of the question.

I can think of multiple ways to do it, but which would be the least worse?

1 Like

I would put a TransparentWidget on top of the button and override the draw() method so that it stays transparent when the colors yellow or green shound be shown, and shows a pink area otherwise.

You can also override the button’s draw() method to do the same thing.

In technical terms of VCV Rack (as far as I understand it), the pink state is something like a LED or Light sitting on top of the SvgSwitch.

The most important thing is that you document clearly know how it works to implement changes (future bugfixes, new features, etc.) quickly without worrying to break the existing code.

1 Like

Surprisingly, overlaying a copy of my buttons does not result in any graphical glitches (such as an offset or thicker border), despite their complexity, so it’d work. But since that means keeping track of the dirty status of the overlay, I was thinking it might be easier to bundle it all in a single widget?

Yes.

What I’m really asking is how concerns should be properly divided?

The LCD I implemented in my previous modules makes a bit of a mess of things - some knobs setting the LCD’s framebuffer dirty when operated, the draw widget reading the data to visualize from the module, but the data to visualize being prepared by the module itself.

It’s something I will clean up eventually, but it works for now, so I want to focus on implementing future custom widgets in a more idiomatic, headless-aware, reusable way.

On creating well designed reusable GUI elements, I would wait until Rack V2 is released. Maybe V2 provides some of that API functionality you need.

Yeah I’m not gonna wait on a new version that’s not expected to come with API changes when I’m still not understanding the current one so well and have no idea when it will finally enter public testing.

So far, to make smart widgets, I made my own altered createParam template, that passes a pointer to the module instance so the widget can fetch its values, and have the framebuffers watch for a bool dirty in the module. I’m not sure how to properly decouple model from view intelligently, there’s a lot of open-source modules to study but it can be hard to tell which are doing it right or wrong.

What I would do is subclass rack::app::Switch. This class implements all mouse behavior of a switch/button but no drawing behavior. So implement your own drawing behavior. I would do this adding a FramebufferWidget as a child and an SvgWidget as a child of that.

Then override the onChange() event which swaps out the svg based on the state of your module and marks the FramebufferWidget as dirty (see the source code for Rack’s SvgSwitch). For example, if you have an int state; in your Module subclass, get it with dynamic_cast<MyModule*>(paramQuantity->module)->state. Of course, verify that paramQuantity is non-NULL before doing that.

I wouldn’t really recommend anything else. Anything else will be a nasty hack, while writing a custom Switch subclass is elegant, supported, and straightforward.

1 Like

Thank you, I got it to work using pretty much this technique! That you’re supposed to use dynamic_cast to do this sort of thing was the part I didn’t really understand.

Pink status is currently randomly simulated for testing, but it works properly as a visual thing entirely decoupled from user input.

There are 4 different kinds of cast in c++, dynamic_cast is the most appropriate here. You can add a field to the subclassed control to hold your module pointer, with the correct type, but it’s usually not worth the extra memory because there is already the route through to the module as in Andrew’s example.

2 Likes

If I don’t misunderstand terms, that’s actually how I did my LCD & their knobs in my previous modules. I have modified them to do it this way now, I don’t really understand all the implications much but the code sure is cleaner.