build(deps): vendor JUCE 7.0.12

This commit is contained in:
2026-09-08 14:55:21 +02:00
parent 5b99f51bd1
commit d6705ab29f
3591 changed files with 1218267 additions and 0 deletions
@@ -0,0 +1,33 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED
void AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler&, Event) {}
#endif
} // namespace juce::detail
@@ -0,0 +1,70 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
struct AccessibilityHelpers
{
AccessibilityHelpers() = delete;
enum class Event
{
elementCreated,
elementDestroyed,
elementMovedOrResized,
focusChanged,
windowOpened,
windowClosed
};
static void notifyAccessibilityEvent (const AccessibilityHandler&, Event);
static String getApplicationOrPluginName()
{
#if defined (JucePlugin_Name)
return JucePlugin_Name;
#else
if (auto* app = JUCEApplicationBase::getInstance())
return app->getApplicationName();
return "JUCE Application";
#endif
}
template <typename MemberFn>
static const AccessibilityHandler* getEnclosingHandlerWithInterface (const AccessibilityHandler* handler, MemberFn fn)
{
if (handler == nullptr)
return nullptr;
if ((handler->*fn)() != nullptr)
return handler;
return getEnclosingHandlerWithInterface (handler->getParent(), fn);
}
};
} // namespace juce::detail
@@ -0,0 +1,115 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
struct AlertWindowHelpers
{
AlertWindowHelpers() = delete;
static std::unique_ptr<ScopedMessageBoxInterface> create (const MessageBoxOptions& opts)
{
class AlertWindowImpl : public detail::ScopedMessageBoxInterface
{
public:
explicit AlertWindowImpl (const MessageBoxOptions& opts) : options (opts) {}
void runAsync (std::function<void (int)> recipient) override
{
if (auto* comp = setUpAlert())
comp->enterModalState (true, ModalCallbackFunction::create (std::move (recipient)), true);
else
NullCheckedInvocation::invoke (recipient, 0);
}
int runSync() override
{
#if JUCE_MODAL_LOOPS_PERMITTED
if (auto comp = rawToUniquePtr (setUpAlert()))
return comp->runModalLoop();
#endif
jassertfalse;
return 0;
}
void close() override
{
if (alert != nullptr)
if (alert->isCurrentlyModal())
alert->exitModalState();
alert = nullptr;
}
private:
Component* setUpAlert()
{
auto* component = options.getAssociatedComponent();
auto& lf = component != nullptr ? component->getLookAndFeel()
: LookAndFeel::getDefaultLookAndFeel();
alert = lf.createAlertWindow (options.getTitle(),
options.getMessage(),
options.getButtonText (0),
options.getButtonText (1),
options.getButtonText (2),
options.getIconType(),
options.getNumButtons(),
component);
if (alert == nullptr)
{
// You have to return an alert box!
jassertfalse;
return nullptr;
}
if (auto* parent = options.getParentComponent())
{
parent->addAndMakeVisible (alert);
if (options.getAssociatedComponent() == nullptr)
alert->setCentrePosition (parent->getLocalBounds().getCentre());
}
alert->setAlwaysOnTop (WindowUtils::areThereAnyAlwaysOnTopWindows());
return alert;
}
const MessageBoxOptions options;
Component::SafePointer<AlertWindow> alert;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlertWindowImpl)
};
return std::make_unique<AlertWindowImpl> (opts);
}
};
} // namespace juce::detail
@@ -0,0 +1,125 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
//==============================================================================
class ButtonAccessibilityHandler : public AccessibilityHandler
{
public:
ButtonAccessibilityHandler (Button& buttonToWrap, AccessibilityRole roleIn)
: AccessibilityHandler (buttonToWrap,
isRadioButton (buttonToWrap) ? AccessibilityRole::radioButton : roleIn,
getAccessibilityActions (buttonToWrap),
getAccessibilityInterfaces (buttonToWrap)),
button (buttonToWrap)
{}
AccessibleState getCurrentState() const override
{
auto state = AccessibilityHandler::getCurrentState();
if (button.isToggleable())
{
state = state.withCheckable();
if (button.getToggleState())
state = state.withChecked();
}
return state;
}
String getTitle() const override
{
auto title = AccessibilityHandler::getTitle();
if (title.isEmpty())
return button.getButtonText();
return title;
}
String getHelp() const override
{
return button.getTooltip();
}
private:
class ButtonValueInterface : public AccessibilityTextValueInterface
{
public:
explicit ButtonValueInterface (Button& buttonToWrap)
: button (buttonToWrap)
{
}
bool isReadOnly() const override { return true; }
String getCurrentValueAsString() const override { return button.getToggleState() ? "On" : "Off"; }
void setValueAsString (const String&) override {}
private:
Button& button;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonValueInterface)
};
static bool isRadioButton (const Button& button) noexcept
{
return button.getRadioGroupId() != 0;
}
static AccessibilityActions getAccessibilityActions (Button& button)
{
auto actions = AccessibilityActions().addAction (AccessibilityActionType::press,
[&button] { button.triggerClick(); });
if (button.isToggleable())
actions = actions.addAction (AccessibilityActionType::toggle,
[&button] { button.setToggleState (! button.getToggleState(), sendNotification); });
return actions;
}
static Interfaces getAccessibilityInterfaces (Button& button)
{
if (button.isToggleable())
return { std::make_unique<ButtonValueInterface> (button) };
return {};
}
Button& button;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAccessibilityHandler)
};
} // namespace juce::detail
@@ -0,0 +1,280 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
constexpr char colourPropertyPrefix[] = "jcclr_";
//==============================================================================
struct ComponentHelpers
{
using SH = ScalingHelpers;
#if JUCE_MODAL_LOOPS_PERMITTED
static void* runModalLoopCallback (void* userData)
{
return (void*) (pointer_sized_int) static_cast<Component*> (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<float> localPoint)
{
const auto intPoint = localPoint.roundToInt();
return Rectangle<int> { comp.getWidth(), comp.getHeight() }.contains (intPoint)
&& comp.hitTest (intPoint.x, intPoint.y);
}
// converts an unscaled position within a peer to the local position within that peer's component
template <typename PointOrRect>
static PointOrRect rawPeerPositionToLocal (const Component& comp, PointOrRect pos) noexcept
{
if (comp.isTransformed())
pos = pos.transformedBy (comp.getTransform().inverted());
return SH::unscaledScreenPosToScaled (comp, pos);
}
// converts a position within a peer's component to the unscaled position within the peer
template <typename PointOrRect>
static PointOrRect localPositionToRawPeerPos (const Component& comp, PointOrRect pos) noexcept
{
if (comp.isTransformed())
pos = pos.transformedBy (comp.getTransform());
return SH::scaledScreenPosToUnscaled (comp, pos);
}
template <typename PointOrRect>
static PointOrRect convertFromParentSpace (const Component& comp, const PointOrRect pointInParentSpace)
{
const auto transformed = comp.affineTransform != nullptr ? pointInParentSpace.transformedBy (comp.affineTransform->inverted())
: pointInParentSpace;
if (comp.isOnDesktop())
{
if (auto* peer = comp.getPeer())
return SH::unscaledScreenPosToScaled (comp, peer->globalToLocal (SH::scaledScreenPosToUnscaled (transformed)));
jassertfalse;
return transformed;
}
if (comp.getParentComponent() == nullptr)
return SH::subtractPosition (SH::unscaledScreenPosToScaled (comp, SH::scaledScreenPosToUnscaled (transformed)), comp);
return SH::subtractPosition (transformed, comp);
}
template <typename PointOrRect>
static PointOrRect convertToParentSpace (const Component& comp, const PointOrRect pointInLocalSpace)
{
const auto preTransform = [&]
{
if (comp.isOnDesktop())
{
if (auto* peer = comp.getPeer())
return SH::unscaledScreenPosToScaled (peer->localToGlobal (SH::scaledScreenPosToUnscaled (comp, pointInLocalSpace)));
jassertfalse;
return pointInLocalSpace;
}
if (comp.getParentComponent() == nullptr)
return SH::unscaledScreenPosToScaled (SH::scaledScreenPosToUnscaled (comp, SH::addPosition (pointInLocalSpace, comp)));
return SH::addPosition (pointInLocalSpace, comp);
}();
return comp.affineTransform != nullptr ? preTransform.transformedBy (*comp.affineTransform)
: preTransform;
}
template <typename PointOrRect>
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);
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011)
return convertFromParentSpace (target, convertFromDistantParentSpace (parent, *directParent, coordInParent));
JUCE_END_IGNORE_WARNINGS_MSVC
}
template <typename PointOrRect>
static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p)
{
while (source != nullptr)
{
if (source == target)
return p;
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011)
if (source->isParentOf (target))
return convertFromDistantParentSpace (source, *target, p);
JUCE_END_IGNORE_WARNINGS_MSVC
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<int> clipRect, Point<int> 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<int> 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);
}
//==============================================================================
static bool modalWouldBlockComponent (const Component& maybeBlocked, Component* modal)
{
return modal != nullptr
&& modal != &maybeBlocked
&& ! modal->isParentOf (&maybeBlocked)
&& ! modal->canModalEventBeSentToComponent (&maybeBlocked);
}
template <typename Function>
static void sendMouseEventToComponentsThatAreBlockedByModal (Component& modal, Function&& function)
{
for (auto& ms : Desktop::getInstance().getMouseSources())
if (auto* c = ms.getComponentUnderMouse())
if (modalWouldBlockComponent (*c, &modal))
(c->*function) (ms, SH::screenPosToLocalPos (*c, ms.getScreenPosition()), Time::getCurrentTime());
}
class ModalComponentManagerChangeNotifier
{
public:
static auto& getInstance()
{
static ModalComponentManagerChangeNotifier instance;
return instance;
}
ErasedScopeGuard addListener (std::function<void()> l)
{
return listeners.addListener (std::move (l));
}
void modalComponentManagerChanged()
{
listeners.call();
}
private:
ModalComponentManagerChangeNotifier() = default;
detail::CallbackListenerList<> listeners;
};
};
} // namespace juce::detail
@@ -0,0 +1,35 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
struct CustomMouseCursorInfo
{
ScaledImage image;
Point<int> hotspot;
};
} // namespace juce::detail
@@ -0,0 +1,117 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
struct FocusHelpers
{
FocusHelpers() = delete;
static int getOrder (const Component* c)
{
auto order = c->getExplicitFocusOrder();
return order > 0 ? order : std::numeric_limits<int>::max();
}
template <typename FocusContainerFn>
static void findAllComponents (Component* parent,
std::vector<Component*>& components,
FocusContainerFn isFocusContainer)
{
if (parent == nullptr || parent->getNumChildComponents() == 0)
return;
std::vector<Component*> localComponents;
for (auto* c : parent->getChildren())
if (c->isVisible() && c->isEnabled())
localComponents.push_back (c);
const auto compareComponents = [&] (const Component* a, const Component* b)
{
const auto getComponentOrderAttributes = [] (const Component* c)
{
return std::make_tuple (getOrder (c),
c->isAlwaysOnTop() ? 0 : 1,
c->getY(),
c->getX());
};
return getComponentOrderAttributes (a) < getComponentOrderAttributes (b);
};
// This will sort so that they are ordered in terms of explicit focus,
// always on top, left-to-right, and then top-to-bottom.
std::stable_sort (localComponents.begin(), localComponents.end(), compareComponents);
for (auto* c : localComponents)
{
components.push_back (c);
if (! (c->*isFocusContainer)())
findAllComponents (c, components, isFocusContainer);
}
}
enum class NavigationDirection { forwards, backwards };
template <typename FocusContainerFn>
static Component* navigateFocus (Component* current,
Component* focusContainer,
NavigationDirection direction,
FocusContainerFn isFocusContainer)
{
if (focusContainer != nullptr)
{
std::vector<Component*> components;
findAllComponents (focusContainer, components, isFocusContainer);
const auto iter = std::find (components.cbegin(), components.cend(), current);
if (iter == components.cend())
return nullptr;
switch (direction)
{
case NavigationDirection::forwards:
if (iter != std::prev (components.cend()))
return *std::next (iter);
break;
case NavigationDirection::backwards:
if (iter != components.cbegin())
return *std::prev (iter);
break;
}
}
return nullptr;
}
};
} // namespace juce::detail
@@ -0,0 +1,46 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
struct FocusRestorer
{
FocusRestorer() : lastFocus (Component::getCurrentlyFocusedComponent()) {}
~FocusRestorer()
{
if (lastFocus != nullptr
&& lastFocus->isShowing()
&& ! lastFocus->isCurrentlyBlockedByAnotherModalComponent())
lastFocus->grabKeyboardFocus();
}
WeakReference<Component> lastFocus;
JUCE_DECLARE_NON_COPYABLE (FocusRestorer)
};
} // namespace juce::detail
@@ -0,0 +1,62 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
struct LookAndFeelHelpers
{
LookAndFeelHelpers() = delete;
static Colour createBaseColour (Colour buttonColour,
bool hasKeyboardFocus,
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) noexcept
{
const float sat = hasKeyboardFocus ? 1.3f : 0.9f;
const Colour baseColour (buttonColour.withMultipliedSaturation (sat));
if (shouldDrawButtonAsDown) return baseColour.contrasting (0.2f);
if (shouldDrawButtonAsHighlighted) return baseColour.contrasting (0.1f);
return baseColour;
}
static TextLayout layoutTooltipText (const String& text, Colour colour) noexcept
{
const float tooltipFontSize = 13.0f;
const int maxToolTipWidth = 400;
AttributedString s;
s.setJustification (Justification::centred);
s.append (text, Font (tooltipFontSize, Font::bold), colour);
TextLayout tl;
tl.createLayoutWithBalancedLineLengths (s, (float) maxToolTipWidth);
return tl;
}
};
} // namespace juce::detail
@@ -0,0 +1,588 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
class MouseInputSourceImpl : private AsyncUpdater
{
public:
using SH = ScalingHelpers;
MouseInputSourceImpl (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 Component* findComponentAt (Point<float> screenPos, ComponentPeer* peer)
{
if (! ComponentPeer::isValidPeer (peer))
return nullptr;
auto relativePos = SH::unscaledScreenPosToScaled (peer->getComponent(),
peer->globalToLocal (screenPos));
auto& comp = peer->getComponent();
// (the contains() call is needed to test for overlapping desktop windows)
if (comp.contains (relativePos))
return comp.getComponentAt (relativePos);
return nullptr;
}
Point<float> 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 SH::unscaledScreenPosToScaled (getRawScreenPosition());
}
Point<float> getRawScreenPosition() const noexcept
{
return unboundedMouseOffset + (inputType != MouseInputSource::InputSourceType::touch ? MouseInputSource::getCurrentRawMousePosition()
: lastPointerState.position);
}
void setScreenPosition (Point<float> p)
{
MouseInputSource::setRawMousePosition (SH::scaledScreenPosToUnscaled (p));
}
//==============================================================================
#if JUCE_DUMP_MOUSE_EVENTS
#define JUCE_MOUSE_EVENT_DBG(desc, screenPos) DBG ("Mouse " << desc << " #" << index \
<< ": " << SH::screenPosToLocalPos (comp, screenPos).toString() \
<< " - Comp: " << String::toHexString ((pointer_sized_int) &comp));
#else
#define JUCE_MOUSE_EVENT_DBG(desc, screenPos)
#endif
void sendMouseEnter (Component& comp, const detail::PointerState& pointerState, Time time)
{
JUCE_MOUSE_EVENT_DBG ("enter", pointerState.position)
comp.internalMouseEnter (MouseInputSource (this),
SH::screenPosToLocalPos (comp, pointerState.position),
time);
}
void sendMouseExit (Component& comp, const detail::PointerState& pointerState, Time time)
{
JUCE_MOUSE_EVENT_DBG ("exit", pointerState.position)
comp.internalMouseExit (MouseInputSource (this),
SH::screenPosToLocalPos (comp, pointerState.position),
time);
}
void sendMouseMove (Component& comp, const detail::PointerState& pointerState, Time time)
{
JUCE_MOUSE_EVENT_DBG ("move", pointerState.position)
comp.internalMouseMove (MouseInputSource (this),
SH::screenPosToLocalPos (comp, pointerState.position),
time);
}
void sendMouseDown (Component& comp, const detail::PointerState& pointerState, Time time)
{
JUCE_MOUSE_EVENT_DBG ("down", pointerState.position)
comp.internalMouseDown (MouseInputSource (this),
pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)),
time);
}
void sendMouseDrag (Component& comp, const detail::PointerState& pointerState, Time time)
{
JUCE_MOUSE_EVENT_DBG ("drag", pointerState.position)
comp.internalMouseDrag (MouseInputSource (this),
pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)),
time);
}
void sendMouseUp (Component& comp, const detail::PointerState& pointerState, Time time, ModifierKeys oldMods)
{
JUCE_MOUSE_EVENT_DBG ("up", pointerState.position)
comp.internalMouseUp (MouseInputSource (this),
pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)),
time,
oldMods);
}
void sendMouseWheel (Component& comp, Point<float> screenPos, Time time, const MouseWheelDetails& wheel)
{
JUCE_MOUSE_EVENT_DBG ("wheel", screenPos)
comp.internalMouseWheel (MouseInputSource (this),
SH::screenPosToLocalPos (comp, screenPos),
time,
wheel);
}
void sendMagnifyGesture (Component& comp, Point<float> screenPos, Time time, float amount)
{
JUCE_MOUSE_EVENT_DBG ("magnify", screenPos)
comp.internalMagnifyGesture (MouseInputSource (this),
SH::screenPosToLocalPos (comp, screenPos),
time,
amount);
}
#undef JUCE_MOUSE_EVENT_DBG
//==============================================================================
// (returns true if the button change caused a modal event loop)
bool setButtons (const detail::PointerState& pointerState, 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()))
setPointerState (pointerState, 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, pointerState.withPositionOffset (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 (pointerState.position, time, *current, buttonState,
inputType == MouseInputSource::InputSourceType::touch);
sendMouseDown (*current, pointerState, time);
}
}
return lastCounter != mouseEventCounter;
}
void setComponentUnderMouse (Component* newComponent, const detail::PointerState& pointerState, Time time)
{
auto* current = getComponentUnderMouse();
if (newComponent != current)
{
WeakReference<Component> safeNewComp (newComponent);
auto originalButtonState = buttonState;
if (current != nullptr)
{
WeakReference<Component> safeOldComp (current);
setButtons (pointerState, time, ModifierKeys());
if (auto oldComp = safeOldComp.get())
{
componentUnderMouse = safeNewComp;
sendMouseExit (*oldComp, pointerState, time);
}
buttonState = originalButtonState;
}
componentUnderMouse = safeNewComp.get();
current = safeNewComp.get();
if (current != nullptr)
sendMouseEnter (*current, pointerState, time);
revealCursor (false);
setButtons (pointerState, time, originalButtonState);
}
}
void setPeer (ComponentPeer& newPeer, const detail::PointerState& pointerState, Time time)
{
if (&newPeer != lastPeer && ( findComponentAt (pointerState.position, &newPeer) != nullptr
|| findComponentAt (pointerState.position, lastPeer) == nullptr))
{
setComponentUnderMouse (nullptr, pointerState, time);
lastPeer = &newPeer;
setComponentUnderMouse (findComponentAt (pointerState.position, getPeer()), pointerState, time);
}
}
void setPointerState (const detail::PointerState& newPointerState, Time time, bool forceUpdate)
{
const auto& newScreenPos = newPointerState.position;
if (! isDragging())
setComponentUnderMouse (findComponentAt (newScreenPos, getPeer()), newPointerState, time);
if ((newPointerState != lastPointerState) || forceUpdate)
{
cancelPendingUpdate();
lastPointerState = newPointerState;
if (auto* current = getComponentUnderMouse())
{
if (isDragging())
{
registerMouseDrag (newScreenPos);
sendMouseDrag (*current, newPointerState.withPositionOffset (unboundedMouseOffset), time);
if (isUnboundedMouseModeOn)
handleUnboundedDrag (*current);
}
else
{
sendMouseMove (*current, newPointerState, time);
}
}
revealCursor (false);
}
}
//==============================================================================
void handleEvent (ComponentPeer& newPeer, Point<float> positionWithinPeer, Time time,
const ModifierKeys newMods, float newPressure, float newOrientation, PenDetails pen)
{
lastTime = time;
++mouseEventCounter;
const auto pointerState = detail::PointerState().withPosition (newPeer.localToGlobal (positionWithinPeer))
.withPressure (newPressure)
.withOrientation (newOrientation)
.withRotation (MouseInputSource::defaultRotation)
.withTiltX (pen.tiltX)
.withTiltY (pen.tiltY);
if (isDragging() && newMods.isAnyMouseButtonDown())
{
setPointerState (pointerState, time, false);
}
else
{
setPeer (newPeer, pointerState, time);
if (auto* peer = getPeer())
{
if (setButtons (pointerState, time, newMods))
return; // some modal events have been dispatched, so the current event is now out-of-date
peer = getPeer();
if (peer != nullptr)
setPointerState (pointerState, time, false);
}
}
}
Component* getTargetForGesture (ComponentPeer& peer, Point<float> positionWithinPeer,
Time time, Point<float>& screenPos)
{
lastTime = time;
++mouseEventCounter;
screenPos = peer.localToGlobal (positionWithinPeer);
const auto pointerState = lastPointerState.withPosition (screenPos);
setPeer (peer, pointerState, time);
setPointerState (pointerState, time, false);
triggerFakeMove();
return getComponentUnderMouse();
}
void handleWheel (ComponentPeer& peer, Point<float> positionWithinPeer,
Time time, const MouseWheelDetails& wheel)
{
Desktop::getInstance().incrementMouseWheelCounter();
Point<float> 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<float> positionWithinPeer,
Time time, const float scaleFactor)
{
Point<float> screenPos;
if (auto* current = getTargetForGesture (peer, positionWithinPeer, time, screenPos))
sendMagnifyGesture (*current, screenPos, time, scaleFactor);
}
//==============================================================================
Time getLastMouseDownTime() const noexcept
{
return mouseDowns[0].time;
}
Point<float> getLastMouseDownPosition() const noexcept
{
return SH::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
{
setPointerState (lastPointerState,
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 (SH::unscaledScreenPosToScaled (lastPointerState.position)));
}
isUnboundedMouseModeOn = enable;
unboundedMouseOffset = {};
revealCursor (true);
}
}
void handleUnboundedDrag (Component& current)
{
auto componentScreenBounds = SH::scaledScreenPosToUnscaled (current.getParentMonitorArea()
.reduced (2, 2)
.toFloat());
if (! componentScreenBounds.contains (lastPointerState.position))
{
auto componentCentre = current.getScreenBounds().toFloat().getCentre();
unboundedMouseOffset += (lastPointerState.position - SH::scaledScreenPosToUnscaled (componentCentre));
setScreenPosition (componentCentre);
}
else if (isCursorVisibleUntilOffscreen
&& (! unboundedMouseOffset.isOrigin())
&& componentScreenBounds.contains (lastPointerState.position + unboundedMouseOffset))
{
MouseInputSource::setRawMousePosition (lastPointerState.position + 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<float> unboundedMouseOffset; // NB: these are unscaled coords
detail::PointerState lastPointerState;
ModifierKeys buttonState;
bool isUnboundedMouseModeOn = false, isCursorVisibleUntilOffscreen = false;
private:
WeakReference<Component> componentUnderMouse, lastNonInertialWheelTarget;
ComponentPeer* lastPeer = nullptr;
void* currentCursorHandle = nullptr;
int mouseEventCounter = 0;
struct RecentMouseDown
{
RecentMouseDown() = default;
Point<float> 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<float> 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<float> screenPos) noexcept
{
movedSignificantly = movedSignificantly || mouseDowns[0].position.getDistanceFrom (screenPos) >= 4;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MouseInputSourceImpl)
};
} // namespace juce::detail
@@ -0,0 +1,154 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
class MouseInputSourceList : public Timer
{
public:
MouseInputSourceList()
{
#if JUCE_ANDROID || JUCE_IOS
auto mainMouseInputType = MouseInputSource::InputSourceType::touch;
#else
auto mainMouseInputType = MouseInputSource::InputSourceType::mouse;
#endif
addSource (0, mainMouseInputType);
}
MouseInputSource* addSource (int index, MouseInputSource::InputSourceType type)
{
auto* s = new MouseInputSourceImpl (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 (0 <= touchIndex && 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->lastPointerState.position = s->getRawScreenPosition();
s->triggerFakeMove();
anyDragging = true;
}
}
if (! anyDragging)
stopTimer();
}
OwnedArray<MouseInputSourceImpl> sources;
Array<MouseInputSource> sourceArray;
private:
bool addSource();
bool canUseTouch() const;
};
} // namespace juce::detail
@@ -0,0 +1,104 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
class PointerState
{
auto tie() const noexcept
{
return std::tie (position, pressure, orientation, rotation, tiltX, tiltY);
}
public:
PointerState() = default;
bool operator== (const PointerState& other) const noexcept { return tie() == other.tie(); }
bool operator!= (const PointerState& other) const noexcept { return tie() != other.tie(); }
[[nodiscard]] PointerState withPositionOffset (Point<float> x) const noexcept { return with (&PointerState::position, position + x); }
[[nodiscard]] PointerState withPosition (Point<float> x) const noexcept { return with (&PointerState::position, x); }
[[nodiscard]] PointerState withPressure (float x) const noexcept { return with (&PointerState::pressure, x); }
[[nodiscard]] PointerState withOrientation (float x) const noexcept { return with (&PointerState::orientation, x); }
[[nodiscard]] PointerState withRotation (float x) const noexcept { return with (&PointerState::rotation, x); }
[[nodiscard]] PointerState withTiltX (float x) const noexcept { return with (&PointerState::tiltX, x); }
[[nodiscard]] PointerState withTiltY (float x) const noexcept { return with (&PointerState::tiltY, x); }
Point<float> position;
float pressure = MouseInputSource::defaultPressure;
float orientation = MouseInputSource::defaultOrientation;
float rotation = MouseInputSource::defaultRotation;
float tiltX = MouseInputSource::defaultTiltX;
float tiltY = MouseInputSource::defaultTiltY;
bool isPressureValid() const noexcept { return 0.0f <= pressure && pressure <= 1.0f; }
bool isOrientationValid() const noexcept { return 0.0f <= orientation && orientation <= MathConstants<float>::twoPi; }
bool isRotationValid() const noexcept { return 0.0f <= rotation && rotation <= MathConstants<float>::twoPi; }
bool isTiltValid (bool isX) const noexcept
{
return isX ? (-1.0f <= tiltX && tiltX <= 1.0f)
: (-1.0f <= tiltY && tiltY <= 1.0f);
}
private:
template <typename Value>
PointerState with (Value PointerState::* member, Value item) const
{
auto copy = *this;
copy.*member = std::move (item);
return copy;
}
};
inline auto makeMouseEvent (MouseInputSource source,
const PointerState& ps,
ModifierKeys modifiers,
Component* eventComponent,
Component* originator,
Time eventTime,
Point<float> mouseDownPos,
Time mouseDownTime,
int numberOfClicks,
bool mouseWasDragged)
{
return MouseEvent (source,
ps.position,
modifiers,
ps.pressure,
ps.orientation,
ps.rotation,
ps.tiltX,
ps.tiltY,
eventComponent,
originator,
eventTime,
mouseDownPos,
mouseDownTime,
numberOfClicks,
mouseWasDragged);
}
} // namespace juce::detail
@@ -0,0 +1,123 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
struct ScalingHelpers
{
template <typename PointOrRect>
static PointOrRect unscaledScreenPosToScaled (float scale, PointOrRect pos) noexcept
{
return ! approximatelyEqual (scale, 1.0f) ? pos / scale : pos;
}
template <typename PointOrRect>
static PointOrRect scaledScreenPosToUnscaled (float scale, PointOrRect pos) noexcept
{
return ! approximatelyEqual (scale, 1.0f) ? pos * scale : pos;
}
// For these, we need to avoid getSmallestIntegerContainer being used, which causes
// judder when moving windows
static Rectangle<int> unscaledScreenPosToScaled (float scale, Rectangle<int> pos) noexcept
{
return ! approximatelyEqual (scale, 1.0f) ? Rectangle<int> (roundToInt ((float) pos.getX() / scale),
roundToInt ((float) pos.getY() / scale),
roundToInt ((float) pos.getWidth() / scale),
roundToInt ((float) pos.getHeight() / scale)) : pos;
}
static Rectangle<int> scaledScreenPosToUnscaled (float scale, Rectangle<int> pos) noexcept
{
return ! approximatelyEqual (scale, 1.0f) ? Rectangle<int> (roundToInt ((float) pos.getX() * scale),
roundToInt ((float) pos.getY() * scale),
roundToInt ((float) pos.getWidth() * scale),
roundToInt ((float) pos.getHeight() * scale)) : pos;
}
static Rectangle<float> unscaledScreenPosToScaled (float scale, Rectangle<float> pos) noexcept
{
return ! approximatelyEqual (scale, 1.0f) ? Rectangle<float> (pos.getX() / scale,
pos.getY() / scale,
pos.getWidth() / scale,
pos.getHeight() / scale) : pos;
}
static Rectangle<float> scaledScreenPosToUnscaled (float scale, Rectangle<float> pos) noexcept
{
return ! approximatelyEqual (scale, 1.0f) ? Rectangle<float> (pos.getX() * scale,
pos.getY() * scale,
pos.getWidth() * scale,
pos.getHeight() * scale) : pos;
}
template <typename PointOrRect>
static PointOrRect unscaledScreenPosToScaled (PointOrRect pos) noexcept
{
return unscaledScreenPosToScaled (Desktop::getInstance().getGlobalScaleFactor(), pos);
}
template <typename PointOrRect>
static PointOrRect scaledScreenPosToUnscaled (PointOrRect pos) noexcept
{
return scaledScreenPosToUnscaled (Desktop::getInstance().getGlobalScaleFactor(), pos);
}
template <typename PointOrRect>
static PointOrRect unscaledScreenPosToScaled (const Component& comp, PointOrRect pos) noexcept
{
return unscaledScreenPosToScaled (comp.getDesktopScaleFactor(), pos);
}
template <typename PointOrRect>
static PointOrRect scaledScreenPosToUnscaled (const Component& comp, PointOrRect pos) noexcept
{
return scaledScreenPosToUnscaled (comp.getDesktopScaleFactor(), pos);
}
static Point<int> addPosition (Point<int> p, const Component& c) noexcept { return p + c.getPosition(); }
static Rectangle<int> addPosition (Rectangle<int> p, const Component& c) noexcept { return p + c.getPosition(); }
static Point<float> addPosition (Point<float> p, const Component& c) noexcept { return p + c.getPosition().toFloat(); }
static Rectangle<float> addPosition (Rectangle<float> p, const Component& c) noexcept { return p + c.getPosition().toFloat(); }
static Point<int> subtractPosition (Point<int> p, const Component& c) noexcept { return p - c.getPosition(); }
static Rectangle<int> subtractPosition (Rectangle<int> p, const Component& c) noexcept { return p - c.getPosition(); }
static Point<float> subtractPosition (Point<float> p, const Component& c) noexcept { return p - c.getPosition().toFloat(); }
static Rectangle<float> subtractPosition (Rectangle<float> p, const Component& c) noexcept { return p - c.getPosition().toFloat(); }
static Point<float> screenPosToLocalPos (Component& comp, Point<float> pos)
{
if (auto* peer = comp.getPeer())
{
pos = peer->globalToLocal (pos);
auto& peerComp = peer->getComponent();
return comp.getLocalPoint (&peerComp, unscaledScreenPosToScaled (peerComp, pos));
}
return comp.getLocalPoint (nullptr, unscaledScreenPosToScaled (comp, pos));
}
};
} // namespace juce::detail
@@ -0,0 +1,98 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
class ConcreteScopedContentSharerImpl : public ScopedMessageBoxImpl,
private AsyncUpdater
{
public:
static ScopedMessageBox show (std::unique_ptr<ScopedContentSharerInterface>&& native,
ContentSharer::Callback callback)
{
return ScopedMessageBox (runAsync (std::move (native), std::move (callback)));
}
~ConcreteScopedContentSharerImpl() override
{
cancelPendingUpdate();
}
void close() override
{
cancelPendingUpdate();
nativeImplementation->close();
self.reset();
}
private:
static std::shared_ptr<ConcreteScopedContentSharerImpl> runAsync (std::unique_ptr<ScopedContentSharerInterface>&& p,
ContentSharer::Callback&& c)
{
std::shared_ptr<ConcreteScopedContentSharerImpl> result (new ConcreteScopedContentSharerImpl (std::move (p), std::move (c)));
result->self = result;
result->triggerAsyncUpdate();
return result;
}
ConcreteScopedContentSharerImpl (std::unique_ptr<ScopedContentSharerInterface>&& p,
ContentSharer::Callback&& c)
: callback (std::move (c)), nativeImplementation (std::move (p)) {}
void handleAsyncUpdate() override
{
nativeImplementation->runAsync ([weakRecipient = std::weak_ptr<ConcreteScopedContentSharerImpl> (self)] (bool result, const String& error)
{
const auto notifyRecipient = [result, error, weakRecipient]
{
if (const auto locked = weakRecipient.lock())
{
NullCheckedInvocation::invoke (locked->callback, result, error);
locked->self.reset();
}
};
if (MessageManager::getInstance()->isThisTheMessageThread())
notifyRecipient();
else
MessageManager::callAsync (notifyRecipient);
});
}
ContentSharer::Callback callback;
std::unique_ptr<ScopedContentSharerInterface> nativeImplementation;
/* The 'old' native message box API doesn't have a concept of content sharer owners.
Instead, content sharers have to clean up after themselves, once they're done displaying.
To allow this mode of usage, the implementation keeps an owning reference to itself,
which is cleared once the content sharer is closed or asked to quit. To display a content
sharer box without a scoped lifetime, just create a Pimpl instance without using
the ScopedContentSharer wrapper, and the Pimpl will destroy itself after it is dismissed.
*/
std::shared_ptr<ConcreteScopedContentSharerImpl> self;
};
} // namespace juce::detail
@@ -0,0 +1,215 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
/*
Instances of this type can show and dismiss a content sharer.
This is an interface rather than a concrete type so that platforms can pick an implementation at
runtime if necessary.
*/
struct ScopedContentSharerInterface
{
virtual ~ScopedContentSharerInterface() = default;
/* Shows the content sharer.
When the content sharer exits normally, it should send the result to the passed-in function.
The passed-in function is safe to call from any thread at any time.
*/
virtual void runAsync (ContentSharer::Callback callback)
{
jassertfalse;
NullCheckedInvocation::invoke (callback, false, "Content sharing not available on this platform!");
}
/* Forcefully closes the content sharer.
This will be called when the content sharer handle has fallen out of scope.
If the content sharer has already been closed by the user, this shouldn't do anything.
*/
virtual void close() {}
/* Implemented differently for each platform. */
static std::unique_ptr<ScopedContentSharerInterface> shareFiles (const Array<URL>&, Component*);
static std::unique_ptr<ScopedContentSharerInterface> shareText (const String&, Component*);
/* Implemented below. */
static std::unique_ptr<ScopedContentSharerInterface> shareImages (const Array<Image>&, std::unique_ptr<ImageFileFormat>, Component*);
static std::unique_ptr<ScopedContentSharerInterface> shareData (MemoryBlock, Component*);
};
class TemporaryFilesDecorator : public ScopedContentSharerInterface,
private AsyncUpdater
{
public:
explicit TemporaryFilesDecorator (Component* parentIn)
: parent (parentIn) {}
void runAsync (ContentSharer::Callback cb) override
{
callback = std::move (cb);
task = std::async (std::launch::async, [this]
{
std::tie (temporaryFiles, error) = prepareTemporaryFiles();
triggerAsyncUpdate();
});
}
void close() override
{
if (inner != nullptr)
inner->close();
}
private:
virtual std::tuple<Array<URL>, String> prepareTemporaryFiles() const = 0;
void handleAsyncUpdate() override
{
if (error.isNotEmpty())
{
NullCheckedInvocation::invoke (callback, false, error);
return;
}
inner = shareFiles (temporaryFiles, parent);
if (inner == nullptr)
{
NullCheckedInvocation::invoke (callback, false, TRANS ("Failed to create file sharer"));
return;
}
inner->runAsync (callback);
}
Array<URL> temporaryFiles;
String error;
std::unique_ptr<ScopedContentSharerInterface> inner;
ContentSharer::Callback callback;
std::future<void> task;
Component* parent = nullptr;
};
std::unique_ptr<ScopedContentSharerInterface> ScopedContentSharerInterface::shareImages (const Array<Image>& images,
std::unique_ptr<ImageFileFormat> format,
Component* parent)
{
class Decorator : public TemporaryFilesDecorator
{
public:
Decorator (Array<Image> imagesIn, std::unique_ptr<ImageFileFormat> formatIn, Component* parentIn)
: TemporaryFilesDecorator (parentIn), images (std::move (imagesIn)), format (std::move (formatIn)) {}
private:
std::tuple<Array<URL>, String> prepareTemporaryFiles() const override
{
const auto extension = format->getFormatName().toLowerCase();
Array<URL> result;
for (const auto& image : images)
{
File tempFile = File::createTempFile (extension);
if (! tempFile.create().wasOk())
return { Array<URL>{}, TRANS ("Failed to create temporary file") };
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
if (outputStream == nullptr)
return { Array<URL>{}, TRANS ("Failed to open temporary file for writing") };
if (format->writeImageToStream (image, *outputStream))
result.add (URL (tempFile));
}
jassert (std::all_of (result.begin(),
result.end(),
[] (const auto& url)
{
return url.isLocalFile() && url.getLocalFile().existsAsFile();
}));
return { std::move (result), String{} };
}
Array<Image> images;
std::unique_ptr<ImageFileFormat> format;
};
return std::make_unique<Decorator> (images,
format == nullptr ? std::make_unique<PNGImageFormat>() : std::move (format),
parent);
}
std::unique_ptr<ScopedContentSharerInterface> ScopedContentSharerInterface::shareData (MemoryBlock mb, Component* parent)
{
class Decorator : public TemporaryFilesDecorator
{
public:
Decorator (MemoryBlock mbIn, Component* parentIn)
: TemporaryFilesDecorator (parentIn), mb (std::move (mbIn)) {}
private:
std::tuple<Array<URL>, String> prepareTemporaryFiles() const override
{
File tempFile = File::createTempFile ("data");
if (! tempFile.create().wasOk())
return { Array<URL>{}, TRANS ("Failed to create temporary file") };
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
if (outputStream == nullptr)
return { Array<URL>{}, TRANS ("Failed to open temporary file for writing") };
size_t pos = 0;
size_t totalSize = mb.getSize();
while (pos < totalSize)
{
size_t numToWrite = std::min ((size_t) 8192, totalSize - pos);
if (! outputStream->write (mb.begin() + pos, numToWrite))
return { Array<URL>{}, TRANS ("Failed to write to temporary file") };
pos += numToWrite;
}
return { Array<URL> { URL (tempFile) }, String{} };
}
MemoryBlock mb;
};
return std::make_unique<Decorator> (std::move (mb), parent);
}
} // namespace juce::detail
@@ -0,0 +1,132 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
//==============================================================================
class ScopedMessageBoxImpl
{
public:
virtual ~ScopedMessageBoxImpl() = default;
virtual void close() = 0;
};
//==============================================================================
class ConcreteScopedMessageBoxImpl : public ScopedMessageBoxImpl,
private AsyncUpdater
{
public:
static ScopedMessageBox show (std::unique_ptr<ScopedMessageBoxInterface>&& native,
std::function<void (int)> callback)
{
return ScopedMessageBox (runAsync (std::move (native),
rawToUniquePtr (ModalCallbackFunction::create (std::move (callback)))));
}
static int showUnmanaged (std::unique_ptr<ScopedMessageBoxInterface>&& native,
ModalComponentManager::Callback* cb)
{
#if JUCE_MODAL_LOOPS_PERMITTED
if (cb == nullptr)
return runSync (std::move (native));
#endif
runAsync (std::move (native), rawToUniquePtr (cb));
return 0;
}
~ConcreteScopedMessageBoxImpl() override
{
cancelPendingUpdate();
}
void close() override
{
cancelPendingUpdate();
nativeImplementation->close();
self.reset();
}
private:
static std::shared_ptr<ConcreteScopedMessageBoxImpl> runAsync (std::unique_ptr<ScopedMessageBoxInterface>&& p,
std::unique_ptr<ModalComponentManager::Callback>&& c)
{
std::shared_ptr<ConcreteScopedMessageBoxImpl> result (new ConcreteScopedMessageBoxImpl (std::move (p), std::move (c)));
result->self = result;
result->triggerAsyncUpdate();
return result;
}
static int runSync (std::unique_ptr<ScopedMessageBoxInterface>&& p)
{
auto local = std::move (p);
return local != nullptr ? local->runSync() : 0;
}
explicit ConcreteScopedMessageBoxImpl (std::unique_ptr<ScopedMessageBoxInterface>&& p)
: ConcreteScopedMessageBoxImpl (std::move (p), nullptr) {}
ConcreteScopedMessageBoxImpl (std::unique_ptr<ScopedMessageBoxInterface>&& p,
std::unique_ptr<ModalComponentManager::Callback>&& c)
: callback (std::move (c)), nativeImplementation (std::move (p)) {}
void handleAsyncUpdate() override
{
nativeImplementation->runAsync ([weakRecipient = std::weak_ptr<ConcreteScopedMessageBoxImpl> (self)] (int result)
{
const auto notifyRecipient = [result, weakRecipient]
{
if (const auto locked = weakRecipient.lock())
{
if (auto* cb = locked->callback.get())
cb->modalStateFinished (result);
locked->self.reset();
}
};
if (MessageManager::getInstance()->isThisTheMessageThread())
notifyRecipient();
else
MessageManager::callAsync (notifyRecipient);
});
}
std::unique_ptr<ModalComponentManager::Callback> callback;
std::unique_ptr<ScopedMessageBoxInterface> nativeImplementation;
/* The 'old' native message box API doesn't have a concept of message box owners.
Instead, message boxes have to clean up after themselves, once they're done displaying.
To allow this mode of usage, the implementation keeps an owning reference to itself,
which is cleared once the message box is closed or asked to quit. To display a native
message box without a scoped lifetime, just create a Pimpl instance without using
the ScopedMessageBox wrapper, and the Pimpl will destroy itself after it is dismissed.
*/
std::shared_ptr<ConcreteScopedMessageBoxImpl> self;
};
} // namespace juce::detail
@@ -0,0 +1,60 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
/*
Instances of this type can show and dismiss a message box.
This is an interface rather than a concrete type so that platforms can pick an implementation at
runtime if necessary.
*/
struct ScopedMessageBoxInterface
{
virtual ~ScopedMessageBoxInterface() = default;
/* Shows the message box.
When the message box exits normally, it should send the result to the passed-in function.
The passed-in function is safe to call from any thread at any time.
*/
virtual void runAsync (std::function<void (int)>) = 0;
/* Shows the message box and blocks. */
virtual int runSync() = 0;
/* Forcefully closes the message box.
This will be called when the message box handle has fallen out of scope.
If the message box has already been closed by the user, this shouldn't do anything.
*/
virtual void close() = 0;
/* Implemented differently for each platform. */
static std::unique_ptr<ScopedMessageBoxInterface> create (const MessageBoxOptions& options);
};
} // namespace juce::detail
@@ -0,0 +1,118 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
class ToolbarItemDragAndDropOverlayComponent : public Component
{
public:
ToolbarItemDragAndDropOverlayComponent()
: isDragging (false)
{
setAlwaysOnTop (true);
setRepaintsOnMouseActivity (true);
setMouseCursor (MouseCursor::DraggingHandCursor);
}
void paint (Graphics& g) override
{
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
{
if (isMouseOverOrDragging()
&& tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar)
{
g.setColour (findColour (Toolbar::editingModeOutlineColourId, true));
g.drawRect (getLocalBounds(), jmin (2, (getWidth() - 1) / 2,
(getHeight() - 1) / 2));
}
}
}
void mouseDown (const MouseEvent& e) override
{
isDragging = false;
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
{
tc->dragOffsetX = e.x;
tc->dragOffsetY = e.y;
}
}
void mouseDrag (const MouseEvent& e) override
{
if (e.mouseWasDraggedSinceMouseDown() && ! isDragging)
{
isDragging = true;
if (DragAndDropContainer* const dnd = DragAndDropContainer::findParentDragContainerFor (this))
{
dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), ScaledImage(), true, nullptr, &e.source);
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
{
tc->isBeingDragged = true;
if (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar)
tc->setVisible (false);
}
}
}
}
void mouseUp (const MouseEvent&) override
{
isDragging = false;
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
{
tc->isBeingDragged = false;
if (Toolbar* const tb = tc->getToolbar())
tb->updateAllItemPositions (true);
else if (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar)
delete tc;
}
}
void parentSizeChanged() override
{
setBounds (0, 0, getParentWidth(), getParentHeight());
}
private:
//==============================================================================
bool isDragging;
ToolbarItemComponent* getToolbarItemComponent() const noexcept
{
return dynamic_cast<ToolbarItemComponent*> (getParentComponent());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarItemDragAndDropOverlayComponent)
};
} // namespace juce::detail
@@ -0,0 +1,136 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
/* Keeps track of the active top level window. */
class TopLevelWindowManager : private Timer,
private DeletedAtShutdown
{
public:
TopLevelWindowManager() = default;
~TopLevelWindowManager() override
{
clearSingletonInstance();
}
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (TopLevelWindowManager)
static void checkCurrentlyFocusedTopLevelWindow()
{
if (auto* wm = TopLevelWindowManager::getInstanceWithoutCreating())
wm->checkFocusAsync();
}
void checkFocusAsync()
{
startTimer (10);
}
void checkFocus()
{
startTimer (jmin (1731, getTimerInterval() * 2));
auto* newActive = findCurrentlyActiveWindow();
if (newActive != currentActive)
{
currentActive = newActive;
for (int i = windows.size(); --i >= 0;)
if (auto* tlw = windows[i])
tlw->setWindowActive (isWindowActive (tlw));
Desktop::getInstance().triggerFocusCallback();
}
}
bool addWindow (TopLevelWindow* const w)
{
windows.add (w);
checkFocusAsync();
return isWindowActive (w);
}
void removeWindow (TopLevelWindow* const w)
{
checkFocusAsync();
if (currentActive == w)
currentActive = nullptr;
windows.removeFirstMatchingValue (w);
if (windows.isEmpty())
deleteInstance();
}
Array<TopLevelWindow*> windows;
private:
TopLevelWindow* currentActive = nullptr;
void timerCallback() override
{
checkFocus();
}
bool isWindowActive (TopLevelWindow* const tlw) const
{
return (tlw == currentActive
|| tlw->isParentOf (currentActive)
|| tlw->hasKeyboardFocus (true))
&& tlw->isShowing();
}
TopLevelWindow* findCurrentlyActiveWindow() const
{
if (Process::isForegroundProcess())
{
auto* focusedComp = Component::getCurrentlyFocusedComponent();
auto* w = dynamic_cast<TopLevelWindow*> (focusedComp);
if (w == nullptr && focusedComp != nullptr)
w = focusedComp->findParentComponentOfClass<TopLevelWindow>();
if (w == nullptr)
w = currentActive;
if (w != nullptr && w->isShowing())
return w;
}
return nullptr;
}
JUCE_DECLARE_NON_COPYABLE (TopLevelWindowManager)
};
JUCE_IMPLEMENT_SINGLETON (TopLevelWindowManager)
} // namespace juce::detail
@@ -0,0 +1,49 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
struct ViewportHelpers
{
ViewportHelpers() = delete;
static bool wouldScrollOnEvent (const Viewport* vp, const MouseInputSource& src)
{
if (vp != nullptr)
{
switch (vp->getScrollOnDragMode())
{
case Viewport::ScrollOnDragMode::all: return true;
case Viewport::ScrollOnDragMode::nonHover: return ! src.canHover();
case Viewport::ScrollOnDragMode::never: return false;
}
}
return false;
}
};
} // namespace juce::detail
@@ -0,0 +1,61 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
struct WindowingHelpers
{
WindowingHelpers() = delete;
static Image createIconForFile (const File& file);
#if JUCE_WINDOWS
static bool isEmbeddedInForegroundProcess (Component* c);
static bool isWindowOnCurrentVirtualDesktop (void*);
#else
static bool isEmbeddedInForegroundProcess (Component*) { return false; }
static bool isWindowOnCurrentVirtualDesktop (void*) { return true; }
#endif
/* Returns true if this process is in the foreground, or if the viewComponent
is embedded into a window owned by the foreground process.
*/
static bool isForegroundOrEmbeddedProcess (Component* viewComponent)
{
return Process::isForegroundProcess() || isEmbeddedInForegroundProcess (viewComponent);
}
template <typename Value>
static BorderSize<int> roundToInt (BorderSize<Value> border)
{
return { ::juce::roundToInt (border.getTop()),
::juce::roundToInt (border.getLeft()),
::juce::roundToInt (border.getBottom()),
::juce::roundToInt (border.getRight()) };
}
};
} // namespace juce::detail