From 175de90c494c469c88e4944942d1186073a1be10 Mon Sep 17 00:00:00 2001 From: attila Date: Tue, 5 Oct 2021 17:17:18 +0200 Subject: [PATCH] DropShadower: Fix issue with shadows disappearing in TabbedComponent The issue was caused by DropShadower using the ComponentListener interface to listen to its target Component's changes and creating shadow Components only if the target was visible during the event callbacks. However it was possible that during the events the target was not yet visible because one of its parents was not visible. When the parent became visible it would not trigger a callback for the observed child component. The fix attaches a ComponentListener recursively to all parents starting from the target and responds to each componentVisibilityChanged() event. --- .../misc/juce_DropShadower.cpp | 83 +++++++++++++++++++ .../juce_gui_basics/misc/juce_DropShadower.h | 2 + 2 files changed, 85 insertions(+) diff --git a/modules/juce_gui_basics/misc/juce_DropShadower.cpp b/modules/juce_gui_basics/misc/juce_DropShadower.cpp index 0994c4ae0b..6decf6d5f7 100644 --- a/modules/juce_gui_basics/misc/juce_DropShadower.cpp +++ b/modules/juce_gui_basics/misc/juce_DropShadower.cpp @@ -75,6 +75,84 @@ private: JUCE_DECLARE_NON_COPYABLE (ShadowWindow) }; +class ParentVisibilityChangedListener : public ComponentListener +{ +public: + ParentVisibilityChangedListener (Component& r, ComponentListener& l) + : root (&r), listener (&l) + { + if (auto* firstParent = root->getParentComponent()) + updateParentHierarchy (firstParent); + } + + ~ParentVisibilityChangedListener() override + { + for (auto& compEntry : observedComponents) + if (auto* comp = compEntry.get()) + comp->removeComponentListener (this); + } + + void componentVisibilityChanged (Component&) override + { + listener->componentVisibilityChanged (*root); + } + + void componentParentHierarchyChanged (Component& component) override + { + if (root == &component) + if (auto* firstParent = root->getParentComponent()) + updateParentHierarchy (firstParent); + } + +private: + class ComponentWithWeakReference + { + public: + explicit ComponentWithWeakReference (Component& c) + : ptr (&c), ref (&c) {} + + Component* get() const { return ref.get(); } + + bool operator< (const ComponentWithWeakReference& other) const { return ptr < other.ptr; } + + private: + Component* ptr; + WeakReference ref; + }; + + void updateParentHierarchy (Component* rootComponent) + { + const auto lastSeenComponents = std::exchange (observedComponents, [&] + { + std::set result; + + for (auto node = rootComponent; node != nullptr; node = node->getParentComponent()) + result.emplace (*node); + + return result; + }()); + + const auto withDifference = [] (const auto& rangeA, const auto& rangeB, auto&& callback) + { + std::vector result; + std::set_difference (rangeA.begin(), rangeA.end(), rangeB.begin(), rangeB.end(), std::back_inserter (result)); + + for (const auto& item : result) + if (auto* c = item.get()) + callback (*c); + }; + + withDifference (lastSeenComponents, observedComponents, [this] (auto& comp) { comp.removeComponentListener (this); }); + withDifference (observedComponents, lastSeenComponents, [this] (auto& comp) { comp.addComponentListener (this); }); + } + + Component* root = nullptr; + ComponentListener* listener = nullptr; + std::set observedComponents; + + JUCE_DECLARE_NON_COPYABLE (ParentVisibilityChangedListener) + JUCE_DECLARE_NON_MOVEABLE (ParentVisibilityChangedListener) +}; //============================================================================== DropShadower::DropShadower (const DropShadow& ds) : shadow (ds) {} @@ -109,6 +187,11 @@ void DropShadower::setOwner (Component* componentToFollow) updateParent(); owner->addComponentListener (this); + // The visibility of `owner` is transitively affected by the visibility of its parents. Thus we need to trigger the + // componentVisibilityChanged() event in case it changes for any of the parents. + visibilityChangedListener = std::make_unique (*owner, + static_cast (*this)); + updateShadows(); } } diff --git a/modules/juce_gui_basics/misc/juce_DropShadower.h b/modules/juce_gui_basics/misc/juce_DropShadower.h index 172dd9f0ea..b2927f7607 100644 --- a/modules/juce_gui_basics/misc/juce_DropShadower.h +++ b/modules/juce_gui_basics/misc/juce_DropShadower.h @@ -75,6 +75,8 @@ private: void updateParent(); void updateShadows(); + std::unique_ptr visibilityChangedListener; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DropShadower) };