@@ -209,7 +209,7 @@ class Component::EffectState
209209 return std::exchange (effect, &i) != &i;
210210 }
211211
212- void paint (Graphics& g, Component& c, bool ignoreAlphaLevel)
212+ void paint (Graphics& g, Component& c, bool ignoreAlphaLevel, OpaqueLayer& opaqueLayer )
213213 {
214214 auto scale = g.getInternalContext ().getPhysicalPixelScaleFactor ();
215215 auto scaledBounds = c.getLocalBounds () * scale;
@@ -238,7 +238,7 @@ class Component::EffectState
238238 Graphics g2 (effectImage);
239239 g2.addTransform (AffineTransform::scale ((float ) scaledBounds.getWidth () / (float ) c.getWidth (),
240240 (float ) scaledBounds.getHeight () / (float ) c.getHeight ()));
241- c.paintComponentAndChildren (g2);
241+ c.paintComponentAndChildren (g2, opaqueLayer );
242242 }
243243
244244 Graphics::ScopedSaveState ss (g);
@@ -257,6 +257,133 @@ class Component::EffectState
257257 ImageEffectFilter* effect;
258258};
259259
260+ // ==============================================================================
261+ class Component ::OpaqueLayer
262+ {
263+ public:
264+ explicit OpaqueLayer (const Component&& c) = delete;
265+ explicit OpaqueLayer (const Component& c)
266+ {
267+ appendOpaqueChildren (c);
268+ }
269+
270+ enum class ObscuredByKind
271+ {
272+ children,
273+ siblings
274+ };
275+
276+ void popComponent (Component& c)
277+ {
278+ // The most likely scenario is that a component isn't opaque
279+ if (! c.isOpaque ())
280+ return ;
281+
282+ // As the component is opaque chances are it's the next item in the list
283+ // of opaque components.
284+ // If not, it has been skipped, probably because it's covered by another opaque component.
285+ // Chances are that the component is only one or two steps away in the list.
286+ for (auto i = currentPosition; i < opaqueComponents.size (); ++i)
287+ {
288+ if (opaqueComponents[i] == &c)
289+ {
290+ currentPosition = i + 1 ;
291+ return ;
292+ }
293+ }
294+
295+ // This suggests we've encountered an opaque component that should be
296+ // in the list but isn't! Please contact the JUCE team if you encounter
297+ // this assertion.
298+ jassert (c.getAlpha () < 1 .0f );
299+ }
300+
301+ Rectangle<int > getNonObscuredBoundsFor (const Component& component,
302+ Rectangle<int > clipBounds,
303+ ObscuredByKind obscuredBy) const
304+ {
305+ auto visibleBounds = component.getBounds ().getIntersection (clipBounds);
306+
307+ if (visibleBounds.isEmpty ())
308+ return {};
309+
310+ RectangleList visibleRegions { visibleBounds };
311+
312+ const auto obscureWith = [&] (const Component& opaqueComponent)
313+ {
314+ const auto offset = positionOffsets[&opaqueComponent] - positionOffsets[&component];
315+ const auto opaqueBounds = opaqueComponent.getBounds () + offset;
316+
317+ if (! opaqueBounds.intersects (visibleBounds))
318+ return ;
319+
320+ if (opaqueBounds.contains (visibleBounds))
321+ visibleRegions.clear ();
322+ else
323+ visibleRegions.subtract (opaqueBounds);
324+
325+ visibleBounds = visibleRegions.getBounds ();
326+ };
327+
328+ if (obscuredBy == ObscuredByKind::children)
329+ {
330+ for (auto i = currentPosition; i < opaqueComponents.size (); ++i)
331+ {
332+ const auto * opaqueComponent = opaqueComponents.getUnchecked (i);
333+
334+ if (! component.isParentOf (opaqueComponent))
335+ break ;
336+
337+ obscureWith (*opaqueComponent);
338+
339+ if (visibleBounds.isEmpty ())
340+ return {};
341+ }
342+ }
343+ else
344+ {
345+ for (int i = opaqueComponents.size (); --i >= currentPosition;)
346+ {
347+ const auto * opaqueComponent = opaqueComponents.getUnchecked (i);
348+
349+ if (component.isParentOf (opaqueComponent))
350+ break ;
351+
352+ obscureWith (*opaqueComponent);
353+
354+ if (visibleBounds.isEmpty ())
355+ return {};
356+ }
357+ }
358+
359+ return visibleBounds;
360+ }
361+
362+ private:
363+ void appendOpaqueChildren (const Component& parent, Point<int > offset = {})
364+ {
365+ for (auto * child : parent.getChildren ())
366+ {
367+ positionOffsets.set (child, offset);
368+
369+ if (! detail::ComponentHelpers::isVisible (*child, false )
370+ || child->isTransformed ())
371+ {
372+ continue ;
373+ }
374+
375+ if (child->isOpaque ())
376+ opaqueComponents.add (child);
377+
378+ appendOpaqueChildren (*child, child->getPosition () + offset);
379+ }
380+ }
381+
382+ Array<Component*> opaqueComponents;
383+ int currentPosition = 0 ;
384+ HashMap<const Component*, Point<int >> positionOffsets;
385+ };
386+
260387// ==============================================================================
261388Component::Component () noexcept
262389 : componentFlags (0 )
@@ -1701,17 +1828,17 @@ void Component::paintOverChildren (Graphics&)
17011828}
17021829
17031830// ==============================================================================
1704- void Component::paintWithinParentContext (Graphics& g)
1831+ void Component::paintWithinParentContext (Graphics& g, OpaqueLayer& opaqueLayer )
17051832{
17061833 g.setOrigin (getPosition ());
17071834
17081835 if (cachedImage != nullptr )
17091836 cachedImage->paint (g);
17101837 else
1711- paintEntireComponent (g, false );
1838+ paintEntireComponent (g, false , opaqueLayer );
17121839}
17131840
1714- void Component::paintComponentAndChildren (Graphics& g)
1841+ void Component::paintComponentAndChildren (Graphics& g, OpaqueLayer& opaqueLayer )
17151842{
17161843 #if JUCE_ETW_TRACELOGGING
17171844 {
@@ -1727,70 +1854,69 @@ void Component::paintComponentAndChildren (Graphics& g)
17271854 }
17281855 #endif
17291856
1730- auto clipBounds = g. getClipBounds () ;
1857+ using ObscuredByKind = OpaqueLayer::ObscuredByKind ;
17311858
1732- if (flags.dontClipGraphicsFlag && getNumChildComponents () == 0 )
1733- {
1734- paint (g);
1735- }
1736- else
1859+ const auto parentClipBounds = opaqueLayer.getNonObscuredBoundsFor (*this , g.getClipBounds () + getPosition (), ObscuredByKind::children);
1860+
1861+ if (! parentClipBounds.isEmpty ())
17371862 {
17381863 Graphics::ScopedSaveState ss (g);
17391864
1740- if (! (detail::ComponentHelpers::clipObscuredRegions (*this , g, clipBounds, {}) && g.isClipEmpty ()))
1741- paint (g);
1865+ if (! isPaintingUnclipped ())
1866+ g.reduceClipRegion (parentClipBounds - getPosition ());
1867+
1868+ paint (g);
17421869 }
17431870
1744- for (int i = 0 ; i < childComponentList. size (); ++i )
1871+ for (auto * child : getChildren () )
17451872 {
1746- auto & child = *childComponentList.getUnchecked (i);
1873+ if (! detail::ComponentHelpers::isVisible (*child))
1874+ continue ;
17471875
1748- if (child. isVisible ())
1876+ if (child-> isTransformed ())
17491877 {
1750- if (child.affineTransform != nullptr )
1751- {
1752- Graphics::ScopedSaveState ss (g);
1878+ Graphics::ScopedSaveState ss (g);
17531879
1754- g.addTransform (*child.affineTransform );
1880+ if (auto & transform = child->affineTransform )
1881+ g.addTransform (*transform);
17551882
1756- if ((child.flags .dontClipGraphicsFlag && ! g.isClipEmpty ()) || g.reduceClipRegion (child.getBounds ()))
1757- child.paintWithinParentContext (g);
1758- }
1759- else if (clipBounds.intersects (child.getBounds ()))
1760- {
1761- Graphics::ScopedSaveState ss (g);
1883+ child->paintWithinParentContext (g, opaqueLayer);
1884+ }
1885+ else
1886+ {
1887+ opaqueLayer.popComponent (*child);
17621888
1763- if (child.flags .dontClipGraphicsFlag )
1764- {
1765- child.paintWithinParentContext (g);
1766- }
1767- else if (g.reduceClipRegion (child.getBounds ()))
1768- {
1769- bool nothingClipped = true ;
1889+ const auto clipBounds = opaqueLayer.getNonObscuredBoundsFor (*child,
1890+ g.getClipBounds (),
1891+ ObscuredByKind::siblings);
17701892
1771- for (int j = i + 1 ; j < childComponentList.size (); ++j)
1772- {
1773- auto & sibling = *childComponentList.getUnchecked (j);
1893+ if (clipBounds.isEmpty ())
1894+ continue ;
17741895
1775- if (sibling.flags .opaqueFlag && sibling.isVisible () && sibling.affineTransform == nullptr )
1776- {
1777- nothingClipped = false ;
1778- g.excludeClipRegion (sibling.getBounds ());
1779- }
1780- }
1896+ Graphics::ScopedSaveState ss (g);
17811897
1782- if (nothingClipped || ! g. isClipEmpty ())
1783- child. paintWithinParentContext (g );
1784- }
1785- }
1898+ if (! child-> isPaintingUnclipped ())
1899+ g. reduceClipRegion (clipBounds );
1900+
1901+ child-> paintWithinParentContext (g, opaqueLayer);
17861902 }
17871903 }
17881904
17891905 Graphics::ScopedSaveState ss (g);
1906+
1907+ if (! isPaintingUnclipped ())
1908+ g.reduceClipRegion (getLocalBounds ());
1909+
17901910 paintOverChildren (g);
17911911}
17921912
17931913void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)
1914+ {
1915+ OpaqueLayer opaqueLayer { *this };
1916+ paintEntireComponent (g, ignoreAlphaLevel, opaqueLayer);
1917+ }
1918+
1919+ void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel, OpaqueLayer& opaqueLayer)
17941920{
17951921 // If sizing a top-level-window and the OS paint message is delivered synchronously
17961922 // before resized() is called, then we'll invoke the callback here, to make sure
@@ -1806,20 +1932,26 @@ void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)
18061932
18071933 if (effectState != nullptr )
18081934 {
1809- effectState->paint (g, *this , ignoreAlphaLevel);
1935+ effectState->paint (g, *this , ignoreAlphaLevel, opaqueLayer );
18101936 }
18111937 else if (componentTransparency > 0 && ! ignoreAlphaLevel)
18121938 {
18131939 if (componentTransparency < 255 )
18141940 {
1941+ OpaqueLayer transparentOpaqueLayer { *this };
18151942 g.beginTransparencyLayer (getAlpha ());
1816- paintComponentAndChildren (g);
1943+ paintComponentAndChildren (g, transparentOpaqueLayer );
18171944 g.endTransparencyLayer ();
18181945 }
18191946 }
1947+ else if (isTransformed ())
1948+ {
1949+ OpaqueLayer transformedOpaqueLayer { *this };
1950+ paintComponentAndChildren (g, transformedOpaqueLayer);
1951+ }
18201952 else
18211953 {
1822- paintComponentAndChildren (g);
1954+ paintComponentAndChildren (g, opaqueLayer );
18231955 }
18241956
18251957 #if JUCE_DEBUG
0 commit comments