From d5f7e53dd6d98b58ab5a6d05fcb3f15b7cb36942 Mon Sep 17 00:00:00 2001 From: falkTX Date: Mon, 1 Mar 2021 11:07:48 +0000 Subject: [PATCH] juce-current tweaks as needed for Vitalium Signed-off-by: falkTX --- libs/juce-current/AppConfig.h | 4 +- .../components/juce_Component.cpp | 10 +- .../components/juce_Component.cpp.orig | 3032 +++++++++++++++++ .../components/juce_Component.cpp.rej | 39 + .../components/juce_Component.h | 11 + .../components/juce_Component.h.orig | 2413 +++++++++++++ .../components/juce_Component.h.rej | 20 + .../mouse/juce_MouseInputSource.cpp | 2 +- .../mouse/juce_MouseInputSource.cpp.orig | 781 +++++ .../mouse/juce_MouseInputSource.cpp.rej | 11 + .../windows/juce_ComponentPeer.cpp | 4 +- .../windows/juce_ComponentPeer.cpp.orig | 593 ++++ .../windows/juce_ComponentPeer.cpp.rej | 20 + .../native/juce_OpenGLExtensions.h | 8 +- .../native/juce_OpenGLExtensions.h.orig | 160 + .../native/juce_OpenGLExtensions.h.rej | 17 + 16 files changed, 7119 insertions(+), 6 deletions(-) create mode 100644 libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp.orig create mode 100644 libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp.rej create mode 100644 libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h.orig create mode 100644 libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h.rej create mode 100644 libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp.orig create mode 100644 libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp.rej create mode 100644 libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp.orig create mode 100644 libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp.rej create mode 100644 libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h.orig create mode 100644 libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h.rej diff --git a/libs/juce-current/AppConfig.h b/libs/juce-current/AppConfig.h index 54c5c506..b9ca8000 100755 --- a/libs/juce-current/AppConfig.h +++ b/libs/juce-current/AppConfig.h @@ -296,7 +296,7 @@ You must respect the FFTW license when enabling this option. */ -#define JUCE_DSP_USE_SHARED_FFTW 0 +#define JUCE_DSP_USE_SHARED_FFTW 1 /** Config: JUCE_DSP_USE_STATIC_FFTW @@ -425,6 +425,8 @@ //============================================================================= // juce_opengl +#define JUCE_OPENGL3 1 + //============================================================================= // drowaudio diff --git a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp index c4404c63..fa8d3ccf 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp @@ -387,6 +387,10 @@ struct Component::ComponentHelpers template static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p) { + float total_scaling = source->getTotalPixelScaling(); + Component* top = nullptr; + if (source) + top = source->getTopLevelComponent(); while (source != nullptr) { if (source == target) @@ -395,6 +399,9 @@ struct Component::ComponentHelpers if (source->isParentOf (target)) return convertFromDistantParentSpace (source, *target, p); + if (source == top) + p /= total_scaling; + p = convertToParentSpace (*source, p); source = source->getParentComponent(); } @@ -1390,13 +1397,14 @@ bool Component::reallyContains (Point point, bool returnTrueIfWithinAChild) Component* Component::getComponentAt (Point position) { + Point scale = (position.toFloat() * getPixelScaling()).roundToInt(); if (flags.visibleFlag && ComponentHelpers::hitTest (*this, position)) { for (int i = childComponentList.size(); --i >= 0;) { auto* child = childComponentList.getUnchecked(i); - child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, position)); + child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, scale)); if (child != nullptr) return child; diff --git a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp.orig b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp.orig new file mode 100644 index 00000000..c4404c63 --- /dev/null +++ b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp.orig @@ -0,0 +1,3032 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 6 End-User License + Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). + + End User License Agreement: www.juce.com/juce-6-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +Component* Component::currentlyFocusedComponent = nullptr; + + +//============================================================================== +class Component::MouseListenerList +{ +public: + MouseListenerList() noexcept {} + + void addListener (MouseListener* newListener, bool wantsEventsForAllNestedChildComponents) + { + if (! listeners.contains (newListener)) + { + if (wantsEventsForAllNestedChildComponents) + { + listeners.insert (0, newListener); + ++numDeepMouseListeners; + } + else + { + listeners.add (newListener); + } + } + } + + void removeListener (MouseListener* listenerToRemove) + { + auto index = listeners.indexOf (listenerToRemove); + + if (index >= 0) + { + if (index < numDeepMouseListeners) + --numDeepMouseListeners; + + listeners.remove (index); + } + } + + // g++ 4.8 cannot deduce the parameter pack inside the function pointer when it has more than one element + #if defined(__GNUC__) && __GNUC__ < 5 && ! defined(__clang__) + template + static void sendMouseEvent (Component& comp, Component::BailOutChecker& checker, + void (MouseListener::*eventMethod) (const MouseEvent&), + Params... params) + { + sendMouseEvent (comp, checker, eventMethod, params...); + } + + template + static void sendMouseEvent (Component& comp, Component::BailOutChecker& checker, + void (MouseListener::*eventMethod) (const MouseEvent&, const MouseWheelDetails&), + Params... params) + { + sendMouseEvent (comp, checker, eventMethod, params...); + } + + template + static void sendMouseEvent (Component& comp, Component::BailOutChecker& checker, + void (MouseListener::*eventMethod) (const MouseEvent&, float), + Params... params) + { + sendMouseEvent (comp, checker, eventMethod, params...); + } + + template + static void sendMouseEvent (Component& comp, Component::BailOutChecker& checker, + EventMethod eventMethod, + Params... params) + #else + template + static void sendMouseEvent (Component& comp, Component::BailOutChecker& checker, + void (MouseListener::*eventMethod) (Params...), + Params... params) + #endif + { + if (checker.shouldBailOut()) + return; + + if (auto* list = comp.mouseListeners.get()) + { + for (int i = list->listeners.size(); --i >= 0;) + { + (list->listeners.getUnchecked(i)->*eventMethod) (params...); + + if (checker.shouldBailOut()) + return; + + i = jmin (i, list->listeners.size()); + } + } + + for (Component* p = comp.parentComponent; p != nullptr; p = p->parentComponent) + { + if (auto* list = p->mouseListeners.get()) + { + if (list->numDeepMouseListeners > 0) + { + BailOutChecker2 checker2 (checker, p); + + for (int i = list->numDeepMouseListeners; --i >= 0;) + { + (list->listeners.getUnchecked(i)->*eventMethod) (params...); + + if (checker2.shouldBailOut()) + return; + + i = jmin (i, list->numDeepMouseListeners); + } + } + } + } + } + +private: + Array listeners; + int numDeepMouseListeners = 0; + + struct BailOutChecker2 + { + BailOutChecker2 (Component::BailOutChecker& boc, Component* comp) + : checker (boc), safePointer (comp) + { + } + + bool shouldBailOut() const noexcept + { + return checker.shouldBailOut() || safePointer == nullptr; + } + + private: + Component::BailOutChecker& checker; + const WeakReference safePointer; + + JUCE_DECLARE_NON_COPYABLE (BailOutChecker2) + }; + + JUCE_DECLARE_NON_COPYABLE (MouseListenerList) +}; + +//============================================================================== +struct FocusRestorer +{ + FocusRestorer() : lastFocus (Component::getCurrentlyFocusedComponent()) {} + + ~FocusRestorer() + { + if (lastFocus != nullptr + && lastFocus->isShowing() + && ! lastFocus->isCurrentlyBlockedByAnotherModalComponent()) + lastFocus->grabKeyboardFocus(); + } + + WeakReference lastFocus; + + JUCE_DECLARE_NON_COPYABLE (FocusRestorer) +}; + +//============================================================================== +struct ScalingHelpers +{ + template + static PointOrRect unscaledScreenPosToScaled (float scale, PointOrRect pos) noexcept + { + return scale != 1.0f ? pos / scale : pos; + } + + template + static PointOrRect scaledScreenPosToUnscaled (float scale, PointOrRect pos) noexcept + { + return scale != 1.0f ? pos * scale : pos; + } + + // For these, we need to avoid getSmallestIntegerContainer being used, which causes + // judder when moving windows + static Rectangle unscaledScreenPosToScaled (float scale, Rectangle pos) noexcept + { + return scale != 1.0f ? Rectangle (roundToInt ((float) pos.getX() / scale), + roundToInt ((float) pos.getY() / scale), + roundToInt ((float) pos.getWidth() / scale), + roundToInt ((float) pos.getHeight() / scale)) : pos; + } + + static Rectangle scaledScreenPosToUnscaled (float scale, Rectangle pos) noexcept + { + return scale != 1.0f ? Rectangle (roundToInt ((float) pos.getX() * scale), + roundToInt ((float) pos.getY() * scale), + roundToInt ((float) pos.getWidth() * scale), + roundToInt ((float) pos.getHeight() * scale)) : pos; + } + + static Rectangle unscaledScreenPosToScaled (float scale, Rectangle pos) noexcept + { + return scale != 1.0f ? Rectangle (pos.getX() / scale, + pos.getY() / scale, + pos.getWidth() / scale, + pos.getHeight() / scale) : pos; + } + + static Rectangle scaledScreenPosToUnscaled (float scale, Rectangle pos) noexcept + { + return scale != 1.0f ? Rectangle (pos.getX() * scale, + pos.getY() * scale, + pos.getWidth() * scale, + pos.getHeight() * scale) : pos; + } + + template + static PointOrRect unscaledScreenPosToScaled (PointOrRect pos) noexcept + { + return unscaledScreenPosToScaled (Desktop::getInstance().getGlobalScaleFactor(), pos); + } + + template + static PointOrRect scaledScreenPosToUnscaled (PointOrRect pos) noexcept + { + return scaledScreenPosToUnscaled (Desktop::getInstance().getGlobalScaleFactor(), pos); + } + + template + static PointOrRect unscaledScreenPosToScaled (const Component& comp, PointOrRect pos) noexcept + { + return unscaledScreenPosToScaled (comp.getDesktopScaleFactor(), pos); + } + + template + static PointOrRect scaledScreenPosToUnscaled (const Component& comp, PointOrRect pos) noexcept + { + return scaledScreenPosToUnscaled (comp.getDesktopScaleFactor(), pos); + } + + static Point addPosition (Point p, const Component& c) noexcept { return p + c.getPosition(); } + static Rectangle addPosition (Rectangle p, const Component& c) noexcept { return p + c.getPosition(); } + static Point addPosition (Point p, const Component& c) noexcept { return p + c.getPosition().toFloat(); } + static Rectangle addPosition (Rectangle p, const Component& c) noexcept { return p + c.getPosition().toFloat(); } + static Point subtractPosition (Point p, const Component& c) noexcept { return p - c.getPosition(); } + static Rectangle subtractPosition (Rectangle p, const Component& c) noexcept { return p - c.getPosition(); } + static Point subtractPosition (Point p, const Component& c) noexcept { return p - c.getPosition().toFloat(); } + static Rectangle subtractPosition (Rectangle p, const Component& c) noexcept { return p - c.getPosition().toFloat(); } +}; + +static const char colourPropertyPrefix[] = "jcclr_"; + +//============================================================================== +struct Component::ComponentHelpers +{ + #if JUCE_MODAL_LOOPS_PERMITTED + static void* runModalLoopCallback (void* userData) + { + return (void*) (pointer_sized_int) static_cast (userData)->runModalLoop(); + } + #endif + + static Identifier getColourPropertyID (int colourID) + { + char buffer[32]; + auto* end = buffer + numElementsInArray (buffer) - 1; + auto* t = end; + *t = 0; + + for (auto v = (uint32) colourID;;) + { + *--t = "0123456789abcdef" [v & 15]; + v >>= 4; + + if (v == 0) + break; + } + + for (int i = (int) sizeof (colourPropertyPrefix) - 1; --i >= 0;) + *--t = colourPropertyPrefix[i]; + + return t; + } + + //============================================================================== + static bool hitTest (Component& comp, Point localPoint) + { + return isPositiveAndBelow (localPoint.x, comp.getWidth()) + && isPositiveAndBelow (localPoint.y, comp.getHeight()) + && comp.hitTest (localPoint.x, localPoint.y); + } + + // converts an unscaled position within a peer to the local position within that peer's component + template + static PointOrRect rawPeerPositionToLocal (const Component& comp, PointOrRect pos) noexcept + { + if (comp.isTransformed()) + pos = pos.transformedBy (comp.getTransform().inverted()); + + return ScalingHelpers::unscaledScreenPosToScaled (comp, pos); + } + + // converts a position within a peer's component to the unscaled position within the peer + template + static PointOrRect localPositionToRawPeerPos (const Component& comp, PointOrRect pos) noexcept + { + if (comp.isTransformed()) + pos = pos.transformedBy (comp.getTransform()); + + return ScalingHelpers::scaledScreenPosToUnscaled (comp, pos); + } + + template + static PointOrRect convertFromParentSpace (const Component& comp, PointOrRect pointInParentSpace) + { + if (comp.affineTransform != nullptr) + pointInParentSpace = pointInParentSpace.transformedBy (comp.affineTransform->inverted()); + + if (comp.isOnDesktop()) + { + if (auto* peer = comp.getPeer()) + pointInParentSpace = ScalingHelpers::unscaledScreenPosToScaled + (comp, peer->globalToLocal (ScalingHelpers::scaledScreenPosToUnscaled (pointInParentSpace))); + else + jassertfalse; + } + else + { + pointInParentSpace = ScalingHelpers::subtractPosition (pointInParentSpace, comp); + } + + return pointInParentSpace; + } + + template + static PointOrRect convertToParentSpace (const Component& comp, PointOrRect pointInLocalSpace) + { + if (comp.isOnDesktop()) + { + if (auto* peer = comp.getPeer()) + pointInLocalSpace = ScalingHelpers::unscaledScreenPosToScaled + (peer->localToGlobal (ScalingHelpers::scaledScreenPosToUnscaled (comp, pointInLocalSpace))); + else + jassertfalse; + } + else + { + pointInLocalSpace = ScalingHelpers::addPosition (pointInLocalSpace, comp); + } + + if (comp.affineTransform != nullptr) + pointInLocalSpace = pointInLocalSpace.transformedBy (*comp.affineTransform); + + return pointInLocalSpace; + } + + template + static PointOrRect convertFromDistantParentSpace (const Component* parent, const Component& target, PointOrRect coordInParent) + { + auto* directParent = target.getParentComponent(); + jassert (directParent != nullptr); + + if (directParent == parent) + return convertFromParentSpace (target, coordInParent); + + return convertFromParentSpace (target, convertFromDistantParentSpace (parent, *directParent, coordInParent)); + } + + template + static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p) + { + while (source != nullptr) + { + if (source == target) + return p; + + if (source->isParentOf (target)) + return convertFromDistantParentSpace (source, *target, p); + + p = convertToParentSpace (*source, p); + source = source->getParentComponent(); + } + + jassert (source == nullptr); + if (target == nullptr) + return p; + + auto* topLevelComp = target->getTopLevelComponent(); + + p = convertFromParentSpace (*topLevelComp, p); + + if (topLevelComp == target) + return p; + + return convertFromDistantParentSpace (topLevelComp, *target, p); + } + + static bool clipObscuredRegions (const Component& comp, Graphics& g, + const Rectangle clipRect, Point delta) + { + bool wasClipped = false; + + for (int i = comp.childComponentList.size(); --i >= 0;) + { + auto& child = *comp.childComponentList.getUnchecked(i); + + if (child.isVisible() && ! child.isTransformed()) + { + auto newClip = clipRect.getIntersection (child.boundsRelativeToParent); + + if (! newClip.isEmpty()) + { + if (child.isOpaque() && child.componentTransparency == 0) + { + g.excludeClipRegion (newClip + delta); + wasClipped = true; + } + else + { + auto childPos = child.getPosition(); + + if (clipObscuredRegions (child, g, newClip - childPos, childPos + delta)) + wasClipped = true; + } + } + } + } + + return wasClipped; + } + + static Rectangle getParentOrMainMonitorBounds (const Component& comp) + { + if (auto* p = comp.getParentComponent()) + return p->getLocalBounds(); + + return Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; + } + + static void releaseAllCachedImageResources (Component& c) + { + if (auto* cached = c.getCachedComponentImage()) + cached->releaseResources(); + + for (auto* child : c.childComponentList) + releaseAllCachedImageResources (*child); + } +}; + +//============================================================================== +Component::Component() noexcept + : componentFlags (0) +{ +} + +Component::Component (const String& name) noexcept + : componentName (name), componentFlags (0) +{ +} + +Component::~Component() +{ + static_assert (sizeof (flags) <= sizeof (componentFlags), "componentFlags has too many bits!"); + + componentListeners.call ([this] (ComponentListener& l) { l.componentBeingDeleted (*this); }); + + masterReference.clear(); + + while (childComponentList.size() > 0) + removeChildComponent (childComponentList.size() - 1, false, true); + + if (parentComponent != nullptr) + parentComponent->removeChildComponent (parentComponent->childComponentList.indexOf (this), true, false); + else if (hasKeyboardFocus (true)) + giveAwayFocus (currentlyFocusedComponent != this); + + if (flags.hasHeavyweightPeerFlag) + removeFromDesktop(); + + // Something has added some children to this component during its destructor! Not a smart idea! + jassert (childComponentList.size() == 0); +} + +//============================================================================== +void Component::setName (const String& name) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + if (componentName != name) + { + componentName = name; + + if (flags.hasHeavyweightPeerFlag) + if (auto* peer = getPeer()) + peer->setTitle (name); + + BailOutChecker checker (this); + componentListeners.callChecked (checker, [this] (ComponentListener& l) { l.componentNameChanged (*this); }); + } +} + +void Component::setComponentID (const String& newID) +{ + componentID = newID; +} + +void Component::setVisible (bool shouldBeVisible) +{ + if (flags.visibleFlag != shouldBeVisible) + { + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + const WeakReference safePointer (this); + flags.visibleFlag = shouldBeVisible; + + if (shouldBeVisible) + repaint(); + else + repaintParent(); + + sendFakeMouseMove(); + + if (! shouldBeVisible) + { + ComponentHelpers::releaseAllCachedImageResources (*this); + + if (hasKeyboardFocus (true)) + { + if (parentComponent != nullptr) + parentComponent->grabKeyboardFocus(); + + if (hasKeyboardFocus (true)) + giveAwayFocus (true); + } + } + + if (safePointer != nullptr) + { + sendVisibilityChangeMessage(); + + if (safePointer != nullptr && flags.hasHeavyweightPeerFlag) + { + if (auto* peer = getPeer()) + { + peer->setVisible (shouldBeVisible); + internalHierarchyChanged(); + } + } + } + } +} + +void Component::visibilityChanged() {} + +void Component::sendVisibilityChangeMessage() +{ + BailOutChecker checker (this); + visibilityChanged(); + + if (! checker.shouldBailOut()) + componentListeners.callChecked (checker, [this] (ComponentListener& l) { l.componentVisibilityChanged (*this); }); +} + +bool Component::isShowing() const +{ + if (! flags.visibleFlag) + return false; + + if (parentComponent != nullptr) + return parentComponent->isShowing(); + + if (auto* peer = getPeer()) + return ! peer->isMinimised(); + + return false; +} + +//============================================================================== +void* Component::getWindowHandle() const +{ + if (auto* peer = getPeer()) + return peer->getNativeHandle(); + + return nullptr; +} + +//============================================================================== +void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + + if (isOpaque()) + styleWanted &= ~ComponentPeer::windowIsSemiTransparent; + else + styleWanted |= ComponentPeer::windowIsSemiTransparent; + + // don't use getPeer(), so that we only get the peer that's specifically + // for this comp, and not for one of its parents. + auto* peer = ComponentPeer::getPeerFor (this); + + if (peer == nullptr || styleWanted != peer->getStyleFlags()) + { + const WeakReference safePointer (this); + + #if JUCE_LINUX + // it's wise to give the component a non-zero size before + // putting it on the desktop, as X windows get confused by this, and + // a (1, 1) minimum size is enforced here. + setSize (jmax (1, getWidth()), + jmax (1, getHeight())); + #endif + + auto topLeft = getScreenPosition(); + + bool wasFullscreen = false; + bool wasMinimised = false; + ComponentBoundsConstrainer* currentConstrainer = nullptr; + Rectangle oldNonFullScreenBounds; + int oldRenderingEngine = -1; + + if (peer != nullptr) + { + std::unique_ptr oldPeerToDelete (peer); + + wasFullscreen = peer->isFullScreen(); + wasMinimised = peer->isMinimised(); + currentConstrainer = peer->getConstrainer(); + oldNonFullScreenBounds = peer->getNonFullScreenBounds(); + oldRenderingEngine = peer->getCurrentRenderingEngine(); + + flags.hasHeavyweightPeerFlag = false; + Desktop::getInstance().removeDesktopComponent (this); + internalHierarchyChanged(); // give comps a chance to react to the peer change before the old peer is deleted. + + if (safePointer == nullptr) + return; + + setTopLeftPosition (topLeft); + } + + if (parentComponent != nullptr) + parentComponent->removeChildComponent (this); + + if (safePointer != nullptr) + { + flags.hasHeavyweightPeerFlag = true; + + peer = createNewPeer (styleWanted, nativeWindowToAttachTo); + + Desktop::getInstance().addDesktopComponent (this); + + boundsRelativeToParent.setPosition (topLeft); + peer->updateBounds(); + + if (oldRenderingEngine >= 0) + peer->setCurrentRenderingEngine (oldRenderingEngine); + + peer->setVisible (isVisible()); + + peer = ComponentPeer::getPeerFor (this); + + if (peer == nullptr) + return; + + if (wasFullscreen) + { + peer->setFullScreen (true); + peer->setNonFullScreenBounds (oldNonFullScreenBounds); + } + + if (wasMinimised) + peer->setMinimised (true); + + #if JUCE_WINDOWS + if (isAlwaysOnTop()) + peer->setAlwaysOnTop (true); + #endif + + peer->setConstrainer (currentConstrainer); + + repaint(); + internalHierarchyChanged(); + } + } +} + +void Component::removeFromDesktop() +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + if (flags.hasHeavyweightPeerFlag) + { + ComponentHelpers::releaseAllCachedImageResources (*this); + + auto* peer = ComponentPeer::getPeerFor (this); + jassert (peer != nullptr); + + flags.hasHeavyweightPeerFlag = false; + delete peer; + + Desktop::getInstance().removeDesktopComponent (this); + } +} + +bool Component::isOnDesktop() const noexcept +{ + return flags.hasHeavyweightPeerFlag; +} + +ComponentPeer* Component::getPeer() const +{ + if (flags.hasHeavyweightPeerFlag) + return ComponentPeer::getPeerFor (this); + + if (parentComponent == nullptr) + return nullptr; + + return parentComponent->getPeer(); +} + +void Component::userTriedToCloseWindow() +{ + /* This means that the user's trying to get rid of your window with the 'close window' system + menu option (on windows) or possibly the task manager - you should really handle this + and delete or hide your component in an appropriate way. + + If you want to ignore the event and don't want to trigger this assertion, just override + this method and do nothing. + */ + jassertfalse; +} + +void Component::minimisationStateChanged (bool) {} + +float Component::getDesktopScaleFactor() const { return Desktop::getInstance().getGlobalScaleFactor(); } + +//============================================================================== +void Component::setOpaque (bool shouldBeOpaque) +{ + if (shouldBeOpaque != flags.opaqueFlag) + { + flags.opaqueFlag = shouldBeOpaque; + + if (flags.hasHeavyweightPeerFlag) + if (auto* peer = ComponentPeer::getPeerFor (this)) + addToDesktop (peer->getStyleFlags()); // recreates the heavyweight window + + repaint(); + } +} + +bool Component::isOpaque() const noexcept +{ + return flags.opaqueFlag; +} + +//============================================================================== +struct StandardCachedComponentImage : public CachedComponentImage +{ + StandardCachedComponentImage (Component& c) noexcept : owner (c) {} + + void paint (Graphics& g) override + { + scale = g.getInternalContext().getPhysicalPixelScaleFactor(); + auto compBounds = owner.getLocalBounds(); + auto imageBounds = compBounds * scale; + + if (image.isNull() || image.getBounds() != imageBounds) + { + image = Image (owner.isOpaque() ? Image::RGB + : Image::ARGB, + jmax (1, imageBounds.getWidth()), + jmax (1, imageBounds.getHeight()), + ! owner.isOpaque()); + + validArea.clear(); + } + + if (! validArea.containsRectangle (compBounds)) + { + Graphics imG (image); + auto& lg = imG.getInternalContext(); + + lg.addTransform (AffineTransform::scale (scale)); + + for (auto& i : validArea) + lg.excludeClipRectangle (i); + + if (! owner.isOpaque()) + { + lg.setFill (Colours::transparentBlack); + lg.fillRect (compBounds, true); + lg.setFill (Colours::black); + } + + owner.paintEntireComponent (imG, true); + } + + validArea = compBounds; + + g.setColour (Colours::black.withAlpha (owner.getAlpha())); + g.drawImageTransformed (image, AffineTransform::scale ((float) compBounds.getWidth() / (float) imageBounds.getWidth(), + (float) compBounds.getHeight() / (float) imageBounds.getHeight()), false); + } + + bool invalidateAll() override { validArea.clear(); return true; } + bool invalidate (const Rectangle& area) override { validArea.subtract (area); return true; } + void releaseResources() override { image = Image(); } + +private: + Image image; + RectangleList validArea; + Component& owner; + float scale = 1.0f; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandardCachedComponentImage) +}; + +void Component::setCachedComponentImage (CachedComponentImage* newCachedImage) +{ + if (cachedImage.get() != newCachedImage) + { + cachedImage.reset (newCachedImage); + repaint(); + } +} + +void Component::setBufferedToImage (bool shouldBeBuffered) +{ + // This assertion means that this component is already using a custom CachedComponentImage, + // so by calling setBufferedToImage, you'll be deleting the custom one - this is almost certainly + // not what you wanted to happen... If you really do know what you're doing here, and want to + // avoid this assertion, just call setCachedComponentImage (nullptr) before setBufferedToImage(). + jassert (cachedImage == nullptr || dynamic_cast (cachedImage.get()) != nullptr); + + if (shouldBeBuffered) + { + if (cachedImage == nullptr) + cachedImage.reset (new StandardCachedComponentImage (*this)); + } + else + { + cachedImage.reset(); + } +} + +//============================================================================== +void Component::reorderChildInternal (int sourceIndex, int destIndex) +{ + if (sourceIndex != destIndex) + { + auto* c = childComponentList.getUnchecked (sourceIndex); + jassert (c != nullptr); + c->repaintParent(); + + childComponentList.move (sourceIndex, destIndex); + + sendFakeMouseMove(); + internalChildrenChanged(); + } +} + +void Component::toFront (bool setAsForeground) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + if (flags.hasHeavyweightPeerFlag) + { + if (auto* peer = getPeer()) + { + peer->toFront (setAsForeground); + + if (setAsForeground && ! hasKeyboardFocus (true)) + grabKeyboardFocus(); + } + } + else if (parentComponent != nullptr) + { + auto& childList = parentComponent->childComponentList; + + if (childList.getLast() != this) + { + auto index = childList.indexOf (this); + + if (index >= 0) + { + int insertIndex = -1; + + if (! flags.alwaysOnTopFlag) + { + insertIndex = childList.size() - 1; + + while (insertIndex > 0 && childList.getUnchecked (insertIndex)->isAlwaysOnTop()) + --insertIndex; + } + + parentComponent->reorderChildInternal (index, insertIndex); + } + } + + if (setAsForeground) + { + internalBroughtToFront(); + + if (isShowing()) + grabKeyboardFocus(); + } + } +} + +void Component::toBehind (Component* other) +{ + if (other != nullptr && other != this) + { + // the two components must belong to the same parent.. + jassert (parentComponent == other->parentComponent); + + if (parentComponent != nullptr) + { + auto& childList = parentComponent->childComponentList; + auto index = childList.indexOf (this); + + if (index >= 0 && childList [index + 1] != other) + { + auto otherIndex = childList.indexOf (other); + + if (otherIndex >= 0) + { + if (index < otherIndex) + --otherIndex; + + parentComponent->reorderChildInternal (index, otherIndex); + } + } + } + else if (isOnDesktop()) + { + jassert (other->isOnDesktop()); + + if (other->isOnDesktop()) + { + auto* us = getPeer(); + auto* them = other->getPeer(); + jassert (us != nullptr && them != nullptr); + + if (us != nullptr && them != nullptr) + us->toBehind (them); + } + } + } +} + +void Component::toBack() +{ + if (isOnDesktop()) + { + jassertfalse; //xxx need to add this to native window + } + else if (parentComponent != nullptr) + { + auto& childList = parentComponent->childComponentList; + + if (childList.getFirst() != this) + { + auto index = childList.indexOf (this); + + if (index > 0) + { + int insertIndex = 0; + + if (flags.alwaysOnTopFlag) + while (insertIndex < childList.size() && ! childList.getUnchecked (insertIndex)->isAlwaysOnTop()) + ++insertIndex; + + parentComponent->reorderChildInternal (index, insertIndex); + } + } + } +} + +void Component::setAlwaysOnTop (bool shouldStayOnTop) +{ + if (shouldStayOnTop != flags.alwaysOnTopFlag) + { + BailOutChecker checker (this); + + flags.alwaysOnTopFlag = shouldStayOnTop; + + if (isOnDesktop()) + { + if (auto* peer = getPeer()) + { + if (! peer->setAlwaysOnTop (shouldStayOnTop)) + { + // some kinds of peer can't change their always-on-top status, so + // for these, we'll need to create a new window + auto oldFlags = peer->getStyleFlags(); + removeFromDesktop(); + addToDesktop (oldFlags); + } + } + } + + if (shouldStayOnTop && ! checker.shouldBailOut()) + toFront (false); + + if (! checker.shouldBailOut()) + internalHierarchyChanged(); + } +} + +bool Component::isAlwaysOnTop() const noexcept +{ + return flags.alwaysOnTopFlag; +} + +//============================================================================== +int Component::proportionOfWidth (float proportion) const noexcept { return roundToInt (proportion * (float) boundsRelativeToParent.getWidth()); } +int Component::proportionOfHeight (float proportion) const noexcept { return roundToInt (proportion * (float) boundsRelativeToParent.getHeight()); } + +int Component::getParentWidth() const noexcept +{ + return parentComponent != nullptr ? parentComponent->getWidth() + : getParentMonitorArea().getWidth(); +} + +int Component::getParentHeight() const noexcept +{ + return parentComponent != nullptr ? parentComponent->getHeight() + : getParentMonitorArea().getHeight(); +} + +Rectangle Component::getParentMonitorArea() const +{ + return Desktop::getInstance().getDisplays().getDisplayForRect (getScreenBounds())->userArea; +} + +int Component::getScreenX() const { return getScreenPosition().x; } +int Component::getScreenY() const { return getScreenPosition().y; } +Point Component::getScreenPosition() const { return localPointToGlobal (Point()); } +Rectangle Component::getScreenBounds() const { return localAreaToGlobal (getLocalBounds()); } + +Point Component::getLocalPoint (const Component* source, Point point) const { return ComponentHelpers::convertCoordinate (this, source, point); } +Point Component::getLocalPoint (const Component* source, Point point) const { return ComponentHelpers::convertCoordinate (this, source, point); } +Rectangle Component::getLocalArea (const Component* source, Rectangle area) const { return ComponentHelpers::convertCoordinate (this, source, area); } +Rectangle Component::getLocalArea (const Component* source, Rectangle area) const { return ComponentHelpers::convertCoordinate (this, source, area); } + +Point Component::localPointToGlobal (Point point) const { return ComponentHelpers::convertCoordinate (nullptr, this, point); } +Point Component::localPointToGlobal (Point point) const { return ComponentHelpers::convertCoordinate (nullptr, this, point); } +Rectangle Component::localAreaToGlobal (Rectangle area) const { return ComponentHelpers::convertCoordinate (nullptr, this, area); } +Rectangle Component::localAreaToGlobal (Rectangle area) const { return ComponentHelpers::convertCoordinate (nullptr, this, area); } + +//============================================================================== +void Component::setBounds (int x, int y, int w, int h) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + if (w < 0) w = 0; + if (h < 0) h = 0; + + const bool wasResized = (getWidth() != w || getHeight() != h); + const bool wasMoved = (getX() != x || getY() != y); + + #if JUCE_DEBUG + // It's a very bad idea to try to resize a window during its paint() method! + jassert (! (flags.isInsidePaintCall && wasResized && isOnDesktop())); + #endif + + if (wasMoved || wasResized) + { + const bool showing = isShowing(); + + if (showing) + { + // send a fake mouse move to trigger enter/exit messages if needed.. + sendFakeMouseMove(); + + if (! flags.hasHeavyweightPeerFlag) + repaintParent(); + } + + boundsRelativeToParent.setBounds (x, y, w, h); + + if (showing) + { + if (wasResized) + repaint(); + else if (! flags.hasHeavyweightPeerFlag) + repaintParent(); + } + else if (cachedImage != nullptr) + { + cachedImage->invalidateAll(); + } + + flags.isMoveCallbackPending = wasMoved; + flags.isResizeCallbackPending = wasResized; + + if (flags.hasHeavyweightPeerFlag) + if (auto* peer = getPeer()) + peer->updateBounds(); + + sendMovedResizedMessagesIfPending(); + } +} + +void Component::sendMovedResizedMessagesIfPending() +{ + const bool wasMoved = flags.isMoveCallbackPending; + const bool wasResized = flags.isResizeCallbackPending; + + if (wasMoved || wasResized) + { + flags.isMoveCallbackPending = false; + flags.isResizeCallbackPending = false; + + sendMovedResizedMessages (wasMoved, wasResized); + } +} + +void Component::sendMovedResizedMessages (bool wasMoved, bool wasResized) +{ + BailOutChecker checker (this); + + if (wasMoved) + { + moved(); + + if (checker.shouldBailOut()) + return; + } + + if (wasResized) + { + resized(); + + if (checker.shouldBailOut()) + return; + + for (int i = childComponentList.size(); --i >= 0;) + { + childComponentList.getUnchecked(i)->parentSizeChanged(); + + if (checker.shouldBailOut()) + return; + + i = jmin (i, childComponentList.size()); + } + } + + if (parentComponent != nullptr) + parentComponent->childBoundsChanged (this); + + if (! checker.shouldBailOut()) + { + componentListeners.callChecked (checker, [this, wasMoved, wasResized] (ComponentListener& l) + { + l.componentMovedOrResized (*this, wasMoved, wasResized); + }); + } +} + +void Component::setSize (int w, int h) { setBounds (getX(), getY(), w, h); } + +void Component::setTopLeftPosition (int x, int y) { setTopLeftPosition ({ x, y }); } +void Component::setTopLeftPosition (Point pos) { setBounds (pos.x, pos.y, getWidth(), getHeight()); } + +void Component::setTopRightPosition (int x, int y) { setTopLeftPosition (x - getWidth(), y); } +void Component::setBounds (Rectangle r) { setBounds (r.getX(), r.getY(), r.getWidth(), r.getHeight()); } + +void Component::setCentrePosition (Point p) { setBounds (getBounds().withCentre (p.transformedBy (getTransform().inverted()))); } +void Component::setCentrePosition (int x, int y) { setCentrePosition ({ x, y }); } + +void Component::setCentreRelative (float x, float y) +{ + setCentrePosition (roundToInt ((float) getParentWidth() * x), + roundToInt ((float) getParentHeight() * y)); +} + +void Component::setBoundsRelative (Rectangle target) +{ + setBounds ((target * Point ((float) getParentWidth(), + (float) getParentHeight())).toNearestInt()); +} + +void Component::setBoundsRelative (float x, float y, float w, float h) +{ + setBoundsRelative ({ x, y, w, h }); +} + +void Component::centreWithSize (int width, int height) +{ + auto parentArea = ComponentHelpers::getParentOrMainMonitorBounds (*this) + .transformedBy (getTransform().inverted()); + + setBounds (parentArea.getCentreX() - width / 2, + parentArea.getCentreY() - height / 2, + width, height); +} + +void Component::setBoundsInset (BorderSize borders) +{ + setBounds (borders.subtractedFrom (ComponentHelpers::getParentOrMainMonitorBounds (*this))); +} + +void Component::setBoundsToFit (Rectangle targetArea, Justification justification, bool onlyReduceInSize) +{ + if (getLocalBounds().isEmpty() || targetArea.isEmpty()) + { + // it's no good calling this method unless both the component and + // target rectangle have a finite size. + jassertfalse; + return; + } + + auto sourceArea = targetArea.withZeroOrigin(); + + if (onlyReduceInSize + && getWidth() <= targetArea.getWidth() + && getHeight() <= targetArea.getHeight()) + { + sourceArea = getLocalBounds(); + } + else + { + auto sourceRatio = getHeight() / (double) getWidth(); + auto targetRatio = targetArea.getHeight() / (double) targetArea.getWidth(); + + if (sourceRatio <= targetRatio) + sourceArea.setHeight (jmin (targetArea.getHeight(), + roundToInt (targetArea.getWidth() * sourceRatio))); + else + sourceArea.setWidth (jmin (targetArea.getWidth(), + roundToInt (targetArea.getHeight() / sourceRatio))); + } + + if (! sourceArea.isEmpty()) + setBounds (justification.appliedToRectangle (sourceArea, targetArea)); +} + +//============================================================================== +void Component::setTransform (const AffineTransform& newTransform) +{ + // If you pass in a transform with no inverse, the component will have no dimensions, + // and there will be all sorts of maths errors when converting coordinates. + jassert (! newTransform.isSingularity()); + + if (newTransform.isIdentity()) + { + if (affineTransform != nullptr) + { + repaint(); + affineTransform.reset(); + repaint(); + sendMovedResizedMessages (false, false); + } + } + else if (affineTransform == nullptr) + { + repaint(); + affineTransform.reset (new AffineTransform (newTransform)); + repaint(); + sendMovedResizedMessages (false, false); + } + else if (*affineTransform != newTransform) + { + repaint(); + *affineTransform = newTransform; + repaint(); + sendMovedResizedMessages (false, false); + } +} + +bool Component::isTransformed() const noexcept +{ + return affineTransform != nullptr; +} + +AffineTransform Component::getTransform() const +{ + return affineTransform != nullptr ? *affineTransform : AffineTransform(); +} + +float Component::getApproximateScaleFactorForComponent (Component* targetComponent) +{ + AffineTransform transform; + + for (auto* target = targetComponent; target != nullptr; target = target->getParentComponent()) + { + transform = transform.followedBy (target->getTransform()); + + if (target->isOnDesktop()) + transform = transform.scaled (target->getDesktopScaleFactor()); + } + + auto transformScale = std::sqrt (std::abs (transform.getDeterminant())); + return transformScale / Desktop::getInstance().getGlobalScaleFactor(); +} + +//============================================================================== +bool Component::hitTest (int x, int y) +{ + if (! flags.ignoresMouseClicksFlag) + return true; + + if (flags.allowChildMouseClicksFlag) + { + for (int i = childComponentList.size(); --i >= 0;) + { + auto& child = *childComponentList.getUnchecked (i); + + if (child.isVisible() + && ComponentHelpers::hitTest (child, ComponentHelpers::convertFromParentSpace (child, Point (x, y)))) + return true; + } + } + + return false; +} + +void Component::setInterceptsMouseClicks (bool allowClicks, + bool allowClicksOnChildComponents) noexcept +{ + flags.ignoresMouseClicksFlag = ! allowClicks; + flags.allowChildMouseClicksFlag = allowClicksOnChildComponents; +} + +void Component::getInterceptsMouseClicks (bool& allowsClicksOnThisComponent, + bool& allowsClicksOnChildComponents) const noexcept +{ + allowsClicksOnThisComponent = ! flags.ignoresMouseClicksFlag; + allowsClicksOnChildComponents = flags.allowChildMouseClicksFlag; +} + +bool Component::contains (Point point) +{ + if (ComponentHelpers::hitTest (*this, point)) + { + if (parentComponent != nullptr) + return parentComponent->contains (ComponentHelpers::convertToParentSpace (*this, point)); + + if (flags.hasHeavyweightPeerFlag) + if (auto* peer = getPeer()) + return peer->contains (ComponentHelpers::localPositionToRawPeerPos (*this, point), true); + } + + return false; +} + +bool Component::reallyContains (Point point, bool returnTrueIfWithinAChild) +{ + if (! contains (point)) + return false; + + auto* top = getTopLevelComponent(); + auto* compAtPosition = top->getComponentAt (top->getLocalPoint (this, point)); + + return (compAtPosition == this) || (returnTrueIfWithinAChild && isParentOf (compAtPosition)); +} + +Component* Component::getComponentAt (Point position) +{ + if (flags.visibleFlag && ComponentHelpers::hitTest (*this, position)) + { + for (int i = childComponentList.size(); --i >= 0;) + { + auto* child = childComponentList.getUnchecked(i); + + child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, position)); + + if (child != nullptr) + return child; + } + + return this; + } + + return nullptr; +} + +Component* Component::getComponentAt (int x, int y) +{ + return getComponentAt ({ x, y }); +} + +//============================================================================== +void Component::addChildComponent (Component& child, int zOrder) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + jassert (this != &child); // adding a component to itself!? + + if (child.parentComponent != this) + { + if (child.parentComponent != nullptr) + child.parentComponent->removeChildComponent (&child); + else + child.removeFromDesktop(); + + child.parentComponent = this; + + if (child.isVisible()) + child.repaintParent(); + + if (! child.isAlwaysOnTop()) + { + if (zOrder < 0 || zOrder > childComponentList.size()) + zOrder = childComponentList.size(); + + while (zOrder > 0) + { + if (! childComponentList.getUnchecked (zOrder - 1)->isAlwaysOnTop()) + break; + + --zOrder; + } + } + + childComponentList.insert (zOrder, &child); + + child.internalHierarchyChanged(); + internalChildrenChanged(); + } +} + +void Component::addAndMakeVisible (Component& child, int zOrder) +{ + child.setVisible (true); + addChildComponent (child, zOrder); +} + +void Component::addChildComponent (Component* child, int zOrder) +{ + if (child != nullptr) + addChildComponent (*child, zOrder); +} + +void Component::addAndMakeVisible (Component* child, int zOrder) +{ + if (child != nullptr) + addAndMakeVisible (*child, zOrder); +} + +void Component::addChildAndSetID (Component* child, const String& childID) +{ + if (child != nullptr) + { + child->setComponentID (childID); + addAndMakeVisible (child); + } +} + +void Component::removeChildComponent (Component* child) +{ + removeChildComponent (childComponentList.indexOf (child), true, true); +} + +Component* Component::removeChildComponent (int index) +{ + return removeChildComponent (index, true, true); +} + +Component* Component::removeChildComponent (int index, bool sendParentEvents, bool sendChildEvents) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + auto* child = childComponentList [index]; + + if (child != nullptr) + { + sendParentEvents = sendParentEvents && child->isShowing(); + + if (sendParentEvents) + { + sendFakeMouseMove(); + + if (child->isVisible()) + child->repaintParent(); + } + + childComponentList.remove (index); + child->parentComponent = nullptr; + + ComponentHelpers::releaseAllCachedImageResources (*child); + + // (NB: there are obscure situations where child->isShowing() = false, but it still has the focus) + if (currentlyFocusedComponent == child || child->isParentOf (currentlyFocusedComponent)) + { + if (sendParentEvents) + { + const WeakReference thisPointer (this); + + giveAwayFocus (sendChildEvents || currentlyFocusedComponent != child); + + if (thisPointer == nullptr) + return child; + + grabKeyboardFocus(); + } + else + { + giveAwayFocus (sendChildEvents || currentlyFocusedComponent != child); + } + } + + if (sendChildEvents) + child->internalHierarchyChanged(); + + if (sendParentEvents) + internalChildrenChanged(); + } + + return child; +} + +//============================================================================== +void Component::removeAllChildren() +{ + while (! childComponentList.isEmpty()) + removeChildComponent (childComponentList.size() - 1); +} + +void Component::deleteAllChildren() +{ + while (! childComponentList.isEmpty()) + delete (removeChildComponent (childComponentList.size() - 1)); +} + +int Component::getNumChildComponents() const noexcept +{ + return childComponentList.size(); +} + +Component* Component::getChildComponent (int index) const noexcept +{ + return childComponentList[index]; +} + +int Component::getIndexOfChildComponent (const Component* child) const noexcept +{ + return childComponentList.indexOf (const_cast (child)); +} + +Component* Component::findChildWithID (StringRef targetID) const noexcept +{ + for (auto* c : childComponentList) + if (c->componentID == targetID) + return c; + + return nullptr; +} + +Component* Component::getTopLevelComponent() const noexcept +{ + auto* comp = this; + + while (comp->parentComponent != nullptr) + comp = comp->parentComponent; + + return const_cast (comp); +} + +bool Component::isParentOf (const Component* possibleChild) const noexcept +{ + while (possibleChild != nullptr) + { + possibleChild = possibleChild->parentComponent; + + if (possibleChild == this) + return true; + } + + return false; +} + +//============================================================================== +void Component::parentHierarchyChanged() {} +void Component::childrenChanged() {} + +void Component::internalChildrenChanged() +{ + if (componentListeners.isEmpty()) + { + childrenChanged(); + } + else + { + BailOutChecker checker (this); + + childrenChanged(); + + if (! checker.shouldBailOut()) + componentListeners.callChecked (checker, [this] (ComponentListener& l) { l.componentChildrenChanged (*this); }); + } +} + +void Component::internalHierarchyChanged() +{ + BailOutChecker checker (this); + + parentHierarchyChanged(); + + if (checker.shouldBailOut()) + return; + + componentListeners.callChecked (checker, [this] (ComponentListener& l) { l.componentParentHierarchyChanged (*this); }); + + if (checker.shouldBailOut()) + return; + + for (int i = childComponentList.size(); --i >= 0;) + { + childComponentList.getUnchecked (i)->internalHierarchyChanged(); + + if (checker.shouldBailOut()) + { + // you really shouldn't delete the parent component during a callback telling you + // that it's changed.. + jassertfalse; + return; + } + + i = jmin (i, childComponentList.size()); + } +} + +//============================================================================== +#if JUCE_MODAL_LOOPS_PERMITTED +int Component::runModalLoop() +{ + if (! MessageManager::getInstance()->isThisTheMessageThread()) + { + // use a callback so this can be called from non-gui threads + return (int) (pointer_sized_int) MessageManager::getInstance() + ->callFunctionOnMessageThread (&ComponentHelpers::runModalLoopCallback, this); + } + + if (! isCurrentlyModal (false)) + enterModalState (true); + + return ModalComponentManager::getInstance()->runEventLoopForCurrentComponent(); +} +#endif + +//============================================================================== +void Component::enterModalState (bool shouldTakeKeyboardFocus, + ModalComponentManager::Callback* callback, + bool deleteWhenDismissed) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + + if (! isCurrentlyModal (false)) + { + auto& mcm = *ModalComponentManager::getInstance(); + mcm.startModal (this, deleteWhenDismissed); + mcm.attachCallback (this, callback); + + setVisible (true); + + if (shouldTakeKeyboardFocus) + grabKeyboardFocus(); + } + else + { + // Probably a bad idea to try to make a component modal twice! + jassertfalse; + } +} + +void Component::exitModalState (int returnValue) +{ + if (isCurrentlyModal (false)) + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + auto& mcm = *ModalComponentManager::getInstance(); + mcm.endModal (this, returnValue); + mcm.bringModalComponentsToFront(); + + // If any of the mouse sources are over another Component when we exit the modal state then send a mouse enter event + for (auto& ms : Desktop::getInstance().getMouseSources()) + if (auto* c = ms.getComponentUnderMouse()) + c->internalMouseEnter (ms, ms.getScreenPosition(), Time::getCurrentTime()); + } + else + { + WeakReference target (this); + + MessageManager::callAsync ([=] + { + if (auto* c = target.get()) + c->exitModalState (returnValue); + }); + } + } +} + +bool Component::isCurrentlyModal (bool onlyConsiderForemostModalComponent) const noexcept +{ + auto& mcm = *ModalComponentManager::getInstance(); + + return onlyConsiderForemostModalComponent ? mcm.isFrontModalComponent (this) + : mcm.isModal (this); +} + +bool Component::isCurrentlyBlockedByAnotherModalComponent() const +{ + auto* mc = getCurrentlyModalComponent(); + + return ! (mc == nullptr || mc == this || mc->isParentOf (this) + || mc->canModalEventBeSentToComponent (this)); +} + +int JUCE_CALLTYPE Component::getNumCurrentlyModalComponents() noexcept +{ + return ModalComponentManager::getInstance()->getNumModalComponents(); +} + +Component* JUCE_CALLTYPE Component::getCurrentlyModalComponent (int index) noexcept +{ + return ModalComponentManager::getInstance()->getModalComponent (index); +} + +//============================================================================== +void Component::setBroughtToFrontOnMouseClick (bool shouldBeBroughtToFront) noexcept +{ + flags.bringToFrontOnClickFlag = shouldBeBroughtToFront; +} + +bool Component::isBroughtToFrontOnMouseClick() const noexcept +{ + return flags.bringToFrontOnClickFlag; +} + +//============================================================================== +void Component::setMouseCursor (const MouseCursor& newCursor) +{ + if (cursor != newCursor) + { + cursor = newCursor; + + if (flags.visibleFlag) + updateMouseCursor(); + } +} + +MouseCursor Component::getMouseCursor() +{ + return cursor; +} + +void Component::updateMouseCursor() const +{ + Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); +} + +//============================================================================== +void Component::setRepaintsOnMouseActivity (bool shouldRepaint) noexcept +{ + flags.repaintOnMouseActivityFlag = shouldRepaint; +} + +//============================================================================== +float Component::getAlpha() const noexcept +{ + return (255 - componentTransparency) / 255.0f; +} + +void Component::setAlpha (float newAlpha) +{ + auto newIntAlpha = (uint8) (255 - jlimit (0, 255, roundToInt (newAlpha * 255.0))); + + if (componentTransparency != newIntAlpha) + { + componentTransparency = newIntAlpha; + alphaChanged(); + } +} + +void Component::alphaChanged() +{ + if (flags.hasHeavyweightPeerFlag) + { + if (auto* peer = getPeer()) + peer->setAlpha (getAlpha()); + } + else + { + repaint(); + } +} + +//============================================================================== +void Component::repaint() +{ + internalRepaintUnchecked (getLocalBounds(), true); +} + +void Component::repaint (int x, int y, int w, int h) +{ + internalRepaint ({ x, y, w, h }); +} + +void Component::repaint (Rectangle area) +{ + internalRepaint (area); +} + +void Component::repaintParent() +{ + if (parentComponent != nullptr) + parentComponent->internalRepaint (ComponentHelpers::convertToParentSpace (*this, getLocalBounds())); +} + +void Component::internalRepaint (Rectangle area) +{ + area = area.getIntersection (getLocalBounds()); + + if (! area.isEmpty()) + internalRepaintUnchecked (area, false); +} + +void Component::internalRepaintUnchecked (Rectangle area, bool isEntireComponent) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + + if (flags.visibleFlag) + { + if (cachedImage != nullptr) + if (! (isEntireComponent ? cachedImage->invalidateAll() + : cachedImage->invalidate (area))) + return; + + if (area.isEmpty()) + return; + + if (flags.hasHeavyweightPeerFlag) + { + if (auto* peer = getPeer()) + { + // Tweak the scaling so that the component's integer size exactly aligns with the peer's scaled size + auto peerBounds = peer->getBounds(); + auto scaled = area * Point ((float) peerBounds.getWidth() / (float) getWidth(), + (float) peerBounds.getHeight() / (float) getHeight()); + + peer->repaint (affineTransform != nullptr ? scaled.transformedBy (*affineTransform) : scaled); + } + } + else + { + if (parentComponent != nullptr) + parentComponent->internalRepaint (ComponentHelpers::convertToParentSpace (*this, area)); + } + } +} + +//============================================================================== +void Component::paint (Graphics&) +{ + // if your component is marked as opaque, you must implement a paint + // method and ensure that its entire area is completely painted. + jassert (getBounds().isEmpty() || ! isOpaque()); +} + +void Component::paintOverChildren (Graphics&) +{ + // all painting is done in the subclasses +} + +//============================================================================== +void Component::paintWithinParentContext (Graphics& g) +{ + g.setOrigin (getPosition()); + + if (cachedImage != nullptr) + cachedImage->paint (g); + else + paintEntireComponent (g, false); +} + +void Component::paintComponentAndChildren (Graphics& g) +{ + auto clipBounds = g.getClipBounds(); + + if (flags.dontClipGraphicsFlag) + { + paint (g); + } + else + { + Graphics::ScopedSaveState ss (g); + + if (! (ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, {}) && g.isClipEmpty())) + paint (g); + } + + for (int i = 0; i < childComponentList.size(); ++i) + { + auto& child = *childComponentList.getUnchecked (i); + + if (child.isVisible()) + { + if (child.affineTransform != nullptr) + { + Graphics::ScopedSaveState ss (g); + + g.addTransform (*child.affineTransform); + + if ((child.flags.dontClipGraphicsFlag && ! g.isClipEmpty()) || g.reduceClipRegion (child.getBounds())) + child.paintWithinParentContext (g); + } + else if (clipBounds.intersects (child.getBounds())) + { + Graphics::ScopedSaveState ss (g); + + if (child.flags.dontClipGraphicsFlag) + { + child.paintWithinParentContext (g); + } + else if (g.reduceClipRegion (child.getBounds())) + { + bool nothingClipped = true; + + for (int j = i + 1; j < childComponentList.size(); ++j) + { + auto& sibling = *childComponentList.getUnchecked (j); + + if (sibling.flags.opaqueFlag && sibling.isVisible() && sibling.affineTransform == nullptr) + { + nothingClipped = false; + g.excludeClipRegion (sibling.getBounds()); + } + } + + if (nothingClipped || ! g.isClipEmpty()) + child.paintWithinParentContext (g); + } + } + } + } + + Graphics::ScopedSaveState ss (g); + paintOverChildren (g); +} + +void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel) +{ + // 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 + // the components inside have had a chance to sort their sizes out.. + #if JUCE_DEBUG + if (! flags.isInsidePaintCall) // (avoids an assertion in plugins hosted in WaveLab) + #endif + sendMovedResizedMessagesIfPending(); + + #if JUCE_DEBUG + flags.isInsidePaintCall = true; + #endif + + if (effect != nullptr) + { + auto scale = g.getInternalContext().getPhysicalPixelScaleFactor(); + + auto scaledBounds = getLocalBounds() * scale; + + Image effectImage (flags.opaqueFlag ? Image::RGB : Image::ARGB, + scaledBounds.getWidth(), scaledBounds.getHeight(), ! flags.opaqueFlag); + { + Graphics g2 (effectImage); + g2.addTransform (AffineTransform::scale ((float) scaledBounds.getWidth() / (float) getWidth(), + (float) scaledBounds.getHeight() / (float) getHeight())); + paintComponentAndChildren (g2); + } + + Graphics::ScopedSaveState ss (g); + + g.addTransform (AffineTransform::scale (1.0f / scale)); + effect->applyEffect (effectImage, g, scale, ignoreAlphaLevel ? 1.0f : getAlpha()); + } + else if (componentTransparency > 0 && ! ignoreAlphaLevel) + { + if (componentTransparency < 255) + { + g.beginTransparencyLayer (getAlpha()); + paintComponentAndChildren (g); + g.endTransparencyLayer(); + } + } + else + { + paintComponentAndChildren (g); + } + + #if JUCE_DEBUG + flags.isInsidePaintCall = false; + #endif +} + +void Component::setPaintingIsUnclipped (bool shouldPaintWithoutClipping) noexcept +{ + flags.dontClipGraphicsFlag = shouldPaintWithoutClipping; +} + +bool Component::isPaintingUnclipped() const noexcept +{ + return flags.dontClipGraphicsFlag; +} + +//============================================================================== +Image Component::createComponentSnapshot (Rectangle areaToGrab, + bool clipImageToComponentBounds, float scaleFactor) +{ + auto r = areaToGrab; + + if (clipImageToComponentBounds) + r = r.getIntersection (getLocalBounds()); + + if (r.isEmpty()) + return {}; + + auto w = roundToInt (scaleFactor * (float) r.getWidth()); + auto h = roundToInt (scaleFactor * (float) r.getHeight()); + + Image image (flags.opaqueFlag ? Image::RGB : Image::ARGB, w, h, true); + + Graphics g (image); + + if (w != getWidth() || h != getHeight()) + g.addTransform (AffineTransform::scale ((float) w / (float) r.getWidth(), + (float) h / (float) r.getHeight())); + g.setOrigin (-r.getPosition()); + + paintEntireComponent (g, true); + + return image; +} + +void Component::setComponentEffect (ImageEffectFilter* newEffect) +{ + if (effect != newEffect) + { + effect = newEffect; + repaint(); + } +} + +//============================================================================== +LookAndFeel& Component::getLookAndFeel() const noexcept +{ + for (auto* c = this; c != nullptr; c = c->parentComponent) + if (auto lf = c->lookAndFeel.get()) + return *lf; + + return LookAndFeel::getDefaultLookAndFeel(); +} + +void Component::setLookAndFeel (LookAndFeel* newLookAndFeel) +{ + if (lookAndFeel != newLookAndFeel) + { + lookAndFeel = newLookAndFeel; + sendLookAndFeelChange(); + } +} + +void Component::lookAndFeelChanged() {} +void Component::colourChanged() {} + +void Component::sendLookAndFeelChange() +{ + const WeakReference safePointer (this); + repaint(); + lookAndFeelChanged(); + + if (safePointer != nullptr) + { + colourChanged(); + + if (safePointer != nullptr) + { + for (int i = childComponentList.size(); --i >= 0;) + { + childComponentList.getUnchecked (i)->sendLookAndFeelChange(); + + if (safePointer == nullptr) + return; + + i = jmin (i, childComponentList.size()); + } + } + } +} + +Colour Component::findColour (int colourID, bool inheritFromParent) const +{ + if (auto* v = properties.getVarPointer (ComponentHelpers::getColourPropertyID (colourID))) + return Colour ((uint32) static_cast (*v)); + + if (inheritFromParent && parentComponent != nullptr + && (lookAndFeel == nullptr || ! lookAndFeel->isColourSpecified (colourID))) + return parentComponent->findColour (colourID, true); + + return getLookAndFeel().findColour (colourID); +} + +bool Component::isColourSpecified (int colourID) const +{ + return properties.contains (ComponentHelpers::getColourPropertyID (colourID)); +} + +void Component::removeColour (int colourID) +{ + if (properties.remove (ComponentHelpers::getColourPropertyID (colourID))) + colourChanged(); +} + +void Component::setColour (int colourID, Colour colour) +{ + if (properties.set (ComponentHelpers::getColourPropertyID (colourID), (int) colour.getARGB())) + colourChanged(); +} + +void Component::copyAllExplicitColoursTo (Component& target) const +{ + bool changed = false; + + for (int i = properties.size(); --i >= 0;) + { + auto name = properties.getName(i); + + if (name.toString().startsWith (colourPropertyPrefix)) + if (target.properties.set (name, properties [name])) + changed = true; + } + + if (changed) + target.colourChanged(); +} + +//============================================================================== +Component::Positioner::Positioner (Component& c) noexcept : component (c) +{ +} + +Component::Positioner* Component::getPositioner() const noexcept +{ + return positioner.get(); +} + +void Component::setPositioner (Positioner* newPositioner) +{ + // You can only assign a positioner to the component that it was created for! + jassert (newPositioner == nullptr || this == &(newPositioner->getComponent())); + positioner.reset (newPositioner); +} + +//============================================================================== +Rectangle Component::getLocalBounds() const noexcept +{ + return boundsRelativeToParent.withZeroOrigin(); +} + +Rectangle Component::getBoundsInParent() const noexcept +{ + return affineTransform == nullptr ? boundsRelativeToParent + : boundsRelativeToParent.transformedBy (*affineTransform); +} + +//============================================================================== +void Component::mouseEnter (const MouseEvent&) {} +void Component::mouseExit (const MouseEvent&) {} +void Component::mouseDown (const MouseEvent&) {} +void Component::mouseUp (const MouseEvent&) {} +void Component::mouseDrag (const MouseEvent&) {} +void Component::mouseMove (const MouseEvent&) {} +void Component::mouseDoubleClick (const MouseEvent&) {} + +void Component::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel) +{ + // the base class just passes this event up to its parent.. + if (parentComponent != nullptr) + parentComponent->mouseWheelMove (e.getEventRelativeTo (parentComponent), wheel); +} + +void Component::mouseMagnify (const MouseEvent& e, float magnifyAmount) +{ + // the base class just passes this event up to its parent.. + if (parentComponent != nullptr) + parentComponent->mouseMagnify (e.getEventRelativeTo (parentComponent), magnifyAmount); +} + +//============================================================================== +void Component::resized() {} +void Component::moved() {} +void Component::childBoundsChanged (Component*) {} +void Component::parentSizeChanged() {} + +void Component::addComponentListener (ComponentListener* newListener) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS + if (getParentComponent() != nullptr) + { + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + } + #endif + + componentListeners.add (newListener); +} + +void Component::removeComponentListener (ComponentListener* listenerToRemove) +{ + componentListeners.remove (listenerToRemove); +} + +//============================================================================== +void Component::inputAttemptWhenModal() +{ + ModalComponentManager::getInstance()->bringModalComponentsToFront(); + getLookAndFeel().playAlertSound(); +} + +bool Component::canModalEventBeSentToComponent (const Component*) +{ + return false; +} + +void Component::internalModalInputAttempt() +{ + if (auto* current = getCurrentlyModalComponent()) + current->inputAttemptWhenModal(); +} + +//============================================================================== +void Component::postCommandMessage (int commandID) +{ + WeakReference target (this); + + MessageManager::callAsync ([=] + { + if (auto* c = target.get()) + c->handleCommandMessage (commandID); + }); +} + +void Component::handleCommandMessage (int) +{ + // used by subclasses +} + +//============================================================================== +void Component::addMouseListener (MouseListener* newListener, + bool wantsEventsForAllNestedChildComponents) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + + // If you register a component as a mouselistener for itself, it'll receive all the events + // twice - once via the direct callback that all components get anyway, and then again as a listener! + jassert ((newListener != this) || wantsEventsForAllNestedChildComponents); + + if (mouseListeners == nullptr) + mouseListeners.reset (new MouseListenerList()); + + mouseListeners->addListener (newListener, wantsEventsForAllNestedChildComponents); +} + +void Component::removeMouseListener (MouseListener* listenerToRemove) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + + if (mouseListeners != nullptr) + mouseListeners->removeListener (listenerToRemove); +} + +//============================================================================== +void Component::internalMouseEnter (MouseInputSource source, Point relativePos, Time time) +{ + if (isCurrentlyBlockedByAnotherModalComponent()) + { + // if something else is modal, always just show a normal mouse cursor + source.showMouseCursor (MouseCursor::NormalCursor); + return; + } + + if (flags.repaintOnMouseActivityFlag) + repaint(); + + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, + MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation, + MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY, + this, this, time, relativePos, time, 0, false); + mouseEnter (me); + + if (checker.shouldBailOut()) + return; + + Desktop::getInstance().getMouseListeners().callChecked (checker, [&] (MouseListener& l) { l.mouseEnter (me); }); + + MouseListenerList::template sendMouseEvent (*this, checker, &MouseListener::mouseEnter, me); +} + +void Component::internalMouseExit (MouseInputSource source, Point relativePos, Time time) +{ + if (isCurrentlyBlockedByAnotherModalComponent()) + { + // if something else is modal, always just show a normal mouse cursor + source.showMouseCursor (MouseCursor::NormalCursor); + return; + } + + if (flags.repaintOnMouseActivityFlag) + repaint(); + + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, + MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation, + MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY, + this, this, time, relativePos, time, 0, false); + + mouseExit (me); + + if (checker.shouldBailOut()) + return; + + Desktop::getInstance().getMouseListeners().callChecked (checker, [&] (MouseListener& l) { l.mouseExit (me); }); + + MouseListenerList::template sendMouseEvent (*this, checker, &MouseListener::mouseExit, me); +} + +void Component::internalMouseDown (MouseInputSource source, Point relativePos, Time time, + float pressure, float orientation, float rotation, float tiltX, float tiltY) +{ + auto& desktop = Desktop::getInstance(); + BailOutChecker checker (this); + + if (isCurrentlyBlockedByAnotherModalComponent()) + { + flags.mouseDownWasBlocked = true; + internalModalInputAttempt(); + + if (checker.shouldBailOut()) + return; + + // If processing the input attempt has exited the modal loop, we'll allow the event + // to be delivered.. + if (isCurrentlyBlockedByAnotherModalComponent()) + { + // allow blocked mouse-events to go to global listeners.. + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), pressure, + orientation, rotation, tiltX, tiltY, this, this, time, relativePos, + time, source.getNumberOfMultipleClicks(), false); + + desktop.getMouseListeners().callChecked (checker, [&] (MouseListener& l) { l.mouseDown (me); }); + return; + } + } + + flags.mouseDownWasBlocked = false; + + for (auto* c = this; c != nullptr; c = c->parentComponent) + { + if (c->isBroughtToFrontOnMouseClick()) + { + c->toFront (true); + + if (checker.shouldBailOut()) + return; + } + } + + if (! flags.dontFocusOnMouseClickFlag) + { + grabFocusInternal (focusChangedByMouseClick, true); + + if (checker.shouldBailOut()) + return; + } + + if (flags.repaintOnMouseActivityFlag) + repaint(); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), pressure, + orientation, rotation, tiltX, tiltY, this, this, time, relativePos, + time, source.getNumberOfMultipleClicks(), false); + mouseDown (me); + + if (checker.shouldBailOut()) + return; + + desktop.getMouseListeners().callChecked (checker, [&] (MouseListener& l) { l.mouseDown (me); }); + + MouseListenerList::template sendMouseEvent (*this, checker, &MouseListener::mouseDown, me); +} + +void Component::internalMouseUp (MouseInputSource source, Point relativePos, Time time, + const ModifierKeys oldModifiers, float pressure, float orientation, float rotation, float tiltX, float tiltY) +{ + if (flags.mouseDownWasBlocked && isCurrentlyBlockedByAnotherModalComponent()) + return; + + BailOutChecker checker (this); + + if (flags.repaintOnMouseActivityFlag) + repaint(); + + const MouseEvent me (source, relativePos, oldModifiers, pressure, orientation, + rotation, tiltX, tiltY, this, this, time, + getLocalPoint (nullptr, source.getLastMouseDownPosition()), + source.getLastMouseDownTime(), + source.getNumberOfMultipleClicks(), + source.isLongPressOrDrag()); + mouseUp (me); + + if (checker.shouldBailOut()) + return; + + auto& desktop = Desktop::getInstance(); + desktop.getMouseListeners().callChecked (checker, [&] (MouseListener& l) { l.mouseUp (me); }); + + MouseListenerList::template sendMouseEvent (*this, checker, &MouseListener::mouseUp, me); + + if (checker.shouldBailOut()) + return; + + // check for double-click + if (me.getNumberOfClicks() >= 2) + { + mouseDoubleClick (me); + + if (checker.shouldBailOut()) + return; + + desktop.mouseListeners.callChecked (checker, [&] (MouseListener& l) { l.mouseDoubleClick (me); }); + MouseListenerList::template sendMouseEvent (*this, checker, &MouseListener::mouseDoubleClick, me); + } +} + +void Component::internalMouseDrag (MouseInputSource source, Point relativePos, Time time, + float pressure, float orientation, float rotation, float tiltX, float tiltY) +{ + if (! isCurrentlyBlockedByAnotherModalComponent()) + { + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + pressure, orientation, rotation, tiltX, tiltY, this, this, time, + getLocalPoint (nullptr, source.getLastMouseDownPosition()), + source.getLastMouseDownTime(), + source.getNumberOfMultipleClicks(), + source.isLongPressOrDrag()); + mouseDrag (me); + + if (checker.shouldBailOut()) + return; + + Desktop::getInstance().getMouseListeners().callChecked (checker, [&] (MouseListener& l) { l.mouseDrag (me); }); + + MouseListenerList::template sendMouseEvent (*this, checker, &MouseListener::mouseDrag, me); + } +} + +void Component::internalMouseMove (MouseInputSource source, Point relativePos, Time time) +{ + auto& desktop = Desktop::getInstance(); + + if (isCurrentlyBlockedByAnotherModalComponent()) + { + // allow blocked mouse-events to go to global listeners.. + desktop.sendMouseMove(); + } + else + { + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, + MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation, + MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY, + this, this, time, relativePos, time, 0, false); + mouseMove (me); + + if (checker.shouldBailOut()) + return; + + desktop.getMouseListeners().callChecked (checker, [&] (MouseListener& l) { l.mouseMove (me); }); + + MouseListenerList::template sendMouseEvent (*this, checker, &MouseListener::mouseMove, me); + } +} + +void Component::internalMouseWheel (MouseInputSource source, Point relativePos, + Time time, const MouseWheelDetails& wheel) +{ + auto& desktop = Desktop::getInstance(); + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, + MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation, + MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY, + this, this, time, relativePos, time, 0, false); + + if (isCurrentlyBlockedByAnotherModalComponent()) + { + // allow blocked mouse-events to go to global listeners.. + desktop.mouseListeners.callChecked (checker, [&] (MouseListener& l) { l.mouseWheelMove (me, wheel); }); + } + else + { + mouseWheelMove (me, wheel); + + if (checker.shouldBailOut()) + return; + + desktop.mouseListeners.callChecked (checker, [&] (MouseListener& l) { l.mouseWheelMove (me, wheel); }); + + if (! checker.shouldBailOut()) + MouseListenerList::template sendMouseEvent (*this, checker, &MouseListener::mouseWheelMove, me, wheel); + } +} + +void Component::internalMagnifyGesture (MouseInputSource source, Point relativePos, + Time time, float amount) +{ + auto& desktop = Desktop::getInstance(); + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, + MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation, + MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY, + this, this, time, relativePos, time, 0, false); + + if (isCurrentlyBlockedByAnotherModalComponent()) + { + // allow blocked mouse-events to go to global listeners.. + desktop.mouseListeners.callChecked (checker, [&] (MouseListener& l) { l.mouseMagnify (me, amount); }); + } + else + { + mouseMagnify (me, amount); + + if (checker.shouldBailOut()) + return; + + desktop.mouseListeners.callChecked (checker, [&] (MouseListener& l) { l.mouseMagnify (me, amount); }); + + if (! checker.shouldBailOut()) + MouseListenerList::template sendMouseEvent (*this, checker, &MouseListener::mouseMagnify, me, amount); + } +} + +void Component::sendFakeMouseMove() const +{ + if (flags.ignoresMouseClicksFlag && ! flags.allowChildMouseClicksFlag) + return; + + auto mainMouse = Desktop::getInstance().getMainMouseSource(); + + if (! mainMouse.isDragging()) + mainMouse.triggerFakeMove(); +} + +void JUCE_CALLTYPE Component::beginDragAutoRepeat (int interval) +{ + Desktop::getInstance().beginDragAutoRepeat (interval); +} + +//============================================================================== +void Component::broughtToFront() +{ +} + +void Component::internalBroughtToFront() +{ + if (flags.hasHeavyweightPeerFlag) + Desktop::getInstance().componentBroughtToFront (this); + + BailOutChecker checker (this); + broughtToFront(); + + if (checker.shouldBailOut()) + return; + + componentListeners.callChecked (checker, [this] (ComponentListener& l) { l.componentBroughtToFront (*this); }); + + if (checker.shouldBailOut()) + return; + + // When brought to the front and there's a modal component blocking this one, + // we need to bring the modal one to the front instead.. + if (auto* cm = getCurrentlyModalComponent()) + if (cm->getTopLevelComponent() != getTopLevelComponent()) + ModalComponentManager::getInstance()->bringModalComponentsToFront (false); // very important that this is false, otherwise in Windows, + // non-front components can't get focus when another modal comp is + // active, and therefore can't receive mouse-clicks +} + +//============================================================================== +void Component::focusGained (FocusChangeType) {} +void Component::focusLost (FocusChangeType) {} +void Component::focusOfChildComponentChanged (FocusChangeType) {} + +void Component::internalFocusGain (FocusChangeType cause) +{ + internalFocusGain (cause, WeakReference (this)); +} + +void Component::internalFocusGain (FocusChangeType cause, const WeakReference& safePointer) +{ + focusGained (cause); + + if (safePointer != nullptr) + internalChildFocusChange (cause, safePointer); +} + +void Component::internalFocusLoss (FocusChangeType cause) +{ + const WeakReference safePointer (this); + + focusLost (cause); + + if (safePointer != nullptr) + internalChildFocusChange (cause, safePointer); +} + +void Component::internalChildFocusChange (FocusChangeType cause, const WeakReference& safePointer) +{ + const bool childIsNowFocused = hasKeyboardFocus (true); + + if (flags.childCompFocusedFlag != childIsNowFocused) + { + flags.childCompFocusedFlag = childIsNowFocused; + + focusOfChildComponentChanged (cause); + + if (safePointer == nullptr) + return; + } + + if (parentComponent != nullptr) + parentComponent->internalChildFocusChange (cause, WeakReference (parentComponent)); +} + +void Component::setWantsKeyboardFocus (bool wantsFocus) noexcept +{ + flags.wantsFocusFlag = wantsFocus; +} + +void Component::setMouseClickGrabsKeyboardFocus (bool shouldGrabFocus) +{ + flags.dontFocusOnMouseClickFlag = ! shouldGrabFocus; +} + +bool Component::getMouseClickGrabsKeyboardFocus() const noexcept +{ + return ! flags.dontFocusOnMouseClickFlag; +} + +bool Component::getWantsKeyboardFocus() const noexcept +{ + return flags.wantsFocusFlag && ! flags.isDisabledFlag; +} + +void Component::setFocusContainer (bool shouldBeFocusContainer) noexcept +{ + flags.isFocusContainerFlag = shouldBeFocusContainer; +} + +bool Component::isFocusContainer() const noexcept +{ + return flags.isFocusContainerFlag; +} + +static const Identifier juce_explicitFocusOrderId ("_jexfo"); + +int Component::getExplicitFocusOrder() const +{ + return properties [juce_explicitFocusOrderId]; +} + +void Component::setExplicitFocusOrder (int newFocusOrderIndex) +{ + properties.set (juce_explicitFocusOrderId, newFocusOrderIndex); +} + +KeyboardFocusTraverser* Component::createFocusTraverser() +{ + if (flags.isFocusContainerFlag || parentComponent == nullptr) + return new KeyboardFocusTraverser(); + + return parentComponent->createFocusTraverser(); +} + +void Component::takeKeyboardFocus (FocusChangeType cause) +{ + // give the focus to this component + if (currentlyFocusedComponent != this) + { + // get the focus onto our desktop window + if (auto* peer = getPeer()) + { + const WeakReference safePointer (this); + peer->grabFocus(); + + if (peer->isFocused() && currentlyFocusedComponent != this) + { + WeakReference componentLosingFocus (currentlyFocusedComponent); + currentlyFocusedComponent = this; + + Desktop::getInstance().triggerFocusCallback(); + + // call this after setting currentlyFocusedComponent so that the one that's + // losing it has a chance to see where focus is going + if (componentLosingFocus != nullptr) + componentLosingFocus->internalFocusLoss (cause); + + if (currentlyFocusedComponent == this) + internalFocusGain (cause, safePointer); + } + } + } +} + +void Component::grabFocusInternal (FocusChangeType cause, bool canTryParent) +{ + if (isShowing()) + { + if (flags.wantsFocusFlag && (isEnabled() || parentComponent == nullptr)) + { + takeKeyboardFocus (cause); + } + else + { + if (isParentOf (currentlyFocusedComponent) + && currentlyFocusedComponent->isShowing()) + { + // do nothing if the focused component is actually a child of ours.. + } + else + { + // find the default child component.. + std::unique_ptr traverser (createFocusTraverser()); + + if (traverser != nullptr) + { + auto* defaultComp = traverser->getDefaultComponent (this); + traverser.reset(); + + if (defaultComp != nullptr) + { + defaultComp->grabFocusInternal (cause, false); + return; + } + } + + if (canTryParent && parentComponent != nullptr) + { + // if no children want it and we're allowed to try our parent comp, + // then pass up to parent, which will try our siblings. + parentComponent->grabFocusInternal (cause, true); + } + } + } + } +} + +void Component::grabKeyboardFocus() +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + + grabFocusInternal (focusChangedDirectly, true); + + // A component can only be focused when it's actually on the screen! + // If this fails then you're probably trying to grab the focus before you've + // added the component to a parent or made it visible. Or maybe one of its parent + // components isn't yet visible. + jassert (isShowing() || isOnDesktop()); +} + +void Component::moveKeyboardFocusToSibling (bool moveToNext) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + + if (parentComponent != nullptr) + { + std::unique_ptr traverser (createFocusTraverser()); + + if (traverser != nullptr) + { + auto* nextComp = moveToNext ? traverser->getNextComponent (this) + : traverser->getPreviousComponent (this); + traverser.reset(); + + if (nextComp != nullptr) + { + if (nextComp->isCurrentlyBlockedByAnotherModalComponent()) + { + const WeakReference nextCompPointer (nextComp); + internalModalInputAttempt(); + + if (nextCompPointer == nullptr || nextComp->isCurrentlyBlockedByAnotherModalComponent()) + return; + } + + nextComp->grabFocusInternal (focusChangedByTabKey, true); + return; + } + } + + parentComponent->moveKeyboardFocusToSibling (moveToNext); + } +} + +bool Component::hasKeyboardFocus (bool trueIfChildIsFocused) const +{ + return (currentlyFocusedComponent == this) + || (trueIfChildIsFocused && isParentOf (currentlyFocusedComponent)); +} + +Component* JUCE_CALLTYPE Component::getCurrentlyFocusedComponent() noexcept +{ + return currentlyFocusedComponent; +} + +void JUCE_CALLTYPE Component::unfocusAllComponents() +{ + if (auto* c = getCurrentlyFocusedComponent()) + c->giveAwayFocus (true); +} + +void Component::giveAwayFocus (bool sendFocusLossEvent) +{ + auto* componentLosingFocus = currentlyFocusedComponent; + currentlyFocusedComponent = nullptr; + + if (sendFocusLossEvent && componentLosingFocus != nullptr) + componentLosingFocus->internalFocusLoss (focusChangedDirectly); + + Desktop::getInstance().triggerFocusCallback(); +} + +//============================================================================== +bool Component::isEnabled() const noexcept +{ + return (! flags.isDisabledFlag) + && (parentComponent == nullptr || parentComponent->isEnabled()); +} + +void Component::setEnabled (bool shouldBeEnabled) +{ + if (flags.isDisabledFlag == shouldBeEnabled) + { + flags.isDisabledFlag = ! shouldBeEnabled; + + // if any parent components are disabled, setting our flag won't make a difference, + // so no need to send a change message + if (parentComponent == nullptr || parentComponent->isEnabled()) + sendEnablementChangeMessage(); + + BailOutChecker checker (this); + componentListeners.callChecked (checker, [this] (ComponentListener& l) { l.componentEnablementChanged (*this); }); + } +} + +void Component::enablementChanged() {} + +void Component::sendEnablementChangeMessage() +{ + const WeakReference safePointer (this); + + enablementChanged(); + + if (safePointer == nullptr) + return; + + for (int i = getNumChildComponents(); --i >= 0;) + { + if (auto* c = getChildComponent (i)) + { + c->sendEnablementChangeMessage(); + + if (safePointer == nullptr) + return; + } + } +} + +//============================================================================== +bool Component::isMouseOver (bool includeChildren) const +{ + for (auto& ms : Desktop::getInstance().getMouseSources()) + { + auto* c = ms.getComponentUnderMouse(); + + if (c == this || (includeChildren && isParentOf (c))) + if (ms.isDragging() || ! (ms.isTouch() || ms.isPen())) + if (c->reallyContains (c->getLocalPoint (nullptr, ms.getScreenPosition()).roundToInt(), false)) + return true; + } + + return false; +} + +bool Component::isMouseButtonDown (bool includeChildren) const +{ + for (auto& ms : Desktop::getInstance().getMouseSources()) + { + auto* c = ms.getComponentUnderMouse(); + + if (c == this || (includeChildren && isParentOf (c))) + if (ms.isDragging()) + return true; + } + + return false; +} + +bool Component::isMouseOverOrDragging (bool includeChildren) const +{ + for (auto& ms : Desktop::getInstance().getMouseSources()) + { + auto* c = ms.getComponentUnderMouse(); + + if (c == this || (includeChildren && isParentOf (c))) + if (ms.isDragging() || ! ms.isTouch()) + return true; + } + + return false; +} + +bool JUCE_CALLTYPE Component::isMouseButtonDownAnywhere() noexcept +{ + return ModifierKeys::currentModifiers.isAnyMouseButtonDown(); +} + +Point Component::getMouseXYRelative() const +{ + return getLocalPoint (nullptr, Desktop::getMousePosition()); +} + +//============================================================================== +void Component::addKeyListener (KeyListener* newListener) +{ + if (keyListeners == nullptr) + keyListeners.reset (new Array()); + + keyListeners->addIfNotAlreadyThere (newListener); +} + +void Component::removeKeyListener (KeyListener* listenerToRemove) +{ + if (keyListeners != nullptr) + keyListeners->removeFirstMatchingValue (listenerToRemove); +} + +bool Component::keyPressed (const KeyPress&) { return false; } +bool Component::keyStateChanged (bool /*isKeyDown*/) { return false; } + +void Component::modifierKeysChanged (const ModifierKeys& modifiers) +{ + if (parentComponent != nullptr) + parentComponent->modifierKeysChanged (modifiers); +} + +void Component::internalModifierKeysChanged() +{ + sendFakeMouseMove(); + modifierKeysChanged (ModifierKeys::currentModifiers); +} + +//============================================================================== +Component::BailOutChecker::BailOutChecker (Component* component) + : safePointer (component) +{ + jassert (component != nullptr); +} + +bool Component::BailOutChecker::shouldBailOut() const noexcept +{ + return safePointer == nullptr; +} + +} // namespace juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp.rej b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp.rej new file mode 100644 index 00000000..503e6bb9 --- /dev/null +++ b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp.rej @@ -0,0 +1,39 @@ +--- modules/juce_gui_basics/components/juce_Component.cpp ++++ modules/juce_gui_basics/components/juce_Component.cpp +@@ -387,6 +387,10 @@ struct Component::ComponentHelpers + template + static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p) + { ++ float total_scaling = source->getTotalPixelScaling(); ++ Component* top = nullptr; ++ if (source) ++ top = source->getTopLevelComponent(); + while (source != nullptr) + { + if (source == target) +@@ -395,6 +399,9 @@ struct Component::ComponentHelpers + if (source->isParentOf (target)) + return convertFromDistantParentSpace (source, *target, p); + ++ if (source == top) ++ p /= total_scaling; ++ + p = convertToParentSpace (*source, p); + source = source->getParentComponent(); + } +@@ -1390,13 +1397,14 @@ bool Component::reallyContains (Point point, bool returnTrueIfWithinAChild) + + Component* Component::getComponentAt (Point position) + { ++ Point scale = (position.toFloat() * getPixelScaling()).roundToInt(); + if (flags.visibleFlag && ComponentHelpers::hitTest (*this, position)) + { + for (int i = childComponentList.size(); --i >= 0;) + { + auto* child = childComponentList.getUnchecked(i); + +- child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, position)); ++ child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, scale)); + + if (child != nullptr) + return child; diff --git a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h index 6b2b0072..ccb2681f 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h +++ b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h @@ -2284,6 +2284,17 @@ public: */ bool getViewportIgnoreDragFlag() const noexcept { return flags.viewportIgnoreDragFlag; } + virtual float getPixelScaling() const { return 1.0f; } + float getTotalPixelScaling() const { + const Component* component = this; + float pixel_scaling = 1.0f; + while (component) { + pixel_scaling *= component->getPixelScaling(); + component = component->getParentComponent(); + } + return pixel_scaling; + } + private: //============================================================================== friend class ComponentPeer; diff --git a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h.orig b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h.orig new file mode 100644 index 00000000..6b2b0072 --- /dev/null +++ b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h.orig @@ -0,0 +1,2413 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 6 End-User License + Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). + + End User License Agreement: www.juce.com/juce-6-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + The base class for all JUCE user-interface objects. + + @tags{GUI} +*/ +class JUCE_API Component : public MouseListener +{ +public: + //============================================================================== + /** Creates a component. + + To get it to actually appear, you'll also need to: + - Either add it to a parent component or use the addToDesktop() method to + make it a desktop window + - Set its size and position to something sensible + - Use setVisible() to make it visible + + And for it to serve any useful purpose, you'll need to write a + subclass of Component or use one of the other types of component from + the library. + */ + Component() noexcept; + + /** Destructor. + + Note that when a component is deleted, any child components it contains are NOT + automatically deleted. It's your responsibility to manage their lifespan - you + may want to use helper methods like deleteAllChildren(), or less haphazard + approaches like using std::unique_ptrs or normal object aggregation to manage them. + + If the component being deleted is currently the child of another one, then during + deletion, it will be removed from its parent, and the parent will receive a childrenChanged() + callback. Any ComponentListener objects that have registered with it will also have their + ComponentListener::componentBeingDeleted() methods called. + */ + ~Component() override; + + //============================================================================== + /** Creates a component, setting its name at the same time. + @see getName, setName + */ + explicit Component (const String& componentName) noexcept; + + /** Returns the name of this component. + @see setName + */ + const String& getName() const noexcept { return componentName; } + + /** Sets the name of this component. + + When the name changes, all registered ComponentListeners will receive a + ComponentListener::componentNameChanged() callback. + + @see getName + */ + virtual void setName (const String& newName); + + /** Returns the ID string that was set by setComponentID(). + @see setComponentID, findChildWithID + */ + const String& getComponentID() const noexcept { return componentID; } + + /** Sets the component's ID string. + You can retrieve the ID using getComponentID(). + @see getComponentID, findChildWithID + */ + void setComponentID (const String& newID); + + //============================================================================== + /** Makes the component visible or invisible. + + This method will show or hide the component. + Note that components default to being non-visible when first created. + Also note that visible components won't be seen unless all their parent components + are also visible. + + This method will call visibilityChanged() and also componentVisibilityChanged() + for any component listeners that are interested in this component. + + @param shouldBeVisible whether to show or hide the component + @see isVisible, isShowing, visibilityChanged, ComponentListener::componentVisibilityChanged + */ + virtual void setVisible (bool shouldBeVisible); + + /** Tests whether the component is visible or not. + + this doesn't necessarily tell you whether this comp is actually on the screen + because this depends on whether all the parent components are also visible - use + isShowing() to find this out. + + @see isShowing, setVisible + */ + bool isVisible() const noexcept { return flags.visibleFlag; } + + /** Called when this component's visibility changes. + @see setVisible, isVisible + */ + virtual void visibilityChanged(); + + /** Tests whether this component and all its parents are visible. + + @returns true only if this component and all its parents are visible. + @see isVisible + */ + bool isShowing() const; + + //============================================================================== + /** Makes this component appear as a window on the desktop. + + Note that before calling this, you should make sure that the component's opacity is + set correctly using setOpaque(). If the component is non-opaque, the windowing + system will try to create a special transparent window for it, which will generally take + a lot more CPU to operate (and might not even be possible on some platforms). + + If the component is inside a parent component at the time this method is called, it + will first be removed from that parent. Likewise if a component is on the desktop + and is subsequently added to another component, it'll be removed from the desktop. + + @param windowStyleFlags a combination of the flags specified in the + ComponentPeer::StyleFlags enum, which define the + window's characteristics. + @param nativeWindowToAttachTo this allows an OS object to be passed-in as the window + in which the juce component should place itself. On Windows, + this would be a HWND, a HIViewRef on the Mac. Not necessarily + supported on all platforms, and best left as 0 unless you know + what you're doing. + @see removeFromDesktop, isOnDesktop, userTriedToCloseWindow, + getPeer, ComponentPeer::setMinimised, ComponentPeer::StyleFlags, + ComponentPeer::getStyleFlags, ComponentPeer::setFullScreen + */ + virtual void addToDesktop (int windowStyleFlags, + void* nativeWindowToAttachTo = nullptr); + + /** If the component is currently showing on the desktop, this will hide it. + + You can also use setVisible() to hide a desktop window temporarily, but + removeFromDesktop() will free any system resources that are being used up. + + @see addToDesktop, isOnDesktop + */ + void removeFromDesktop(); + + /** Returns true if this component is currently showing on the desktop. + @see addToDesktop, removeFromDesktop + */ + bool isOnDesktop() const noexcept; + + /** Returns the heavyweight window that contains this component. + + If this component is itself on the desktop, this will return the window + object that it is using. Otherwise, it will return the window of + its top-level parent component. + + This may return nullptr if there isn't a desktop component. + + @see addToDesktop, isOnDesktop + */ + ComponentPeer* getPeer() const; + + /** For components on the desktop, this is called if the system wants to close the window. + + This is a signal that either the user or the system wants the window to close. The + default implementation of this method will trigger an assertion to warn you that your + component should do something about it, but you can override this to ignore the event + if you want. + */ + virtual void userTriedToCloseWindow(); + + /** Called for a desktop component which has just been minimised or un-minimised. + This will only be called for components on the desktop. + @see getPeer, ComponentPeer::setMinimised, ComponentPeer::isMinimised + */ + virtual void minimisationStateChanged (bool isNowMinimised); + + /** Returns the default scale factor to use for this component when it is placed + on the desktop. + The default implementation of this method just returns the value from + Desktop::getGlobalScaleFactor(), but it can be overridden if a particular component + has different requirements. The method only used if this component is added + to the desktop - it has no effect for child components. + */ + virtual float getDesktopScaleFactor() const; + + //============================================================================== + /** Brings the component to the front of its siblings. + + If some of the component's siblings have had their 'always-on-top' flag set, + then they will still be kept in front of this one (unless of course this + one is also 'always-on-top'). + + @param shouldAlsoGainFocus if true, this will also try to assign keyboard focus + to the component (see grabKeyboardFocus() for more details) + @see toBack, toBehind, setAlwaysOnTop + */ + void toFront (bool shouldAlsoGainFocus); + + /** Changes this component's z-order to be at the back of all its siblings. + + If the component is set to be 'always-on-top', it will only be moved to the + back of the other other 'always-on-top' components. + + @see toFront, toBehind, setAlwaysOnTop + */ + void toBack(); + + /** Changes this component's z-order so that it's just behind another component. + @see toFront, toBack + */ + void toBehind (Component* other); + + /** Sets whether the component should always be kept at the front of its siblings. + @see isAlwaysOnTop + */ + void setAlwaysOnTop (bool shouldStayOnTop); + + /** Returns true if this component is set to always stay in front of its siblings. + @see setAlwaysOnTop + */ + bool isAlwaysOnTop() const noexcept; + + //============================================================================== + /** Returns the x coordinate of the component's left edge. + This is a distance in pixels from the left edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. + */ + int getX() const noexcept { return boundsRelativeToParent.getX(); } + + /** Returns the y coordinate of the top of this component. + This is a distance in pixels from the top edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. + */ + int getY() const noexcept { return boundsRelativeToParent.getY(); } + + /** Returns the component's width in pixels. */ + int getWidth() const noexcept { return boundsRelativeToParent.getWidth(); } + + /** Returns the component's height in pixels. */ + int getHeight() const noexcept { return boundsRelativeToParent.getHeight(); } + + /** Returns the x coordinate of the component's right-hand edge. + This is a distance in pixels from the left edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. + */ + int getRight() const noexcept { return boundsRelativeToParent.getRight(); } + + /** Returns the component's top-left position as a Point. */ + Point getPosition() const noexcept { return boundsRelativeToParent.getPosition(); } + + /** Returns the y coordinate of the bottom edge of this component. + This is a distance in pixels from the top edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. + */ + int getBottom() const noexcept { return boundsRelativeToParent.getBottom(); } + + /** Returns this component's bounding box. + The rectangle returned is relative to the top-left of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. + */ + Rectangle getBounds() const noexcept { return boundsRelativeToParent; } + + /** Returns the component's bounds, relative to its own origin. + This is like getBounds(), but returns the rectangle in local coordinates, In practice, it'll + return a rectangle with position (0, 0), and the same size as this component. + */ + Rectangle getLocalBounds() const noexcept; + + /** Returns the area of this component's parent which this component covers. + + The returned area is relative to the parent's coordinate space. + If the component has an affine transform specified, then the resulting area will be + the smallest rectangle that fully covers the component's transformed bounding box. + If this component has no parent, the return value will simply be the same as getBounds(). + */ + Rectangle getBoundsInParent() const noexcept; + + //============================================================================== + /** Returns this component's x coordinate relative the screen's top-left origin. + @see getX, localPointToGlobal + */ + int getScreenX() const; + + /** Returns this component's y coordinate relative the screen's top-left origin. + @see getY, localPointToGlobal + */ + int getScreenY() const; + + /** Returns the position of this component's top-left corner relative to the screen's top-left. + @see getScreenBounds + */ + Point getScreenPosition() const; + + /** Returns the bounds of this component, relative to the screen's top-left. + @see getScreenPosition + */ + Rectangle getScreenBounds() const; + + /** Converts a point to be relative to this component's coordinate space. + + This takes a point relative to a different component, and returns its position relative to this + component. If the sourceComponent parameter is null, the source point is assumed to be a global + screen coordinate. + */ + Point getLocalPoint (const Component* sourceComponent, + Point pointRelativeToSourceComponent) const; + + /** Converts a point to be relative to this component's coordinate space. + + This takes a point relative to a different component, and returns its position relative to this + component. If the sourceComponent parameter is null, the source point is assumed to be a global + screen coordinate. + */ + Point getLocalPoint (const Component* sourceComponent, + Point pointRelativeToSourceComponent) const; + + /** Converts a rectangle to be relative to this component's coordinate space. + + This takes a rectangle that is relative to a different component, and returns its position relative + to this component. If the sourceComponent parameter is null, the source rectangle is assumed to be + a screen coordinate. + + If you've used setTransform() to apply one or more transforms to components, then the source rectangle + may not actually be rectangular when converted to the target space, so in that situation this will return + the smallest rectangle that fully contains the transformed area. + */ + Rectangle getLocalArea (const Component* sourceComponent, + Rectangle areaRelativeToSourceComponent) const; + + /** Converts a rectangle to be relative to this component's coordinate space. + + This takes a rectangle that is relative to a different component, and returns its position relative + to this component. If the sourceComponent parameter is null, the source rectangle is assumed to be + a screen coordinate. + + If you've used setTransform() to apply one or more transforms to components, then the source rectangle + may not actually be rectangular when converted to the target space, so in that situation this will return + the smallest rectangle that fully contains the transformed area. + */ + Rectangle getLocalArea (const Component* sourceComponent, + Rectangle areaRelativeToSourceComponent) const; + + /** Converts a point relative to this component's top-left into a screen coordinate. + @see getLocalPoint, localAreaToGlobal + */ + Point localPointToGlobal (Point localPoint) const; + + /** Converts a point relative to this component's top-left into a screen coordinate. + @see getLocalPoint, localAreaToGlobal + */ + Point localPointToGlobal (Point localPoint) const; + + /** Converts a rectangle from this component's coordinate space to a screen coordinate. + + If you've used setTransform() to apply one or more transforms to components, then the source rectangle + may not actually be rectangular when converted to the target space, so in that situation this will return + the smallest rectangle that fully contains the transformed area. + @see getLocalPoint, localPointToGlobal + */ + Rectangle localAreaToGlobal (Rectangle localArea) const; + + /** Converts a rectangle from this component's coordinate space to a screen coordinate. + + If you've used setTransform() to apply one or more transforms to components, then the source rectangle + may not actually be rectangular when converted to the target space, so in that situation this will return + the smallest rectangle that fully contains the transformed area. + @see getLocalPoint, localPointToGlobal + */ + Rectangle localAreaToGlobal (Rectangle localArea) const; + + //============================================================================== + /** Moves the component to a new position. + + Changes the component's top-left position (without changing its size). + The position is relative to the top-left of the component's parent. + + If the component actually moves, this method will make a synchronous call to moved(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + + @see setBounds, ComponentListener::componentMovedOrResized + */ + void setTopLeftPosition (int x, int y); + + /** Moves the component to a new position. + + Changes the component's top-left position (without changing its size). + The position is relative to the top-left of the component's parent. + + If the component actually moves, this method will make a synchronous call to moved(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + + @see setBounds, ComponentListener::componentMovedOrResized + */ + void setTopLeftPosition (Point newTopLeftPosition); + + /** Moves the component to a new position. + + Changes the position of the component's top-right corner (keeping it the same size). + The position is relative to the top-left of the component's parent. + + If the component actually moves, this method will make a synchronous call to moved(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + */ + void setTopRightPosition (int x, int y); + + /** Changes the size of the component. + + A synchronous call to resized() will occur if the size actually changes. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + */ + void setSize (int newWidth, int newHeight); + + /** Changes the component's position and size. + + The coordinates are relative to the top-left of the component's parent, or relative + to the origin of the screen if the component is on the desktop. + + If this method changes the component's top-left position, it will make a synchronous + call to moved(). If it changes the size, it will also make a call to resized(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + + @see setTopLeftPosition, setSize, ComponentListener::componentMovedOrResized + */ + void setBounds (int x, int y, int width, int height); + + /** Changes the component's position and size. + + The coordinates are relative to the top-left of the component's parent, or relative + to the origin of the screen if the component is on the desktop. + + If this method changes the component's top-left position, it will make a synchronous + call to moved(). If it changes the size, it will also make a call to resized(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + + @see setBounds + */ + void setBounds (Rectangle newBounds); + + /** Changes the component's position and size in terms of fractions of its parent's size. + + The values are factors of the parent's size, so for example + setBoundsRelative (0.2f, 0.2f, 0.5f, 0.5f) would give it half the + width and height of the parent, with its top-left position 20% of + the way across and down the parent. + + @see setBounds + */ + void setBoundsRelative (float proportionalX, float proportionalY, + float proportionalWidth, float proportionalHeight); + + /** Changes the component's position and size in terms of fractions of its parent's size. + + The values are factors of the parent's size, so for example + setBoundsRelative ({ 0.2f, 0.2f, 0.5f, 0.5f }) would give it half the + width and height of the parent, with its top-left position 20% of + the way across and down the parent. + + @see setBounds + */ + void setBoundsRelative (Rectangle proportionalArea); + + /** Changes the component's position and size based on the amount of space to leave around it. + + This will position the component within its parent, leaving the specified number of + pixels around each edge. + + @see setBounds + */ + void setBoundsInset (BorderSize borders); + + /** Positions the component within a given rectangle, keeping its proportions + unchanged. + + If onlyReduceInSize is false, the component will be resized to fill as much of the + rectangle as possible without changing its aspect ratio (the component's + current size is used to determine its aspect ratio, so a zero-size component + won't work here). If onlyReduceInSize is true, it will only be resized if it's + too big to fit inside the rectangle. + + It will then be positioned within the rectangle according to the justification flags + specified. + + @see setBounds + */ + void setBoundsToFit (Rectangle targetArea, + Justification justification, + bool onlyReduceInSize); + + /** Changes the position of the component's centre. + + Leaves the component's size unchanged, but sets the position of its centre + relative to its parent's top-left. + + @see setBounds + */ + void setCentrePosition (int x, int y); + + /** Changes the position of the component's centre. + + Leaves the component's size unchanged, but sets the position of its centre + relative to its parent's top-left. + + @see setBounds + */ + void setCentrePosition (Point newCentrePosition); + + /** Changes the position of the component's centre. + + Leaves the size unchanged, but positions its centre relative to its parent's size. + E.g. setCentreRelative (0.5f, 0.5f) would place it centrally in its parent. + */ + void setCentreRelative (float x, float y); + + /** Changes the component's size and centres it within its parent. + + After changing the size, the component will be moved so that it's + centred within its parent. If the component is on the desktop (or has no + parent component), then it'll be centred within the main monitor area. + */ + void centreWithSize (int width, int height); + + //============================================================================== + /** Sets a transform matrix to be applied to this component. + + If you set a transform for a component, the component's position will be warped by it, relative to + the component's parent's top-left origin. This means that the values you pass into setBounds() will no + longer reflect the actual area within the parent that the component covers, as the bounds will be + transformed and the component will probably end up actually appearing somewhere else within its parent. + + When using transforms you need to be extremely careful when converting coordinates between the + coordinate spaces of different components or the screen - you should always use getLocalPoint(), + getLocalArea(), etc to do this, and never just manually add a component's position to a point in order to + convert it between different components (but I'm sure you would never have done that anyway...). + + Currently, transforms are not supported for desktop windows, so the transform will be ignored if you + put a component on the desktop. + + To remove a component's transform, simply pass AffineTransform() as the parameter to this method. + */ + void setTransform (const AffineTransform& transform); + + /** Returns the transform that is currently being applied to this component. + For more details about transforms, see setTransform(). + @see setTransform + */ + AffineTransform getTransform() const; + + /** Returns true if a non-identity transform is being applied to this component. + For more details about transforms, see setTransform(). + @see setTransform + */ + bool isTransformed() const noexcept; + + /** Returns the approximate scale factor for a given component by traversing its parent hierarchy + and applying each transform and finally scaling this by the global scale factor. + */ + static float JUCE_CALLTYPE getApproximateScaleFactorForComponent (Component* targetComponent); + + //============================================================================== + /** Returns a proportion of the component's width. + This is a handy equivalent of (getWidth() * proportion). + */ + int proportionOfWidth (float proportion) const noexcept; + + /** Returns a proportion of the component's height. + This is a handy equivalent of (getHeight() * proportion). + */ + int proportionOfHeight (float proportion) const noexcept; + + /** Returns the width of the component's parent. + + If the component has no parent (i.e. if it's on the desktop), this will return + the width of the screen. + */ + int getParentWidth() const noexcept; + + /** Returns the height of the component's parent. + + If the component has no parent (i.e. if it's on the desktop), this will return + the height of the screen. + */ + int getParentHeight() const noexcept; + + /** Returns the screen coordinates of the monitor that contains this component. + + If there's only one monitor, this will return its size - if there are multiple + monitors, it will return the area of the monitor that contains the component's + centre. + */ + Rectangle getParentMonitorArea() const; + + //============================================================================== + /** Returns the number of child components that this component contains. + + @see getChildren, getChildComponent, getIndexOfChildComponent + */ + int getNumChildComponents() const noexcept; + + /** Returns one of this component's child components, by it index. + + The component with index 0 is at the back of the z-order, the one at the + front will have index (getNumChildComponents() - 1). + + If the index is out-of-range, this will return a null pointer. + + @see getChildren, getNumChildComponents, getIndexOfChildComponent + */ + Component* getChildComponent (int index) const noexcept; + + /** Returns the index of this component in the list of child components. + + A value of 0 means it is first in the list (i.e. behind all other components). Higher + values are further towards the front. + + Returns -1 if the component passed-in is not a child of this component. + + @see getChildren, getNumChildComponents, getChildComponent, addChildComponent, toFront, toBack, toBehind + */ + int getIndexOfChildComponent (const Component* child) const noexcept; + + /** Provides access to the underlying array of child components. + The most likely reason you may want to use this is for iteration in a range-based for loop. + */ + const Array& getChildren() const noexcept { return childComponentList; } + + /** Looks for a child component with the specified ID. + @see setComponentID, getComponentID + */ + Component* findChildWithID (StringRef componentID) const noexcept; + + /** Adds a child component to this one. + + Adding a child component does not mean that the component will own or delete the child - it's + your responsibility to delete the component. Note that it's safe to delete a component + without first removing it from its parent - doing so will automatically remove it and + send out the appropriate notifications before the deletion completes. + + If the child is already a child of this component, then no action will be taken, and its + z-order will be left unchanged. + + @param child the new component to add. If the component passed-in is already + the child of another component, it'll first be removed from it current parent. + @param zOrder The index in the child-list at which this component should be inserted. + A value of -1 will insert it in front of the others, 0 is the back. + @see removeChildComponent, addAndMakeVisible, addChildAndSetID, getChild, ComponentListener::componentChildrenChanged + */ + void addChildComponent (Component* child, int zOrder = -1); + + /** Adds a child component to this one. + + Adding a child component does not mean that the component will own or delete the child - it's + your responsibility to delete the component. Note that it's safe to delete a component + without first removing it from its parent - doing so will automatically remove it and + send out the appropriate notifications before the deletion completes. + + If the child is already a child of this component, then no action will be taken, and its + z-order will be left unchanged. + + @param child the new component to add. If the component passed-in is already + the child of another component, it'll first be removed from it current parent. + @param zOrder The index in the child-list at which this component should be inserted. + A value of -1 will insert it in front of the others, 0 is the back. + @see removeChildComponent, addAndMakeVisible, addChildAndSetID, getChild, ComponentListener::componentChildrenChanged + */ + void addChildComponent (Component& child, int zOrder = -1); + + /** Adds a child component to this one, and also makes the child visible if it isn't already. + + This is the same as calling setVisible (true) on the child and then addChildComponent(). + See addChildComponent() for more details. + + @param child the new component to add. If the component passed-in is already + the child of another component, it'll first be removed from it current parent. + @param zOrder The index in the child-list at which this component should be inserted. + A value of -1 will insert it in front of the others, 0 is the back. + */ + void addAndMakeVisible (Component* child, int zOrder = -1); + + /** Adds a child component to this one, and also makes the child visible if it isn't already. + + This is the same as calling setVisible (true) on the child and then addChildComponent(). + See addChildComponent() for more details. + + @param child the new component to add. If the component passed-in is already + the child of another component, it'll first be removed from it current parent. + @param zOrder The index in the child-list at which this component should be inserted. + A value of -1 will insert it in front of the others, 0 is the back. + */ + void addAndMakeVisible (Component& child, int zOrder = -1); + + /** Adds a child component to this one, makes it visible, and sets its component ID. + @see addAndMakeVisible, addChildComponent + */ + void addChildAndSetID (Component* child, const String& componentID); + + /** Removes one of this component's child-components. + + If the child passed-in isn't actually a child of this component (either because + it's invalid or is the child of a different parent), then no action is taken. + + Note that removing a child will not delete it! But it's ok to delete a component + without first removing it - doing so will automatically remove it and send out the + appropriate notifications before the deletion completes. + + @see addChildComponent, ComponentListener::componentChildrenChanged + */ + void removeChildComponent (Component* childToRemove); + + /** Removes one of this component's child-components by index. + + This will return a pointer to the component that was removed, or null if + the index was out-of-range. + + Note that removing a child will not delete it! But it's ok to delete a component + without first removing it - doing so will automatically remove it and send out the + appropriate notifications before the deletion completes. + + @see addChildComponent, ComponentListener::componentChildrenChanged + */ + Component* removeChildComponent (int childIndexToRemove); + + /** Removes all this component's children. + Note that this won't delete them! To do that, use deleteAllChildren() instead. + */ + void removeAllChildren(); + + /** Removes and deletes all of this component's children. + My advice is to avoid this method! It's an old function that is only kept here for + backwards-compatibility with legacy code, and should be viewed with extreme + suspicion by anyone attempting to write modern C++. In almost all cases, it's much + smarter to manage the lifetimes of your child components via modern RAII techniques + such as simply making them member variables, or using std::unique_ptr, OwnedArray, + etc to manage their lifetimes appropriately. + @see removeAllChildren + */ + void deleteAllChildren(); + + /** Returns the component which this component is inside. + + If this is the highest-level component or hasn't yet been added to + a parent, this will return null. + */ + Component* getParentComponent() const noexcept { return parentComponent; } + + /** Searches the parent components for a component of a specified class. + + For example findParentComponentOfClass \() would return the first parent + component that can be dynamically cast to a MyComp, or will return nullptr if none + of the parents are suitable. + */ + template + TargetClass* findParentComponentOfClass() const + { + for (auto* p = parentComponent; p != nullptr; p = p->parentComponent) + if (auto* target = dynamic_cast (p)) + return target; + + return nullptr; + } + + /** Returns the highest-level component which contains this one or its parents. + + This will search upwards in the parent-hierarchy from this component, until it + finds the highest one that doesn't have a parent (i.e. is on the desktop or + not yet added to a parent), and will return that. + */ + Component* getTopLevelComponent() const noexcept; + + /** Checks whether a component is anywhere inside this component or its children. + + This will recursively check through this component's children to see if the + given component is anywhere inside. + */ + bool isParentOf (const Component* possibleChild) const noexcept; + + //============================================================================== + /** Called to indicate that the component's parents have changed. + + When a component is added or removed from its parent, this method will + be called on all of its children (recursively - so all children of its + children will also be called as well). + + Subclasses can override this if they need to react to this in some way. + + @see getParentComponent, isShowing, ComponentListener::componentParentHierarchyChanged + */ + virtual void parentHierarchyChanged(); + + /** Subclasses can use this callback to be told when children are added or removed, or + when their z-order changes. + @see parentHierarchyChanged, ComponentListener::componentChildrenChanged + */ + virtual void childrenChanged(); + + //============================================================================== + /** Tests whether a given point is inside the component. + + Overriding this method allows you to create components which only intercept + mouse-clicks within a user-defined area. + + This is called to find out whether a particular x, y coordinate is + considered to be inside the component or not, and is used by methods such + as contains() and getComponentAt() to work out which component + the mouse is clicked on. + + Components with custom shapes will probably want to override it to perform + some more complex hit-testing. + + The default implementation of this method returns either true or false, + depending on the value that was set by calling setInterceptsMouseClicks() (true + is the default return value). + + Note that the hit-test region is not related to the opacity with which + areas of a component are painted. + + Applications should never call hitTest() directly - instead use the + contains() method, because this will also test for occlusion by the + component's parent. + + Note that for components on the desktop, this method will be ignored, because it's + not always possible to implement this behaviour on all platforms. + + @param x the x coordinate to test, relative to the left hand edge of this + component. This value is guaranteed to be greater than or equal to + zero, and less than the component's width + @param y the y coordinate to test, relative to the top edge of this + component. This value is guaranteed to be greater than or equal to + zero, and less than the component's height + @returns true if the click is considered to be inside the component + @see setInterceptsMouseClicks, contains + */ + virtual bool hitTest (int x, int y); + + /** Changes the default return value for the hitTest() method. + + Setting this to false is an easy way to make a component pass all its mouse events + (not just clicks) through to the components behind it. + + When a component is created, the default setting for this is true. + + @param allowClicksOnThisComponent if true, hitTest() will always return true; if false, it will + return false (or true for child components if allowClicksOnChildComponents + is true) + @param allowClicksOnChildComponents if this is true and allowClicksOnThisComponent is false, then child + components can be clicked on as normal but clicks on this component pass + straight through; if this is false and allowClicksOnThisComponent + is false, then neither this component nor any child components can + be clicked on + @see hitTest, getInterceptsMouseClicks + */ + void setInterceptsMouseClicks (bool allowClicksOnThisComponent, + bool allowClicksOnChildComponents) noexcept; + + /** Retrieves the current state of the mouse-click interception flags. + + On return, the two parameters are set to the state used in the last call to + setInterceptsMouseClicks(). + + @see setInterceptsMouseClicks + */ + void getInterceptsMouseClicks (bool& allowsClicksOnThisComponent, + bool& allowsClicksOnChildComponents) const noexcept; + + + /** Returns true if a given point lies within this component or one of its children. + + Never override this method! Use hitTest to create custom hit regions. + + @param localPoint the coordinate to test, relative to this component's top-left. + @returns true if the point is within the component's hit-test area, but only if + that part of the component isn't clipped by its parent component. Note + that this won't take into account any overlapping sibling components + which might be in the way - for that, see reallyContains() + @see hitTest, reallyContains, getComponentAt + */ + bool contains (Point localPoint); + + /** Returns true if a given point lies in this component, taking any overlapping + siblings into account. + + @param localPoint the coordinate to test, relative to this component's top-left. + @param returnTrueIfWithinAChild if the point actually lies within a child of this component, + this determines whether that is counted as a hit. + @see contains, getComponentAt + */ + bool reallyContains (Point localPoint, bool returnTrueIfWithinAChild); + + /** Returns the component at a certain point within this one. + + @param x the x coordinate to test, relative to this component's left edge. + @param y the y coordinate to test, relative to this component's top edge. + @returns the component that is at this position - which may be 0, this component, + or one of its children. Note that overlapping siblings that might actually + be in the way are not taken into account by this method - to account for these, + instead call getComponentAt on the top-level parent of this component. + @see hitTest, contains, reallyContains + */ + Component* getComponentAt (int x, int y); + + /** Returns the component at a certain point within this one. + + @param position the coordinate to test, relative to this component's top-left. + @returns the component that is at this position - which may be 0, this component, + or one of its children. Note that overlapping siblings that might actually + be in the way are not taken into account by this method - to account for these, + instead call getComponentAt on the top-level parent of this component. + @see hitTest, contains, reallyContains + */ + Component* getComponentAt (Point position); + + //============================================================================== + /** Marks the whole component as needing to be redrawn. + + Calling this will not do any repainting immediately, but will mark the component + as 'dirty'. At some point in the near future the operating system will send a paint + message, which will redraw all the dirty regions of all components. + There's no guarantee about how soon after calling repaint() the redraw will actually + happen, and other queued events may be delivered before a redraw is done. + + If the setBufferedToImage() method has been used to cause this component to use a + buffer, the repaint() call will invalidate the cached buffer. If setCachedComponentImage() + has been used to provide a custom image cache, that cache will be invalidated appropriately. + + To redraw just a subsection of the component rather than the whole thing, + use the repaint (int, int, int, int) method. + + @see paint + */ + void repaint(); + + /** Marks a subsection of this component as needing to be redrawn. + + Calling this will not do any repainting immediately, but will mark the given region + of the component as 'dirty'. At some point in the near future the operating system + will send a paint message, which will redraw all the dirty regions of all components. + There's no guarantee about how soon after calling repaint() the redraw will actually + happen, and other queued events may be delivered before a redraw is done. + + The region that is passed in will be clipped to keep it within the bounds of this + component. + + @see repaint() + */ + void repaint (int x, int y, int width, int height); + + /** Marks a subsection of this component as needing to be redrawn. + + Calling this will not do any repainting immediately, but will mark the given region + of the component as 'dirty'. At some point in the near future the operating system + will send a paint message, which will redraw all the dirty regions of all components. + There's no guarantee about how soon after calling repaint() the redraw will actually + happen, and other queued events may be delivered before a redraw is done. + + The region that is passed in will be clipped to keep it within the bounds of this + component. + + @see repaint() + */ + void repaint (Rectangle area); + + //============================================================================== + /** Makes the component use an internal buffer to optimise its redrawing. + + Setting this flag to true will cause the component to allocate an + internal buffer into which it paints itself and all its child components, so that + when asked to redraw itself, it can use this buffer rather than actually calling + the paint() method. + + Parts of the buffer are invalidated when repaint() is called on this component + or its children. The buffer is then repainted at the next paint() callback. + + @see repaint, paint, createComponentSnapshot + */ + void setBufferedToImage (bool shouldBeBuffered); + + /** Generates a snapshot of part of this component. + + This will return a new Image, the size of the rectangle specified, + containing a snapshot of the specified area of the component and all + its children. + + The image may or may not have an alpha-channel, depending on whether the + image is opaque or not. + + If the clipImageToComponentBounds parameter is true and the area is greater than + the size of the component, it'll be clipped. If clipImageToComponentBounds is false + then parts of the component beyond its bounds can be drawn. + + @see paintEntireComponent + */ + Image createComponentSnapshot (Rectangle areaToGrab, + bool clipImageToComponentBounds = true, + float scaleFactor = 1.0f); + + /** Draws this component and all its subcomponents onto the specified graphics + context. + + You should very rarely have to use this method, it's simply there in case you need + to draw a component with a custom graphics context for some reason, e.g. for + creating a snapshot of the component. + + It calls paint(), paintOverChildren() and recursively calls paintEntireComponent() + on its children in order to render the entire tree. + + The graphics context may be left in an undefined state after this method returns, + so you may need to reset it if you're going to use it again. + + If ignoreAlphaLevel is false, then the component will be drawn with the opacity level + specified by getAlpha(); if ignoreAlphaLevel is true, then this will be ignored and + an alpha of 1.0 will be used. + */ + void paintEntireComponent (Graphics& context, bool ignoreAlphaLevel); + + /** This allows you to indicate that this component doesn't require its graphics + context to be clipped when it is being painted. + + Most people will never need to use this setting, but in situations where you have a very large + number of simple components being rendered, and where they are guaranteed never to do any drawing + beyond their own boundaries, setting this to true will reduce the overhead involved in clipping + the graphics context that gets passed to the component's paint() callback. + 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 + artifacts. Your component also can't have any child components that may be placed beyond its + bounds. + */ + void setPaintingIsUnclipped (bool shouldPaintWithoutClipping) noexcept; + + /** Returns true if this component doesn't require its graphics context to be clipped + when it is being painted. + */ + bool isPaintingUnclipped() const noexcept; + + //============================================================================== + /** Adds an effect filter to alter the component's appearance. + + When a component has an effect filter set, then this is applied to the + results of its paint() method. There are a few preset effects, such as + a drop-shadow or glow, but they can be user-defined as well. + + The effect that is passed in will not be deleted by the component - the + caller must take care of deleting it. + + To remove an effect from a component, pass a null pointer in as the parameter. + + @see ImageEffectFilter, DropShadowEffect, GlowEffect + */ + void setComponentEffect (ImageEffectFilter* newEffect); + + /** Returns the current component effect. + @see setComponentEffect + */ + ImageEffectFilter* getComponentEffect() const noexcept { return effect; } + + //============================================================================== + /** Finds the appropriate look-and-feel to use for this component. + + If the component hasn't had a look-and-feel explicitly set, this will + return the parent's look-and-feel, or just the default one if there's no + parent. + + @see setLookAndFeel, lookAndFeelChanged + */ + LookAndFeel& getLookAndFeel() const noexcept; + + /** Sets the look and feel to use for this component. + + This will also change the look and feel for any child components that haven't + had their look set explicitly. + + The object passed in will not be deleted by the component, so it's the caller's + responsibility to manage it. It may be used at any time until this component + has been deleted. + + Calling this method will also invoke the sendLookAndFeelChange() method. + + @see getLookAndFeel, lookAndFeelChanged + */ + void setLookAndFeel (LookAndFeel* newLookAndFeel); + + /** Called to let the component react to a change in the look-and-feel setting. + + When the look-and-feel is changed for a component, this will be called in + all its child components, recursively. + + It can also be triggered manually by the sendLookAndFeelChange() method, in case + an application uses a LookAndFeel class that might have changed internally. + + @see sendLookAndFeelChange, getLookAndFeel + */ + virtual void lookAndFeelChanged(); + + /** Calls the lookAndFeelChanged() method in this component and all its children. + + This will recurse through the children and their children, calling lookAndFeelChanged() + on them all. + + @see lookAndFeelChanged + */ + void sendLookAndFeelChange(); + + //============================================================================== + /** Indicates whether any parts of the component might be transparent. + + Components that always paint all of their contents with solid colour and + thus completely cover any components behind them should use this method + to tell the repaint system that they are opaque. + + This information is used to optimise drawing, because it means that + objects underneath opaque windows don't need to be painted. + + By default, components are considered transparent, unless this is used to + make it otherwise. + + @see isOpaque + */ + void setOpaque (bool shouldBeOpaque); + + /** Returns true if no parts of this component are transparent. + + @returns the value that was set by setOpaque, (the default being false) + @see setOpaque + */ + bool isOpaque() const noexcept; + + //============================================================================== + /** Indicates whether the component should be brought to the front when clicked. + + Setting this flag to true will cause the component to be brought to the front + when the mouse is clicked somewhere inside it or its child components. + + Note that a top-level desktop window might still be brought to the front by the + operating system when it's clicked, depending on how the OS works. + + By default this is set to false. + + @see setMouseClickGrabsKeyboardFocus + */ + void setBroughtToFrontOnMouseClick (bool shouldBeBroughtToFront) noexcept; + + /** Indicates whether the component should be brought to the front when clicked-on. + @see setBroughtToFrontOnMouseClick + */ + bool isBroughtToFrontOnMouseClick() const noexcept; + + //============================================================================== + // Keyboard focus methods + + /** Sets a flag to indicate whether this component needs keyboard focus or not. + + By default components aren't actually interested in gaining the + focus, but this method can be used to turn this on. + + See the grabKeyboardFocus() method for details about the way a component + is chosen to receive the focus. + + @see grabKeyboardFocus, getWantsKeyboardFocus + */ + void setWantsKeyboardFocus (bool wantsFocus) noexcept; + + /** Returns true if the component is interested in getting keyboard focus. + + This returns the flag set by setWantsKeyboardFocus(). The default + setting is false. + + @see setWantsKeyboardFocus + */ + bool getWantsKeyboardFocus() const noexcept; + + //============================================================================== + /** Chooses whether a click on this component automatically grabs the focus. + + By default this is set to true, but you might want a component which can + be focused, but where you don't want the user to be able to affect it directly + by clicking. + */ + void setMouseClickGrabsKeyboardFocus (bool shouldGrabFocus); + + /** Returns the last value set with setMouseClickGrabsKeyboardFocus(). + See setMouseClickGrabsKeyboardFocus() for more info. + */ + bool getMouseClickGrabsKeyboardFocus() const noexcept; + + //============================================================================== + /** Tries to give keyboard focus to this component. + + When the user clicks on a component or its grabKeyboardFocus() + method is called, the following procedure is used to work out which + component should get it: + + - if the component that was clicked on actually wants focus (as indicated + by calling getWantsKeyboardFocus), it gets it. + - if the component itself doesn't want focus, it will try to pass it + on to whichever of its children is the default component, as determined by + KeyboardFocusTraverser::getDefaultComponent() + - if none of its children want focus at all, it will pass it up to its + parent instead, unless it's a top-level component without a parent, + in which case it just takes the focus itself. + + Important note! It's obviously not possible for a component to be focused + unless it's actually visible, on-screen, and inside a window that is also + visible. So there's no point trying to call this in the component's own + constructor or before all of its parent hierarchy has been fully instantiated. + + @see setWantsKeyboardFocus, getWantsKeyboardFocus, hasKeyboardFocus, + getCurrentlyFocusedComponent, focusGained, focusLost, + keyPressed, keyStateChanged + */ + void grabKeyboardFocus(); + + /** Returns true if this component currently has the keyboard focus. + + @param trueIfChildIsFocused if this is true, then the method returns true if + either this component or any of its children (recursively) + have the focus. If false, the method only returns true if + this component has the focus. + + @see grabKeyboardFocus, setWantsKeyboardFocus, getCurrentlyFocusedComponent, + focusGained, focusLost + */ + bool hasKeyboardFocus (bool trueIfChildIsFocused) const; + + /** Returns the component that currently has the keyboard focus. + @returns the focused component, or null if nothing is focused. + */ + static Component* JUCE_CALLTYPE getCurrentlyFocusedComponent() noexcept; + + /** If any component has keyboard focus, this will defocus it. */ + static void JUCE_CALLTYPE unfocusAllComponents(); + + //============================================================================== + /** Tries to move the keyboard focus to one of this component's siblings. + + This will try to move focus to either the next or previous component. (This + is the method that is used when shifting focus by pressing the tab key). + + Components for which getWantsKeyboardFocus() returns false are not looked at. + + @param moveToNext if true, the focus will move forwards; if false, it will + move backwards + @see grabKeyboardFocus, setFocusContainer, setWantsKeyboardFocus + */ + void moveKeyboardFocusToSibling (bool moveToNext); + + /** Creates a KeyboardFocusTraverser object to use to determine the logic by + which focus should be passed from this component. + + The default implementation of this method will return a default + KeyboardFocusTraverser if this component is a focus container (as determined + by the setFocusContainer() method). If the component isn't a focus + container, then it will recursively ask its parents for a KeyboardFocusTraverser. + + If you override this to return a custom KeyboardFocusTraverser, then + this component and all its sub-components will use the new object to + make their focusing decisions. + + The method should return a new object, which the caller is required to + delete when no longer needed. + */ + virtual KeyboardFocusTraverser* createFocusTraverser(); + + /** Returns the focus order of this component, if one has been specified. + + By default components don't have a focus order - in that case, this + will return 0. Lower numbers indicate that the component will be + earlier in the focus traversal order. + + To change the order, call setExplicitFocusOrder(). + + The focus order may be used by the KeyboardFocusTraverser class as part of + its algorithm for deciding the order in which components should be traversed. + See the KeyboardFocusTraverser class for more details on this. + + @see moveKeyboardFocusToSibling, createFocusTraverser, KeyboardFocusTraverser + */ + int getExplicitFocusOrder() const; + + /** Sets the index used in determining the order in which focusable components + should be traversed. + + A value of 0 or less is taken to mean that no explicit order is wanted, and + that traversal should use other factors, like the component's position. + + @see getExplicitFocusOrder, moveKeyboardFocusToSibling + */ + void setExplicitFocusOrder (int newFocusOrderIndex); + + /** Indicates whether this component is a parent for components that can have + their focus traversed. + + This flag is used by the default implementation of the createFocusTraverser() + method, which uses the flag to find the first parent component (of the currently + focused one) which wants to be a focus container. + + So using this method to set the flag to 'true' causes this component to + act as the top level within which focus is passed around. + + @see isFocusContainer, createFocusTraverser, moveKeyboardFocusToSibling + */ + void setFocusContainer (bool shouldBeFocusContainer) noexcept; + + /** Returns true if this component has been marked as a focus container. + + See setFocusContainer() for more details. + + @see setFocusContainer, moveKeyboardFocusToSibling, createFocusTraverser + */ + bool isFocusContainer() const noexcept; + + //============================================================================== + /** Returns true if the component (and all its parents) are enabled. + + Components are enabled by default, and can be disabled with setEnabled(). Exactly + what difference this makes to the component depends on the type. E.g. buttons + and sliders will choose to draw themselves differently, etc. + + Note that if one of this component's parents is disabled, this will always + return false, even if this component itself is enabled. + + @see setEnabled, enablementChanged + */ + bool isEnabled() const noexcept; + + /** Enables or disables this component. + + Disabling a component will also cause all of its child components to become + disabled. + + Similarly, enabling a component which is inside a disabled parent + component won't make any difference until the parent is re-enabled. + + @see isEnabled, enablementChanged + */ + void setEnabled (bool shouldBeEnabled); + + /** Callback to indicate that this component has been enabled or disabled. + + This can be triggered by one of the component's parent components + being enabled or disabled, as well as changes to the component itself. + + The default implementation of this method does nothing; your class may + wish to repaint itself or something when this happens. + + @see setEnabled, isEnabled + */ + virtual void enablementChanged(); + + //============================================================================== + /** Returns the component's current transparency level. + See setAlpha() for more details. + */ + float getAlpha() const noexcept; + + /** Changes the transparency of this component. + When painted, the entire component and all its children will be rendered + with this as the overall opacity level, where 0 is completely invisible, and + 1.0 is fully opaque (i.e. normal). + + @see getAlpha, alphaChanged + */ + void setAlpha (float newAlpha); + + /** Called when setAlpha() is used to change the alpha value of this component. + If you override this, you should also invoke the base class's implementation + during your overridden function, as it performs some repainting behaviour. + */ + virtual void alphaChanged(); + + //============================================================================== + /** Changes the mouse cursor shape to use when the mouse is over this component. + + Note that the cursor set by this method can be overridden by the getMouseCursor + method. + + @see MouseCursor + */ + void setMouseCursor (const MouseCursor& cursorType); + + /** Returns the mouse cursor shape to use when the mouse is over this component. + + The default implementation will return the cursor that was set by setCursor() + but can be overridden for more specialised purposes, e.g. returning different + cursors depending on the mouse position. + + @see MouseCursor + */ + virtual MouseCursor getMouseCursor(); + + /** Forces the current mouse cursor to be updated. + + If you're overriding the getMouseCursor() method to control which cursor is + displayed, then this will only be checked each time the user moves the mouse. So + if you want to force the system to check that the cursor being displayed is + up-to-date (even if the mouse is just sitting there), call this method. + + (If you're changing the cursor using setMouseCursor(), you don't need to bother + calling this). + */ + void updateMouseCursor() const; + + //============================================================================== + /** Components can override this method to draw their content. + + The paint() method gets called when a region of a component needs redrawing, + either because the component's repaint() method has been called, or because + something has happened on the screen that means a section of a window needs + to be redrawn. + + Any child components will draw themselves over whatever this method draws. If + you need to paint over the top of your child components, you can also implement + the paintOverChildren() method to do this. + + If you want to cause a component to redraw itself, this is done asynchronously - + calling the repaint() method marks a region of the component as "dirty", and the + paint() method will automatically be called sometime later, by the message thread, + to paint any bits that need refreshing. In JUCE (and almost all modern UI frameworks), + you never redraw something synchronously. + + You should never need to call this method directly - to take a snapshot of the + component you could use createComponentSnapshot() or paintEntireComponent(). + + @param g the graphics context that must be used to do the drawing operations. + @see repaint, paintOverChildren, Graphics + */ + virtual void paint (Graphics& g); + + /** Components can override this method to draw over the top of their children. + + For most drawing operations, it's better to use the normal paint() method, + but if you need to overlay something on top of the children, this can be + used. + + @see paint, Graphics + */ + virtual void paintOverChildren (Graphics& g); + + + //============================================================================== + /** Called when the mouse moves inside a component. + + If the mouse button isn't pressed and the mouse moves over a component, + this will be called to let the component react to this. + + A component will always get a mouseEnter callback before a mouseMove. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseEnter, mouseExit, mouseDrag, contains + */ + void mouseMove (const MouseEvent& event) override; + + /** Called when the mouse first enters a component. + + If the mouse button isn't pressed and the mouse moves into a component, + this will be called to let the component react to this. + + When the mouse button is pressed and held down while being moved in + or out of a component, no mouseEnter or mouseExit callbacks are made - only + mouseDrag messages are sent to the component that the mouse was originally + clicked on, until the button is released. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseExit, mouseDrag, mouseMove, contains + */ + void mouseEnter (const MouseEvent& event) override; + + /** Called when the mouse moves out of a component. + + This will be called when the mouse moves off the edge of this + component. + + If the mouse button was pressed, and it was then dragged off the + edge of the component and released, then this callback will happen + when the button is released, after the mouseUp callback. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseEnter, mouseDrag, mouseMove, contains + */ + void mouseExit (const MouseEvent& event) override; + + /** Called when a mouse button is pressed. + + The MouseEvent object passed in contains lots of methods for finding out + which button was pressed, as well as which modifier keys (e.g. shift, ctrl) + were held down at the time. + + Once a button is held down, the mouseDrag method will be called when the + mouse moves, until the button is released. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseUp, mouseDrag, mouseDoubleClick, contains + */ + void mouseDown (const MouseEvent& event) override; + + /** Called when the mouse is moved while a button is held down. + + When a mouse button is pressed inside a component, that component + receives mouseDrag callbacks each time the mouse moves, even if the + mouse strays outside the component's bounds. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseDown, mouseUp, mouseMove, contains, setDragRepeatInterval + */ + void mouseDrag (const MouseEvent& event) override; + + /** Called when a mouse button is released. + + A mouseUp callback is sent to the component in which a button was pressed + even if the mouse is actually over a different component when the + button is released. + + The MouseEvent object passed in contains lots of methods for finding out + which buttons were down just before they were released. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseDown, mouseDrag, mouseDoubleClick, contains + */ + void mouseUp (const MouseEvent& event) override; + + /** Called when a mouse button has been double-clicked on a component. + + The MouseEvent object passed in contains lots of methods for finding out + which button was pressed, as well as which modifier keys (e.g. shift, ctrl) + were held down at the time. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseDown, mouseUp + */ + void mouseDoubleClick (const MouseEvent& event) override; + + /** Called when the mouse-wheel is moved. + + This callback is sent to the component that the mouse is over when the + wheel is moved. + + If not overridden, a component will forward this message to its parent, so + that parent components can collect mouse-wheel messages that happen to + child components which aren't interested in them. (Bear in mind that if + you attach a component as a mouse-listener to other components, then + those wheel moves will also end up calling this method and being passed up + to the parents, which may not be what you intended to happen). + + @param event details about the mouse event + @param wheel details about the mouse wheel movement + */ + void mouseWheelMove (const MouseEvent& event, + const MouseWheelDetails& wheel) override; + + /** Called when a pinch-to-zoom mouse-gesture is used. + + If not overridden, a component will forward this message to its parent, so + that parent components can collect gesture messages that are unused by child + components. + + @param event details about the mouse event + @param scaleFactor a multiplier to indicate by how much the size of the target + should be changed. A value of 1.0 would indicate no change, + values greater than 1.0 mean it should be enlarged. + */ + void mouseMagnify (const MouseEvent& event, float scaleFactor) override; + + //============================================================================== + /** Ensures that a non-stop stream of mouse-drag events will be sent during the + current mouse-drag operation. + + This allows you to make sure that mouseDrag() events are sent continuously, even + when the mouse isn't moving. This can be useful for things like auto-scrolling + components when the mouse is near an edge. + + Call this method during a mouseDown() or mouseDrag() callback, specifying the + minimum interval between consecutive mouse drag callbacks. The callbacks + will continue until the mouse is released, and then the interval will be reset, + so you need to make sure it's called every time you begin a drag event. + Passing an interval of 0 or less will cancel the auto-repeat. + + @see mouseDrag, Desktop::beginDragAutoRepeat + */ + static void JUCE_CALLTYPE beginDragAutoRepeat (int millisecondsBetweenCallbacks); + + /** Causes automatic repaints when the mouse enters or exits this component. + + If turned on, then when the mouse enters/exits, or when the button is pressed/released + on the component, it will trigger a repaint. + + This is handy for things like buttons that need to draw themselves differently when + the mouse moves over them, and it avoids having to override all the different mouse + callbacks and call repaint(). + + @see mouseEnter, mouseExit, mouseDown, mouseUp + */ + void setRepaintsOnMouseActivity (bool shouldRepaint) noexcept; + + /** Registers a listener to be told when mouse events occur in this component. + + If you need to get informed about mouse events in a component but can't or + don't want to override its methods, you can attach any number of listeners + to the component, and these will get told about the events in addition to + the component's own callbacks being called. + + Note that a MouseListener can also be attached to more than one component. + + @param newListener the listener to register + @param wantsEventsForAllNestedChildComponents if true, the listener will receive callbacks + for events that happen to any child component + within this component, including deeply-nested + child components. If false, it will only be + told about events that this component handles. + @see MouseListener, removeMouseListener + */ + void addMouseListener (MouseListener* newListener, + bool wantsEventsForAllNestedChildComponents); + + /** Deregisters a mouse listener. + @see addMouseListener, MouseListener + */ + void removeMouseListener (MouseListener* listenerToRemove); + + //============================================================================== + /** Adds a listener that wants to hear about keypresses that this component receives. + + The listeners that are registered with a component are called by its keyPressed() or + keyStateChanged() methods (assuming these haven't been overridden to do something else). + + If you add an object as a key listener, be careful to remove it when the object + is deleted, or the component will be left with a dangling pointer. + + @see keyPressed, keyStateChanged, removeKeyListener + */ + void addKeyListener (KeyListener* newListener); + + /** Removes a previously-registered key listener. + @see addKeyListener + */ + void removeKeyListener (KeyListener* listenerToRemove); + + /** Called when a key is pressed. + + When a key is pressed, the component that has the keyboard focus will have this + method called. Remember that a component will only be given the focus if its + setWantsKeyboardFocus() method has been used to enable this. + + If your implementation returns true, the event will be consumed and not passed + on to any other listeners. If it returns false, the key will be passed to any + KeyListeners that have been registered with this component. As soon as one of these + returns true, the process will stop, but if they all return false, the event will + be passed upwards to this component's parent, and so on. + + The default implementation of this method does nothing and returns false. + + @see keyStateChanged, getCurrentlyFocusedComponent, addKeyListener + */ + virtual bool keyPressed (const KeyPress& key); + + /** Called when a key is pressed or released. + + Whenever a key on the keyboard is pressed or released (including modifier keys + like shift and ctrl), this method will be called on the component that currently + has the keyboard focus. Remember that a component will only be given the focus if + its setWantsKeyboardFocus() method has been used to enable this. + + If your implementation returns true, the event will be consumed and not passed + on to any other listeners. If it returns false, then any KeyListeners that have + been registered with this component will have their keyStateChanged methods called. + As soon as one of these returns true, the process will stop, but if they all return + false, the event will be passed upwards to this component's parent, and so on. + + The default implementation of this method does nothing and returns false. + + To find out which keys are up or down at any time, see the KeyPress::isKeyCurrentlyDown() + method. + + @param isKeyDown true if a key has been pressed; false if it has been released + + @see keyPressed, KeyPress, getCurrentlyFocusedComponent, addKeyListener + */ + virtual bool keyStateChanged (bool isKeyDown); + + /** Called when a modifier key is pressed or released. + + Whenever the shift, control, alt or command keys are pressed or released, + this method will be called on the component that currently has the keyboard focus. + Remember that a component will only be given the focus if its setWantsKeyboardFocus() + method has been used to enable this. + + The default implementation of this method actually calls its parent's modifierKeysChanged + method, so that focused components which aren't interested in this will give their + parents a chance to act on the event instead. + + @see keyStateChanged, ModifierKeys + */ + virtual void modifierKeysChanged (const ModifierKeys& modifiers); + + //============================================================================== + /** Enumeration used by the focusGained() and focusLost() methods. */ + enum FocusChangeType + { + focusChangedByMouseClick, /**< Means that the user clicked the mouse to change focus. */ + focusChangedByTabKey, /**< Means that the user pressed the tab key to move the focus. */ + focusChangedDirectly /**< Means that the focus was changed by a call to grabKeyboardFocus(). */ + }; + + /** Called to indicate that this component has just acquired the keyboard focus. + @see focusLost, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus + */ + virtual void focusGained (FocusChangeType cause); + + /** Called to indicate that this component has just lost the keyboard focus. + @see focusGained, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus + */ + virtual void focusLost (FocusChangeType cause); + + /** Called to indicate a change in whether or not this component is the parent of the + currently-focused component. + + Essentially this is called when the return value of a call to hasKeyboardFocus (true) has + changed. It happens when focus moves from one of this component's children (at any depth) + to a component that isn't contained in this one, (or vice-versa). + Note that this method does NOT get called to when focus simply moves from one of its + child components to another. + + @see focusGained, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus + */ + virtual void focusOfChildComponentChanged (FocusChangeType cause); + + //============================================================================== + /** Returns true if the mouse is currently over this component. + + If the mouse isn't over the component, this will return false, even if the + mouse is currently being dragged - so you can use this in your mouseDrag + method to find out whether it's really over the component or not. + + Note that when the mouse button is being held down, then the only component + for which this method will return true is the one that was originally + clicked on. + + Also note that on a touch-screen device, this will only return true when a finger + is actually down - as soon as all touch is released, isMouseOver will always + return false. + + If includeChildren is true, then this will also return true if the mouse is over + any of the component's children (recursively) as well as the component itself. + + @see isMouseButtonDown. isMouseOverOrDragging, mouseDrag + */ + bool isMouseOver (bool includeChildren = false) const; + + /** Returns true if the mouse button is currently held down in this component. + + Note that this is a test to see whether the mouse is being pressed in this + component, so it'll return false if called on component A when the mouse + is actually being dragged in component B. + + @see isMouseButtonDownAnywhere, isMouseOver, isMouseOverOrDragging + */ + bool isMouseButtonDown (bool includeChildren = false) const; + + /** True if the mouse is over this component, or if it's being dragged in this component. + This is a handy equivalent to (isMouseOver() || isMouseButtonDown()). + @see isMouseOver, isMouseButtonDown, isMouseButtonDownAnywhere + */ + bool isMouseOverOrDragging (bool includeChildren = false) const; + + /** Returns true if a mouse button is currently down. + + Unlike isMouseButtonDown, this will test the current state of the + buttons without regard to which component (if any) it has been + pressed in. + + @see isMouseButtonDown, ModifierKeys + */ + static bool JUCE_CALLTYPE isMouseButtonDownAnywhere() noexcept; + + /** Returns the mouse's current position, relative to this component. + The return value is relative to the component's top-left corner. + */ + Point getMouseXYRelative() const; + + //============================================================================== + /** Called when this component's size has been changed. + + A component can implement this method to do things such as laying out its + child components when its width or height changes. + + The method is called synchronously as a result of the setBounds or setSize + methods, so repeatedly changing a components size will repeatedly call its + resized method (unlike things like repainting, where multiple calls to repaint + are coalesced together). + + If the component is a top-level window on the desktop, its size could also + be changed by operating-system factors beyond the application's control. + + @see moved, setSize + */ + virtual void resized(); + + /** Called when this component's position has been changed. + + This is called when the position relative to its parent changes, not when + its absolute position on the screen changes (so it won't be called for + all child components when a parent component is moved). + + The method is called synchronously as a result of the setBounds, setTopLeftPosition + or any of the other repositioning methods, and like resized(), it will be + called each time those methods are called. + + If the component is a top-level window on the desktop, its position could also + be changed by operating-system factors beyond the application's control. + + @see resized, setBounds + */ + virtual void moved(); + + /** Called when one of this component's children is moved or resized. + + If the parent wants to know about changes to its immediate children (not + to children of its children), this is the method to override. + + @see moved, resized, parentSizeChanged + */ + virtual void childBoundsChanged (Component* child); + + /** Called when this component's immediate parent has been resized. + + If the component is a top-level window, this indicates that the screen size + has changed. + + @see childBoundsChanged, moved, resized + */ + virtual void parentSizeChanged(); + + /** Called when this component has been moved to the front of its siblings. + + The component may have been brought to the front by the toFront() method, or + by the operating system if it's a top-level window. + + @see toFront + */ + virtual void broughtToFront(); + + /** Adds a listener to be told about changes to the component hierarchy or position. + + Component listeners get called when this component's size, position or children + change - see the ComponentListener class for more details. + + @param newListener the listener to register - if this is already registered, it + will be ignored. + @see ComponentListener, removeComponentListener + */ + void addComponentListener (ComponentListener* newListener); + + /** Removes a component listener. + @see addComponentListener + */ + void removeComponentListener (ComponentListener* listenerToRemove); + + //============================================================================== + /** Dispatches a numbered message to this component. + + This is a quick and cheap way of allowing simple asynchronous messages to + be sent to components. It's also safe, because if the component that you + send the message to is a null or dangling pointer, this won't cause an error. + + The command ID is later delivered to the component's handleCommandMessage() method by + the application's message queue. + + @see handleCommandMessage + */ + void postCommandMessage (int commandId); + + /** Called to handle a command that was sent by postCommandMessage(). + + This is called by the message thread when a command message arrives, and + the component can override this method to process it in any way it needs to. + + @see postCommandMessage + */ + virtual void handleCommandMessage (int commandId); + + //============================================================================== + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN + /** Runs a component modally, waiting until the loop terminates. + + This method first makes the component visible, brings it to the front and + gives it the keyboard focus. + + It then runs a loop, dispatching messages from the system message queue, but + blocking all mouse or keyboard messages from reaching any components other + than this one and its children. + + This loop continues until the component's exitModalState() method is called (or + the component is deleted), and then this method returns, returning the value + passed into exitModalState(). + + Note that you SHOULD NEVER USE THIS METHOD! Modal loops are a dangerous construct + because things that happen during the events that they dispatch could affect the + state of objects which are currently in use somewhere on the stack, so when the + loop finishes and the stack unwinds, horrible problems can occur. This is especially + bad in plugins, where the host may choose to delete the plugin during runModalLoop(), + so that when it returns, the entire DLL could have been unloaded from memory! + Also, some OSes deliberately make it impossible to run modal loops (e.g. Android), + so this method won't even exist on some platforms. + + @see enterModalState, exitModalState, isCurrentlyModal, getCurrentlyModalComponent, + isCurrentlyBlockedByAnotherModalComponent, ModalComponentManager + */ + int runModalLoop(); + #endif + + /** Puts the component into a modal state. + + This makes the component modal, so that messages are blocked from reaching + any components other than this one and its children, but unlike runModalLoop(), + this method returns immediately. + + If takeKeyboardFocus is true, the component will use grabKeyboardFocus() to + get the focus, which is usually what you'll want it to do. If not, it will leave + the focus unchanged. + + The callback is an optional object which will receive a callback when the modal + component loses its modal status, either by being hidden or when exitModalState() + is called. If you pass an object in here, the system will take care of deleting it + later, after making the callback + + If deleteWhenDismissed is true, then when it is dismissed, the component will be + deleted and then the callback will be called. (This will safely handle the situation + where the component is deleted before its exitModalState() method is called). + + @see exitModalState, runModalLoop, ModalComponentManager::attachCallback + */ + void enterModalState (bool takeKeyboardFocus = true, + ModalComponentManager::Callback* callback = nullptr, + bool deleteWhenDismissed = false); + + /** Ends a component's modal state. + + If this component is currently modal, this will turn off its modalness, and return + a value to the runModalLoop() method that might have be running its modal loop. + + @see runModalLoop, enterModalState, isCurrentlyModal + */ + void exitModalState (int returnValue); + + /** Returns true if this component is the modal one. + + It's possible to have nested modal components, e.g. a pop-up dialog box + that launches another pop-up. If onlyConsiderForemostModalComponent is + true then isCurrentlyModal will only return true for the one at the top + of the stack. If onlyConsiderForemostModalComponent is false then + isCurrentlyModal will return true for any modal component in the stack. + + @see getCurrentlyModalComponent + */ + bool isCurrentlyModal (bool onlyConsiderForemostModalComponent = true) const noexcept; + + /** Returns the number of components that are currently in a modal state. + @see getCurrentlyModalComponent + */ + static int JUCE_CALLTYPE getNumCurrentlyModalComponents() noexcept; + + /** Returns one of the components that are currently modal. + + The index specifies which of the possible modal components to return. The order + of the components in this list is the reverse of the order in which they became + modal - so the component at index 0 is always the active component, and the others + are progressively earlier ones that are themselves now blocked by later ones. + + @returns the modal component, or null if no components are modal (or if the + index is out of range) + @see getNumCurrentlyModalComponents, runModalLoop, isCurrentlyModal + */ + static Component* JUCE_CALLTYPE getCurrentlyModalComponent (int index = 0) noexcept; + + /** Checks whether there's a modal component somewhere that's stopping this one + from receiving messages. + + If there is a modal component, its canModalEventBeSentToComponent() method + will be called to see if it will still allow this component to receive events. + + @see runModalLoop, getCurrentlyModalComponent + */ + bool isCurrentlyBlockedByAnotherModalComponent() const; + + /** When a component is modal, this callback allows it to choose which other + components can still receive events. + + When a modal component is active and the user clicks on a non-modal component, + this method is called on the modal component, and if it returns true, the + event is allowed to reach its target. If it returns false, the event is blocked + and the inputAttemptWhenModal() callback is made. + + It called by the isCurrentlyBlockedByAnotherModalComponent() method. The default + implementation just returns false in all cases. + */ + virtual bool canModalEventBeSentToComponent (const Component* targetComponent); + + /** Called when the user tries to click on a component that is blocked by another + modal component. + + When a component is modal and the user clicks on one of the other components, + the modal component will receive this callback. + + The default implementation of this method will play a beep, and bring the currently + modal component to the front, but it can be overridden to do other tasks. + + @see isCurrentlyBlockedByAnotherModalComponent, canModalEventBeSentToComponent + */ + virtual void inputAttemptWhenModal(); + + + //============================================================================== + /** Returns the set of properties that belong to this component. + Each component has a NamedValueSet object which you can use to attach arbitrary + items of data to it. + */ + NamedValueSet& getProperties() noexcept { return properties; } + + /** Returns the set of properties that belong to this component. + Each component has a NamedValueSet object which you can use to attach arbitrary + items of data to it. + */ + const NamedValueSet& getProperties() const noexcept { return properties; } + + //============================================================================== + /** Looks for a colour that has been registered with the given colour ID number. + + If a colour has been set for this ID number using setColour(), then it is + returned. If none has been set, the method will try calling the component's + LookAndFeel class's findColour() method. If none has been registered with the + look-and-feel either, it will just return black. + + The colour IDs for various purposes are stored as enums in the components that + they are relevant to - for an example, see Slider::ColourIds, + Label::ColourIds, TextEditor::ColourIds, TreeView::ColourIds, etc. + + @see setColour, isColourSpecified, colourChanged, LookAndFeel::findColour, LookAndFeel::setColour + */ + Colour findColour (int colourID, bool inheritFromParent = false) const; + + /** Registers a colour to be used for a particular purpose. + + Changing a colour will cause a synchronous callback to the colourChanged() + method, which your component can override if it needs to do something when + colours are altered. + + For more details about colour IDs, see the comments for findColour(). + + @see findColour, isColourSpecified, colourChanged, LookAndFeel::findColour, LookAndFeel::setColour + */ + void setColour (int colourID, Colour newColour); + + /** If a colour has been set with setColour(), this will remove it. + This allows you to make a colour revert to its default state. + */ + void removeColour (int colourID); + + /** Returns true if the specified colour ID has been explicitly set for this + component using the setColour() method. + */ + bool isColourSpecified (int colourID) const; + + /** This looks for any colours that have been specified for this component, + and copies them to the specified target component. + */ + void copyAllExplicitColoursTo (Component& target) const; + + /** This method is called when a colour is changed by the setColour() method. + @see setColour, findColour + */ + virtual void colourChanged(); + + //============================================================================== + /** Returns the underlying native window handle for this component. + + This is platform-dependent and strictly for power-users only! + */ + void* getWindowHandle() const; + + //============================================================================== + /** Holds a pointer to some type of Component, which automatically becomes null if + the component is deleted. + + If you're using a component which may be deleted by another event that's outside + of your control, use a SafePointer instead of a normal pointer to refer to it, + and you can test whether it's null before using it to see if something has deleted + it. + + The ComponentType template parameter must be Component, or some subclass of Component. + + You may also want to use a WeakReference object for the same purpose. + */ + template + class SafePointer + { + public: + /** Creates a null SafePointer. */ + SafePointer() = default; + + /** Creates a SafePointer that points at the given component. */ + SafePointer (ComponentType* component) : weakRef (component) {} + + /** Creates a copy of another SafePointer. */ + SafePointer (const SafePointer& other) noexcept : weakRef (other.weakRef) {} + + /** Copies another pointer to this one. */ + SafePointer& operator= (const SafePointer& other) { weakRef = other.weakRef; return *this; } + + /** Copies another pointer to this one. */ + SafePointer& operator= (ComponentType* newComponent) { weakRef = newComponent; return *this; } + + /** Returns the component that this pointer refers to, or null if the component no longer exists. */ + ComponentType* getComponent() const noexcept { return dynamic_cast (weakRef.get()); } + + /** Returns the component that this pointer refers to, or null if the component no longer exists. */ + operator ComponentType*() const noexcept { return getComponent(); } + + /** Returns the component that this pointer refers to, or null if the component no longer exists. */ + ComponentType* operator->() noexcept { return getComponent(); } + + /** Returns the component that this pointer refers to, or null if the component no longer exists. */ + const ComponentType* operator->() const noexcept { return getComponent(); } + + /** If the component is valid, this deletes it and sets this pointer to null. */ + void deleteAndZero() { delete getComponent(); } + + bool operator== (ComponentType* component) const noexcept { return weakRef == component; } + bool operator!= (ComponentType* component) const noexcept { return weakRef != component; } + + private: + WeakReference weakRef; + }; + + //============================================================================== + /** A class to keep an eye on a component and check for it being deleted. + + This is designed for use with the ListenerList::callChecked() methods, to allow + the list iterator to stop cleanly if the component is deleted by a listener callback + while the list is still being iterated. + */ + class JUCE_API BailOutChecker + { + public: + /** Creates a checker that watches one component. */ + BailOutChecker (Component* component); + + /** Returns true if either of the two components have been deleted since this object was created. */ + bool shouldBailOut() const noexcept; + + private: + const WeakReference safePointer; + + JUCE_DECLARE_NON_COPYABLE (BailOutChecker) + }; + + //============================================================================== + /** + Base class for objects that can be used to automatically position a component according to + some kind of algorithm. + + The component class simply holds onto a reference to a Positioner, but doesn't actually do + anything with it - all the functionality must be implemented by the positioner itself (e.g. + it might choose to watch some kind of value and move the component when the value changes). + */ + class JUCE_API Positioner + { + public: + /** Creates a Positioner which can control the specified component. */ + explicit Positioner (Component& component) noexcept; + /** Destructor. */ + virtual ~Positioner() = default; + + /** Returns the component that this positioner controls. */ + Component& getComponent() const noexcept { return component; } + + /** Attempts to set the component's position to the given rectangle. + Unlike simply calling Component::setBounds(), this may involve the positioner + being smart enough to adjust itself to fit the new bounds. + */ + virtual void applyNewBounds (const Rectangle& newBounds) = 0; + + private: + Component& component; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Positioner) + }; + + /** Returns the Positioner object that has been set for this component. + @see setPositioner() + */ + Positioner* getPositioner() const noexcept; + + /** Sets a new Positioner object for this component. + If there's currently another positioner set, it will be deleted. The object that is passed in + will be deleted automatically by this component when it's no longer required. Pass a null pointer + to clear the current positioner. + @see getPositioner() + */ + void setPositioner (Positioner* newPositioner); + + /** Gives the component a CachedComponentImage that should be used to buffer its painting. + The object that is passed-in will be owned by this component, and will be deleted automatically + later on. + @see setBufferedToImage + */ + void setCachedComponentImage (CachedComponentImage* newCachedImage); + + /** Returns the object that was set by setCachedComponentImage(). + @see setCachedComponentImage + */ + CachedComponentImage* getCachedComponentImage() const noexcept { return cachedImage.get(); } + + /** Sets a flag to indicate whether mouse drag events on this Component should be ignored when it is inside a + Viewport with drag-to-scroll functionality enabled. This is useful for Components such as sliders that + should not move when their parent Viewport when dragged. + */ + void setViewportIgnoreDragFlag (bool ignoreDrag) noexcept { flags.viewportIgnoreDragFlag = ignoreDrag; } + + /** Retrieves the current state of the Viewport drag-to-scroll functionality flag. + @see setViewportIgnoreDragFlag + */ + bool getViewportIgnoreDragFlag() const noexcept { return flags.viewportIgnoreDragFlag; } + +private: + //============================================================================== + friend class ComponentPeer; + friend class MouseInputSource; + friend class MouseInputSourceInternal; + + #ifndef DOXYGEN + static Component* currentlyFocusedComponent; + + //============================================================================== + String componentName, componentID; + Component* parentComponent = nullptr; + Rectangle boundsRelativeToParent; + std::unique_ptr positioner; + std::unique_ptr affineTransform; + Array childComponentList; + WeakReference lookAndFeel; + MouseCursor cursor; + ImageEffectFilter* effect = nullptr; + std::unique_ptr cachedImage; + + class MouseListenerList; + std::unique_ptr mouseListeners; + std::unique_ptr> keyListeners; + ListenerList componentListeners; + NamedValueSet properties; + + friend class WeakReference; + WeakReference::Master masterReference; + + struct ComponentFlags + { + bool hasHeavyweightPeerFlag : 1; + bool visibleFlag : 1; + bool opaqueFlag : 1; + bool ignoresMouseClicksFlag : 1; + bool allowChildMouseClicksFlag : 1; + bool wantsFocusFlag : 1; + bool isFocusContainerFlag : 1; + bool dontFocusOnMouseClickFlag : 1; + bool alwaysOnTopFlag : 1; + bool bufferToImageFlag : 1; + bool bringToFrontOnClickFlag : 1; + bool repaintOnMouseActivityFlag : 1; + bool isDisabledFlag : 1; + bool childCompFocusedFlag : 1; + bool dontClipGraphicsFlag : 1; + bool mouseDownWasBlocked : 1; + bool isMoveCallbackPending : 1; + bool isResizeCallbackPending : 1; + bool viewportIgnoreDragFlag : 1; + #if JUCE_DEBUG + bool isInsidePaintCall : 1; + #endif + }; + + union + { + uint32 componentFlags; + ComponentFlags flags; + }; + + uint8 componentTransparency = 0; + + //============================================================================== + void internalMouseEnter (MouseInputSource, Point, Time); + void internalMouseExit (MouseInputSource, Point, Time); + void internalMouseDown (MouseInputSource, Point, Time, float, float, float, float, float); + void internalMouseUp (MouseInputSource, Point, Time, const ModifierKeys oldModifiers, float, float, float, float, float); + void internalMouseDrag (MouseInputSource, Point, Time, float, float, float, float, float); + void internalMouseMove (MouseInputSource, Point, Time); + void internalMouseWheel (MouseInputSource, Point, Time, const MouseWheelDetails&); + void internalMagnifyGesture (MouseInputSource, Point, Time, float); + void internalBroughtToFront(); + void internalFocusGain (FocusChangeType, const WeakReference&); + void internalFocusGain (FocusChangeType); + void internalFocusLoss (FocusChangeType); + void internalChildFocusChange (FocusChangeType, const WeakReference&); + void internalModalInputAttempt(); + void internalModifierKeysChanged(); + void internalChildrenChanged(); + void internalHierarchyChanged(); + void internalRepaint (Rectangle); + void internalRepaintUnchecked (Rectangle, bool); + Component* removeChildComponent (int index, bool sendParentEvents, bool sendChildEvents); + void reorderChildInternal (int sourceIndex, int destIndex); + void paintComponentAndChildren (Graphics&); + void paintWithinParentContext (Graphics&); + void sendMovedResizedMessages (bool wasMoved, bool wasResized); + void sendMovedResizedMessagesIfPending(); + void repaintParent(); + void sendFakeMouseMove() const; + void takeKeyboardFocus (FocusChangeType); + void grabFocusInternal (FocusChangeType, bool canTryParent); + static void giveAwayFocus (bool sendFocusLossEvent); + void sendEnablementChangeMessage(); + void sendVisibilityChangeMessage(); + + struct ComponentHelpers; + friend struct ComponentHelpers; + + /* Components aren't allowed to have copy constructors, as this would mess up parent hierarchies. + You might need to give your subclasses a private dummy constructor to avoid compiler warnings. + */ + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Component) + + //============================================================================== + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE + // This is included here just to cause a compile error if your code is still handling + // drag-and-drop with this method. If so, just update it to use the new FileDragAndDropTarget + // class, which is easy (just make your class inherit from FileDragAndDropTarget, and + // implement its methods instead of this Component method). + virtual void filesDropped (const StringArray&, int, int) {} + + // This is included here to cause an error if you use or overload it - it has been deprecated in + // favour of contains (Point) + void contains (int, int) = delete; + #endif + +protected: + //============================================================================== + /** @internal */ + virtual ComponentPeer* createNewPeer (int styleFlags, void* nativeWindowToAttachTo); + #endif +}; + +} // namespace juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h.rej b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h.rej new file mode 100644 index 00000000..a7d7294a --- /dev/null +++ b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h.rej @@ -0,0 +1,20 @@ +--- modules/juce_gui_basics/components/juce_Component.h ++++ modules/juce_gui_basics/components/juce_Component.h +@@ -2284,6 +2284,17 @@ class JUCE_API Component : public MouseListener + */ + bool getViewportIgnoreDragFlag() const noexcept { return flags.viewportIgnoreDragFlag; } + ++ virtual float getPixelScaling() const { return 1.0f; } ++ float getTotalPixelScaling() const { ++ const Component* component = this; ++ float pixel_scaling = 1.0f; ++ while (component) { ++ pixel_scaling *= component->getPixelScaling(); ++ component = component->getParentComponent(); ++ } ++ return pixel_scaling; ++ } ++ + private: + //============================================================================== + friend class ComponentPeer; diff --git a/libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp b/libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp index a8c2c283..ddb15b88 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp @@ -61,7 +61,7 @@ public: { if (auto* peer = comp.getPeer()) { - pos = peer->globalToLocal (pos); + pos = peer->globalToLocal (pos) * comp.getTotalPixelScaling(); auto& peerComp = peer->getComponent(); return comp.getLocalPoint (&peerComp, ScalingHelpers::unscaledScreenPosToScaled (peerComp, pos)); } diff --git a/libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp.orig b/libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp.orig new file mode 100644 index 00000000..a8c2c283 --- /dev/null +++ b/libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp.orig @@ -0,0 +1,781 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 6 End-User License + Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). + + End User License Agreement: www.juce.com/juce-6-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +class MouseInputSourceInternal : private AsyncUpdater +{ +public: + MouseInputSourceInternal (int i, MouseInputSource::InputSourceType type) : index (i), inputType (type) + { + } + + //============================================================================== + bool isDragging() const noexcept + { + return buttonState.isAnyMouseButtonDown(); + } + + Component* getComponentUnderMouse() const noexcept + { + return componentUnderMouse.get(); + } + + ModifierKeys getCurrentModifiers() const noexcept + { + return ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (buttonState.getRawFlags()); + } + + ComponentPeer* getPeer() noexcept + { + if (! ComponentPeer::isValidPeer (lastPeer)) + lastPeer = nullptr; + + return lastPeer; + } + + static Point screenPosToLocalPos (Component& comp, Point pos) + { + if (auto* peer = comp.getPeer()) + { + pos = peer->globalToLocal (pos); + auto& peerComp = peer->getComponent(); + return comp.getLocalPoint (&peerComp, ScalingHelpers::unscaledScreenPosToScaled (peerComp, pos)); + } + + return comp.getLocalPoint (nullptr, ScalingHelpers::unscaledScreenPosToScaled (comp, pos)); + } + + Component* findComponentAt (Point screenPos) + { + if (auto* peer = getPeer()) + { + auto relativePos = ScalingHelpers::unscaledScreenPosToScaled (peer->getComponent(), + peer->globalToLocal (screenPos)); + auto& comp = peer->getComponent(); + auto pos = relativePos.roundToInt(); + + // (the contains() call is needed to test for overlapping desktop windows) + if (comp.contains (pos)) + return comp.getComponentAt (pos); + } + + return nullptr; + } + + Point getScreenPosition() const noexcept + { + // This needs to return the live position if possible, but it mustn't update the lastScreenPos + // value, because that can cause continuity problems. + return ScalingHelpers::unscaledScreenPosToScaled (getRawScreenPosition()); + } + + Point getRawScreenPosition() const noexcept + { + return unboundedMouseOffset + (inputType != MouseInputSource::InputSourceType::touch ? MouseInputSource::getCurrentRawMousePosition() + : lastScreenPos); + } + + void setScreenPosition (Point p) + { + MouseInputSource::setRawMousePosition (ScalingHelpers::scaledScreenPosToUnscaled (p)); + } + + bool isPressureValid() const noexcept { return pressure >= 0.0f && pressure <= 1.0f; } + bool isOrientationValid() const noexcept { return orientation >= 0.0f && orientation <= MathConstants::twoPi; } + bool isRotationValid() const noexcept { return rotation >= 0.0f && rotation <= MathConstants::twoPi; } + bool isTiltValid (bool isX) const noexcept { return isX ? (tiltX >= -1.0f && tiltX <= 1.0f) : (tiltY >= -1.0f && tiltY <= 1.0f); } + + //============================================================================== + #if JUCE_DUMP_MOUSE_EVENTS + #define JUCE_MOUSE_EVENT_DBG(desc) DBG ("Mouse " << desc << " #" << index \ + << ": " << screenPosToLocalPos (comp, screenPos).toString() \ + << " - Comp: " << String::toHexString ((pointer_sized_int) &comp)); + #else + #define JUCE_MOUSE_EVENT_DBG(desc) + #endif + + void sendMouseEnter (Component& comp, Point screenPos, Time time) + { + JUCE_MOUSE_EVENT_DBG ("enter") + comp.internalMouseEnter (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time); + } + + void sendMouseExit (Component& comp, Point screenPos, Time time) + { + JUCE_MOUSE_EVENT_DBG ("exit") + comp.internalMouseExit (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time); + } + + void sendMouseMove (Component& comp, Point screenPos, Time time) + { + JUCE_MOUSE_EVENT_DBG ("move") + comp.internalMouseMove (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time); + } + + void sendMouseDown (Component& comp, Point screenPos, Time time) + { + JUCE_MOUSE_EVENT_DBG ("down") + comp.internalMouseDown (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, pressure, orientation, rotation, tiltX, tiltY); + } + + void sendMouseDrag (Component& comp, Point screenPos, Time time) + { + JUCE_MOUSE_EVENT_DBG ("drag") + comp.internalMouseDrag (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, pressure, orientation, rotation, tiltX, tiltY); + } + + void sendMouseUp (Component& comp, Point screenPos, Time time, ModifierKeys oldMods) + { + JUCE_MOUSE_EVENT_DBG ("up") + comp.internalMouseUp (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, oldMods, pressure, orientation, rotation, tiltX, tiltY); + } + + void sendMouseWheel (Component& comp, Point screenPos, Time time, const MouseWheelDetails& wheel) + { + JUCE_MOUSE_EVENT_DBG ("wheel") + comp.internalMouseWheel (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, wheel); + } + + void sendMagnifyGesture (Component& comp, Point screenPos, Time time, float amount) + { + JUCE_MOUSE_EVENT_DBG ("magnify") + comp.internalMagnifyGesture (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, amount); + } + + //============================================================================== + // (returns true if the button change caused a modal event loop) + bool setButtons (Point screenPos, Time time, ModifierKeys newButtonState) + { + if (buttonState == newButtonState) + return false; + + // (avoid sending a spurious mouse-drag when we receive a mouse-up) + if (! (isDragging() && ! newButtonState.isAnyMouseButtonDown())) + setScreenPos (screenPos, time, false); + + // (ignore secondary clicks when there's already a button down) + if (buttonState.isAnyMouseButtonDown() == newButtonState.isAnyMouseButtonDown()) + { + buttonState = newButtonState; + return false; + } + + auto lastCounter = mouseEventCounter; + + if (buttonState.isAnyMouseButtonDown()) + { + if (auto* current = getComponentUnderMouse()) + { + auto oldMods = getCurrentModifiers(); + buttonState = newButtonState; // must change this before calling sendMouseUp, in case it runs a modal loop + + sendMouseUp (*current, screenPos + unboundedMouseOffset, time, oldMods); + + if (lastCounter != mouseEventCounter) + return true; // if a modal loop happened, then newButtonState is no longer valid. + } + + enableUnboundedMouseMovement (false, false); + } + + buttonState = newButtonState; + + if (buttonState.isAnyMouseButtonDown()) + { + Desktop::getInstance().incrementMouseClickCounter(); + + if (auto* current = getComponentUnderMouse()) + { + registerMouseDown (screenPos, time, *current, buttonState, + inputType == MouseInputSource::InputSourceType::touch); + sendMouseDown (*current, screenPos, time); + } + } + + return lastCounter != mouseEventCounter; + } + + void setComponentUnderMouse (Component* newComponent, Point screenPos, Time time) + { + auto* current = getComponentUnderMouse(); + + if (newComponent != current) + { + WeakReference safeNewComp (newComponent); + auto originalButtonState = buttonState; + + if (current != nullptr) + { + WeakReference safeOldComp (current); + setButtons (screenPos, time, ModifierKeys()); + + if (auto oldComp = safeOldComp.get()) + { + componentUnderMouse = safeNewComp; + sendMouseExit (*oldComp, screenPos, time); + } + + buttonState = originalButtonState; + } + + componentUnderMouse = safeNewComp.get(); + current = safeNewComp.get(); + + if (current != nullptr) + sendMouseEnter (*current, screenPos, time); + + revealCursor (false); + setButtons (screenPos, time, originalButtonState); + } + } + + void setPeer (ComponentPeer& newPeer, Point screenPos, Time time) + { + if (&newPeer != lastPeer) + { + setComponentUnderMouse (nullptr, screenPos, time); + lastPeer = &newPeer; + setComponentUnderMouse (findComponentAt (screenPos), screenPos, time); + } + } + + void setScreenPos (Point newScreenPos, Time time, bool forceUpdate) + { + if (! isDragging()) + setComponentUnderMouse (findComponentAt (newScreenPos), newScreenPos, time); + + if (newScreenPos != lastScreenPos || forceUpdate) + { + cancelPendingUpdate(); + + if (newScreenPos != MouseInputSource::offscreenMousePos) + lastScreenPos = newScreenPos; + + if (auto* current = getComponentUnderMouse()) + { + if (isDragging()) + { + registerMouseDrag (newScreenPos); + sendMouseDrag (*current, newScreenPos + unboundedMouseOffset, time); + + if (isUnboundedMouseModeOn) + handleUnboundedDrag (*current); + } + else + { + sendMouseMove (*current, newScreenPos, time); + } + } + + revealCursor (false); + } + } + + //============================================================================== + void handleEvent (ComponentPeer& newPeer, Point positionWithinPeer, Time time, + const ModifierKeys newMods, float newPressure, float newOrientation, PenDetails pen) + { + lastTime = time; + + const bool pressureChanged = (pressure != newPressure); + pressure = newPressure; + + const bool orientationChanged = (orientation != newOrientation); + orientation = newOrientation; + + const bool rotationChanged = (rotation != pen.rotation); + rotation = pen.rotation; + + const bool tiltChanged = (tiltX != pen.tiltX || tiltY != pen.tiltY); + tiltX = pen.tiltX; + tiltY = pen.tiltY; + + const bool shouldUpdate = (pressureChanged || orientationChanged || rotationChanged || tiltChanged); + + ++mouseEventCounter; + + auto screenPos = newPeer.localToGlobal (positionWithinPeer); + + if (isDragging() && newMods.isAnyMouseButtonDown()) + { + setScreenPos (screenPos, time, shouldUpdate); + } + else + { + setPeer (newPeer, screenPos, time); + + if (auto* peer = getPeer()) + { + if (setButtons (screenPos, time, newMods)) + return; // some modal events have been dispatched, so the current event is now out-of-date + + peer = getPeer(); + + if (peer != nullptr) + setScreenPos (screenPos, time, shouldUpdate); + } + } + } + + Component* getTargetForGesture (ComponentPeer& peer, Point positionWithinPeer, + Time time, Point& screenPos) + { + lastTime = time; + ++mouseEventCounter; + + screenPos = peer.localToGlobal (positionWithinPeer); + setPeer (peer, screenPos, time); + setScreenPos (screenPos, time, false); + triggerFakeMove(); + + return getComponentUnderMouse(); + } + + void handleWheel (ComponentPeer& peer, Point positionWithinPeer, + Time time, const MouseWheelDetails& wheel) + { + Desktop::getInstance().incrementMouseWheelCounter(); + Point screenPos; + + // This will make sure that when the wheel spins in its inertial phase, any events + // continue to be sent to the last component that the mouse was over when it was being + // actively controlled by the user. This avoids confusion when scrolling through nested + // scrollable components. + if (lastNonInertialWheelTarget == nullptr || ! wheel.isInertial) + lastNonInertialWheelTarget = getTargetForGesture (peer, positionWithinPeer, time, screenPos); + else + screenPos = peer.localToGlobal (positionWithinPeer); + + if (auto target = lastNonInertialWheelTarget.get()) + sendMouseWheel (*target, screenPos, time, wheel); + } + + void handleMagnifyGesture (ComponentPeer& peer, Point positionWithinPeer, + Time time, const float scaleFactor) + { + Point screenPos; + + if (auto* current = getTargetForGesture (peer, positionWithinPeer, time, screenPos)) + sendMagnifyGesture (*current, screenPos, time, scaleFactor); + } + + //============================================================================== + Time getLastMouseDownTime() const noexcept { return mouseDowns[0].time; } + Point getLastMouseDownPosition() const noexcept { return ScalingHelpers::unscaledScreenPosToScaled (mouseDowns[0].position); } + + int getNumberOfMultipleClicks() const noexcept + { + int numClicks = 1; + + if (! isLongPressOrDrag()) + { + for (int i = 1; i < numElementsInArray (mouseDowns); ++i) + { + if (mouseDowns[0].canBePartOfMultipleClickWith (mouseDowns[i], MouseEvent::getDoubleClickTimeout() * jmin (i, 2))) + ++numClicks; + else + break; + } + } + + return numClicks; + } + + bool isLongPressOrDrag() const noexcept + { + return movedSignificantly || lastTime > mouseDowns[0].time + RelativeTime::milliseconds (300); + } + + bool hasMovedSignificantlySincePressed() const noexcept + { + return movedSignificantly; + } + + // Deprecated method + bool hasMouseMovedSignificantlySincePressed() const noexcept + { + return isLongPressOrDrag(); + } + + //============================================================================== + void triggerFakeMove() + { + triggerAsyncUpdate(); + } + + void handleAsyncUpdate() override + { + setScreenPos (lastScreenPos, jmax (lastTime, Time::getCurrentTime()), true); + } + + //============================================================================== + void enableUnboundedMouseMovement (bool enable, bool keepCursorVisibleUntilOffscreen) + { + enable = enable && isDragging(); + isCursorVisibleUntilOffscreen = keepCursorVisibleUntilOffscreen; + + if (enable != isUnboundedMouseModeOn) + { + if ((! enable) && ((! isCursorVisibleUntilOffscreen) || ! unboundedMouseOffset.isOrigin())) + { + // when released, return the mouse to within the component's bounds + if (auto* current = getComponentUnderMouse()) + setScreenPosition (current->getScreenBounds().toFloat() + .getConstrainedPoint (ScalingHelpers::unscaledScreenPosToScaled (lastScreenPos))); + } + + isUnboundedMouseModeOn = enable; + unboundedMouseOffset = {}; + + revealCursor (true); + } + } + + void handleUnboundedDrag (Component& current) + { + auto componentScreenBounds = ScalingHelpers::scaledScreenPosToUnscaled (current.getParentMonitorArea().reduced (2, 2).toFloat()); + + if (! componentScreenBounds.contains (lastScreenPos)) + { + auto componentCentre = current.getScreenBounds().toFloat().getCentre(); + unboundedMouseOffset += (lastScreenPos - ScalingHelpers::scaledScreenPosToUnscaled (componentCentre)); + setScreenPosition (componentCentre); + } + else if (isCursorVisibleUntilOffscreen + && (! unboundedMouseOffset.isOrigin()) + && componentScreenBounds.contains (lastScreenPos + unboundedMouseOffset)) + { + MouseInputSource::setRawMousePosition (lastScreenPos + unboundedMouseOffset); + unboundedMouseOffset = {}; + } + } + + //============================================================================== + void showMouseCursor (MouseCursor cursor, bool forcedUpdate) + { + if (isUnboundedMouseModeOn && ((! unboundedMouseOffset.isOrigin()) || ! isCursorVisibleUntilOffscreen)) + { + cursor = MouseCursor::NoCursor; + forcedUpdate = true; + } + + if (forcedUpdate || cursor.getHandle() != currentCursorHandle) + { + currentCursorHandle = cursor.getHandle(); + cursor.showInWindow (getPeer()); + } + } + + void hideCursor() + { + showMouseCursor (MouseCursor::NoCursor, true); + } + + void revealCursor (bool forcedUpdate) + { + MouseCursor mc (MouseCursor::NormalCursor); + + if (auto* current = getComponentUnderMouse()) + mc = current->getLookAndFeel().getMouseCursorFor (*current); + + showMouseCursor (mc, forcedUpdate); + } + + //============================================================================== + const int index; + const MouseInputSource::InputSourceType inputType; + Point lastScreenPos, unboundedMouseOffset; // NB: these are unscaled coords + ModifierKeys buttonState; + float pressure = 0; + float orientation = 0; + float rotation = 0; + float tiltX = 0; + float tiltY = 0; + + bool isUnboundedMouseModeOn = false, isCursorVisibleUntilOffscreen = false; + +private: + WeakReference componentUnderMouse, lastNonInertialWheelTarget; + ComponentPeer* lastPeer = nullptr; + + void* currentCursorHandle = nullptr; + int mouseEventCounter = 0; + + struct RecentMouseDown + { + RecentMouseDown() = default; + + Point position; + Time time; + ModifierKeys buttons; + uint32 peerID = 0; + bool isTouch = false; + + bool canBePartOfMultipleClickWith (const RecentMouseDown& other, int maxTimeBetweenMs) const noexcept + { + return time - other.time < RelativeTime::milliseconds (maxTimeBetweenMs) + && std::abs (position.x - other.position.x) < (float) getPositionToleranceForInputType() + && std::abs (position.y - other.position.y) < (float) getPositionToleranceForInputType() + && buttons == other.buttons + && peerID == other.peerID; + } + + int getPositionToleranceForInputType() const noexcept { return isTouch ? 25 : 8; } + }; + + RecentMouseDown mouseDowns[4]; + Time lastTime; + bool movedSignificantly = false; + + void registerMouseDown (Point screenPos, Time time, Component& component, + const ModifierKeys modifiers, bool isTouchSource) noexcept + { + for (int i = numElementsInArray (mouseDowns); --i > 0;) + mouseDowns[i] = mouseDowns[i - 1]; + + mouseDowns[0].position = screenPos; + mouseDowns[0].time = time; + mouseDowns[0].buttons = modifiers.withOnlyMouseButtons(); + mouseDowns[0].isTouch = isTouchSource; + + if (auto* peer = component.getPeer()) + mouseDowns[0].peerID = peer->getUniqueID(); + else + mouseDowns[0].peerID = 0; + + movedSignificantly = false; + lastNonInertialWheelTarget = nullptr; + } + + void registerMouseDrag (Point screenPos) noexcept + { + movedSignificantly = movedSignificantly || mouseDowns[0].position.getDistanceFrom (screenPos) >= 4; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MouseInputSourceInternal) +}; + +//============================================================================== +MouseInputSource::MouseInputSource (MouseInputSourceInternal* s) noexcept : pimpl (s) {} +MouseInputSource::MouseInputSource (const MouseInputSource& other) noexcept : pimpl (other.pimpl) {} +MouseInputSource::~MouseInputSource() noexcept {} + +MouseInputSource& MouseInputSource::operator= (const MouseInputSource& other) noexcept +{ + pimpl = other.pimpl; + return *this; +} + +MouseInputSource::InputSourceType MouseInputSource::getType() const noexcept { return pimpl->inputType; } +bool MouseInputSource::isMouse() const noexcept { return (getType() == MouseInputSource::InputSourceType::mouse); } +bool MouseInputSource::isTouch() const noexcept { return (getType() == MouseInputSource::InputSourceType::touch); } +bool MouseInputSource::isPen() const noexcept { return (getType() == MouseInputSource::InputSourceType::pen); } +bool MouseInputSource::canHover() const noexcept { return ! isTouch(); } +bool MouseInputSource::hasMouseWheel() const noexcept { return ! isTouch(); } +int MouseInputSource::getIndex() const noexcept { return pimpl->index; } +bool MouseInputSource::isDragging() const noexcept { return pimpl->isDragging(); } +Point MouseInputSource::getScreenPosition() const noexcept { return pimpl->getScreenPosition(); } +Point MouseInputSource::getRawScreenPosition() const noexcept { return pimpl->getRawScreenPosition(); } +ModifierKeys MouseInputSource::getCurrentModifiers() const noexcept { return pimpl->getCurrentModifiers(); } +float MouseInputSource::getCurrentPressure() const noexcept { return pimpl->pressure; } +bool MouseInputSource::isPressureValid() const noexcept { return pimpl->isPressureValid(); } +float MouseInputSource::getCurrentOrientation() const noexcept { return pimpl->orientation; } +bool MouseInputSource::isOrientationValid() const noexcept { return pimpl->isOrientationValid(); } +float MouseInputSource::getCurrentRotation() const noexcept { return pimpl->rotation; } +bool MouseInputSource::isRotationValid() const noexcept { return pimpl->isRotationValid(); } +float MouseInputSource::getCurrentTilt (bool tiltX) const noexcept { return tiltX ? pimpl->tiltX : pimpl->tiltY; } +bool MouseInputSource::isTiltValid (bool isX) const noexcept { return pimpl->isTiltValid (isX); } +Component* MouseInputSource::getComponentUnderMouse() const { return pimpl->getComponentUnderMouse(); } +void MouseInputSource::triggerFakeMove() const { pimpl->triggerFakeMove(); } +int MouseInputSource::getNumberOfMultipleClicks() const noexcept { return pimpl->getNumberOfMultipleClicks(); } +Time MouseInputSource::getLastMouseDownTime() const noexcept { return pimpl->getLastMouseDownTime(); } +Point MouseInputSource::getLastMouseDownPosition() const noexcept { return pimpl->getLastMouseDownPosition(); } +bool MouseInputSource::isLongPressOrDrag() const noexcept { return pimpl->isLongPressOrDrag(); } +bool MouseInputSource::hasMovedSignificantlySincePressed() const noexcept { return pimpl->hasMovedSignificantlySincePressed(); } +bool MouseInputSource::canDoUnboundedMovement() const noexcept { return ! isTouch(); } +void MouseInputSource::enableUnboundedMouseMovement (bool isEnabled, bool keepCursorVisibleUntilOffscreen) const + { pimpl->enableUnboundedMouseMovement (isEnabled, keepCursorVisibleUntilOffscreen); } +bool MouseInputSource::isUnboundedMouseMovementEnabled() const { return pimpl->isUnboundedMouseModeOn; } +bool MouseInputSource::hasMouseCursor() const noexcept { return ! isTouch(); } +void MouseInputSource::showMouseCursor (const MouseCursor& cursor) { pimpl->showMouseCursor (cursor, false); } +void MouseInputSource::hideCursor() { pimpl->hideCursor(); } +void MouseInputSource::revealCursor() { pimpl->revealCursor (false); } +void MouseInputSource::forceMouseCursorUpdate() { pimpl->revealCursor (true); } +void MouseInputSource::setScreenPosition (Point p) { pimpl->setScreenPosition (p); } + +void MouseInputSource::handleEvent (ComponentPeer& peer, Point pos, int64 time, ModifierKeys mods, + float pressure, float orientation, const PenDetails& penDetails) +{ + pimpl->handleEvent (peer, pos, Time (time), mods.withOnlyMouseButtons(), pressure, orientation, penDetails); +} + +void MouseInputSource::handleWheel (ComponentPeer& peer, Point pos, int64 time, const MouseWheelDetails& wheel) +{ + pimpl->handleWheel (peer, pos, Time (time), wheel); +} + +void MouseInputSource::handleMagnifyGesture (ComponentPeer& peer, Point pos, int64 time, float scaleFactor) +{ + pimpl->handleMagnifyGesture (peer, pos, Time (time), scaleFactor); +} + +const float MouseInputSource::invalidPressure = 0.0f; +const float MouseInputSource::invalidOrientation = 0.0f; +const float MouseInputSource::invalidRotation = 0.0f; + +const float MouseInputSource::invalidTiltX = 0.0f; +const float MouseInputSource::invalidTiltY = 0.0f; + +const Point MouseInputSource::offscreenMousePos { -10.0f, -10.0f }; + +// Deprecated method +bool MouseInputSource::hasMouseMovedSignificantlySincePressed() const noexcept { return pimpl->hasMouseMovedSignificantlySincePressed(); } + +//============================================================================== +struct MouseInputSource::SourceList : public Timer +{ + SourceList() + { + #if JUCE_ANDROID || JUCE_IOS + auto mainMouseInputType = MouseInputSource::InputSourceType::touch; + #else + auto mainMouseInputType = MouseInputSource::InputSourceType::mouse; + #endif + + addSource (0, mainMouseInputType); + } + + bool addSource(); + bool canUseTouch(); + + MouseInputSource* addSource (int index, MouseInputSource::InputSourceType type) + { + auto* s = new MouseInputSourceInternal (index, type); + sources.add (s); + sourceArray.add (MouseInputSource (s)); + + return &sourceArray.getReference (sourceArray.size() - 1); + } + + MouseInputSource* getMouseSource (int index) noexcept + { + return isPositiveAndBelow (index, sourceArray.size()) ? &sourceArray.getReference (index) + : nullptr; + } + + MouseInputSource* getOrCreateMouseInputSource (MouseInputSource::InputSourceType type, int touchIndex = 0) + { + if (type == MouseInputSource::InputSourceType::mouse || type == MouseInputSource::InputSourceType::pen) + { + for (auto& m : sourceArray) + if (type == m.getType()) + return &m; + + addSource (0, type); + } + else if (type == MouseInputSource::InputSourceType::touch) + { + jassert (touchIndex >= 0 && touchIndex < 100); // sanity-check on number of fingers + + for (auto& m : sourceArray) + if (type == m.getType() && touchIndex == m.getIndex()) + return &m; + + if (canUseTouch()) + return addSource (touchIndex, type); + } + + return nullptr; + } + + int getNumDraggingMouseSources() const noexcept + { + int num = 0; + + for (auto* s : sources) + if (s->isDragging()) + ++num; + + return num; + } + + MouseInputSource* getDraggingMouseSource (int index) noexcept + { + int num = 0; + + for (auto& s : sourceArray) + { + if (s.isDragging()) + { + if (index == num) + return &s; + + ++num; + } + } + + return nullptr; + } + + void beginDragAutoRepeat (int interval) + { + if (interval > 0) + { + if (getTimerInterval() != interval) + startTimer (interval); + } + else + { + stopTimer(); + } + } + + void timerCallback() override + { + bool anyDragging = false; + + for (auto* s : sources) + { + // NB: when doing auto-repeat, we need to force an update of the current position and button state, + // because on some OSes the queue can get overloaded with messages so that mouse-events don't get through.. + if (s->isDragging() && ComponentPeer::getCurrentModifiersRealtime().isAnyMouseButtonDown()) + { + s->lastScreenPos = s->getRawScreenPosition(); + s->triggerFakeMove(); + anyDragging = true; + } + } + + if (! anyDragging) + stopTimer(); + } + + OwnedArray sources; + Array sourceArray; +}; + +} // namespace juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp.rej b/libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp.rej new file mode 100644 index 00000000..d27946db --- /dev/null +++ b/libs/juce-current/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp.rej @@ -0,0 +1,11 @@ +--- modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp ++++ modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp +@@ -61,7 +61,7 @@ class MouseInputSourceInternal : private AsyncUpdater + { + if (auto* peer = comp.getPeer()) + { +- pos = peer->globalToLocal (pos); ++ pos = peer->globalToLocal (pos) * comp.getTotalPixelScaling(); + auto& peerComp = peer->getComponent(); + return comp.getLocalPoint (&peerComp, ScalingHelpers::unscaledScreenPosToScaled (peerComp, pos)); + } diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp index 8d7febd4..7ec8fbb0 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp @@ -474,7 +474,7 @@ bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) if (DragHelpers::isSuitableTarget (info, newTarget)) { dragAndDropTargetComponent = newTarget; - auto pos = newTarget->getLocalPoint (&component, info.position); + auto pos = newTarget->getLocalPoint (&component, info.position * newTarget->getTotalPixelScaling()); if (DragHelpers::isFileDrag (info)) dynamic_cast (newTarget)->fileDragEnter (info.files, pos.x, pos.y); @@ -491,7 +491,7 @@ bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) if (! DragHelpers::isSuitableTarget (info, newTarget)) return false; - auto pos = newTarget->getLocalPoint (&component, info.position); + auto pos = newTarget->getLocalPoint (&component, info.position * newTarget->getTotalPixelScaling()); if (DragHelpers::isFileDrag (info)) dynamic_cast (newTarget)->fileDragMove (info.files, pos.x, pos.y); diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp.orig b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp.orig new file mode 100644 index 00000000..8d7febd4 --- /dev/null +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp.orig @@ -0,0 +1,593 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 6 End-User License + Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). + + End User License Agreement: www.juce.com/juce-6-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +static uint32 lastUniquePeerID = 1; + +//============================================================================== +ComponentPeer::ComponentPeer (Component& comp, int flags) + : component (comp), + styleFlags (flags), + uniqueID (lastUniquePeerID += 2) // increment by 2 so that this can never hit 0 +{ + Desktop::getInstance().peers.add (this); +} + +ComponentPeer::~ComponentPeer() +{ + auto& desktop = Desktop::getInstance(); + desktop.peers.removeFirstMatchingValue (this); + desktop.triggerFocusCallback(); +} + +//============================================================================== +int ComponentPeer::getNumPeers() noexcept +{ + return Desktop::getInstance().peers.size(); +} + +ComponentPeer* ComponentPeer::getPeer (const int index) noexcept +{ + return Desktop::getInstance().peers [index]; +} + +ComponentPeer* ComponentPeer::getPeerFor (const Component* const component) noexcept +{ + for (auto* peer : Desktop::getInstance().peers) + if (&(peer->getComponent()) == component) + return peer; + + return nullptr; +} + +bool ComponentPeer::isValidPeer (const ComponentPeer* const peer) noexcept +{ + return Desktop::getInstance().peers.contains (const_cast (peer)); +} + +void ComponentPeer::updateBounds() +{ + setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, component.getBoundsInParent()), false); +} + +bool ComponentPeer::isKioskMode() const +{ + return Desktop::getInstance().getKioskModeComponent() == &component; +} + +//============================================================================== +void ComponentPeer::handleMouseEvent (MouseInputSource::InputSourceType type, Point pos, ModifierKeys newMods, + float newPressure, float newOrientation, int64 time, PenDetails pen, int touchIndex) +{ + if (auto* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (type, touchIndex)) + MouseInputSource (*mouse).handleEvent (*this, pos, time, newMods, newPressure, newOrientation, pen); +} + +void ComponentPeer::handleMouseWheel (MouseInputSource::InputSourceType type, Point pos, int64 time, const MouseWheelDetails& wheel, int touchIndex) +{ + if (auto* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (type, touchIndex)) + MouseInputSource (*mouse).handleWheel (*this, pos, time, wheel); +} + +void ComponentPeer::handleMagnifyGesture (MouseInputSource::InputSourceType type, Point pos, int64 time, float scaleFactor, int touchIndex) +{ + if (auto* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (type, touchIndex)) + MouseInputSource (*mouse).handleMagnifyGesture (*this, pos, time, scaleFactor); +} + +//============================================================================== +void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo) +{ + Graphics g (contextToPaintTo); + + if (component.isTransformed()) + g.addTransform (component.getTransform()); + + auto peerBounds = getBounds(); + auto componentBounds = component.getLocalBounds(); + + if (component.isTransformed()) + componentBounds = componentBounds.transformedBy (component.getTransform()); + + if (peerBounds.getWidth() != componentBounds.getWidth() || peerBounds.getHeight() != componentBounds.getHeight()) + // Tweak the scaling so that the component's integer size exactly aligns with the peer's scaled size + g.addTransform (AffineTransform::scale ((float) peerBounds.getWidth() / (float) componentBounds.getWidth(), + (float) peerBounds.getHeight() / (float) componentBounds.getHeight())); + + #if JUCE_ENABLE_REPAINT_DEBUGGING + #ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE + if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE) + #endif + { + g.saveState(); + } + #endif + + JUCE_TRY + { + component.paintEntireComponent (g, true); + } + JUCE_CATCH_EXCEPTION + + #if JUCE_ENABLE_REPAINT_DEBUGGING + #ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE + if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE) + #endif + { + // enabling this code will fill all areas that get repainted with a colour overlay, to show + // clearly when things are being repainted. + g.restoreState(); + + static Random rng; + + g.fillAll (Colour ((uint8) rng.nextInt (255), + (uint8) rng.nextInt (255), + (uint8) rng.nextInt (255), + (uint8) 0x50)); + } + #endif + + /** If this fails, it's probably be because your CPU floating-point precision mode has + been set to low.. This setting is sometimes changed by things like Direct3D, and can + mess up a lot of the calculations that the library needs to do. + */ + jassert (roundToInt (10.1f) == 10); +} + +Component* ComponentPeer::getTargetForKeyPress() +{ + auto* c = Component::getCurrentlyFocusedComponent(); + + if (c == nullptr) + c = &component; + + if (c->isCurrentlyBlockedByAnotherModalComponent()) + if (auto* currentModalComp = Component::getCurrentlyModalComponent()) + c = currentModalComp; + + return c; +} + +bool ComponentPeer::handleKeyPress (const int keyCode, const juce_wchar textCharacter) +{ + return handleKeyPress (KeyPress (keyCode, + ModifierKeys::currentModifiers.withoutMouseButtons(), + textCharacter)); +} + + +bool ComponentPeer::handleKeyPress (const KeyPress& keyInfo) +{ + bool keyWasUsed = false; + + for (auto* target = getTargetForKeyPress(); target != nullptr; target = target->getParentComponent()) + { + const WeakReference deletionChecker (target); + + if (auto* keyListeners = target->keyListeners.get()) + { + for (int i = keyListeners->size(); --i >= 0;) + { + keyWasUsed = keyListeners->getUnchecked(i)->keyPressed (keyInfo, target); + + if (keyWasUsed || deletionChecker == nullptr) + return keyWasUsed; + + i = jmin (i, keyListeners->size()); + } + } + + keyWasUsed = target->keyPressed (keyInfo); + + if (keyWasUsed || deletionChecker == nullptr) + break; + + if (auto* currentlyFocused = Component::getCurrentlyFocusedComponent()) + { + const bool isTab = (keyInfo == KeyPress::tabKey); + const bool isShiftTab = (keyInfo == KeyPress (KeyPress::tabKey, ModifierKeys::shiftModifier, 0)); + + if (isTab || isShiftTab) + { + currentlyFocused->moveKeyboardFocusToSibling (isTab); + keyWasUsed = (currentlyFocused != Component::getCurrentlyFocusedComponent()); + + if (keyWasUsed || deletionChecker == nullptr) + break; + } + } + } + + return keyWasUsed; +} + +bool ComponentPeer::handleKeyUpOrDown (const bool isKeyDown) +{ + bool keyWasUsed = false; + + for (auto* target = getTargetForKeyPress(); target != nullptr; target = target->getParentComponent()) + { + const WeakReference deletionChecker (target); + + keyWasUsed = target->keyStateChanged (isKeyDown); + + if (keyWasUsed || deletionChecker == nullptr) + break; + + if (auto* keyListeners = target->keyListeners.get()) + { + for (int i = keyListeners->size(); --i >= 0;) + { + keyWasUsed = keyListeners->getUnchecked(i)->keyStateChanged (isKeyDown, target); + + if (keyWasUsed || deletionChecker == nullptr) + return keyWasUsed; + + i = jmin (i, keyListeners->size()); + } + } + } + + return keyWasUsed; +} + +void ComponentPeer::handleModifierKeysChange() +{ + auto* target = Desktop::getInstance().getMainMouseSource().getComponentUnderMouse(); + + if (target == nullptr) + target = Component::getCurrentlyFocusedComponent(); + + if (target == nullptr) + target = &component; + + target->internalModifierKeysChanged(); +} + +TextInputTarget* ComponentPeer::findCurrentTextInputTarget() +{ + auto* c = Component::getCurrentlyFocusedComponent(); + + if (c == &component || component.isParentOf (c)) + if (auto* ti = dynamic_cast (c)) + if (ti->isTextInputActive()) + return ti; + + return nullptr; +} + +void ComponentPeer::dismissPendingTextInput() {} + +//============================================================================== +void ComponentPeer::handleBroughtToFront() +{ + component.internalBroughtToFront(); +} + +void ComponentPeer::setConstrainer (ComponentBoundsConstrainer* const newConstrainer) noexcept +{ + constrainer = newConstrainer; +} + +void ComponentPeer::handleMovedOrResized() +{ + const bool nowMinimised = isMinimised(); + + if (component.flags.hasHeavyweightPeerFlag && ! nowMinimised) + { + const WeakReference deletionChecker (&component); + + auto newBounds = Component::ComponentHelpers::rawPeerPositionToLocal (component, getBounds()); + auto oldBounds = component.getBounds(); + + const bool wasMoved = (oldBounds.getPosition() != newBounds.getPosition()); + const bool wasResized = (oldBounds.getWidth() != newBounds.getWidth() || oldBounds.getHeight() != newBounds.getHeight()); + + if (wasMoved || wasResized) + { + component.boundsRelativeToParent = newBounds; + + if (wasResized) + component.repaint(); + + component.sendMovedResizedMessages (wasMoved, wasResized); + + if (deletionChecker == nullptr) + return; + } + } + + if (isWindowMinimised != nowMinimised) + { + isWindowMinimised = nowMinimised; + component.minimisationStateChanged (nowMinimised); + component.sendVisibilityChangeMessage(); + } + + if (! isFullScreen()) + lastNonFullscreenBounds = component.getBounds(); +} + +void ComponentPeer::handleFocusGain() +{ + if (component.isParentOf (lastFocusedComponent) + && lastFocusedComponent->isShowing() + && lastFocusedComponent->getWantsKeyboardFocus()) + { + Component::currentlyFocusedComponent = lastFocusedComponent; + Desktop::getInstance().triggerFocusCallback(); + lastFocusedComponent->internalFocusGain (Component::focusChangedDirectly); + } + else + { + if (! component.isCurrentlyBlockedByAnotherModalComponent()) + component.grabKeyboardFocus(); + else + ModalComponentManager::getInstance()->bringModalComponentsToFront(); + } +} + +void ComponentPeer::handleFocusLoss() +{ + if (component.hasKeyboardFocus (true)) + { + lastFocusedComponent = Component::currentlyFocusedComponent; + + if (lastFocusedComponent != nullptr) + { + Component::currentlyFocusedComponent = nullptr; + Desktop::getInstance().triggerFocusCallback(); + lastFocusedComponent->internalFocusLoss (Component::focusChangedByMouseClick); + } + } +} + +Component* ComponentPeer::getLastFocusedSubcomponent() const noexcept +{ + return (component.isParentOf (lastFocusedComponent) && lastFocusedComponent->isShowing()) + ? static_cast (lastFocusedComponent) + : &component; +} + +void ComponentPeer::handleScreenSizeChange() +{ + component.parentSizeChanged(); + handleMovedOrResized(); +} + +void ComponentPeer::setNonFullScreenBounds (const Rectangle& newBounds) noexcept +{ + lastNonFullscreenBounds = newBounds; +} + +const Rectangle& ComponentPeer::getNonFullScreenBounds() const noexcept +{ + return lastNonFullscreenBounds; +} + +Point ComponentPeer::localToGlobal (Point p) { return localToGlobal (p.toFloat()).roundToInt(); } +Point ComponentPeer::globalToLocal (Point p) { return globalToLocal (p.toFloat()).roundToInt(); } + +Rectangle ComponentPeer::localToGlobal (const Rectangle& relativePosition) +{ + return relativePosition.withPosition (localToGlobal (relativePosition.getPosition())); +} + +Rectangle ComponentPeer::globalToLocal (const Rectangle& screenPosition) +{ + return screenPosition.withPosition (globalToLocal (screenPosition.getPosition())); +} + +Rectangle ComponentPeer::localToGlobal (const Rectangle& relativePosition) +{ + return relativePosition.withPosition (localToGlobal (relativePosition.getPosition())); +} + +Rectangle ComponentPeer::globalToLocal (const Rectangle& screenPosition) +{ + return screenPosition.withPosition (globalToLocal (screenPosition.getPosition())); +} + +Rectangle ComponentPeer::getAreaCoveredBy (Component& subComponent) const +{ + return ScalingHelpers::scaledScreenPosToUnscaled + (component, component.getLocalArea (&subComponent, subComponent.getLocalBounds())); +} + +//============================================================================== +namespace DragHelpers +{ + static bool isFileDrag (const ComponentPeer::DragInfo& info) + { + return ! info.files.isEmpty(); + } + + static bool isSuitableTarget (const ComponentPeer::DragInfo& info, Component* target) + { + return isFileDrag (info) ? dynamic_cast (target) != nullptr + : dynamic_cast (target) != nullptr; + } + + static bool isInterested (const ComponentPeer::DragInfo& info, Component* target) + { + return isFileDrag (info) ? dynamic_cast (target)->isInterestedInFileDrag (info.files) + : dynamic_cast (target)->isInterestedInTextDrag (info.text); + } + + static Component* findDragAndDropTarget (Component* c, const ComponentPeer::DragInfo& info, Component* lastOne) + { + for (; c != nullptr; c = c->getParentComponent()) + if (isSuitableTarget (info, c) && (c == lastOne || isInterested (info, c))) + return c; + + return nullptr; + } +} + +bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) +{ + auto* compUnderMouse = component.getComponentAt (info.position); + auto* lastTarget = dragAndDropTargetComponent.get(); + Component* newTarget = nullptr; + + if (compUnderMouse != lastDragAndDropCompUnderMouse) + { + lastDragAndDropCompUnderMouse = compUnderMouse; + newTarget = DragHelpers::findDragAndDropTarget (compUnderMouse, info, lastTarget); + + if (newTarget != lastTarget) + { + if (lastTarget != nullptr) + { + if (DragHelpers::isFileDrag (info)) + dynamic_cast (lastTarget)->fileDragExit (info.files); + else + dynamic_cast (lastTarget)->textDragExit (info.text); + } + + dragAndDropTargetComponent = nullptr; + + if (DragHelpers::isSuitableTarget (info, newTarget)) + { + dragAndDropTargetComponent = newTarget; + auto pos = newTarget->getLocalPoint (&component, info.position); + + if (DragHelpers::isFileDrag (info)) + dynamic_cast (newTarget)->fileDragEnter (info.files, pos.x, pos.y); + else + dynamic_cast (newTarget)->textDragEnter (info.text, pos.x, pos.y); + } + } + } + else + { + newTarget = lastTarget; + } + + if (! DragHelpers::isSuitableTarget (info, newTarget)) + return false; + + auto pos = newTarget->getLocalPoint (&component, info.position); + + if (DragHelpers::isFileDrag (info)) + dynamic_cast (newTarget)->fileDragMove (info.files, pos.x, pos.y); + else + dynamic_cast (newTarget)->textDragMove (info.text, pos.x, pos.y); + + return true; +} + +bool ComponentPeer::handleDragExit (const ComponentPeer::DragInfo& info) +{ + DragInfo info2 (info); + info2.position.setXY (-1, -1); + const bool used = handleDragMove (info2); + + jassert (dragAndDropTargetComponent == nullptr); + lastDragAndDropCompUnderMouse = nullptr; + return used; +} + +bool ComponentPeer::handleDragDrop (const ComponentPeer::DragInfo& info) +{ + handleDragMove (info); + + if (WeakReference targetComp = dragAndDropTargetComponent) + { + dragAndDropTargetComponent = nullptr; + lastDragAndDropCompUnderMouse = nullptr; + + if (DragHelpers::isSuitableTarget (info, targetComp)) + { + if (targetComp->isCurrentlyBlockedByAnotherModalComponent()) + { + targetComp->internalModalInputAttempt(); + + if (targetComp->isCurrentlyBlockedByAnotherModalComponent()) + return true; + } + + ComponentPeer::DragInfo infoCopy (info); + infoCopy.position = targetComp->getLocalPoint (&component, info.position); + + // We'll use an async message to deliver the drop, because if the target decides + // to run a modal loop, it can gum-up the operating system.. + MessageManager::callAsync ([=] + { + if (auto* c = targetComp.get()) + { + if (DragHelpers::isFileDrag (info)) + dynamic_cast (c)->filesDropped (infoCopy.files, infoCopy.position.x, infoCopy.position.y); + else + dynamic_cast (c)->textDropped (infoCopy.text, infoCopy.position.x, infoCopy.position.y); + } + }); + + return true; + } + } + + return false; +} + +//============================================================================== +void ComponentPeer::handleUserClosingWindow() +{ + component.userTriedToCloseWindow(); +} + +bool ComponentPeer::setDocumentEditedStatus (bool) +{ + return false; +} + +void ComponentPeer::setRepresentedFile (const File&) +{ +} + +//============================================================================== +int ComponentPeer::getCurrentRenderingEngine() const { return 0; } +void ComponentPeer::setCurrentRenderingEngine (int index) { jassert (index == 0); ignoreUnused (index); } + +//============================================================================== +std::function ComponentPeer::getNativeRealtimeModifiers = nullptr; + +ModifierKeys ComponentPeer::getCurrentModifiersRealtime() noexcept +{ + if (getNativeRealtimeModifiers != nullptr) + return getNativeRealtimeModifiers(); + + return ModifierKeys::currentModifiers; +} + +//============================================================================== +void ComponentPeer::forceDisplayUpdate() +{ + Desktop::getInstance().displays->refresh(); +} + +} // namespace juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp.rej b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp.rej new file mode 100644 index 00000000..4e8c4c4b --- /dev/null +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp.rej @@ -0,0 +1,20 @@ +--- modules/juce_gui_basics/windows/juce_ComponentPeer.cpp ++++ modules/juce_gui_basics/windows/juce_ComponentPeer.cpp +@@ -474,7 +474,7 @@ bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) + if (DragHelpers::isSuitableTarget (info, newTarget)) + { + dragAndDropTargetComponent = newTarget; +- auto pos = newTarget->getLocalPoint (&component, info.position); ++ auto pos = newTarget->getLocalPoint (&component, info.position * newTarget->getTotalPixelScaling()); + + if (DragHelpers::isFileDrag (info)) + dynamic_cast (newTarget)->fileDragEnter (info.files, pos.x, pos.y); +@@ -491,7 +491,7 @@ bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) + if (! DragHelpers::isSuitableTarget (info, newTarget)) + return false; + +- auto pos = newTarget->getLocalPoint (&component, info.position); ++ auto pos = newTarget->getLocalPoint (&component, info.position * newTarget->getTotalPixelScaling()); + + if (DragHelpers::isFileDrag (info)) + dynamic_cast (newTarget)->fileDragMove (info.files, pos.x, pos.y); diff --git a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h index e7eab9db..d7039b14 100644 --- a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h +++ b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h @@ -83,7 +83,13 @@ namespace juce USE_FUNCTION (glCheckFramebufferStatus, GLenum, (GLenum p1), (p1))\ USE_FUNCTION (glFramebufferTexture2D, void, (GLenum p1, GLenum p2, GLenum p3, GLuint p4, GLint p5), (p1, p2, p3, p4, p5))\ USE_FUNCTION (glFramebufferRenderbuffer, void, (GLenum p1, GLenum p2, GLenum p3, GLuint p4), (p1, p2, p3, p4))\ - USE_FUNCTION (glGetFramebufferAttachmentParameteriv, void, (GLenum p1, GLenum p2, GLenum p3, GLint* p4), (p1, p2, p3, p4)) + USE_FUNCTION (glGetFramebufferAttachmentParameteriv, void, (GLenum p1, GLenum p2, GLenum p3, GLint* p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glTransformFeedbackVaryings, void, (GLuint p1, GLsizei p2, const char **p3, GLenum p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glBeginTransformFeedback, void, (GLenum p1), (p1))\ + USE_FUNCTION (glEndTransformFeedback, void, (), ())\ + USE_FUNCTION (glBindBufferBase, void, (GLenum p1, GLuint p2, GLuint p3), (p1, p2, p3))\ + USE_FUNCTION (glMapBufferRange, void*, (GLenum p1, GLintptr p2, GLsizeiptr p3, GLbitfield p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glUnmapBuffer, GLboolean, (GLenum p1), (p1)); /** @internal This macro contains a list of GL extension functions that need to be dynamically loaded on Windows/Linux. @see OpenGLExtensionFunctions diff --git a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h.orig b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h.orig new file mode 100644 index 00000000..e7eab9db --- /dev/null +++ b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h.orig @@ -0,0 +1,160 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 6 End-User License + Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). + + End User License Agreement: www.juce.com/juce-6-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +/** @internal This macro contains a list of GL extension functions that need to be dynamically loaded on Windows/Linux. + @see OpenGLExtensionFunctions +*/ +#define JUCE_GL_BASE_FUNCTIONS(USE_FUNCTION) \ + USE_FUNCTION (glActiveTexture, void, (GLenum p1), (p1))\ + USE_FUNCTION (glBindBuffer, void, (GLenum p1, GLuint p2), (p1, p2))\ + USE_FUNCTION (glDeleteBuffers, void, (GLsizei p1, const GLuint* p2), (p1, p2))\ + USE_FUNCTION (glGenBuffers, void, (GLsizei p1, GLuint* p2), (p1, p2))\ + USE_FUNCTION (glBufferData, void, (GLenum p1, GLsizeiptr p2, const GLvoid* p3, GLenum p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glBufferSubData, void, (GLenum p1, GLintptr p2, GLsizeiptr p3, const GLvoid* p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glCreateProgram, GLuint, (), ())\ + USE_FUNCTION (glDeleteProgram, void, (GLuint p1), (p1))\ + USE_FUNCTION (glCreateShader, GLuint, (GLenum p1), (p1))\ + USE_FUNCTION (glDeleteShader, void, (GLuint p1), (p1))\ + USE_FUNCTION (glShaderSource, void, (GLuint p1, GLsizei p2, const GLchar** p3, const GLint* p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glCompileShader, void, (GLuint p1), (p1))\ + USE_FUNCTION (glAttachShader, void, (GLuint p1, GLuint p2), (p1, p2))\ + USE_FUNCTION (glLinkProgram, void, (GLuint p1), (p1))\ + USE_FUNCTION (glUseProgram, void, (GLuint p1), (p1))\ + USE_FUNCTION (glGetShaderiv, void, (GLuint p1, GLenum p2, GLint* p3), (p1, p2, p3))\ + USE_FUNCTION (glGetShaderInfoLog, void, (GLuint p1, GLsizei p2, GLsizei* p3, GLchar* p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glGetProgramInfoLog, void, (GLuint p1, GLsizei p2, GLsizei* p3, GLchar* p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glGetProgramiv, void, (GLuint p1, GLenum p2, GLint* p3), (p1, p2, p3))\ + USE_FUNCTION (glGetUniformLocation, GLint, (GLuint p1, const GLchar* p2), (p1, p2))\ + USE_FUNCTION (glGetAttribLocation, GLint, (GLuint p1, const GLchar* p2), (p1, p2))\ + USE_FUNCTION (glVertexAttribPointer, void, (GLuint p1, GLint p2, GLenum p3, GLboolean p4, GLsizei p5, const GLvoid* p6), (p1, p2, p3, p4, p5, p6))\ + USE_FUNCTION (glEnableVertexAttribArray, void, (GLuint p1), (p1))\ + USE_FUNCTION (glDisableVertexAttribArray, void, (GLuint p1), (p1))\ + USE_FUNCTION (glUniform1f, void, (GLint p1, GLfloat p2), (p1, p2))\ + USE_FUNCTION (glUniform1i, void, (GLint p1, GLint p2), (p1, p2))\ + USE_FUNCTION (glUniform2f, void, (GLint p1, GLfloat p2, GLfloat p3), (p1, p2, p3))\ + USE_FUNCTION (glUniform3f, void, (GLint p1, GLfloat p2, GLfloat p3, GLfloat p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glUniform4f, void, (GLint p1, GLfloat p2, GLfloat p3, GLfloat p4, GLfloat p5), (p1, p2, p3, p4, p5))\ + USE_FUNCTION (glUniform4i, void, (GLint p1, GLint p2, GLint p3, GLint p4, GLint p5), (p1, p2, p3, p4, p5))\ + USE_FUNCTION (glUniform1fv, void, (GLint p1, GLsizei p2, const GLfloat* p3), (p1, p2, p3))\ + USE_FUNCTION (glUniformMatrix2fv, void, (GLint p1, GLsizei p2, GLboolean p3, const GLfloat* p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glUniformMatrix3fv, void, (GLint p1, GLsizei p2, GLboolean p3, const GLfloat* p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glUniformMatrix4fv, void, (GLint p1, GLsizei p2, GLboolean p3, const GLfloat* p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glBindAttribLocation, void, (GLuint p1, GLuint p2, const GLchar* p3), (p1, p2, p3)) + +/** @internal This macro contains a list of GL extension functions that need to be dynamically loaded on Windows/Linux. + @see OpenGLExtensionFunctions +*/ +#define JUCE_GL_EXTENSION_FUNCTIONS(USE_FUNCTION) \ + USE_FUNCTION (glIsRenderbuffer, GLboolean, (GLuint p1), (p1))\ + USE_FUNCTION (glBindRenderbuffer, void, (GLenum p1, GLuint p2), (p1, p2))\ + USE_FUNCTION (glDeleteRenderbuffers, void, (GLsizei p1, const GLuint* p2), (p1, p2))\ + USE_FUNCTION (glGenRenderbuffers, void, (GLsizei p1, GLuint* p2), (p1, p2))\ + USE_FUNCTION (glRenderbufferStorage, void, (GLenum p1, GLenum p2, GLsizei p3, GLsizei p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glGetRenderbufferParameteriv, void, (GLenum p1, GLenum p2, GLint* p3), (p1, p2, p3))\ + USE_FUNCTION (glIsFramebuffer, GLboolean, (GLuint p1), (p1))\ + USE_FUNCTION (glBindFramebuffer, void, (GLenum p1, GLuint p2), (p1, p2))\ + USE_FUNCTION (glDeleteFramebuffers, void, (GLsizei p1, const GLuint* p2), (p1, p2))\ + USE_FUNCTION (glGenFramebuffers, void, (GLsizei p1, GLuint* p2), (p1, p2))\ + USE_FUNCTION (glCheckFramebufferStatus, GLenum, (GLenum p1), (p1))\ + USE_FUNCTION (glFramebufferTexture2D, void, (GLenum p1, GLenum p2, GLenum p3, GLuint p4, GLint p5), (p1, p2, p3, p4, p5))\ + USE_FUNCTION (glFramebufferRenderbuffer, void, (GLenum p1, GLenum p2, GLenum p3, GLuint p4), (p1, p2, p3, p4))\ + USE_FUNCTION (glGetFramebufferAttachmentParameteriv, void, (GLenum p1, GLenum p2, GLenum p3, GLint* p4), (p1, p2, p3, p4)) + +/** @internal This macro contains a list of GL extension functions that need to be dynamically loaded on Windows/Linux. + @see OpenGLExtensionFunctions +*/ +#define JUCE_GL_VERTEXBUFFER_FUNCTIONS(USE_FUNCTION) \ + USE_FUNCTION (glGenVertexArrays, void, (GLsizei p1, GLuint* p2), (p1, p2))\ + USE_FUNCTION (glDeleteVertexArrays, void, (GLsizei p1, const GLuint* p2), (p1, p2))\ + USE_FUNCTION (glBindVertexArray, void, (GLuint p1), (p1)) + + +/** This class contains a generated list of OpenGL extension functions, which are either dynamically loaded + for a specific GL context, or simply call-through to the appropriate OS function where available. + + @tags{OpenGL} +*/ +struct OpenGLExtensionFunctions +{ + void initialise(); + + #if JUCE_WINDOWS && ! DOXYGEN + typedef char GLchar; + typedef pointer_sized_int GLsizeiptr; + typedef pointer_sized_int GLintptr; + #endif + + //============================================================================== + #if JUCE_WINDOWS + #define JUCE_DECLARE_GL_FUNCTION(name, returnType, params, callparams) typedef returnType (__stdcall *type_ ## name) params; type_ ## name name; + JUCE_GL_BASE_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + JUCE_GL_EXTENSION_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + #if JUCE_OPENGL3 + JUCE_GL_VERTEXBUFFER_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + #endif + + //============================================================================== + #elif JUCE_LINUX + #define JUCE_DECLARE_GL_FUNCTION(name, returnType, params, callparams) typedef returnType (*type_ ## name) params; type_ ## name name; + JUCE_GL_BASE_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + JUCE_GL_EXTENSION_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + #if JUCE_OPENGL3 + JUCE_GL_VERTEXBUFFER_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + #endif + + //============================================================================== + #elif JUCE_OPENGL_ES + #define JUCE_DECLARE_GL_FUNCTION(name, returnType, params, callparams) static returnType name params noexcept; + JUCE_GL_BASE_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + JUCE_GL_EXTENSION_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + JUCE_GL_VERTEXBUFFER_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + + //============================================================================== + #else + #define JUCE_DECLARE_GL_FUNCTION(name, returnType, params, callparams) static returnType name params noexcept { return ::name callparams; } + JUCE_GL_BASE_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + + #ifndef GL3_PROTOTYPES + #undef JUCE_DECLARE_GL_FUNCTION + #define JUCE_DECLARE_GL_FUNCTION(name, returnType, params, callparams) static returnType name params noexcept { return ::name ## EXT callparams; } + #endif + JUCE_GL_EXTENSION_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + + #if JUCE_OPENGL3 + #ifndef GL3_PROTOTYPES + #undef JUCE_DECLARE_GL_FUNCTION + #define JUCE_DECLARE_GL_FUNCTION(name, returnType, params, callparams) static returnType name params noexcept { return ::name ## APPLE callparams; } + #endif + JUCE_GL_VERTEXBUFFER_FUNCTIONS (JUCE_DECLARE_GL_FUNCTION) + #endif + #endif + + #undef JUCE_DECLARE_GL_FUNCTION +}; + +} // namespace juce diff --git a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h.rej b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h.rej new file mode 100644 index 00000000..3ea1da47 --- /dev/null +++ b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGLExtensions.h.rej @@ -0,0 +1,17 @@ +--- modules/juce_opengl/native/juce_OpenGLExtensions.h ++++ modules/juce_opengl/native/juce_OpenGLExtensions.h +@@ -83,7 +83,13 @@ namespace juce + USE_FUNCTION (glCheckFramebufferStatus, GLenum, (GLenum p1), (p1))\ + USE_FUNCTION (glFramebufferTexture2D, void, (GLenum p1, GLenum p2, GLenum p3, GLuint p4, GLint p5), (p1, p2, p3, p4, p5))\ + USE_FUNCTION (glFramebufferRenderbuffer, void, (GLenum p1, GLenum p2, GLenum p3, GLuint p4), (p1, p2, p3, p4))\ +- USE_FUNCTION (glGetFramebufferAttachmentParameteriv, void, (GLenum p1, GLenum p2, GLenum p3, GLint* p4), (p1, p2, p3, p4)) ++ USE_FUNCTION (glGetFramebufferAttachmentParameteriv, void, (GLenum p1, GLenum p2, GLenum p3, GLint* p4), (p1, p2, p3, p4))\ ++ USE_FUNCTION (glTransformFeedbackVaryings, void, (GLuint p1, GLsizei p2, const char **p3, GLenum p4), (p1, p2, p3, p4))\ ++ USE_FUNCTION (glBeginTransformFeedback, void, (GLenum p1), (p1))\ ++ USE_FUNCTION (glEndTransformFeedback, void, (), ())\ ++ USE_FUNCTION (glBindBufferBase, void, (GLenum p1, GLuint p2, GLuint p3), (p1, p2, p3))\ ++ USE_FUNCTION (glMapBufferRange, void*, (GLenum p1, GLintptr p2, GLsizeiptr p3, GLbitfield p4), (p1, p2, p3, p4))\ ++ USE_FUNCTION (glUnmapBuffer, GLboolean, (GLenum p1), (p1)); + + /** @internal This macro contains a list of GL extension functions that need to be dynamically loaded on Windows/Linux. + @see OpenGLExtensionFunctions