Bug Report: Slider being unresponsive when appearing in a component over top a GUI element that is being repainted

So a year or more ago I began using JUCE and it has been a long, difficult year in many coffee shops all over town. But I am pleased to report that I am now a seasoned enough veteran to finally post about my first bug report! I discovered this issue troubleshooting my app and narrowed down what was causing the issue.

The issue happens where a slider begins to become unresponsive, as in, it skips values and jumps values and makes it difficult to end up on a specific value.

I have one component that appears with using setVisible(true), and that appears over-top of a element that is below. the top element contains a slider. there is a timer callback that is running that updates the lower component pretty much every second, and it is calling repaint on this lower component. Because they both occupy the same screen space, I am guessing, this is the cause of the issue. The slider within the top component loses accuracy only when the timer callback is running and that call back is repainting the component it is appearing in front of. After extensive testing I have ruled this out to be the issue. It is very possible to just make it so the repaint call only happens if the component is visible or shown, which is how I will work around this issue but I think it is worth reporting the bug so the devs can fix it.

There are many factors that could lead to such a behaviour. Without more information we can just guess.
It could be that the redrawing of the background component (that also redraws all the children on top of it) takes too long and blocks the UI thread. Because of this the slider freezes when you drag it and the redraw happens.
You maybe have to optimise your UI (shadows, pictures, gradients, other calculations…). Make sure you test this in release mode (much faster for UI) and use a CPU profiler to find the problem. You can also try to make your UI smaller (reduce the width and height) and see the difference.

Important to know is, that a Component is considered to be opaque by default, even if it doesn’t draw all pixels in your implementation.
This setting will exclude that area from any paint() calls (in some situations that are out of your control it might still draw it, e.g. resizes) and will make the component in front catch the mouse events first.
To avoid this behavior of painting, call setOpaque (false).
To let mouse events pass through to the component behind, call setInterceptsMouseClicks (false, false)
And to get a Component to appear in front of it’s siblings, call toFront()

The child added first (i.e. order of addAndMakeVisible or addChildComponent) is the back-most.

Hope that helps

Okay thanks for the replies. I was thinking it was some type of issue where the timer callback was delaying something about how the slider was updating its UI part of itself. originally I thought the way I had set up using the broadcaster / listener system was locking this update or interfering with it in some way.

The fix I used, was to simply just check if the top component is visible as part of the lower components callback. if its visible then it wont call repaint(), and now everything is working smoothly.

This is used in the way like a certain UI Panel is used like an ‘alert window’ that accepts input, and its over-top of a lower one that normally just displays default information. hope this helps anyone who might encounter this.

Instead of keeping the timer running, you could override visibilityChanged():

void visibilityChanged() override
    if (isVisible())
        startTimerHz (60);

Actually not, as mentionned in the documentation of setOpaque() :
By default, components are considered transparent, unless this is used to make it otherwise.

Thank you, I stand corrected. I remembered that wrong.

perhaps here it is better to use “isShowing()” instead of “isVisible()”?
If the component is set to be visible but its parent is hidden, ultimately it is not showing (isShowing() will return false) while isVisible() will still return true causing the timer to run unnecessarily.

I was thinking about that too. The problem is, you could turn it visible, but the parent is invisible. So the timer is not started.
Later the parent becomes visible… Somebody needs to check, if visibilityChanged() is called, if the parent becomes visible?
Looking at setVisible() and subsequently sendVisibilityChangeMessage() this isn’t the case, so the timer wouldn’t start.

EDIT: maybe a good feature request to call sendVisibilityChanged on children as well? IDK, leave that to the juce team

Ah I always assumed “visibilityChanged()” was recursive into children too (I’m not using it often).

Perhaps a new callback called “showingChanged()” could be introduced to do the right thing in this case and other numerous similar scenarios.

It feels like a better solution than starting the timer unnecessarily (as in the code above), or frequently polling for isShowing() (which is another alternative)

IMHO a new callback is cluttering the interface, and we both expected visibilityChanged to be called in that case, so it is rather fixing a bug than changing behaviour.
And I cannot see a drawback from a few extra calls, since it is a pure “heads-up signal”.

I agree with you and if it were my code, I’d certainly go the way you suggested.

What I’m afraid of is that someone out there may have written some spaghetti code where visibilityChanged() in one child acts on the visibility of some parent Component.

In that case, making it recursive may cause an endless loop

True, I haven’t considered that.

Somtimes the evil side of me thinks, some code should be broken to give the developer the chance to make it properly :wink:

Totally agree, when there’s an easy way to signal the fact to the developer. Unfortunately, catching an endless recursion here and trigger an assertion would require adding a “bool isInVisibilityChange” member to Component, which seems overkill in this case :sweat_smile:

Maybe ComponentMovementWatcher::componentVisibilityChanged?

ComponentMovementWatcher is implemented as ComponentListener.
The ComponentListener is triggered from sendVisibilityChangeMessage(), and also only on the component itself.
So this approach will suffer the same.

But ComponentMovementWatcher’s constructor registers with all the parents, so it’s listening to the whole hierarchy. It also updates this when the hierarchy changes.

Oh right, I missed that.
Interesting approach, although personally I don’t like it. It doesn’t take into account, if the hierarchy changes after attaching the ComponentMovementWatcher.

I agree, it is a rare use case, but if it happens, you won’t find that bug anytime soon.

Hmm, doesn’t it do that in
ComponentMovementWatcher::componentParentHierarchyChanged (Component&)?

That method is called whenever the component of one of its parents changes something in the hierarchy, because the ComponentMovementWatcher is registered with all of them.

And at the end of its body, it calls the componentVisibilityChanged(Component&) method to check whether the visibility has changed. Am I missing something?

(although I agree, if it were implemented in Component natively with visibilityChanged(), that’d be far less convoluted)

Ok, I give up, didn’t intend to follow the worakound of ComponentMovementWatcher to it’s end.
Wasn’t JUCE’s brightest hour when that was added, but that’s just my opinion.

1 Like