Skip to content

Commit

Permalink
DropShadower: fix behaviour on virtual desktop changes on Windows, re…
Browse files Browse the repository at this point in the history
…factor

- IsWindowOnCurrentVirtualDesktop is not used anymore. Instead, the shadow windows are owned (in the Win32 sense) by the shadower's owner.
- ParentVisibilityChangedListener's functionality is integrated into DropShadower, which simplifies the code.
  • Loading branch information
kamedin committed Jun 21, 2022
1 parent 828880e commit 23a666a
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 206 deletions.
62 changes: 1 addition & 61 deletions modules/juce_gui_basics/juce_gui_basics.cpp
Expand Up @@ -120,8 +120,6 @@ namespace juce
return Process::isForegroundProcess() || isEmbeddedInForegroundProcess (viewComponent);
}

bool isWindowOnCurrentVirtualDesktop (void*);

struct CustomMouseCursorInfo
{
ScaledImage image;
Expand Down Expand Up @@ -344,65 +342,7 @@ namespace juce
}

//==============================================================================
#if JUCE_WINDOWS
namespace juce
{

JUCE_COMCLASS (JuceIVirtualDesktopManager, "a5cd92ff-29be-454c-8d04-d82879fb3f1b") : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE IsWindowOnCurrentVirtualDesktop(
__RPC__in HWND topLevelWindow,
__RPC__out BOOL * onCurrentDesktop) = 0;

virtual HRESULT STDMETHODCALLTYPE GetWindowDesktopId(
__RPC__in HWND topLevelWindow,
__RPC__out GUID * desktopId) = 0;

virtual HRESULT STDMETHODCALLTYPE MoveWindowToDesktop(
__RPC__in HWND topLevelWindow,
__RPC__in REFGUID desktopId) = 0;
};

JUCE_COMCLASS (JuceVirtualDesktopManager, "aa509086-5ca9-4c25-8f95-589d3c07b48a");

} // namespace juce

#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL (juce::JuceIVirtualDesktopManager, 0xa5cd92ff, 0x29be, 0x454c, 0x8d, 0x04, 0xd8, 0x28, 0x79, 0xfb, 0x3f, 0x1b)
__CRT_UUID_DECL (juce::JuceVirtualDesktopManager, 0xaa509086, 0x5ca9, 0x4c25, 0x8f, 0x95, 0x58, 0x9d, 0x3c, 0x07, 0xb4, 0x8a)
#endif

bool juce::isWindowOnCurrentVirtualDesktop (void* x)
{
if (x == nullptr)
return false;

static auto* desktopManager = []
{
JuceIVirtualDesktopManager* result = nullptr;

JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")

if (SUCCEEDED (CoCreateInstance (__uuidof (JuceVirtualDesktopManager), nullptr, CLSCTX_ALL, IID_PPV_ARGS (&result))))
return result;

JUCE_END_IGNORE_WARNINGS_GCC_LIKE

return static_cast<JuceIVirtualDesktopManager*> (nullptr);
}();

BOOL current = false;

if (auto* dm = desktopManager)
if (SUCCEEDED (dm->IsWindowOnCurrentVirtualDesktop (static_cast<HWND> (x), &current)))
return current != false;

return true;
}

#else
bool juce::isWindowOnCurrentVirtualDesktop (void*) { return true; }
#if ! JUCE_WINDOWS
juce::ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() { ignoreUnused (previousContext); }
juce::ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() {}
#endif
Expand Down
190 changes: 52 additions & 138 deletions modules/juce_gui_basics/misc/juce_DropShadower.cpp
Expand Up @@ -52,7 +52,12 @@ class DropShadower::ShadowWindow : public Component
setSize (1, 1); // to keep the OS happy by not having zero-size windows
addToDesktop (ComponentPeer::windowIgnoresMouseClicks
| ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses);
| ComponentPeer::windowIgnoresKeyPresses
#if JUCE_WINDOWS
| ComponentPeer::windowIsOwned,
comp->getWindowHandle()
#endif
);
}
else if (Component* const parent = comp->getParentComponent())
{
Expand Down Expand Up @@ -86,122 +91,29 @@ class DropShadower::ShadowWindow : public Component
JUCE_DECLARE_NON_COPYABLE (ShadowWindow)
};

