mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Component: Improve performance and opaque checks
This commit is contained in:
parent
21ae78c373
commit
157e115d09
3 changed files with 372 additions and 94 deletions
|
|
@ -209,7 +209,7 @@ public:
|
||||||
return std::exchange (effect, &i) != &i;
|
return std::exchange (effect, &i) != &i;
|
||||||
}
|
}
|
||||||
|
|
||||||
void paint (Graphics& g, Component& c, bool ignoreAlphaLevel)
|
void paint (Graphics& g, Component& c, bool ignoreAlphaLevel, OpaqueLayer& opaqueLayer)
|
||||||
{
|
{
|
||||||
auto scale = g.getInternalContext().getPhysicalPixelScaleFactor();
|
auto scale = g.getInternalContext().getPhysicalPixelScaleFactor();
|
||||||
auto scaledBounds = c.getLocalBounds() * scale;
|
auto scaledBounds = c.getLocalBounds() * scale;
|
||||||
|
|
@ -238,7 +238,7 @@ public:
|
||||||
Graphics g2 (effectImage);
|
Graphics g2 (effectImage);
|
||||||
g2.addTransform (AffineTransform::scale ((float) scaledBounds.getWidth() / (float) c.getWidth(),
|
g2.addTransform (AffineTransform::scale ((float) scaledBounds.getWidth() / (float) c.getWidth(),
|
||||||
(float) scaledBounds.getHeight() / (float) c.getHeight()));
|
(float) scaledBounds.getHeight() / (float) c.getHeight()));
|
||||||
c.paintComponentAndChildren (g2);
|
c.paintComponentAndChildren (g2, opaqueLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
Graphics::ScopedSaveState ss (g);
|
Graphics::ScopedSaveState ss (g);
|
||||||
|
|
@ -257,6 +257,183 @@ private:
|
||||||
ImageEffectFilter* effect;
|
ImageEffectFilter* effect;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class Component::OpaqueLayer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit OpaqueLayer (const Component* c)
|
||||||
|
: currentComponent (c)
|
||||||
|
{
|
||||||
|
jassert (c != nullptr);
|
||||||
|
appendOpaqueChildren (*c, {}, c->getLocalBounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto pushComponent (Component& component)
|
||||||
|
{
|
||||||
|
removeOpaqueComponent (component);
|
||||||
|
currentComponent = &component;
|
||||||
|
|
||||||
|
const auto pos = component.getPosition();
|
||||||
|
offsetFromOrigin += pos;
|
||||||
|
|
||||||
|
return ScopeGuard { [this, pos]
|
||||||
|
{
|
||||||
|
offsetFromOrigin -= pos;
|
||||||
|
currentComponent = nullptr;
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle<int> getCurrentComponentBounds (const juce::Graphics& g)
|
||||||
|
{
|
||||||
|
// This function must only be called while the object returned by
|
||||||
|
// pushComponent() is still alive!
|
||||||
|
jassert (currentComponent != nullptr);
|
||||||
|
const auto pos = currentComponent->getPosition();
|
||||||
|
const auto bounds = getNonOccludedBoundsForCurrentComponent<ObscuredBy::allButChildren> (g.getClipBounds() - pos);
|
||||||
|
|
||||||
|
if (! bounds.isEmpty())
|
||||||
|
return bounds + pos;
|
||||||
|
|
||||||
|
removeOpaqueChildren (*currentComponent);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle<int> getCurrentComponentPaintBounds (const juce::Graphics& g)
|
||||||
|
{
|
||||||
|
// This function must only be called while the object returned by
|
||||||
|
// pushComponent() is still alive!
|
||||||
|
jassert (currentComponent != nullptr);
|
||||||
|
return getNonOccludedBoundsForCurrentComponent<ObscuredBy::childrenOnly> (g.getClipBounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class ObscuredBy
|
||||||
|
{
|
||||||
|
childrenOnly,
|
||||||
|
allButChildren
|
||||||
|
};
|
||||||
|
|
||||||
|
template <ObscuredBy obscuredBy>
|
||||||
|
Rectangle<int> getNonOccludedBoundsForCurrentComponent (Rectangle<int> clipBounds) const
|
||||||
|
{
|
||||||
|
auto visibleBounds = currentComponent->getLocalBounds().getIntersection (clipBounds);
|
||||||
|
|
||||||
|
if (visibleBounds.isEmpty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
RectangleList visibleRegions { visibleBounds };
|
||||||
|
|
||||||
|
const auto occlude = [&] (const OpaqueComponentInfo& opaqueComponentInfo)
|
||||||
|
{
|
||||||
|
const auto opaqueBounds = opaqueComponentInfo.clippedBounds - offsetFromOrigin;
|
||||||
|
|
||||||
|
if (! opaqueBounds.intersects (visibleBounds))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (opaqueBounds.contains (visibleBounds))
|
||||||
|
visibleRegions.clear();
|
||||||
|
else
|
||||||
|
visibleRegions.subtract (opaqueBounds);
|
||||||
|
|
||||||
|
visibleBounds = visibleRegions.getBounds();
|
||||||
|
};
|
||||||
|
|
||||||
|
if constexpr (obscuredBy == ObscuredBy::childrenOnly)
|
||||||
|
{
|
||||||
|
for (auto i = currentPosition; i < opaqueComponents.size(); ++i)
|
||||||
|
{
|
||||||
|
const auto& opaqueComponentInfo = opaqueComponents.getReference (i);
|
||||||
|
|
||||||
|
if (! currentComponent->isParentOf (opaqueComponentInfo.component))
|
||||||
|
break;
|
||||||
|
|
||||||
|
occlude (opaqueComponentInfo);
|
||||||
|
|
||||||
|
if (visibleBounds.isEmpty())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = opaqueComponents.size(); --i >= currentPosition;)
|
||||||
|
{
|
||||||
|
const auto& opaqueComponentInfo = opaqueComponents.getReference (i);
|
||||||
|
|
||||||
|
if (currentComponent->isParentOf (opaqueComponentInfo.component))
|
||||||
|
break;
|
||||||
|
|
||||||
|
occlude (opaqueComponentInfo);
|
||||||
|
|
||||||
|
if (visibleBounds.isEmpty())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visibleBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeOpaqueComponent (const Component& component)
|
||||||
|
{
|
||||||
|
if (opaqueComponents[currentPosition].component == &component)
|
||||||
|
++currentPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeOpaqueChildren (const Component& component)
|
||||||
|
{
|
||||||
|
for (const auto* child : component.getChildren())
|
||||||
|
{
|
||||||
|
if (currentPosition == opaqueComponents.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (! isVisibleToLayer (*child))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
removeOpaqueComponent (*child);
|
||||||
|
removeOpaqueChildren (*child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isVisibleToLayer (const Component& c)
|
||||||
|
{
|
||||||
|
return detail::ComponentHelpers::isVisibleWithNonZeroArea (c)
|
||||||
|
&& c.componentTransparency == 0
|
||||||
|
&& ! c.isTransformed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendOpaqueChildren (const Component& parent,
|
||||||
|
Point<int> parentOrigin,
|
||||||
|
Rectangle<int> parentClippedBounds)
|
||||||
|
{
|
||||||
|
for (auto* child : parent.getChildren())
|
||||||
|
{
|
||||||
|
if (! isVisibleToLayer (*child))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto childBounds = child->getBounds() + parentOrigin;
|
||||||
|
const auto childClippedBounds = parentClippedBounds.getIntersection (childBounds);
|
||||||
|
|
||||||
|
if (childClippedBounds.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (child->isOpaque())
|
||||||
|
opaqueComponents.add ({ child, childClippedBounds });
|
||||||
|
|
||||||
|
appendOpaqueChildren (*child, childBounds.getPosition(), childClippedBounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OpaqueComponentInfo
|
||||||
|
{
|
||||||
|
const Component* component;
|
||||||
|
Rectangle<int> clippedBounds;
|
||||||
|
};
|
||||||
|
|
||||||
|
int currentPosition{};
|
||||||
|
Point<int> offsetFromOrigin{};
|
||||||
|
const Component* currentComponent;
|
||||||
|
Array<OpaqueComponentInfo> opaqueComponents;
|
||||||
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
Component::Component() noexcept
|
Component::Component() noexcept
|
||||||
: componentFlags (0)
|
: componentFlags (0)
|
||||||
|
|
@ -1701,17 +1878,17 @@ void Component::paintOverChildren (Graphics&)
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void Component::paintWithinParentContext (Graphics& g)
|
void Component::paintWithinParentContext (Graphics& g, OpaqueLayer& opaqueLayer)
|
||||||
{
|
{
|
||||||
g.setOrigin (getPosition());
|
g.setOrigin (getPosition());
|
||||||
|
|
||||||
if (cachedImage != nullptr)
|
if (cachedImage != nullptr)
|
||||||
cachedImage->paint (g);
|
cachedImage->paint (g);
|
||||||
else
|
else
|
||||||
paintEntireComponent (g, false);
|
paintEntireComponent (g, false, opaqueLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Component::paintComponentAndChildren (Graphics& g)
|
void Component::paintComponentAndChildren (Graphics& g, OpaqueLayer& opaqueLayer)
|
||||||
{
|
{
|
||||||
#if JUCE_ETW_TRACELOGGING
|
#if JUCE_ETW_TRACELOGGING
|
||||||
{
|
{
|
||||||
|
|
@ -1727,70 +1904,64 @@ void Component::paintComponentAndChildren (Graphics& g)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto clipBounds = g.getClipBounds();
|
const auto paintBounds = opaqueLayer.getCurrentComponentPaintBounds (g);
|
||||||
|
|
||||||
if (flags.dontClipGraphicsFlag && getNumChildComponents() == 0)
|
if (! paintBounds.isEmpty())
|
||||||
{
|
{
|
||||||
|
Graphics::ScopedSaveState ss (g);
|
||||||
|
|
||||||
|
if (! isPaintingUnclipped())
|
||||||
|
g.reduceClipRegion (paintBounds);
|
||||||
|
|
||||||
paint (g);
|
paint (g);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto* child : getChildren())
|
||||||
|
{
|
||||||
|
if (! detail::ComponentHelpers::isVisibleWithNonZeroArea (*child))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (child->isTransformed() || child->componentTransparency != 0)
|
||||||
|
{
|
||||||
|
Graphics::ScopedSaveState ss (g);
|
||||||
|
|
||||||
|
if (auto& transform = child->affineTransform)
|
||||||
|
g.addTransform (*transform);
|
||||||
|
|
||||||
|
child->paintWithinParentContext (g, opaqueLayer);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
const auto componentPopper = opaqueLayer.pushComponent (*child);
|
||||||
|
const auto componentBounds = opaqueLayer.getCurrentComponentBounds (g);
|
||||||
|
|
||||||
|
if (componentBounds.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
Graphics::ScopedSaveState ss (g);
|
Graphics::ScopedSaveState ss (g);
|
||||||
|
|
||||||
if (! (detail::ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, {}) && g.isClipEmpty()))
|
if (! child->isPaintingUnclipped())
|
||||||
paint (g);
|
g.reduceClipRegion (componentBounds);
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < childComponentList.size(); ++i)
|
child->paintWithinParentContext (g, opaqueLayer);
|
||||||
{
|
|
||||||
auto& child = *childComponentList.getUnchecked (i);
|
|
||||||
|
|
||||||
if (child.isVisible())
|
|
||||||
{
|
|
||||||
if (child.affineTransform != nullptr)
|
|
||||||
{
|
|
||||||
Graphics::ScopedSaveState ss (g);
|
|
||||||
|
|
||||||
g.addTransform (*child.affineTransform);
|
|
||||||
|
|
||||||
if ((child.flags.dontClipGraphicsFlag && ! g.isClipEmpty()) || g.reduceClipRegion (child.getBounds()))
|
|
||||||
child.paintWithinParentContext (g);
|
|
||||||
}
|
|
||||||
else if (clipBounds.intersects (child.getBounds()))
|
|
||||||
{
|
|
||||||
Graphics::ScopedSaveState ss (g);
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
auto& 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Graphics::ScopedSaveState ss (g);
|
Graphics::ScopedSaveState ss (g);
|
||||||
|
|
||||||
|
if (! isPaintingUnclipped())
|
||||||
|
g.reduceClipRegion (getLocalBounds());
|
||||||
|
|
||||||
paintOverChildren (g);
|
paintOverChildren (g);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)
|
void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)
|
||||||
|
{
|
||||||
|
OpaqueLayer opaqueLayer { this };
|
||||||
|
paintEntireComponent (g, ignoreAlphaLevel, opaqueLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel, OpaqueLayer& opaqueLayer)
|
||||||
{
|
{
|
||||||
// If sizing a top-level-window and the OS paint message is delivered synchronously
|
// If sizing a top-level-window and the OS paint message is delivered synchronously
|
||||||
// before resized() is called, then we'll invoke the callback here, to make sure
|
// before resized() is called, then we'll invoke the callback here, to make sure
|
||||||
|
|
@ -1806,20 +1977,26 @@ void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)
|
||||||
|
|
||||||
if (effectState != nullptr)
|
if (effectState != nullptr)
|
||||||
{
|
{
|
||||||
effectState->paint (g, *this, ignoreAlphaLevel);
|
effectState->paint (g, *this, ignoreAlphaLevel, opaqueLayer);
|
||||||
}
|
}
|
||||||
else if (componentTransparency > 0 && ! ignoreAlphaLevel)
|
else if (componentTransparency > 0 && ! ignoreAlphaLevel)
|
||||||
{
|
{
|
||||||
if (componentTransparency < 255)
|
if (componentTransparency < 255)
|
||||||
{
|
{
|
||||||
|
OpaqueLayer transparentOpaqueLayer { this };
|
||||||
g.beginTransparencyLayer (getAlpha());
|
g.beginTransparencyLayer (getAlpha());
|
||||||
paintComponentAndChildren (g);
|
paintComponentAndChildren (g, transparentOpaqueLayer);
|
||||||
g.endTransparencyLayer();
|
g.endTransparencyLayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (isTransformed())
|
||||||
|
{
|
||||||
|
OpaqueLayer transformedOpaqueLayer { this };
|
||||||
|
paintComponentAndChildren (g, transformedOpaqueLayer);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
paintComponentAndChildren (g);
|
paintComponentAndChildren (g, opaqueLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JUCE_DEBUG
|
#if JUCE_DEBUG
|
||||||
|
|
@ -3090,7 +3267,13 @@ struct ComponentTests : public UnitTest
|
||||||
++numPaintCalls;
|
++numPaintCalls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void paintOverChildren (Graphics&) final
|
||||||
|
{
|
||||||
|
++numPaintOverChildrenCalls;
|
||||||
|
}
|
||||||
|
|
||||||
int numPaintCalls = 0;
|
int numPaintCalls = 0;
|
||||||
|
int numPaintOverChildrenCalls = 0;
|
||||||
Rectangle<int> lastClipBounds;
|
Rectangle<int> lastClipBounds;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -3116,12 +3299,16 @@ struct ComponentTests : public UnitTest
|
||||||
parent->addAndMakeVisible (*child);
|
parent->addAndMakeVisible (*child);
|
||||||
|
|
||||||
expectEquals (parent->numPaintCalls, 0);
|
expectEquals (parent->numPaintCalls, 0);
|
||||||
|
expectEquals (parent->numPaintOverChildrenCalls, 0);
|
||||||
expectEquals (child->numPaintCalls, 0);
|
expectEquals (child->numPaintCalls, 0);
|
||||||
|
expectEquals (child->numPaintOverChildrenCalls, 0);
|
||||||
|
|
||||||
paintComponentBounds (*parent);
|
paintComponentBounds (*parent);
|
||||||
|
|
||||||
expectEquals (parent->numPaintCalls, 1);
|
expectEquals (parent->numPaintCalls, 1);
|
||||||
|
expectEquals (parent->numPaintOverChildrenCalls, 1);
|
||||||
expectEquals (child->numPaintCalls, 1);
|
expectEquals (child->numPaintCalls, 1);
|
||||||
|
expectEquals (child->numPaintOverChildrenCalls, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
beginTest ("Non-opaque children require their parent to repaint");
|
beginTest ("Non-opaque children require their parent to repaint");
|
||||||
|
|
@ -3157,6 +3344,7 @@ struct ComponentTests : public UnitTest
|
||||||
|
|
||||||
expectEquals (parent->numPaintCalls, 0);
|
expectEquals (parent->numPaintCalls, 0);
|
||||||
expectEquals (child->numPaintCalls, 1);
|
expectEquals (child->numPaintCalls, 1);
|
||||||
|
expectEquals (parent->numPaintOverChildrenCalls, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
beginTest ("Opaque children don't require their parent to repaint (even when the parent uses setPaintingIsUnclipped (true))");
|
beginTest ("Opaque children don't require their parent to repaint (even when the parent uses setPaintingIsUnclipped (true))");
|
||||||
|
|
@ -3176,6 +3364,7 @@ struct ComponentTests : public UnitTest
|
||||||
|
|
||||||
expectEquals (parent->numPaintCalls, 0);
|
expectEquals (parent->numPaintCalls, 0);
|
||||||
expectEquals (child->numPaintCalls, 1);
|
expectEquals (child->numPaintCalls, 1);
|
||||||
|
expectEquals (parent->numPaintOverChildrenCalls, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
beginTest ("A partially obscured parent will repaint with reduced clip bounds");
|
beginTest ("A partially obscured parent will repaint with reduced clip bounds");
|
||||||
|
|
@ -3218,6 +3407,7 @@ struct ComponentTests : public UnitTest
|
||||||
paintComponentBounds (*parent);
|
paintComponentBounds (*parent);
|
||||||
|
|
||||||
expectEquals (parent->numPaintCalls, 0);
|
expectEquals (parent->numPaintCalls, 0);
|
||||||
|
expectEquals (parent->numPaintOverChildrenCalls, 1);
|
||||||
expectEquals (child1->numPaintCalls, 1);
|
expectEquals (child1->numPaintCalls, 1);
|
||||||
expectEquals (child2->numPaintCalls, 1);
|
expectEquals (child2->numPaintCalls, 1);
|
||||||
}
|
}
|
||||||
|
|
@ -3245,11 +3435,44 @@ struct ComponentTests : public UnitTest
|
||||||
paintComponentBounds (*parent);
|
paintComponentBounds (*parent);
|
||||||
|
|
||||||
expectEquals (parent->numPaintCalls, 0);
|
expectEquals (parent->numPaintCalls, 0);
|
||||||
|
expectEquals (parent->numPaintOverChildrenCalls, 1);
|
||||||
expectEquals (child1->numPaintCalls, 0);
|
expectEquals (child1->numPaintCalls, 0);
|
||||||
|
expectEquals (child1->numPaintOverChildrenCalls, 0);
|
||||||
expectEquals (child2->numPaintCalls, 1);
|
expectEquals (child2->numPaintCalls, 1);
|
||||||
expectEquals (child3->numPaintCalls, 1);
|
expectEquals (child3->numPaintCalls, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beginTest ("An opaque component will hide parent-sibling components behind it");
|
||||||
|
{
|
||||||
|
const auto parent = std::make_unique<TestComponent>();
|
||||||
|
const auto child1 = std::make_unique<TestComponent>();
|
||||||
|
const auto child2 = std::make_unique<TestComponent>();
|
||||||
|
const auto child3 = std::make_unique<TestComponent>();
|
||||||
|
|
||||||
|
Rectangle<int> bounds { 0, 0, 100, 100 };
|
||||||
|
parent->setBounds (bounds);
|
||||||
|
|
||||||
|
child1->setBounds (bounds);
|
||||||
|
parent->addAndMakeVisible (*child1);
|
||||||
|
|
||||||
|
child2->setBounds (bounds);
|
||||||
|
parent->addAndMakeVisible (*child2);
|
||||||
|
|
||||||
|
child3->setBounds (bounds);
|
||||||
|
child3->setOpaque (true);
|
||||||
|
child2->addAndMakeVisible (*child3);
|
||||||
|
|
||||||
|
paintComponentBounds (*parent);
|
||||||
|
|
||||||
|
expectEquals (parent->numPaintCalls, 0);
|
||||||
|
expectEquals (parent->numPaintOverChildrenCalls, 1);
|
||||||
|
expectEquals (child1->numPaintCalls, 0);
|
||||||
|
expectEquals (child1->numPaintOverChildrenCalls, 0);
|
||||||
|
expectEquals (child2->numPaintCalls, 0);
|
||||||
|
expectEquals (child2->numPaintOverChildrenCalls, 1);
|
||||||
|
expectEquals (child3->numPaintCalls, 1);
|
||||||
|
}
|
||||||
|
|
||||||
beginTest ("An opaque component will reduce the clip bounds of sibling components behind it");
|
beginTest ("An opaque component will reduce the clip bounds of sibling components behind it");
|
||||||
{
|
{
|
||||||
const auto parent = std::make_unique<TestComponent>();
|
const auto parent = std::make_unique<TestComponent>();
|
||||||
|
|
@ -3427,7 +3650,7 @@ struct ComponentTests : public UnitTest
|
||||||
expectEquals (grandchild->numPaintCalls, 0);
|
expectEquals (grandchild->numPaintCalls, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
beginTest ("Components with a width of 0 will not have their paint function called");
|
beginTest ("Components with a width of 0 will not have their paint functions called");
|
||||||
{
|
{
|
||||||
const auto parent = std::make_unique<TestComponent>();
|
const auto parent = std::make_unique<TestComponent>();
|
||||||
const auto child = std::make_unique<TestComponent>();
|
const auto child = std::make_unique<TestComponent>();
|
||||||
|
|
@ -3442,9 +3665,10 @@ struct ComponentTests : public UnitTest
|
||||||
|
|
||||||
expectEquals (parent->numPaintCalls, 1);
|
expectEquals (parent->numPaintCalls, 1);
|
||||||
expectEquals (child->numPaintCalls, 0);
|
expectEquals (child->numPaintCalls, 0);
|
||||||
|
expectEquals (child->numPaintOverChildrenCalls, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
beginTest ("Components with a height of 0 will not have their paint function called");
|
beginTest ("Components with a height of 0 will not have their paint functions called");
|
||||||
{
|
{
|
||||||
const auto parent = std::make_unique<TestComponent>();
|
const auto parent = std::make_unique<TestComponent>();
|
||||||
const auto child = std::make_unique<TestComponent>();
|
const auto child = std::make_unique<TestComponent>();
|
||||||
|
|
@ -3459,6 +3683,7 @@ struct ComponentTests : public UnitTest
|
||||||
|
|
||||||
expectEquals (parent->numPaintCalls, 1);
|
expectEquals (parent->numPaintCalls, 1);
|
||||||
expectEquals (child->numPaintCalls, 0);
|
expectEquals (child->numPaintCalls, 0);
|
||||||
|
expectEquals (child->numPaintOverChildrenCalls, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
beginTest ("Transparent components will not be considered opaque");
|
beginTest ("Transparent components will not be considered opaque");
|
||||||
|
|
@ -3480,6 +3705,30 @@ struct ComponentTests : public UnitTest
|
||||||
expectEquals (child->numPaintCalls, 1);
|
expectEquals (child->numPaintCalls, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beginTest ("Opaque components will only be considered opaque up to a transparent parent");
|
||||||
|
{
|
||||||
|
const auto parent = std::make_unique<TestComponent>();
|
||||||
|
const auto child1 = std::make_unique<TestComponent>();
|
||||||
|
const auto child2 = std::make_unique<TestComponent>();
|
||||||
|
|
||||||
|
Rectangle<int> bounds { 0, 0, 100, 100 };
|
||||||
|
parent->setBounds (bounds);
|
||||||
|
|
||||||
|
child1->setBounds (bounds);
|
||||||
|
child1->setAlpha (0.5f);
|
||||||
|
parent->addAndMakeVisible (*child1);
|
||||||
|
|
||||||
|
child2->setBounds (bounds);
|
||||||
|
child2->setOpaque (true);
|
||||||
|
child1->addAndMakeVisible (*child2);
|
||||||
|
|
||||||
|
paintComponentBounds (*parent);
|
||||||
|
|
||||||
|
expectEquals (parent->numPaintCalls, 1);
|
||||||
|
expectEquals (child1->numPaintCalls, 0);
|
||||||
|
expectEquals (child2->numPaintCalls, 1);
|
||||||
|
}
|
||||||
|
|
||||||
beginTest ("Transformed components will not be considered opaque");
|
beginTest ("Transformed components will not be considered opaque");
|
||||||
{
|
{
|
||||||
const auto parent = std::make_unique<TestComponent>();
|
const auto parent = std::make_unique<TestComponent>();
|
||||||
|
|
@ -3499,6 +3748,30 @@ struct ComponentTests : public UnitTest
|
||||||
expectEquals (child->numPaintCalls, 1);
|
expectEquals (child->numPaintCalls, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beginTest ("Opaque components will only be considered opaque up to a transformed parent");
|
||||||
|
{
|
||||||
|
const auto parent = std::make_unique<TestComponent>();
|
||||||
|
const auto child1 = std::make_unique<TestComponent>();
|
||||||
|
const auto child2 = std::make_unique<TestComponent>();
|
||||||
|
|
||||||
|
Rectangle<int> bounds { 0, 0, 100, 100 };
|
||||||
|
parent->setBounds (bounds);
|
||||||
|
|
||||||
|
child1->setBounds (bounds);
|
||||||
|
child1->setTransform (AffineTransform::rotation (degreesToRadians (45.0f)));
|
||||||
|
parent->addAndMakeVisible (*child1);
|
||||||
|
|
||||||
|
child2->setBounds (bounds);
|
||||||
|
child2->setOpaque (true);
|
||||||
|
child1->addAndMakeVisible (*child2);
|
||||||
|
|
||||||
|
paintComponentBounds (*parent);
|
||||||
|
|
||||||
|
expectEquals (parent->numPaintCalls, 1);
|
||||||
|
expectEquals (child1->numPaintCalls, 0);
|
||||||
|
expectEquals (child2->numPaintCalls, 1);
|
||||||
|
}
|
||||||
|
|
||||||
beginTest ("Nested opaque components prevent parents from being painted");
|
beginTest ("Nested opaque components prevent parents from being painted");
|
||||||
{
|
{
|
||||||
const auto parent = std::make_unique<TestComponent>();
|
const auto parent = std::make_unique<TestComponent>();
|
||||||
|
|
@ -3522,6 +3795,31 @@ struct ComponentTests : public UnitTest
|
||||||
expectEquals (child1->numPaintCalls, 0);
|
expectEquals (child1->numPaintCalls, 0);
|
||||||
expectEquals (child2->numPaintCalls, 1);
|
expectEquals (child2->numPaintCalls, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beginTest ("Areas of an opaque component outside its parent will not be considered opaque");
|
||||||
|
{
|
||||||
|
const auto parent = std::make_unique<TestComponent>();
|
||||||
|
const auto child1 = std::make_unique<TestComponent>();
|
||||||
|
const auto child2 = std::make_unique<TestComponent>();
|
||||||
|
const auto child3 = std::make_unique<TestComponent>();
|
||||||
|
|
||||||
|
parent->setBounds ({ 0, 0, 100, 100 });
|
||||||
|
|
||||||
|
child1->setBounds ({ 50, 0, 50, 100 });
|
||||||
|
parent->addAndMakeVisible (*child1);
|
||||||
|
|
||||||
|
child2->setBounds ({ -50, 0, 100, 100 });
|
||||||
|
child2->setOpaque (true);
|
||||||
|
child1->addAndMakeVisible (*child2);
|
||||||
|
|
||||||
|
paintComponentBounds (*parent);
|
||||||
|
|
||||||
|
expectEquals (parent->numPaintCalls, 1);
|
||||||
|
expect (parent->lastClipBounds == Rectangle { 0, 0, 50, 100 });
|
||||||
|
expectEquals (child1->numPaintCalls, 0);
|
||||||
|
expectEquals (child2->numPaintCalls, 1);
|
||||||
|
expect (child2->lastClipBounds == Rectangle { 50, 0, 50, 100 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1194,7 +1194,7 @@ public:
|
||||||
|
|
||||||
If you enable this mode, you'll need to make sure your paint method doesn't call anything like
|
If you enable this mode, you'll need to make sure your paint method doesn't call anything like
|
||||||
Graphics::fillAll(), and doesn't draw beyond the component's bounds, because that'll produce
|
Graphics::fillAll(), and doesn't draw beyond the component's bounds, because that'll produce
|
||||||
artifacts. This option will have no effect on components that contain any child components.
|
artifacts.
|
||||||
*/
|
*/
|
||||||
void setPaintingIsUnclipped (bool shouldPaintWithoutClipping) noexcept;
|
void setPaintingIsUnclipped (bool shouldPaintWithoutClipping) noexcept;
|
||||||
|
|
||||||
|
|
@ -1276,14 +1276,20 @@ public:
|
||||||
/** Indicates whether any parts of the component might be transparent.
|
/** Indicates whether any parts of the component might be transparent.
|
||||||
|
|
||||||
Components that always paint all of their contents with solid colour and
|
Components that always paint all of their contents with solid colour and
|
||||||
thus completely cover any components behind them should use this method
|
thus completely cover any components behind them, can use this method to
|
||||||
to tell the repaint system that they are opaque.
|
to tell the repaint system that they are opaque.
|
||||||
|
|
||||||
This information is used to optimise drawing, because it means that
|
This information is used to optimise drawing, because it means that
|
||||||
objects underneath opaque windows don't need to be painted.
|
objects underneath opaque components or windows don't need to be painted
|
||||||
|
or can have their clip bounds reduced to a smaller size.
|
||||||
|
|
||||||
By default, components are considered transparent, unless this is used to
|
Note however that there is a cost for every other component to check if
|
||||||
make it otherwise.
|
it is being obscured by opaque components. This cost should be carefully
|
||||||
|
weighed up against the benefits before deciding to enable this.
|
||||||
|
|
||||||
|
The default value of this property is false, which means that a
|
||||||
|
component will be considered transparent unless setOpaque (true) is
|
||||||
|
called on that component.
|
||||||
|
|
||||||
@see isOpaque
|
@see isOpaque
|
||||||
*/
|
*/
|
||||||
|
|
@ -2712,6 +2718,8 @@ private:
|
||||||
uint8 componentTransparency = 0;
|
uint8 componentTransparency = 0;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
class OpaqueLayer;
|
||||||
|
|
||||||
static void internalMouseEnter (SafePointer<Component>, MouseInputSource, Point<float>, Time);
|
static void internalMouseEnter (SafePointer<Component>, MouseInputSource, Point<float>, Time);
|
||||||
static void internalMouseExit (SafePointer<Component>, MouseInputSource, Point<float>, Time);
|
static void internalMouseExit (SafePointer<Component>, MouseInputSource, Point<float>, Time);
|
||||||
static void internalMouseDown (SafePointer<Component>, MouseInputSource, const detail::PointerState&, Time);
|
static void internalMouseDown (SafePointer<Component>, MouseInputSource, const detail::PointerState&, Time);
|
||||||
|
|
@ -2733,8 +2741,9 @@ private:
|
||||||
void internalRepaintUnchecked (Rectangle<int>, bool);
|
void internalRepaintUnchecked (Rectangle<int>, bool);
|
||||||
Component* removeChildComponent (int index, bool sendParentEvents, bool sendChildEvents);
|
Component* removeChildComponent (int index, bool sendParentEvents, bool sendChildEvents);
|
||||||
void reorderChildInternal (int sourceIndex, int destIndex);
|
void reorderChildInternal (int sourceIndex, int destIndex);
|
||||||
void paintComponentAndChildren (Graphics&);
|
void paintEntireComponent (Graphics&, bool, OpaqueLayer&);
|
||||||
void paintWithinParentContext (Graphics&);
|
void paintComponentAndChildren (Graphics&, OpaqueLayer&);
|
||||||
|
void paintWithinParentContext (Graphics&, OpaqueLayer&);
|
||||||
void sendMovedResizedMessages (bool wasMoved, bool wasResized);
|
void sendMovedResizedMessages (bool wasMoved, bool wasResized);
|
||||||
void sendMovedResizedMessagesIfPending();
|
void sendMovedResizedMessagesIfPending();
|
||||||
void repaintParent();
|
void repaintParent();
|
||||||
|
|
|
||||||
|
|
@ -193,42 +193,6 @@ struct ComponentHelpers
|
||||||
return convertFromDistantParentSpace (topLevelComp, *target, p);
|
return convertFromDistantParentSpace (topLevelComp, *target, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool clipChildComponent (const Component& child,
|
|
||||||
Graphics& g,
|
|
||||||
const Rectangle<int> clipRect,
|
|
||||||
Point<int> delta)
|
|
||||||
{
|
|
||||||
if (! child.isVisible() || child.isTransformed())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const auto newClip = clipRect.getIntersection (child.boundsRelativeToParent);
|
|
||||||
|
|
||||||
if (newClip.isEmpty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (child.isOpaque() && child.componentTransparency == 0)
|
|
||||||
{
|
|
||||||
g.excludeClipRegion (newClip + delta);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto childPos = child.getPosition();
|
|
||||||
return clipObscuredRegions (child, g, newClip - childPos, childPos + delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool clipObscuredRegions (const Component& comp,
|
|
||||||
Graphics& g,
|
|
||||||
const Rectangle<int> clipRect,
|
|
||||||
Point<int> delta)
|
|
||||||
{
|
|
||||||
auto wasClipped = false;
|
|
||||||
|
|
||||||
for (int i = comp.childComponentList.size(); --i >= 0;)
|
|
||||||
wasClipped |= clipChildComponent (*comp.childComponentList.getUnchecked (i), g, clipRect, delta);
|
|
||||||
|
|
||||||
return wasClipped;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Rectangle<int> getParentOrMainMonitorBounds (const Component& comp)
|
static Rectangle<int> getParentOrMainMonitorBounds (const Component& comp)
|
||||||
{
|
{
|
||||||
if (auto* p = comp.getParentComponent())
|
if (auto* p = comp.getParentComponent())
|
||||||
|
|
@ -263,6 +227,13 @@ struct ComponentHelpers
|
||||||
function (c, ms, SH::screenPosToLocalPos (*c, ms.getScreenPosition()), Time::getCurrentTime());
|
function (c, ms, SH::screenPosToLocalPos (*c, ms.getScreenPosition()), Time::getCurrentTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isVisibleWithNonZeroArea (const Component& component)
|
||||||
|
{
|
||||||
|
return component.isVisible()
|
||||||
|
&& component.getWidth() > 0
|
||||||
|
&& component.getHeight() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
class ModalComponentManagerChangeNotifier
|
class ModalComponentManagerChangeNotifier
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue