It would be great to help users with “best practices” around repainting by providing a couple pragmatic things out of the box:
- A VBlankAttachment as a default member on each
juce::Component. - A mechanism to avoid thread safety issues when calling
repaint().
The use case: I see both beginners and experienced devs have a hard time remembering in practice that a component can be a ParameterListener or a ChangeListener but cannot actually do anything component-y in the listeners. I just caught myself (via pluginval) yesterday adding repaint() to a function that was called off a ChangeListener (fired by the UndoManager, which was called by the apvts on the audio thread).
Providing a good default in the framework would both highlight and solve this issue (vs. leaving this for users to discover the hard way and research a solution).
My way of solving is by adding a dirty mechanism. I have class like this to my components:
class RepaintWhenDirty
{
public:
explicit RepaintWhenDirty (juce::Component* component) : component (component)
{
vBlankCallback = { component, [this] {
if (this->isDirty)
{
this->component->repaint();
}
} };
}
void dirty() { isDirty = true; }
void clean() { isDirty = false; }
private:
juce::Component::SafePointer<juce::Component> component;
juce::VBlankAttachment vBlankCallback {};
bool isDirty = false;
};
Then, you can dirty the flag like so:
void changeListenerCallback (juce::ChangeBroadcaster* source) override
{
// this can't call repaint directly
// as it could be called from the audio thread
repaintWhenDirty.dirty();
}
And mark it as clean in repaint:
void paint (juce::Graphics& g) override
{
repaintWhenDirty.clean();
// do your repainting
}
(The flag could be atomic, in practice I’m not sure it matters.)
I realize adding anything to the behemoth that is juce::Component is a tough ask or even a no-no. However, it does feel like components would receive more benefit from being VBlank-aware and being able to be marked “dirty” from callbacks out of the box. Having a VBlankAttachment come standard with components would also reduce friction when setting up scaffolding for the new animation in JUCE 8.
Maybe another way of thinking about this — If we were rewriting juce::Component in 2025, would it make sense for repaint to be a flag or a message-thread-only function? Would moving to a flag significantly improve developer experience? Heck, could repaint() be deprecated entirely or made private in favor of a flag that JUCE automatically consumes and processes at the start of every paint call?