class DropShadower::ParentVisibilityChangedListener : public ComponentListener,
private Timer
class DropShadower::ComponentWithWeakReference
{
public:
ParentVisibilityChangedListener (Component& r, ComponentListener& l)
: root (&r), listener (&l)
{
updateParentHierarchy();

if ((SystemStats::getOperatingSystemType() & SystemStats::Windows) != 0)
{
isOnVirtualDesktop = isWindowOnCurrentVirtualDesktop (root->getWindowHandle());
startTimerHz (5);
}
}

~ParentVisibilityChangedListener() override
{
for (auto& compEntry : observedComponents)
if (auto* comp = compEntry.get())
comp->removeComponentListener (this);
}
explicit ComponentWithWeakReference (Component& c)
: ptr (&c), ref (&c) {}

void componentVisibilityChanged (Component& component) override
{
if (root != &component)
listener->componentVisibilityChanged (*root);
}

void componentParentHierarchyChanged (Component& component) override
{
if (root == &component)
updateParentHierarchy();
}
Component* get() const { return ref.get(); }

bool isWindowOnVirtualDesktop() const noexcept { return isOnVirtualDesktop; }
bool operator< (const ComponentWithWeakReference& other) const { return ptr < other.ptr; }

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<Component> ref;
};

void updateParentHierarchy()
{
const auto lastSeenComponents = std::exchange (observedComponents, [&]
{
std::set<ComponentWithWeakReference> result;

for (auto node = root; node != nullptr; node = node->getParentComponent())
result.emplace (*node);

return result;
}());

const auto withDifference = [] (const auto& rangeA, const auto& rangeB, auto&& callback)
{
std::vector<ComponentWithWeakReference> 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); });
}

void timerCallback() override
{
WeakReference<DropShadower> deletionChecker { static_cast<DropShadower*> (listener) };

const auto wasOnVirtualDesktop = std::exchange (isOnVirtualDesktop,
isWindowOnCurrentVirtualDesktop (root->getWindowHandle()));

// on Windows, isWindowOnCurrentVirtualDesktop() may cause synchronous messages to be dispatched
// to the HWND so we need to check if the shadower is still valid after calling
if (deletionChecker == nullptr)
return;

if (isOnVirtualDesktop != wasOnVirtualDesktop)
listener->componentVisibilityChanged (*root);
}

Component* root = nullptr;
ComponentListener* listener = nullptr;
std::set<ComponentWithWeakReference> observedComponents;
bool isOnVirtualDesktop = true;

JUCE_DECLARE_NON_COPYABLE (ParentVisibilityChangedListener)
JUCE_DECLARE_NON_MOVEABLE (ParentVisibilityChangedListener)
Component* ptr;
WeakReference<Component> ref;
};

//==============================================================================
DropShadower::DropShadower (const DropShadow& ds) : shadow (ds) {}

DropShadower::~DropShadower()
{
if (owner != nullptr)
{
owner->removeComponentListener (this);
owner = nullptr;
}

updateParent();
for (auto& compEntry : observedComponents)
if (auto* comp = compEntry.get())
comp->removeComponentListener (this);

const ScopedValueSetter<bool> setter (reentrant, true);
shadowWindows.clear();
Expand All @@ -211,38 +123,16 @@ void DropShadower::setOwner (Component* componentToFollow)
{
if (componentToFollow != owner)
{
if (owner != nullptr)
owner->removeComponentListener (this);

// (the component can't be null)
jassert (componentToFollow != nullptr);

owner = componentToFollow;
jassert (owner != nullptr);

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<ParentVisibilityChangedListener> (*owner,
static_cast<ComponentListener&> (*this));

updateShadows();
updateParentHierarchy();
}
}

void DropShadower::updateParent()
{
if (Component* p = lastParentComp)
p->removeComponentListener (this);

lastParentComp = owner != nullptr ? owner->getParentComponent() : nullptr;

if (Component* p = lastParentComp)
p->addComponentListener (this);
}

void DropShadower::componentMovedOrResized (Component& c, bool /*wasMoved*/, bool /*wasResized*/)
{
if (owner == &c)
Expand All @@ -255,24 +145,49 @@ void DropShadower::componentBroughtToFront (Component& c)
updateShadows();
}

void DropShadower::componentChildrenChanged (Component&)
void DropShadower::componentChildrenChanged (Component& c)
{
updateShadows();
if (owner != nullptr && owner->getParentComponent() == &c)
updateShadows();
}

void DropShadower::componentParentHierarchyChanged (Component& c)
{
if (owner == &c)
{
updateParent();
updateShadows();
}
updateParentHierarchy();
}

void DropShadower::componentVisibilityChanged (Component& c)
void DropShadower::componentVisibilityChanged (Component&)
{
if (owner == &c)
updateShadows();
updateShadows();
}

void DropShadower::updateParentHierarchy()
{
const auto lastSeenComponents = std::exchange (observedComponents, [&]
{
std::set<ComponentWithWeakReference> result;

for (auto node = &*owner; node != nullptr; node = node->getParentComponent())
result.emplace (*node);

return result;
}());

const auto withDifference = [] (const auto& rangeA, const auto& rangeB, auto&& callback)
{
std::vector<ComponentWithWeakReference> 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); });

updateShadows();
}

void DropShadower::updateShadows()
Expand All @@ -285,8 +200,7 @@ void DropShadower::updateShadows()
if (owner != nullptr
&& owner->isShowing()
&& owner->getWidth() > 0 && owner->getHeight() > 0
&& (Desktop::canUseSemiTransparentWindows() || owner->getParentComponent() != nullptr)
&& (visibilityChangedListener != nullptr && visibilityChangedListener->isWindowOnVirtualDesktop()))
&& (Desktop::canUseSemiTransparentWindows() || owner->getParentComponent() != nullptr))
{
while (shadowWindows.size() < 4)
shadowWindows.add (new ShadowWindow (owner, shadow));
Expand Down
9 changes: 3 additions & 6 deletions modules/juce_gui_basics/misc/juce_DropShadower.h
Expand Up @@ -63,22 +63,19 @@ class JUCE_API DropShadower : private ComponentListener
void componentParentHierarchyChanged (Component&) override;
void componentVisibilityChanged (Component&) override;

void updateParent();
void updateParentHierarchy();
void updateShadows();

class ShadowWindow;
class ComponentWithWeakReference;

WeakReference<Component> owner;
std::set<ComponentWithWeakReference> observedComponents;
OwnedArray<Component> shadowWindows;
DropShadow shadow;
bool reentrant = false;
WeakReference<Component> lastParentComp;

class ParentVisibilityChangedListener;
std::unique_ptr<ParentVisibilityChangedListener> visibilityChangedListener;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DropShadower)
JUCE_DECLARE_WEAK_REFERENCEABLE (DropShadower)
};

} // namespace juce
3 changes: 2 additions & 1 deletion modules/juce_gui_basics/native/juce_win32_Windowing.cpp
Expand Up @@ -2517,7 +2517,8 @@ class HWNDComponentPeer : public ComponentPeer,
}
else if (parentToAddTo != nullptr)
{
type |= WS_CHILD;
if ((styleFlags & windowIsOwned) == 0)
type |= WS_CHILD;
}
else
{
Expand Down
1 change: 1 addition & 0 deletions modules/juce_gui_basics/windows/juce_ComponentPeer.h
Expand Up @@ -75,6 +75,7 @@ class JUCE_API ComponentPeer : private FocusChangeListener
asynchronous Core Graphics drawing operations. Use this if there
are issues with regions not being redrawn at the expected time
(macOS and iOS only). */
windowIsOwned = (1 << 12),
windowIsSemiTransparent = (1 << 30) /**< Not intended for public use - makes a window transparent. */

};
Expand Down

0 comments on commit 23a666a

Please sign in to comment.