mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
Component: Improve opaque component checks
- Improve default performance when components check if they are opaque - Allows all components to take advantage of setPaintingIsUnclipped - Give more control to opt out of opaque checks separate from setPaintingIsUnclipped
This commit is contained in:
parent
79dfa1d392
commit
961ff32b9e
3 changed files with 204 additions and 92 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,133 @@ private:
|
||||||
ImageEffectFilter* effect;
|
ImageEffectFilter* effect;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class Component::OpaqueLayer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit OpaqueLayer (const Component&& c) = delete;
|
||||||
|
explicit OpaqueLayer (const Component& c)
|
||||||
|
{
|
||||||
|
appendOpaqueChildren (c);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ObscuredByKind
|
||||||
|
{
|
||||||
|
children,
|
||||||
|
siblings
|
||||||
|
};
|
||||||
|
|
||||||
|
void popComponent (Component& c)
|
||||||
|
{
|
||||||
|
// The most likely scenario is that a component isn't opaque
|
||||||
|
if (! c.isOpaque())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// As the component is opaque chances are it's the next item in the list
|
||||||
|
// of opaque components.
|
||||||
|
// If not, it has been skipped, probably because it's covered by another opaque component.
|
||||||
|
// Chances are that the component is only one or two steps away in the list.
|
||||||
|
for (auto i = currentPosition; i < opaqueComponents.size(); ++i)
|
||||||
|
{
|
||||||
|
if (opaqueComponents[i] == &c)
|
||||||
|
{
|
||||||
|
currentPosition = i + 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This suggests we've encountered an opaque component that should be
|
||||||
|
// in the list but isn't! Please contact the JUCE team if you encounter
|
||||||
|
// this assertion.
|
||||||
|
jassert (c.getAlpha() < 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle<int> getNonObscuredBoundsFor (const Component& component,
|
||||||
|
Rectangle<int> clipBounds,
|
||||||
|
ObscuredByKind obscuredBy) const
|
||||||
|
{
|
||||||
|
auto visibleBounds = component.getBounds().getIntersection (clipBounds);
|
||||||
|
|
||||||
|
if (visibleBounds.isEmpty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
RectangleList visibleRegions { visibleBounds };
|
||||||
|
|
||||||
|
const auto obscureWith = [&] (const Component& opaqueComponent)
|
||||||
|
{
|
||||||
|
const auto offset = positionOffsets[&opaqueComponent] - positionOffsets[&component];
|
||||||
|
const auto opaqueBounds = opaqueComponent.getBounds() + offset;
|
||||||
|
|
||||||
|
if (! opaqueBounds.intersects (visibleBounds))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (opaqueBounds.contains (visibleBounds))
|
||||||
|
visibleRegions.clear();
|
||||||
|
else
|
||||||
|
visibleRegions.subtract (opaqueBounds);
|
||||||
|
|
||||||
|
visibleBounds = visibleRegions.getBounds();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (obscuredBy == ObscuredByKind::children)
|
||||||
|
{
|
||||||
|
for (auto i = currentPosition; i < opaqueComponents.size(); ++i)
|
||||||
|
{
|
||||||
|
const auto* opaqueComponent = opaqueComponents.getUnchecked (i);
|
||||||
|
|
||||||
|
if (! component.isParentOf (opaqueComponent))
|
||||||
|
break;
|
||||||
|
|
||||||
|
obscureWith (*opaqueComponent);
|
||||||
|
|
||||||
|
if (visibleBounds.isEmpty())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = opaqueComponents.size(); --i >= currentPosition;)
|
||||||
|
{
|
||||||
|
const auto* opaqueComponent = opaqueComponents.getUnchecked (i);
|
||||||
|
|
||||||
|
if (component.isParentOf (opaqueComponent))
|
||||||
|
break;
|
||||||
|
|
||||||
|
obscureWith (*opaqueComponent);
|
||||||
|
|
||||||
|
if (visibleBounds.isEmpty())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visibleBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void appendOpaqueChildren (const Component& parent, Point<int> offset = {})
|
||||||
|
{
|
||||||
|
for (auto* child : parent.getChildren())
|
||||||
|
{
|
||||||
|
positionOffsets.set (child, offset);
|
||||||
|
|
||||||
|
if (! detail::ComponentHelpers::isVisible (*child, false)
|
||||||
|
|| child->isTransformed())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child->isOpaque())
|
||||||
|
opaqueComponents.add (child);
|
||||||
|
|
||||||
|
appendOpaqueChildren (*child, child->getPosition() + offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Array<Component*> opaqueComponents;
|
||||||
|
int currentPosition = 0;
|
||||||
|
HashMap<const Component*, Point<int>> positionOffsets;
|
||||||
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
Component::Component() noexcept
|
Component::Component() noexcept
|
||||||
: componentFlags (0)
|
: componentFlags (0)
|
||||||
|
|
@ -1701,17 +1828,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 +1854,69 @@ void Component::paintComponentAndChildren (Graphics& g)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto clipBounds = g.getClipBounds();
|
using ObscuredByKind = OpaqueLayer::ObscuredByKind;
|
||||||
|
|
||||||
if (flags.dontClipGraphicsFlag && getNumChildComponents() == 0)
|
const auto parentClipBounds = opaqueLayer.getNonObscuredBoundsFor (*this, g.getClipBounds() + getPosition(), ObscuredByKind::children);
|
||||||
{
|
|
||||||
paint (g);
|
if (! parentClipBounds.isEmpty())
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
Graphics::ScopedSaveState ss (g);
|
Graphics::ScopedSaveState ss (g);
|
||||||
|
|
||||||
if (! (detail::ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, {}) && g.isClipEmpty()))
|
if (! isPaintingUnclipped())
|
||||||
paint (g);
|
g.reduceClipRegion (parentClipBounds - getPosition());
|
||||||
|
|
||||||
|
paint (g);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < childComponentList.size(); ++i)
|
for (auto* child : getChildren())
|
||||||
{
|
{
|
||||||
auto& child = *childComponentList.getUnchecked (i);
|
if (! detail::ComponentHelpers::isVisible (*child))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (child.isVisible())
|
if (child->isTransformed())
|
||||||
{
|
{
|
||||||
if (child.affineTransform != nullptr)
|
Graphics::ScopedSaveState ss (g);
|
||||||
{
|
|
||||||
Graphics::ScopedSaveState ss (g);
|
|
||||||
|
|
||||||
g.addTransform (*child.affineTransform);
|
if (auto& transform = child->affineTransform)
|
||||||
|
g.addTransform (*transform);
|
||||||
|
|
||||||
if ((child.flags.dontClipGraphicsFlag && ! g.isClipEmpty()) || g.reduceClipRegion (child.getBounds()))
|
child->paintWithinParentContext (g, opaqueLayer);
|
||||||
child.paintWithinParentContext (g);
|
}
|
||||||
}
|
else
|
||||||
else if (clipBounds.intersects (child.getBounds()))
|
{
|
||||||
{
|
opaqueLayer.popComponent (*child);
|
||||||
Graphics::ScopedSaveState ss (g);
|
|
||||||
|
|
||||||
if (child.flags.dontClipGraphicsFlag)
|
const auto clipBounds = opaqueLayer.getNonObscuredBoundsFor (*child,
|
||||||
{
|
g.getClipBounds(),
|
||||||
child.paintWithinParentContext (g);
|
ObscuredByKind::siblings);
|
||||||
}
|
|
||||||
else if (g.reduceClipRegion (child.getBounds()))
|
|
||||||
{
|
|
||||||
bool nothingClipped = true;
|
|
||||||
|
|
||||||
for (int j = i + 1; j < childComponentList.size(); ++j)
|
if (clipBounds.isEmpty())
|
||||||
{
|
continue;
|
||||||
auto& sibling = *childComponentList.getUnchecked (j);
|
|
||||||
|
|
||||||
if (sibling.flags.opaqueFlag && sibling.isVisible() && sibling.affineTransform == nullptr)
|
Graphics::ScopedSaveState ss (g);
|
||||||
{
|
|
||||||
nothingClipped = false;
|
|
||||||
g.excludeClipRegion (sibling.getBounds());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nothingClipped || ! g.isClipEmpty())
|
if (! child->isPaintingUnclipped())
|
||||||
child.paintWithinParentContext (g);
|
g.reduceClipRegion (clipBounds);
|
||||||
}
|
|
||||||
}
|
child->paintWithinParentContext (g, opaqueLayer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +1932,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
|
||||||
|
|
|
||||||
|
|
@ -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,19 @@ 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.
|
||||||
|
|
||||||
|
By default, components are considered transparent, unless this is used
|
||||||
|
to make it otherwise.
|
||||||
|
|
||||||
@see isOpaque
|
@see isOpaque
|
||||||
*/
|
*/
|
||||||
|
|
@ -2712,6 +2717,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 +2740,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();
|
||||||
|
|
|
||||||
|
|
@ -191,42 +191,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())
|
||||||
|
|
@ -261,6 +225,14 @@ struct ComponentHelpers
|
||||||
function (c, ms, SH::screenPosToLocalPos (*c, ms.getScreenPosition()), Time::getCurrentTime());
|
function (c, ms, SH::screenPosToLocalPos (*c, ms.getScreenPosition()), Time::getCurrentTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isVisible (const Component& component, bool allowTransparency = true)
|
||||||
|
{
|
||||||
|
return component.isVisible()
|
||||||
|
&& component.getWidth() > 0
|
||||||
|
&& component.getHeight() > 0
|
||||||
|
&& component.componentTransparency < (allowTransparency ? 255 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
class ModalComponentManagerChangeNotifier
|
class ModalComponentManagerChangeNotifier
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue