Button + primitive drawn graphics updates without redrawing everything on the GUI

I’m trying to implement redraw efficient way of doing the following:

A simple ShapeButton with couple of LED style graphics right next to it, which update when the button is pressed.

Only the button area should react to mouse clicks. Clicking on the LEDs should not do anything.

I’m implementing it like this at the moment:

LED colors (or rather their state) are set in ShapeButton.onClick() callback function. Can’t draw in the callback function since there’s no access to Graphics class.

The LED graphics are drawn in Paint() method with Graphics.fillEllipse() so they aren’t child objects of the button itself. To get the Paint() method to be called each time a button is being pressed, I call repaint() in the ShapeButton.onClick() callback, which seems to be not so good idea: the whole GUI seems to flicker now and then if I press the button repeatedly. I.e. there’s tons of redrawing happening, I believe.

What’s the best way to implement this kind of button/LED combo feature?

Should I just create a more complicated system which itself keeps track of the clicked buttons and GUI components and redraws only those? Is there a more convenient way of doing this?

Sounds like you accidently got into a loop of GUI updates. Make sure that your paint() implementation

  • doesn’t change the state
  • doesn’t call any component methods like setBounds(), repaint() or alike
  • and the same applies to the resized() method, just in case…

And IIRC the onClick will trigger a repaint() already. That means you can leave that out, but that shouldn’t be the root of your problem.

1 Like

Also, as an aid to testing drawing methods, putting ‘JUCE_ENABLE_REPAINT_DEBUGGING = 1’ into your ‘Preprocessor Definitions’ will allow you to see the redrawing happening, by drawing random colours over the component painted areas. That way you’ll know if something is being drawn all the time.

1 Like

OK. I removed the repaint() from the onClick() function (nothing drawing related in there anymore) and added the JUCE_ENABLE_REPAINT_DEBUGGING = 1. Here’s what I found out:

The whole screen seems to get drawn each time I click the button. The whole screen also gets redrawn if I grab a knob/slider. This happens even if I remove everything from the UI’s paint(Graphics& g) method. I assume this should not happen?

Hmm, what happens if you remove ALL your own calls to ‘repaint’ ?
Juce should just repaint things that move. It might also be worth checking you’re not calling ‘paint’ anywhere, or ‘resize’

Unless it is otherwise required, have you set all of your child components to opaque? otherwise they are transparent, and the background always needs to be rendered to fill in the background of the child component.

No repaint/paint/resize anywhere.

All colors have been set so that their alpha is 0xFF. I assume JUCE automatically then decides the component is opaque?

There must be something obvious I’m missing somewhere in my code…

EDIT: Just to be more clear, when I click a button, the whole window background color changes in addition to the one button I clicked. Other screen elements don’t seem to change their color. I assume this means the background gets redrawn?

There will always be situations, where the UI is drawn more often than you think. As long as it is not a problem, I would rather focus to make your paint calls as fast as possible, since you won’t be able to control every paint calling situation.

The things you can do:

  • setting as many components to opaque like @cpr2323 wrote above. That removes the situations, where the background needs to be redrawn (drawing the parent)
  • even that doesn’t always work, since due to GUI scaling and logical pixels, the resulting bounds might have a sub-pixel size, so the parent needs to be drawn for the half pixels
  • if you have a continuous repaint in a timerCallback, e.g. meters, try to supply a rect smaller than getLocalBounds() to avoid the previous situation
  • the OS coalesces multiple paint calls, some will in the result span over other components, so you have again unexpected paint calls.

This is an incomplete summary of many conversations here on the forum from memory, if you want to dig deeper, the forum search will reveal many edge cases.

TL;DR: make your paint fast, setOpaque as much as possible, use as small as possible rectangles in repaint().

1 Like

That’s… something I’d avoid like the plague. I was about to suggest doing setBufferedToImage on the background if it has any complexity and you have anything semitransparent drawing often on it, as it would cause the background to repaint. But repainting the whole background will cause all children to be repainted. You could try making the background a separate component, so that it’s a sibling of everything else and its repaint doesn’t affect the whole tree.

I’m not redrawing the background myself. It just changes color due to the ‘JUCE_ENABLE_REPAINT_DEBUGGING = 1’ preprocessor definition. What causes it to happen each time I press one of the buttons, I do not know.

That is a debugging feature to allow you easily to observe a redraw event. You can switch it off again by removing that preprocessor macro, if you don’t need that information any more.

Oh, sorry for the confusion. Still,

Which component draws the ellipses in its paint() and you call repaint() on? The button itself, some other component, or the main window? If it’s the main window, that’s your answer. In any case, somewhere there must be something that ends up calling repaint() on the background after these events. I just checked my (semitransparent, animated) buttons and sliders and they don’t cause the whole tree to repaint.

Sorry for the late answer: I was on vacation.

Ah, this probably is the root of the problem: I’m drawing the ellipses in the main window.

If I make the ShapeButton responsible for drawing the ellipses, I need to make the bounds larger to show the ellipses next to the button graphics. I believe, this in turn means that the button will respond to clicks outside the button graphics. I.e. when I click the area with the extra ellipses (LED graphics), the button will respond also?

What’s the correct way around this, if I want the button to respond to clicks ONLY inside the button area? I.e. button should not respond when I click the LED graphics or the text next to it. I assume a parent object is needed. This in turn should create the original problem: I would need to call parent’s repaint() method to get all the button related graphics updated when the button itself is being clicked with mouse.

Make a separate Component for the leds, maybe another for the text if it changes too. Make a composite Component with the button, the leds and the text. Then make button clicks trigger led repaints, all inside the composite. In general, when you need a local repaint, that’s a good candidate for a separate Component.

1 Like