diff --git a/modules/juce_gui_basics/components/juce_Component.cpp b/modules/juce_gui_basics/components/juce_Component.cpp index 921c705394..f788231a2b 100644 --- a/modules/juce_gui_basics/components/juce_Component.cpp +++ b/modules/juce_gui_basics/components/juce_Component.cpp @@ -3205,4 +3205,536 @@ AccessibilityHandler* Component::getAccessibilityHandler() return accessibilityHandler.get(); } +#if JUCE_UNIT_TESTS + +struct ComponentTests : public UnitTest +{ + ComponentTests() + : UnitTest ("Component", UnitTestCategories::gui) + { + } + + struct TestComponent : Component + { + void paint (Graphics& g) final + { + lastClipBounds = g.getClipBounds(); + ++numPaintCalls; + } + + int numPaintCalls = 0; + Rectangle lastClipBounds; + }; + + void paintComponentBounds (Component& componentToRepaint) + { + auto* topLevelComponent = componentToRepaint.getTopLevelComponent(); + topLevelComponent->createComponentSnapshot (topLevelComponent->getLocalArea (&componentToRepaint, componentToRepaint.getLocalBounds())); + } + + void runTest() override + { + ScopedJuceInitialiser_GUI libraryInitialiser; + + beginTest ("Painting a parents bounds paints both parent and child"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds.reduced (25)); + parent->addAndMakeVisible (*child); + + expectEquals (parent->numPaintCalls, 0); + expectEquals (child->numPaintCalls, 0); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 1); + expectEquals (child->numPaintCalls, 1); + } + + beginTest ("Non-opaque children require their parent to repaint"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds.reduced (25)); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*child); + + expectEquals (parent->numPaintCalls, 1); + expectEquals (child->numPaintCalls, 1); + } + + beginTest ("Opaque children don't require their parent to repaint"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds.reduced (25)); + child->setOpaque (true); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*child); + + expectEquals (parent->numPaintCalls, 0); + expectEquals (child->numPaintCalls, 1); + } + + beginTest ("Opaque children don't require their parent to repaint (even when the parent uses setPaintingIsUnclipped (true))"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setPaintingIsUnclipped (true); + parent->setBounds (bounds); + + child->setBounds (bounds.reduced (25)); + child->setOpaque (true); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*child); + + expectEquals (parent->numPaintCalls, 0); + expectEquals (child->numPaintCalls, 1); + } + + beginTest ("A partially obscured parent will repaint with reduced clip bounds"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds.removeFromTop (50)); + child->setOpaque (true); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 1); + expectEquals (child->numPaintCalls, 1); + + expect (parent->lastClipBounds == bounds); + } + + beginTest ("A totally obscured parent will never repaint"); + { + const auto parent = std::make_unique(); + const auto child1 = std::make_unique(); + const auto child2 = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child1->setBounds (bounds.removeFromTop (50)); + child1->setOpaque (true); + parent->addAndMakeVisible (*child1); + + child2->setBounds (bounds); + child2->setOpaque (true); + parent->addAndMakeVisible (*child2); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 0); + expectEquals (child1->numPaintCalls, 1); + expectEquals (child2->numPaintCalls, 1); + } + + beginTest ("An opaque component will hide sibling components behind it"); + { + const auto parent = std::make_unique(); + const auto child1 = std::make_unique(); + const auto child2 = std::make_unique(); + const auto child3 = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child1->setBounds (bounds); + parent->addAndMakeVisible (*child1); + + child2->setBounds (bounds); + child2->setOpaque (true); + parent->addAndMakeVisible (*child2); + + child3->setBounds (bounds); + parent->addAndMakeVisible (*child3); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 0); + expectEquals (child1->numPaintCalls, 0); + expectEquals (child2->numPaintCalls, 1); + expectEquals (child3->numPaintCalls, 1); + } + + beginTest ("An opaque component will hide parent-sibling components behind it"); + { + const auto parent = std::make_unique(); + const auto child1 = std::make_unique(); + const auto child2 = std::make_unique(); + const auto child3 = std::make_unique(); + + Rectangle 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 (child1->numPaintCalls, 0); + expectEquals (child2->numPaintCalls, 0); + expectEquals (child3->numPaintCalls, 1); + } + + beginTest ("An opaque component will reduce the clip bounds of sibling components behind it"); + { + const auto parent = std::make_unique(); + const auto child1 = std::make_unique(); + const auto child2 = std::make_unique(); + const auto child3 = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child1->setBounds (bounds); + parent->addAndMakeVisible (*child1); + + child2->setBounds (bounds.removeFromTop (50)); + child2->setOpaque (true); + parent->addAndMakeVisible (*child2); + + child3->setBounds (child1->getBounds()); + parent->addAndMakeVisible (*child3); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 1); + expectEquals (child1->numPaintCalls, 1); + expectEquals (child2->numPaintCalls, 1); + expectEquals (child3->numPaintCalls, 1); + + expect (child1->lastClipBounds == bounds); + expect (child3->lastClipBounds == child3->getBounds()); + } + + beginTest ("A child component will be clipped when painted"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds.reduced (25)); + parent->addAndMakeVisible (*child); + + expect (parent->lastClipBounds.isEmpty()); + expect (child->lastClipBounds.isEmpty()); + + paintComponentBounds (*parent); + + expect (parent->lastClipBounds == parent->getLocalBounds()); + expect (child->lastClipBounds == child->getLocalBounds()); + } + + beginTest ("setPaintingIsUnclipped (true) will cause a child to have its parents clip bounds"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds.reduced (25)); + child->setPaintingIsUnclipped (true); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*parent); + + expect (child->lastClipBounds == child->getLocalArea (parent.get(), parent->getLocalBounds())); + } + + beginTest ("Opaque components hide parents that use setPaintingIsUnclipped (true)"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + parent->setPaintingIsUnclipped (true); + + child->setBounds (bounds); + child->setOpaque (true); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 0); + expectEquals (child->numPaintCalls, 1); + } + + beginTest ("Opaque components hide parents that use setPaintingIsUnclipped (true)"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + parent->setPaintingIsUnclipped (true); + + child->setBounds (bounds); + child->setOpaque (true); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 0); + expectEquals (child->numPaintCalls, 1); + } + + beginTest ("Invisible child components will not be considered opaque"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds); + child->setOpaque (true); + parent->addChildComponent (*child); + + expect (! child->isVisible()); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 1); + expectEquals (child->numPaintCalls, 0); + } + + beginTest ("Invisible sibling components will not be considered opaque"); + { + const auto parent = std::make_unique(); + const auto child1 = std::make_unique(); + const auto child2 = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child1->setBounds (bounds); + parent->addAndMakeVisible (*child1); + + child2->setBounds (bounds); + child2->setOpaque (true); + parent->addChildComponent (*child2); + + expect ( child1->isVisible()); + expect (! child2->isVisible()); + + paintComponentBounds (*parent); + + expectEquals (child1->numPaintCalls, 1); + expectEquals (child2->numPaintCalls, 0); + } + + beginTest ("Components with an invisible parent will not be considered opaque"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + const auto grandchild = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds); + parent->addChildComponent (*child); + + grandchild->setBounds (bounds); + grandchild->setOpaque (true); + child->addAndMakeVisible (*grandchild); + + expect (! child->isVisible()); + expect (grandchild->isVisible()); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 1); + expectEquals (child->numPaintCalls, 0); + expectEquals (grandchild->numPaintCalls, 0); + } + + beginTest ("Components with a width of 0 will not have their paint function called"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds.withWidth (0)); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 1); + expectEquals (child->numPaintCalls, 0); + } + + beginTest ("Components with a height of 0 will not have their paint function called"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds.withHeight (0)); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 1); + expectEquals (child->numPaintCalls, 0); + } + + beginTest ("Transparent components will not be considered opaque"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds); + child->setOpaque (true); + child->setAlpha (0.5f); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*parent); + + expectEquals (parent->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(); + const auto child1 = std::make_unique(); + const auto child2 = std::make_unique(); + + Rectangle 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"); + { + const auto parent = std::make_unique(); + const auto child = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child->setBounds (bounds); + child->setOpaque (true); + child->setTransform (AffineTransform::rotation (degreesToRadians (45.0f))); + parent->addAndMakeVisible (*child); + + paintComponentBounds (*parent); + + expectEquals (parent->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(); + const auto child1 = std::make_unique(); + const auto child2 = std::make_unique(); + + Rectangle 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"); + { + const auto parent = std::make_unique(); + const auto child1 = std::make_unique(); + const auto child2 = std::make_unique(); + + Rectangle bounds { 0, 0, 100, 100 }; + parent->setBounds (bounds); + + child1->setBounds (bounds); + child1->setOpaque (true); + parent->addAndMakeVisible (*child1); + + child2->setBounds (bounds); + child2->setOpaque (true); + child1->addAndMakeVisible (*child2); + + paintComponentBounds (*parent); + + expectEquals (parent->numPaintCalls, 0); + expectEquals (child1->numPaintCalls, 0); + expectEquals (child2->numPaintCalls, 1); + } + } +}; + +static ComponentTests componentTests; + +#endif + } // namespace juce