Because the operating system generates the “repaint invalid areas” signal, and provides Juce with the region to update.
Imagine two disjoint opaque child Components with (w,h)=(1,1) and coodinates (x,y) = (0,0) and (2,2) respectively (in other words, non overlapping children).
Now imagine that you request a repaint of both components. Under both Mac OS X and Windows, the resulting update region consists of two disjoint single pixels.
On Windows, Juce is able to convert the update region into a RectangleList, “subtract out” the bounds of all of the opaque children, and recognize that the parent of these children does not need to update (since the inverse of the update region intersected with the union of the opaque childrens’ bounds is an empty RectangleList).
All this magic happens in Component::paintComponentAndChildren()
juce_Component.cpp
void Component::paintComponentAndChildren (Graphics& g)
{
const Rectangle<int> clipBounds (g.getClipBounds());
if (flags.dontClipGraphicsFlag)
{
paint (g);
}
else
{
g.saveState();
ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, Point<int>());
if (! g.isClipEmpty())
paint (g);
g.restoreState();
}
for (int i = 0; i < childComponentList.size(); ++i)
{
Component& child = *childComponentList.getUnchecked (i);
if (child.isVisible())
{
if (child.affineTransform != nullptr)
{
g.saveState();
g.addTransform (*child.affineTransform);
if ((child.flags.dontClipGraphicsFlag && ! g.isClipEmpty()) || g.reduceClipRegion (child.getBounds()))
child.paintWithinParentContext (g);
g.restoreState();
}
else if (clipBounds.intersects (child.getBounds()))
{
g.saveState();
if (child.flags.dontClipGraphicsFlag)
{
child.paintWithinParentContext (g);
}
else if (g.reduceClipRegion (child.getBounds()))
{
bool nothingClipped = true;
for (int j = i + 1; j < childComponentList.size(); ++j)
{
const Component& sibling = *childComponentList.getUnchecked (j);
if (sibling.flags.opaqueFlag && sibling.isVisible() && sibling.affineTransform == nullptr)
{
nothingClipped = false;
g.excludeClipRegion (sibling.getBounds());
}
}
if (nothingClipped || ! g.isClipEmpty())
child.paintWithinParentContext (g);
}
g.restoreState();
}
}
}
g.saveState();
paintOverChildren (g);
g.restoreState();
}
Since this code is shared on all platforms (juce_Component.cpp is platform independent), the feature should in theory work everywhere.
Unfortunately, the desired optimal behavior of Component::paintComponentAndChildren() for the case you mentioned (which by the way, already perfectly implements your suggestion in A proposal to improve repainting of Juce components) depends on the clip region being set correctly in the Graphics.
The reason the repaints are ‘forced’ to go through the parent is because Mac OS X provides only a bounding box and not a region to indicate the area requiring update. In the original example, with two sibling components having (t,l,w,h) coordinates of (0,0,1,1) and (2,2,1,1) the resulting bounding box for both platforms is (0,0,3,3). But on Windows, we can extract from the HRGN, the two disjoint rectangles. On Mac OS X we have only the larger, inefficient rectangle which forces additional drawing.
You will have created a duplicate of the code in Component::paintComponentAndChildren(). Hopefully you used the existing juce::RectangleList code which makes it easy to compose regions, instead of reinventing the wheel. If you follow this path to its logical conclusion you will discover what Jules has discovered about OS X - that you do not receive a rich description of the area requiring update.
I suggest, two alternative courses of action:
-
Change your code to draw faster, by using image caching and reworking your UI
-
Research the topic of getting the update region on Mac OS X to see if this can be fixed in Juce heavyweight peer for OS X.
I’m no Mac expert but I think the code of interest is here (Jules?)
juce_mac_NSViewComponentPeer.mm
void NSViewComponentPeer::drawRect (NSRect r)
{
if (r.size.width < 1.0f || r.size.height < 1.0f)
return;
CGContextRef cg = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
if (! component->isOpaque())
CGContextClearRect (cg, CGContextGetClipBoundingBox (cg));
#if USE_COREGRAPHICS_RENDERING
if (usingCoreGraphics)
{
CoreGraphicsContext context (cg, (float) [view frame].size.height);
insideDrawRect = true;
handlePaint (context);
insideDrawRect = false;
}
else
#endif
{
const int xOffset = -roundToInt (r.origin.x);
const int yOffset = -roundToInt ([view frame].size.height - (r.origin.y + r.size.height));
const int clipW = (int) (r.size.width + 0.5f);
const int clipH = (int) (r.size.height + 0.5f);
RectangleList clip;
getClipRects (clip, view, xOffset, yOffset, clipW, clipH);
if (! clip.isEmpty())
{
Image temp (getComponent()->isOpaque() ? Image::RGB : Image::ARGB,
clipW, clipH, ! getComponent()->isOpaque());
{
ScopedPointer<LowLevelGraphicsContext> context (component->getLookAndFeel()
.createGraphicsContext (temp, Point<int> (xOffset, yOffset), clip));
insideDrawRect = true;
handlePaint (*context);
insideDrawRect = false;
}
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
CGImageRef image = juce_createCoreGraphicsImage (temp, false, colourSpace, false);
CGColorSpaceRelease (colourSpace);
CGContextDrawImage (cg, CGRectMake (r.origin.x, r.origin.y, clipW, clipH), image);
CGImageRelease (image);
}
}
}
Note that when USE_COREGRAPHICS_RENDERING is set to 1, the resulting graphics context has only a boring rectangle for the bounds, regardless of the complexity of the clip region. This goes back to what I was saying that you can’t extract the clip when using Core Graphics.
Why don’t you try not using CoreGraphics to render?
Dunno what to say about iOS, it seems CoreGraphics is the only rendering choice from looking at juce_ios_UIViewComponentPeer.mm.