After more investigation, this issue boiled down to how JUCE painting is optimized.
Take the following:
…with the following component hierarchy:
├─ Green Component
└─ Blue Modal
CarentComponent blinks, it invalidates a 2px portion of the screen (for example with bounds
117 59 2 63).
The blue and green components here are siblings. As a paint call filters down the component tree with these clip bounds, it looks the siblings, asking if they are opaque. If the modal is opaque, it clips out the modal from the clip bounds and the green component ends up with empty clip bounds (and not painting).
If the modal is transparent, both components end up with a paint call for that 2px portion. This is because at this point, the painting logic doesn’t check children of siblings — so the
TextEditor being opaque has no effect on anything in the green component’s “branch” of the tree.
If your UI needs a transparent modal for Reasons (rounded corners, actual transparency), making it a child instead of a sibling improves things:
└─ Green Component
└─ Blue Modal
This is because
clipObscuredRegions recursively traverses the children and eventually finds and removes the
TextEditor region (as it’s
setOpaque(true)), preventing the main component AND modal from being repainted — just what we want!
setOpaque (true) to do its job (prevent paint calls), the components occupying the same bounds (i.e. “behind” the component) must be direct ancestors (parents, parents of parents, etc) or direct siblings. They can’t be a sibling of an ancestor.
In other words: Your Aunties and Uncles don’t care how opaque you are.
Composing components in direct parent->child relationships best takes advantage of JUCE’s bounds clipping paint optimizations.
2 downsides here:
The screen location doesn’t necessarily correlate with semantic function. For example, a preset browser has nothing to with a filter section it might overlay, so nesting these makes no sense.
A component might overlay several other components in different parts of the hierarchy…which is just awkward…
Potential framework improvement?
setOpaque implementation: When true, add the component to a list called
alwaysClipComponents. At the top of a paint call, check components in this list for
isShowing(), and if true, clip these bounds out of the graphics context. This would replace the expensive recursion overhead in
clipObscuredRegions and improve
setOpaque's “coverage” so it doesn’t have exceptions.
setBufferedToImage (true) on the Uncle and Auntie components reduces the work happening on something like a caret repaint. Imperfect, as they shouldn’t paint to begin with, but functional.
In Uncle and Auntie paint methods (and their children), if
g.getClipBounds() is 2px wide, do an early