/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ class DropShadower::ShadowWindow : public Component { public: ShadowWindow (Component& owner, const int type_, const Image shadowImageSections [12]) : topLeft (shadowImageSections [type_ * 3]), bottomRight (shadowImageSections [type_ * 3 + 1]), filler (shadowImageSections [type_ * 3 + 2]), type (type_) { setInterceptsMouseClicks (false, false); if (owner.isOnDesktop()) { setSize (1, 1); // to keep the OS happy by not having zero-size windows addToDesktop (ComponentPeer::windowIgnoresMouseClicks | ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresKeyPresses); } else if (owner.getParentComponent() != nullptr) { owner.getParentComponent()->addChildComponent (this); } } void paint (Graphics& g) { g.setOpacity (1.0f); if (type < 2) { int imH = jmin (topLeft.getHeight(), getHeight() / 2); g.drawImage (topLeft, 0, 0, topLeft.getWidth(), imH, 0, 0, topLeft.getWidth(), imH); imH = jmin (bottomRight.getHeight(), getHeight() - getHeight() / 2); g.drawImage (bottomRight, 0, getHeight() - imH, bottomRight.getWidth(), imH, 0, bottomRight.getHeight() - imH, bottomRight.getWidth(), imH); g.setTiledImageFill (filler, 0, 0, 1.0f); g.fillRect (0, topLeft.getHeight(), getWidth(), getHeight() - (topLeft.getHeight() + bottomRight.getHeight())); } else { int imW = jmin (topLeft.getWidth(), getWidth() / 2); g.drawImage (topLeft, 0, 0, imW, topLeft.getHeight(), 0, 0, imW, topLeft.getHeight()); imW = jmin (bottomRight.getWidth(), getWidth() - getWidth() / 2); g.drawImage (bottomRight, getWidth() - imW, 0, imW, bottomRight.getHeight(), bottomRight.getWidth() - imW, 0, imW, bottomRight.getHeight()); g.setTiledImageFill (filler, 0, 0, 1.0f); g.fillRect (topLeft.getWidth(), 0, getWidth() - (topLeft.getWidth() + bottomRight.getWidth()), getHeight()); } } void resized() { repaint(); // (needed for correct repainting) } private: const Image topLeft, bottomRight, filler; const int type; // 0 = left, 1 = right, 2 = top, 3 = bottom. left + right are full-height JUCE_DECLARE_NON_COPYABLE (ShadowWindow); }; //============================================================================== DropShadower::DropShadower (const DropShadow& shadow_) : owner (nullptr), shadow (shadow_), reentrant (false) { } DropShadower::~DropShadower() { if (owner != nullptr) owner->removeComponentListener (this); reentrant = true; shadowWindows.clear(); } 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); jassert (owner->isOpaque()); // doesn't work properly for semi-transparent comps! owner->addComponentListener (this); updateShadows(); } } void DropShadower::componentMovedOrResized (Component&, bool /*wasMoved*/, bool /*wasResized*/) { updateShadows(); } void DropShadower::componentBroughtToFront (Component&) { bringShadowWindowsToFront(); } void DropShadower::componentParentHierarchyChanged (Component&) { shadowWindows.clear(); updateShadows(); } void DropShadower::componentVisibilityChanged (Component&) { updateShadows(); } void DropShadower::updateShadows() { if (reentrant || owner == nullptr) return; ComponentPeer* const peer = owner->getPeer(); const bool isOwnerVisible = owner->isVisible() && (peer == nullptr || ! peer->isMinimised()); const bool createShadowWindows = shadowWindows.size() == 0 && owner->getWidth() > 0 && owner->getHeight() > 0 && isOwnerVisible && (Desktop::canUseSemiTransparentWindows() || owner->getParentComponent() != nullptr); { const ScopedValueSetter setter (reentrant, true, false); const int shadowEdge = jmax (shadow.offset.x, shadow.offset.y) + shadow.radius; if (createShadowWindows) { const int shadowEdge2 = shadowEdge * 2; const int imageSize = shadowEdge * 5; // keep a cached version of the image to save doing the gaussian too often int64 hash = shadow.radius ^ 0x2342dfa7; hash = hash * 101 + shadow.offset.x; hash = hash * 101 + shadow.offset.y; hash = hash * 65537 + shadow.colour.getARGB(); Image bigIm (ImageCache::getFromHashCode (hash)); if (bigIm.isNull()) { bigIm = Image (Image::ARGB, imageSize, imageSize, true); Graphics g (bigIm); Path p; p.addRectangle ((float) (shadowEdge + shadow.offset.x), (float) (shadowEdge + shadow.offset.y), (float) (imageSize - shadowEdge2), (float) (imageSize - shadowEdge2)); shadow.drawForPath (g, p); ImageCache::addImageToCache (bigIm, hash); } jassert (imageSize == bigIm.getWidth() && imageSize == bigIm.getHeight()); setShadowImage (bigIm, 0, shadowEdge, shadowEdge2, 0, 0); setShadowImage (bigIm, 1, shadowEdge, shadowEdge2, 0, imageSize - shadowEdge2); setShadowImage (bigIm, 2, shadowEdge, shadowEdge, 0, shadowEdge2); setShadowImage (bigIm, 3, shadowEdge, shadowEdge2, imageSize - shadowEdge, 0); setShadowImage (bigIm, 4, shadowEdge, shadowEdge2, imageSize - shadowEdge, imageSize - shadowEdge2); setShadowImage (bigIm, 5, shadowEdge, shadowEdge, imageSize - shadowEdge, shadowEdge2); setShadowImage (bigIm, 6, shadowEdge, shadowEdge, shadowEdge, 0); setShadowImage (bigIm, 7, shadowEdge, shadowEdge, imageSize - shadowEdge2, 0); setShadowImage (bigIm, 8, shadowEdge, shadowEdge, shadowEdge2, 0); setShadowImage (bigIm, 9, shadowEdge, shadowEdge, shadowEdge, imageSize - shadowEdge); setShadowImage (bigIm, 10, shadowEdge, shadowEdge, imageSize - shadowEdge2, imageSize - shadowEdge); setShadowImage (bigIm, 11, shadowEdge, shadowEdge, shadowEdge2, imageSize - shadowEdge); for (int i = 0; i < 4; ++i) shadowWindows.add (new ShadowWindow (*owner, i, shadowImageSections)); } if (shadowWindows.size() >= 4) { const int x = owner->getX(); const int y = owner->getY() - shadowEdge; const int w = owner->getWidth(); const int h = owner->getHeight() + shadowEdge + shadowEdge; for (int i = shadowWindows.size(); --i >= 0;) { // there seem to be rare situations where the dropshadower may be deleted by // callbacks during this loop, so use a weak ref to watch out for this.. WeakReference sw (shadowWindows[i]); if (sw != nullptr) sw->setAlwaysOnTop (owner->isAlwaysOnTop()); if (sw != nullptr) sw->setVisible (isOwnerVisible); if (sw != nullptr) { switch (i) { case 0: sw->setBounds (x - shadowEdge, y, shadowEdge, h); break; case 1: sw->setBounds (x + w, y, shadowEdge, h); break; case 2: sw->setBounds (x, y, w, shadowEdge); break; case 3: sw->setBounds (x, owner->getBottom(), w, shadowEdge); break; default: break; } } if (sw == nullptr) return; } } } if (createShadowWindows) bringShadowWindowsToFront(); } void DropShadower::setShadowImage (const Image& src, const int num, const int w, const int h, const int sx, const int sy) { shadowImageSections[num] = Image (Image::ARGB, w, h, true); Graphics g (shadowImageSections[num]); g.drawImage (src, 0, 0, w, h, sx, sy, w, h); } void DropShadower::bringShadowWindowsToFront() { if (! reentrant) { updateShadows(); const ScopedValueSetter setter (reentrant, true, false); for (int i = shadowWindows.size(); --i >= 0;) shadowWindows.getUnchecked(i)->toBehind (owner); } }