git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,67 @@
/*
==============================================================================
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
{
ComponentDragger::ComponentDragger() {}
ComponentDragger::~ComponentDragger() {}
//==============================================================================
void ComponentDragger::startDraggingComponent (Component* const componentToDrag, const MouseEvent& e)
{
jassert (componentToDrag != nullptr);
jassert (e.mods.isAnyMouseButtonDown()); // The event has to be a drag event!
if (componentToDrag != nullptr)
mouseDownWithinTarget = e.getEventRelativeTo (componentToDrag).getMouseDownPosition();
}
void ComponentDragger::dragComponent (Component* const componentToDrag, const MouseEvent& e,
ComponentBoundsConstrainer* const constrainer)
{
jassert (componentToDrag != nullptr);
jassert (e.mods.isAnyMouseButtonDown()); // The event has to be a drag event!
if (componentToDrag != nullptr)
{
auto bounds = componentToDrag->getBounds();
// If the component is a window, multiple mouse events can get queued while it's in the same position,
// so their coordinates become wrong after the first one moves the window, so in that case, we'll use
// the current mouse position instead of the one that the event contains...
if (componentToDrag->isOnDesktop())
bounds += componentToDrag->getLocalPoint (nullptr, e.source.getScreenPosition()).roundToInt() - mouseDownWithinTarget;
else
bounds += e.getEventRelativeTo (componentToDrag).getPosition() - mouseDownWithinTarget;
if (constrainer != nullptr)
constrainer->setBoundsForComponent (componentToDrag, bounds, false, false, false, false);
else
componentToDrag->setBounds (bounds);
}
}
} // namespace juce

View File

@ -0,0 +1,101 @@
/*
==============================================================================
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
{
//==============================================================================
/**
An object to take care of the logic for dragging components around with the mouse.
Very easy to use - in your mouseDown() callback, call startDraggingComponent(),
then in your mouseDrag() callback, call dragComponent().
When starting a drag, you can give it a ComponentBoundsConstrainer to use
to limit the component's position and keep it on-screen.
e.g. @code
class MyDraggableComp
{
ComponentDragger myDragger;
void mouseDown (const MouseEvent& e)
{
myDragger.startDraggingComponent (this, e);
}
void mouseDrag (const MouseEvent& e)
{
myDragger.dragComponent (this, e, nullptr);
}
};
@endcode
@tags{GUI}
*/
class JUCE_API ComponentDragger
{
public:
//==============================================================================
/** Creates a ComponentDragger. */
ComponentDragger();
/** Destructor. */
virtual ~ComponentDragger();
//==============================================================================
/** Call this from your component's mouseDown() method, to prepare for dragging.
@param componentToDrag the component that you want to drag
@param e the mouse event that is triggering the drag
@see dragComponent
*/
void startDraggingComponent (Component* componentToDrag,
const MouseEvent& e);
/** Call this from your mouseDrag() callback to move the component.
This will move the component, using the given constrainer object to check
the new position.
@param componentToDrag the component that you want to drag
@param e the current mouse-drag event
@param constrainer an optional constrainer object that should be used
to apply limits to the component's position. Pass
null if you don't want to constrain the movement.
@see startDraggingComponent
*/
void dragComponent (Component* componentToDrag,
const MouseEvent& e,
ComponentBoundsConstrainer* constrainer);
private:
//==============================================================================
Point<int> mouseDownWithinTarget;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentDragger)
};
} // namespace juce

View File

@ -0,0 +1,628 @@
/*
==============================================================================
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
{
bool juce_performDragDropFiles (const StringArray&, const bool copyFiles, bool& shouldStop);
bool juce_performDragDropText (const String&, bool& shouldStop);
//==============================================================================
class DragAndDropContainer::DragImageComponent : public Component,
private Timer
{
public:
DragImageComponent (const Image& im,
const var& desc,
Component* const sourceComponent,
const MouseInputSource* draggingSource,
DragAndDropContainer& ddc,
Point<int> offset)
: sourceDetails (desc, sourceComponent, Point<int>()),
image (im), owner (ddc),
mouseDragSource (draggingSource->getComponentUnderMouse()),
imageOffset (transformOffsetCoordinates (sourceComponent, offset)),
originalInputSourceIndex (draggingSource->getIndex()),
originalInputSourceType (draggingSource->getType())
{
updateSize();
if (mouseDragSource == nullptr)
mouseDragSource = sourceComponent;
mouseDragSource->addMouseListener (this, false);
startTimer (200);
setInterceptsMouseClicks (false, false);
setAlwaysOnTop (true);
}
~DragImageComponent() override
{
owner.dragImageComponents.remove (owner.dragImageComponents.indexOf (this), false);
if (mouseDragSource != nullptr)
{
mouseDragSource->removeMouseListener (this);
if (auto* current = getCurrentlyOver())
if (current->isInterestedInDragSource (sourceDetails))
current->itemDragExit (sourceDetails);
}
owner.dragOperationEnded (sourceDetails);
}
void paint (Graphics& g) override
{
if (isOpaque())
g.fillAll (Colours::white);
g.setOpacity (1.0f);
g.drawImageAt (image, 0, 0);
}
void mouseUp (const MouseEvent& e) override
{
if (e.originalComponent != this && isOriginalInputSource (e.source))
{
if (mouseDragSource != nullptr)
mouseDragSource->removeMouseListener (this);
// (note: use a local copy of this in case the callback runs
// a modal loop and deletes this object before the method completes)
auto details = sourceDetails;
DragAndDropTarget* finalTarget = nullptr;
auto wasVisible = isVisible();
setVisible (false);
Component* unused;
finalTarget = findTarget (e.getScreenPosition(), details.localPosition, unused);
if (wasVisible) // fade the component and remove it - it'll be deleted later by the timer callback
dismissWithAnimation (finalTarget == nullptr);
if (auto* parent = getParentComponent())
parent->removeChildComponent (this);
if (finalTarget != nullptr)
{
currentlyOverComp = nullptr;
finalTarget->itemDropped (details);
}
// careful - this object could now be deleted..
}
}
void mouseDrag (const MouseEvent& e) override
{
if (e.originalComponent != this && isOriginalInputSource (e.source))
updateLocation (true, e.getScreenPosition());
}
void updateLocation (const bool canDoExternalDrag, Point<int> screenPos)
{
auto details = sourceDetails;
setNewScreenPos (screenPos);
Component* newTargetComp;
auto* newTarget = findTarget (screenPos, details.localPosition, newTargetComp);
setVisible (newTarget == nullptr || newTarget->shouldDrawDragImageWhenOver());
if (newTargetComp != currentlyOverComp)
{
if (auto* lastTarget = getCurrentlyOver())
if (details.sourceComponent != nullptr && lastTarget->isInterestedInDragSource (details))
lastTarget->itemDragExit (details);
currentlyOverComp = newTargetComp;
if (newTarget != nullptr
&& newTarget->isInterestedInDragSource (details))
newTarget->itemDragEnter (details);
}
sendDragMove (details);
if (canDoExternalDrag)
{
auto now = Time::getCurrentTime();
if (getCurrentlyOver() != nullptr)
lastTimeOverTarget = now;
else if (now > lastTimeOverTarget + RelativeTime::milliseconds (700))
checkForExternalDrag (details, screenPos);
}
forceMouseCursorUpdate();
}
void updateImage (const Image& newImage)
{
image = newImage;
updateSize();
repaint();
}
void timerCallback() override
{
forceMouseCursorUpdate();
if (sourceDetails.sourceComponent == nullptr)
{
deleteSelf();
}
else
{
for (auto& s : Desktop::getInstance().getMouseSources())
{
if (isOriginalInputSource (s) && ! s.isDragging())
{
if (mouseDragSource != nullptr)
mouseDragSource->removeMouseListener (this);
deleteSelf();
break;
}
}
}
}
bool keyPressed (const KeyPress& key) override
{
if (key == KeyPress::escapeKey)
{
dismissWithAnimation (true);
deleteSelf();
return true;
}
return false;
}
bool canModalEventBeSentToComponent (const Component* targetComponent) override
{
return targetComponent == mouseDragSource;
}
// (overridden to avoid beeps when dragging)
void inputAttemptWhenModal() override {}
DragAndDropTarget::SourceDetails sourceDetails;
private:
Image image;
DragAndDropContainer& owner;
WeakReference<Component> mouseDragSource, currentlyOverComp;
const Point<int> imageOffset;
bool hasCheckedForExternalDrag = false;
Time lastTimeOverTarget;
int originalInputSourceIndex;
MouseInputSource::InputSourceType originalInputSourceType;
void updateSize()
{
setSize (image.getWidth(), image.getHeight());
}
void forceMouseCursorUpdate()
{
Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
}
DragAndDropTarget* getCurrentlyOver() const noexcept
{
return dynamic_cast<DragAndDropTarget*> (currentlyOverComp.get());
}
static Component* findDesktopComponentBelow (Point<int> screenPos)
{
auto& desktop = Desktop::getInstance();
for (auto i = desktop.getNumComponents(); --i >= 0;)
{
auto* desktopComponent = desktop.getComponent (i);
auto dPoint = desktopComponent->getLocalPoint (nullptr, screenPos);
if (auto* c = desktopComponent->getComponentAt (dPoint))
{
auto cPoint = c->getLocalPoint (desktopComponent, dPoint);
if (c->hitTest (cPoint.getX(), cPoint.getY()))
return c;
}
}
return nullptr;
}
Point<int> transformOffsetCoordinates (const Component* const sourceComponent, Point<int> offsetInSource) const
{
return getLocalPoint (sourceComponent, offsetInSource) - getLocalPoint (sourceComponent, Point<int>());
}
DragAndDropTarget* findTarget (Point<int> screenPos, Point<int>& relativePos,
Component*& resultComponent) const
{
auto* hit = getParentComponent();
if (hit == nullptr)
hit = findDesktopComponentBelow (screenPos);
else
hit = hit->getComponentAt (hit->getLocalPoint (nullptr, screenPos));
// (note: use a local copy of this in case the callback runs
// a modal loop and deletes this object before the method completes)
auto details = sourceDetails;
while (hit != nullptr)
{
if (auto* ddt = dynamic_cast<DragAndDropTarget*> (hit))
{
if (ddt->isInterestedInDragSource (details))
{
relativePos = hit->getLocalPoint (nullptr, screenPos);
resultComponent = hit;
return ddt;
}
}
hit = hit->getParentComponent();
}
resultComponent = nullptr;
return nullptr;
}
void setNewScreenPos (Point<int> screenPos)
{
auto newPos = screenPos - imageOffset;
if (auto* p = getParentComponent())
newPos = p->getLocalPoint (nullptr, newPos);
setTopLeftPosition (newPos);
}
void sendDragMove (DragAndDropTarget::SourceDetails& details) const
{
if (auto* target = getCurrentlyOver())
if (target->isInterestedInDragSource (details))
target->itemDragMove (details);
}
void checkForExternalDrag (DragAndDropTarget::SourceDetails& details, Point<int> screenPos)
{
if (! hasCheckedForExternalDrag)
{
if (Desktop::getInstance().findComponentAt (screenPos) == nullptr)
{
hasCheckedForExternalDrag = true;
if (ComponentPeer::getCurrentModifiersRealtime().isAnyMouseButtonDown())
{
StringArray files;
auto canMoveFiles = false;
if (owner.shouldDropFilesWhenDraggedExternally (details, files, canMoveFiles) && ! files.isEmpty())
{
MessageManager::callAsync ([=] { DragAndDropContainer::performExternalDragDropOfFiles (files, canMoveFiles); });
deleteSelf();
return;
}
String text;
if (owner.shouldDropTextWhenDraggedExternally (details, text) && text.isNotEmpty())
{
MessageManager::callAsync ([=] { DragAndDropContainer::performExternalDragDropOfText (text); });
deleteSelf();
return;
}
}
}
}
}
void deleteSelf()
{
delete this;
}
void dismissWithAnimation (const bool shouldSnapBack)
{
setVisible (true);
auto& animator = Desktop::getInstance().getAnimator();
if (shouldSnapBack && sourceDetails.sourceComponent != nullptr)
{
auto target = sourceDetails.sourceComponent->localPointToGlobal (sourceDetails.sourceComponent->getLocalBounds().getCentre());
auto ourCentre = localPointToGlobal (getLocalBounds().getCentre());
animator.animateComponent (this,
getBounds() + (target - ourCentre),
0.0f, 120,
true, 1.0, 1.0);
}
else
{
animator.fadeOut (this, 120);
}
}
bool isOriginalInputSource (const MouseInputSource& sourceToCheck)
{
return (sourceToCheck.getType() == originalInputSourceType
&& sourceToCheck.getIndex() == originalInputSourceIndex);
}
JUCE_DECLARE_NON_COPYABLE (DragImageComponent)
};
//==============================================================================
DragAndDropContainer::DragAndDropContainer()
{
}
DragAndDropContainer::~DragAndDropContainer()
{
}
void DragAndDropContainer::startDragging (const var& sourceDescription,
Component* sourceComponent,
Image dragImage,
const bool allowDraggingToExternalWindows,
const Point<int>* imageOffsetFromMouse,
const MouseInputSource* inputSourceCausingDrag)
{
if (isAlreadyDragging (sourceComponent))
return;
auto* draggingSource = getMouseInputSourceForDrag (sourceComponent, inputSourceCausingDrag);
if (draggingSource == nullptr || ! draggingSource->isDragging())
{
jassertfalse; // You must call startDragging() from within a mouseDown or mouseDrag callback!
return;
}
auto lastMouseDown = draggingSource->getLastMouseDownPosition().roundToInt();
Point<int> imageOffset;
if (dragImage.isNull())
{
dragImage = sourceComponent->createComponentSnapshot (sourceComponent->getLocalBounds())
.convertedToFormat (Image::ARGB);
dragImage.multiplyAllAlphas (0.6f);
auto lo = 150;
auto hi = 400;
auto relPos = sourceComponent->getLocalPoint (nullptr, lastMouseDown);
auto clipped = dragImage.getBounds().getConstrainedPoint (relPos);
Random random;
for (auto y = dragImage.getHeight(); --y >= 0;)
{
auto dy = (y - clipped.getY()) * (y - clipped.getY());
for (auto x = dragImage.getWidth(); --x >= 0;)
{
auto dx = x - clipped.getX();
auto distance = roundToInt (std::sqrt (dx * dx + dy));
if (distance > lo)
{
auto alpha = (distance > hi) ? 0
: (float) (hi - distance) / (float) (hi - lo)
+ random.nextFloat() * 0.008f;
dragImage.multiplyAlphaAt (x, y, alpha);
}
}
}
imageOffset = clipped;
}
else
{
if (imageOffsetFromMouse == nullptr)
imageOffset = dragImage.getBounds().getCentre();
else
imageOffset = dragImage.getBounds().getConstrainedPoint (-*imageOffsetFromMouse);
}
auto* dragImageComponent = dragImageComponents.add (new DragImageComponent (dragImage, sourceDescription, sourceComponent,
draggingSource, *this, imageOffset));
if (allowDraggingToExternalWindows)
{
if (! Desktop::canUseSemiTransparentWindows())
dragImageComponent->setOpaque (true);
dragImageComponent->addToDesktop (ComponentPeer::windowIgnoresMouseClicks
| ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses);
}
else
{
if (auto* thisComp = dynamic_cast<Component*> (this))
{
thisComp->addChildComponent (dragImageComponent);
}
else
{
jassertfalse; // Your DragAndDropContainer needs to be a Component!
return;
}
}
dragImageComponent->sourceDetails.localPosition = sourceComponent->getLocalPoint (nullptr, lastMouseDown);
dragImageComponent->updateLocation (false, lastMouseDown);
#if JUCE_WINDOWS
// Under heavy load, the layered window's paint callback can often be lost by the OS,
// so forcing a repaint at least once makes sure that the window becomes visible..
if (auto* peer = dragImageComponent->getPeer())
peer->performAnyPendingRepaintsNow();
#endif
dragOperationStarted (dragImageComponent->sourceDetails);
}
bool DragAndDropContainer::isDragAndDropActive() const
{
return dragImageComponents.size() > 0;
}
int DragAndDropContainer::getNumCurrentDrags() const
{
return dragImageComponents.size();
}
var DragAndDropContainer::getCurrentDragDescription() const
{
// If you are performing drag and drop in a multi-touch environment then
// you should use the getDragDescriptionForIndex() method instead!
jassert (dragImageComponents.size() < 2);
return dragImageComponents.size() != 0 ? dragImageComponents[0]->sourceDetails.description
: var();
}
var DragAndDropContainer::getDragDescriptionForIndex (int index) const
{
if (! isPositiveAndBelow (index, dragImageComponents.size()))
return {};
return dragImageComponents.getUnchecked (index)->sourceDetails.description;
}
void DragAndDropContainer::setCurrentDragImage (const Image& newImage)
{
// If you are performing drag and drop in a multi-touch environment then
// you should use the setDragImageForIndex() method instead!
jassert (dragImageComponents.size() < 2);
dragImageComponents[0]->updateImage (newImage);
}
void DragAndDropContainer::setDragImageForIndex (int index, const Image& newImage)
{
if (isPositiveAndBelow (index, dragImageComponents.size()))
dragImageComponents.getUnchecked (index)->updateImage (newImage);
}
DragAndDropContainer* DragAndDropContainer::findParentDragContainerFor (Component* c)
{
return c != nullptr ? c->findParentComponentOfClass<DragAndDropContainer>() : nullptr;
}
bool DragAndDropContainer::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails&, StringArray&, bool&)
{
return false;
}
bool DragAndDropContainer::shouldDropTextWhenDraggedExternally (const DragAndDropTarget::SourceDetails&, String&)
{
return false;
}
void DragAndDropContainer::dragOperationStarted (const DragAndDropTarget::SourceDetails&) {}
void DragAndDropContainer::dragOperationEnded (const DragAndDropTarget::SourceDetails&) {}
const MouseInputSource* DragAndDropContainer::getMouseInputSourceForDrag (Component* sourceComponent,
const MouseInputSource* inputSourceCausingDrag)
{
if (inputSourceCausingDrag == nullptr)
{
auto minDistance = std::numeric_limits<float>::max();
auto& desktop = Desktop::getInstance();
auto centrePoint = sourceComponent ? sourceComponent->getScreenBounds().getCentre().toFloat() : Point<float>();
auto numDragging = desktop.getNumDraggingMouseSources();
for (auto i = 0; i < numDragging; ++i)
{
if (auto* ms = desktop.getDraggingMouseSource (i))
{
auto distance = ms->getScreenPosition().getDistanceSquaredFrom (centrePoint);
if (distance < minDistance)
{
minDistance = distance;
inputSourceCausingDrag = ms;
}
}
}
}
// You must call startDragging() from within a mouseDown or mouseDrag callback!
jassert (inputSourceCausingDrag != nullptr && inputSourceCausingDrag->isDragging());
return inputSourceCausingDrag;
}
bool DragAndDropContainer::isAlreadyDragging (Component* component) const noexcept
{
for (auto* dragImageComp : dragImageComponents)
{
if (dragImageComp->sourceDetails.sourceComponent == component)
return true;
}
return false;
}
//==============================================================================
DragAndDropTarget::SourceDetails::SourceDetails (const var& desc, Component* comp, Point<int> pos) noexcept
: description (desc),
sourceComponent (comp),
localPosition (pos)
{
}
void DragAndDropTarget::itemDragEnter (const SourceDetails&) {}
void DragAndDropTarget::itemDragMove (const SourceDetails&) {}
void DragAndDropTarget::itemDragExit (const SourceDetails&) {}
bool DragAndDropTarget::shouldDrawDragImageWhenOver() { return true; }
//==============================================================================
void FileDragAndDropTarget::fileDragEnter (const StringArray&, int, int) {}
void FileDragAndDropTarget::fileDragMove (const StringArray&, int, int) {}
void FileDragAndDropTarget::fileDragExit (const StringArray&) {}
void TextDragAndDropTarget::textDragEnter (const String&, int, int) {}
void TextDragAndDropTarget::textDragMove (const String&, int, int) {}
void TextDragAndDropTarget::textDragExit (const String&) {}
} // namespace juce

View File

@ -0,0 +1,244 @@
/*
==============================================================================
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
{
//==============================================================================
/**
Enables drag-and-drop behaviour for a component and all its sub-components.
For a component to be able to make or receive drag-and-drop events, one of its parent
components must derive from this class. It's probably best for the top-level
component to implement it.
Then to start a drag operation, any sub-component can just call the startDragging()
method, and this object will take over, tracking the mouse and sending appropriate
callbacks to any child components derived from DragAndDropTarget which the mouse
moves over.
Note: If all that you need to do is to respond to files being drag-and-dropped from
the operating system onto your component, you don't need any of these classes: you can do this
simply by overriding FileDragAndDropTarget::filesDropped().
@see DragAndDropTarget
@tags{GUI}
*/
class JUCE_API DragAndDropContainer
{
public:
//==============================================================================
/** Creates a DragAndDropContainer.
The object that derives from this class must also be a Component.
*/
DragAndDropContainer();
/** Destructor. */
virtual ~DragAndDropContainer();
//==============================================================================
/** Begins a drag-and-drop operation.
This starts a drag-and-drop operation - call it when the user drags the
mouse in your drag-source component, and this object will track mouse
movements until the user lets go of the mouse button, and will send
appropriate messages to DragAndDropTarget objects that the mouse moves
over.
findParentDragContainerFor() is a handy method to call to find the
drag container to use for a component.
@param sourceDescription a string or value to use as the description of the thing being dragged -
this will be passed to the objects that might be dropped-onto so they can
decide whether they want to handle it
@param sourceComponent the component that is being dragged
@param dragImage the image to drag around underneath the mouse. If this is a null image,
a snapshot of the sourceComponent will be used instead.
@param allowDraggingToOtherJuceWindows if true, the dragged component will appear as a desktop
window, and can be dragged to DragAndDropTargets that are the
children of components other than this one.
@param imageOffsetFromMouse if an image has been passed-in, this specifies the offset
at which the image should be drawn from the mouse. If it isn't
specified, then the image will be centred around the mouse. If
an image hasn't been passed-in, this will be ignored.
@param inputSourceCausingDrag the mouse input source which started the drag. When calling
from within a mouseDown or mouseDrag event, you can pass
MouseEvent::source to this method. If this param is nullptr then JUCE
will use the mouse input source which is currently dragging. If there
are several dragging mouse input sources (which can often occur on mobile)
then JUCE will use the mouseInputSource which is closest to the sourceComponent.
*/
void startDragging (const var& sourceDescription,
Component* sourceComponent,
Image dragImage = Image(),
bool allowDraggingToOtherJuceWindows = false,
const Point<int>* imageOffsetFromMouse = nullptr,
const MouseInputSource* inputSourceCausingDrag = nullptr);
/** Returns true if something is currently being dragged. */
bool isDragAndDropActive() const;
/** Returns the number of things currently being dragged. */
int getNumCurrentDrags() const;
/** Returns the description of the thing that's currently being dragged.
If nothing's being dragged, this will return a null var, otherwise it'll return
the var that was passed into startDragging().
If you are using drag and drop in a multi-touch environment then you should use the
getDragDescriptionForIndex() method instead which takes a touch index parameter.
@see startDragging, getDragDescriptionForIndex
*/
var getCurrentDragDescription() const;
/** Same as the getCurrentDragDescription() method but takes a touch index parameter.
@see getCurrentDragDescription
*/
var getDragDescriptionForIndex (int index) const;
/** If a drag is in progress, this allows the image being shown to be dynamically updated.
If you are using drag and drop in a multi-touch environment then you should use the
setDragImageForIndex() method instead which takes a touch index parameter.
@see setDragImageForIndex
*/
void setCurrentDragImage (const Image& newImage);
/** Same as the setCurrentDragImage() method but takes a touch index parameter.
@see setCurrentDragImage
*/
void setDragImageForIndex (int index, const Image& newImage);
/** Utility to find the DragAndDropContainer for a given Component.
This will search up this component's parent hierarchy looking for the first
parent component which is a DragAndDropContainer.
It's useful when a component wants to call startDragging but doesn't know
the DragAndDropContainer it should to use.
Obviously this may return nullptr if it doesn't find a suitable component.
*/
static DragAndDropContainer* findParentDragContainerFor (Component* childComponent);
//==============================================================================
/** This performs an asynchronous drag-and-drop of a set of files to some external
application.
You can call this function in response to a mouseDrag callback, and it will
use a native operating system drag-and-drop operation to move or copy some
files to another application.
@param files a list of filenames to drag
@param canMoveFiles if true, the app that receives the files is allowed to move the files to a new location
(if this is appropriate). If false, the receiver is expected to make a copy of them.
@param sourceComponent normally, JUCE will assume that the component under the mouse is the source component
of the drag, but you can use this parameter to override this.
@param callback an optional completion callback that will be called when the operation has ended.
@returns true if the drag operation was successfully started, or false if it failed for some reason
@see performExternalDragDropOfText
*/
static bool performExternalDragDropOfFiles (const StringArray& files, bool canMoveFiles,
Component* sourceComponent = nullptr,
std::function<void()> callback = nullptr);
/** This performs an asynchronous drag-and-drop of a block of text to some external
application.
You can call this function in response to a mouseDrag callback, and it will
use a native operating system drag-and-drop operation to move or copy some
text to another application.
@param text the text to copy
@param sourceComponent Normally, JUCE will assume that the component under the mouse is the source component
of the drag, but you can use this parameter to override this.
@param callback an optional completion callback that will be called when the operation has ended.
@returns true if the drag operation was successfully started, or false if it failed for some reason
@see performExternalDragDropOfFiles
*/
static bool performExternalDragDropOfText (const String& text, Component* sourceComponent = nullptr,
std::function<void()> callback = nullptr);
protected:
/** Override this if you want to be able to perform an external drag of a set of files
when the user drags outside of this container component.
This method will be called when a drag operation moves outside the JUCE window,
and if you want it to then perform a file drag-and-drop, add the filenames you want
to the array passed in, and return true.
@param sourceDetails information about the source of the drag operation
@param files on return, the filenames you want to drag
@param canMoveFiles on return, true if it's ok for the receiver to move the files; false if
it must make a copy of them (see the performExternalDragDropOfFiles() method)
@see performExternalDragDropOfFiles, shouldDropTextWhenDraggedExternally
*/
virtual bool shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
StringArray& files, bool& canMoveFiles);
/** Override this if you want to be able to perform an external drag of text
when the user drags outside of this container component.
This method will be called when a drag operation moves outside the JUCE window,
and if you want it to then perform a text drag-and-drop, copy the text you want to
be dragged into the argument provided and return true.
@param sourceDetails information about the source of the drag operation
@param text on return, the text you want to drag
@see shouldDropFilesWhenDraggedExternally
*/
virtual bool shouldDropTextWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
String& text);
/** Subclasses can override this to be told when a drag starts. */
virtual void dragOperationStarted (const DragAndDropTarget::SourceDetails&);
/** Subclasses can override this to be told when a drag finishes. */
virtual void dragOperationEnded (const DragAndDropTarget::SourceDetails&);
private:
//==============================================================================
class DragImageComponent;
OwnedArray<DragImageComponent> dragImageComponents;
const MouseInputSource* getMouseInputSourceForDrag (Component* sourceComponent, const MouseInputSource* inputSourceCausingDrag);
bool isAlreadyDragging (Component* sourceComponent) const noexcept;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DragAndDropContainer)
};
} // namespace juce

View File

@ -0,0 +1,140 @@
/*
==============================================================================
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
{
//==============================================================================
/**
Components derived from this class can have things dropped onto them by a DragAndDropContainer.
To create a component that can receive things drag-and-dropped by a DragAndDropContainer,
derive your component from this class, and make sure that it is somewhere inside a
DragAndDropContainer component.
Note: If all that you need to do is to respond to files being drag-and-dropped from
the operating system onto your component, you don't need any of these classes: instead
see the FileDragAndDropTarget class.
@see DragAndDropContainer, FileDragAndDropTarget
@tags{GUI}
*/
class JUCE_API DragAndDropTarget
{
public:
/** Destructor. */
virtual ~DragAndDropTarget() = default;
//==============================================================================
/** Contains details about the source of a drag-and-drop operation. */
class JUCE_API SourceDetails
{
public:
/** Creates a SourceDetails object from its various settings. */
SourceDetails (const var& description,
Component* sourceComponent,
Point<int> localPosition) noexcept;
/** A descriptor for the drag - this is set DragAndDropContainer::startDragging(). */
var description;
/** The component from the drag operation was started. */
WeakReference<Component> sourceComponent;
/** The local position of the mouse, relative to the target component.
Note that for calls such as isInterestedInDragSource(), this may be a null position.
*/
Point<int> localPosition;
};
//==============================================================================
/** Callback to check whether this target is interested in the type of object being
dragged.
@param dragSourceDetails contains information about the source of the drag operation.
@returns true if this component wants to receive the other callbacks regarding this
type of object; if it returns false, no other callbacks will be made.
*/
virtual bool isInterestedInDragSource (const SourceDetails& dragSourceDetails) = 0;
/** Callback to indicate that something is being dragged over this component.
This gets called when the user moves the mouse into this component while dragging
something.
Use this callback as a trigger to make your component repaint itself to give the
user feedback about whether the item can be dropped here or not.
@param dragSourceDetails contains information about the source of the drag operation.
@see itemDragExit
*/
virtual void itemDragEnter (const SourceDetails& dragSourceDetails);
/** Callback to indicate that the user is dragging something over this component.
This gets called when the user moves the mouse over this component while dragging
something. Normally overriding itemDragEnter() and itemDragExit() are enough, but
this lets you know what happens in-between.
@param dragSourceDetails contains information about the source of the drag operation.
*/
virtual void itemDragMove (const SourceDetails& dragSourceDetails);
/** Callback to indicate that something has been dragged off the edge of this component.
This gets called when the user moves the mouse out of this component while dragging
something.
If you've used itemDragEnter() to repaint your component and give feedback, use this
as a signal to repaint it in its normal state.
@param dragSourceDetails contains information about the source of the drag operation.
@see itemDragEnter
*/
virtual void itemDragExit (const SourceDetails& dragSourceDetails);
/** Callback to indicate that the user has dropped something onto this component.
When the user drops an item this get called, and you can use the description to
work out whether your object wants to deal with it or not.
Note that after this is called, the itemDragExit method may not be called, so you should
clean up in here if there's anything you need to do when the drag finishes.
@param dragSourceDetails contains information about the source of the drag operation.
*/
virtual void itemDropped (const SourceDetails& dragSourceDetails) = 0;
/** Overriding this allows the target to tell the drag container whether to
draw the drag image while the cursor is over it.
By default it returns true, but if you return false, then the normal drag
image will not be shown when the cursor is over this target.
*/
virtual bool shouldDrawDragImageWhenOver();
};
} // namespace juce

View File

@ -0,0 +1,106 @@
/*
==============================================================================
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
{
/**
Components derived from this class can have files dropped onto them by an external application.
@see DragAndDropContainer
@tags{GUI}
*/
class JUCE_API FileDragAndDropTarget
{
public:
/** Destructor. */
virtual ~FileDragAndDropTarget() = default;
/** Callback to check whether this target is interested in the set of files being offered.
Note that this will be called repeatedly when the user is dragging the mouse around over your
component, so don't do anything time-consuming in here, like opening the files to have a look
inside them!
@param files the set of (absolute) pathnames of the files that the user is dragging
@returns true if this component wants to receive the other callbacks regarding this
type of object; if it returns false, no other callbacks will be made.
*/
virtual bool isInterestedInFileDrag (const StringArray& files) = 0;
/** Callback to indicate that some files are being dragged over this component.
This gets called when the user moves the mouse into this component while dragging.
Use this callback as a trigger to make your component repaint itself to give the
user feedback about whether the files can be dropped here or not.
@param files the set of (absolute) pathnames of the files that the user is dragging
@param x the mouse x position, relative to this component
@param y the mouse y position, relative to this component
*/
virtual void fileDragEnter (const StringArray& files, int x, int y);
/** Callback to indicate that the user is dragging some files over this component.
This gets called when the user moves the mouse over this component while dragging.
Normally overriding itemDragEnter() and itemDragExit() are enough, but
this lets you know what happens in-between.
@param files the set of (absolute) pathnames of the files that the user is dragging
@param x the mouse x position, relative to this component
@param y the mouse y position, relative to this component
*/
virtual void fileDragMove (const StringArray& files, int x, int y);
/** Callback to indicate that the mouse has moved away from this component.
This gets called when the user moves the mouse out of this component while dragging
the files.
If you've used fileDragEnter() to repaint your component and give feedback, use this
as a signal to repaint it in its normal state.
@param files the set of (absolute) pathnames of the files that the user is dragging
*/
virtual void fileDragExit (const StringArray& files);
/** Callback to indicate that the user has dropped the files onto this component.
When the user drops the files, this get called, and you can use the files in whatever
way is appropriate.
Note that after this is called, the fileDragExit method may not be called, so you should
clean up in here if there's anything you need to do when the drag finishes.
@param files the set of (absolute) pathnames of the files that the user is dragging
@param x the mouse x position, relative to this component
@param y the mouse y position, relative to this component
*/
virtual void filesDropped (const StringArray& files, int x, int y) = 0;
};
} // namespace juce

View File

@ -0,0 +1,224 @@
/*
==============================================================================
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
{
//==============================================================================
/**
A class used by the LassoComponent to manage the things that it selects.
This allows the LassoComponent to find out which items are within the lasso,
and to change the list of selected items.
@see LassoComponent, SelectedItemSet
@tags{GUI}
*/
template <class SelectableItemType>
class LassoSource
{
public:
/** Destructor. */
virtual ~LassoSource() = default;
/** Returns the set of items that lie within a given lassoable region.
Your implementation of this method must find all the relevant items that lie
within the given rectangle. and add them to the itemsFound array.
The coordinates are relative to the top-left of the lasso component's parent
component. (i.e. they are the same as the size and position of the lasso
component itself).
*/
virtual void findLassoItemsInArea (Array<SelectableItemType>& itemsFound,
const Rectangle<int>& area) = 0;
/** Returns the SelectedItemSet that the lasso should update.
This set will be continuously updated by the LassoComponent as it gets
dragged around, so make sure that you've got a ChangeListener attached to
the set so that your UI objects will know when the selection changes and
be able to update themselves appropriately.
*/
virtual SelectedItemSet<SelectableItemType>& getLassoSelection() = 0;
};
//==============================================================================
/**
A component that acts as a rectangular selection region, which you drag with
the mouse to select groups of objects (in conjunction with a SelectedItemSet).
To use one of these:
- In your mouseDown or mouseDrag event, add the LassoComponent to your parent
component, and call its beginLasso() method, giving it a
suitable LassoSource object that it can use to find out which items are in
the active area.
- Each time your parent component gets a mouseDrag event, call dragLasso()
to update the lasso's position - it will use its LassoSource to calculate and
update the current selection.
- After the drag has finished and you get a mouseUp callback, you should call
endLasso() to clean up. This will make the lasso component invisible, and you
can remove it from the parent component, or delete it.
The class takes into account the modifier keys that are being held down while
the lasso is being dragged, so if shift is pressed, then any lassoed items will
be added to the original selection; if ctrl or command is pressed, they will be
xor'ed with any previously selected items.
@see LassoSource, SelectedItemSet
@tags{GUI}
*/
template <class SelectableItemType>
class LassoComponent : public Component
{
public:
//==============================================================================
/** Creates a Lasso component. */
LassoComponent() = default;
//==============================================================================
/** Call this in your mouseDown event, to initialise a drag.
Pass in a suitable LassoSource object which the lasso will use to find
the items and change the selection.
After using this method to initialise the lasso, repeatedly call dragLasso()
in your component's mouseDrag callback.
@see dragLasso, endLasso, LassoSource
*/
void beginLasso (const MouseEvent& e, LassoSource<SelectableItemType>* lassoSource)
{
jassert (source == nullptr); // this suggests that you didn't call endLasso() after the last drag...
jassert (lassoSource != nullptr); // the source can't be null!
jassert (getParentComponent() != nullptr); // you need to add this to a parent component for it to work!
source = lassoSource;
if (lassoSource != nullptr)
originalSelection = lassoSource->getLassoSelection().getItemArray();
setSize (0, 0);
dragStartPos = e.getMouseDownPosition();
}
/** Call this in your mouseDrag event, to update the lasso's position.
This must be repeatedly calling when the mouse is dragged, after you've
first initialised the lasso with beginLasso().
This method takes into account the modifier keys that are being held down, so
if shift is pressed, then the lassoed items will be added to any that were
previously selected; if ctrl or command is pressed, then they will be xor'ed
with previously selected items.
@see beginLasso, endLasso
*/
void dragLasso (const MouseEvent& e)
{
if (source != nullptr)
{
setBounds (Rectangle<int> (dragStartPos, e.getPosition()));
setVisible (true);
Array<SelectableItemType> itemsInLasso;
source->findLassoItemsInArea (itemsInLasso, getBounds());
if (e.mods.isShiftDown())
{
itemsInLasso.removeValuesIn (originalSelection); // to avoid duplicates
itemsInLasso.addArray (originalSelection);
}
else if (e.mods.isCommandDown() || e.mods.isAltDown())
{
auto originalMinusNew = originalSelection;
originalMinusNew.removeValuesIn (itemsInLasso);
itemsInLasso.removeValuesIn (originalSelection);
itemsInLasso.addArray (originalMinusNew);
}
source->getLassoSelection() = SelectedItemSet<SelectableItemType> (itemsInLasso);
}
}
/** Call this in your mouseUp event, after the lasso has been dragged.
@see beginLasso, dragLasso
*/
void endLasso()
{
source = nullptr;
originalSelection.clear();
setVisible (false);
}
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the label.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
Note that you can also use the constants from TextEditor::ColourIds to change the
colour of the text editor that is opened when a label is editable.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
lassoFillColourId = 0x1000440, /**< The colour to fill the lasso rectangle with. */
lassoOutlineColourId = 0x1000441, /**< The colour to draw the outline with. */
};
//==============================================================================
/** @internal */
void paint (Graphics& g) override
{
getLookAndFeel().drawLasso (g, *this);
// this suggests that you've left a lasso comp lying around after the
// mouse drag has finished.. Be careful to call endLasso() when you get a
// mouse-up event.
jassert (isMouseButtonDownAnywhere());
}
/** @internal */
bool hitTest (int, int) override { return false; }
private:
//==============================================================================
Array<SelectableItemType> originalSelection;
LassoSource<SelectableItemType>* source = nullptr;
Point<int> dragStartPos;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LassoComponent)
};
} // namespace juce

View File

@ -0,0 +1,218 @@
/*
==============================================================================
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
{
struct CustomMouseCursorInfo
{
CustomMouseCursorInfo (const Image& im, Point<int> hs, float scale = 1.0f) noexcept
: image (im), hotspot (hs), scaleFactor (scale)
{}
void* create() const;
Image image;
const Point<int> hotspot;
const float scaleFactor;
JUCE_DECLARE_NON_COPYABLE (CustomMouseCursorInfo)
};
class MouseCursor::SharedCursorHandle
{
public:
explicit SharedCursorHandle (const MouseCursor::StandardCursorType type)
: handle (createStandardMouseCursor (type)),
standardType (type),
isStandard (true)
{
}
SharedCursorHandle (const Image& image, Point<int> hotSpot, float scaleFactor)
: info (new CustomMouseCursorInfo (image, hotSpot, scaleFactor)),
handle (info->create()),
standardType (MouseCursor::NormalCursor),
isStandard (false)
{
// your hotspot needs to be within the bounds of the image!
jassert (image.getBounds().contains (hotSpot));
}
~SharedCursorHandle()
{
deleteMouseCursor (handle, isStandard);
}
static SharedCursorHandle* createStandard (const MouseCursor::StandardCursorType type)
{
jassert (isPositiveAndBelow (type, MouseCursor::NumStandardCursorTypes));
const SpinLock::ScopedLockType sl (lock);
auto& c = getSharedCursor (type);
if (c == nullptr)
c = new SharedCursorHandle (type);
else
c->retain();
return c;
}
bool isStandardType (MouseCursor::StandardCursorType type) const noexcept
{
return type == standardType && isStandard;
}
SharedCursorHandle* retain() noexcept
{
++refCount;
return this;
}
void release()
{
if (--refCount == 0)
{
if (isStandard)
{
const SpinLock::ScopedLockType sl (lock);
getSharedCursor (standardType) = nullptr;
}
delete this;
}
}
void* getHandle() const noexcept { return handle; }
void setHandle (void* newHandle) { handle = newHandle; }
MouseCursor::StandardCursorType getType() const noexcept { return standardType; }
CustomMouseCursorInfo* getCustomInfo() const noexcept { return info.get(); }
private:
std::unique_ptr<CustomMouseCursorInfo> info;
void* handle;
Atomic<int> refCount { 1 };
const MouseCursor::StandardCursorType standardType;
const bool isStandard;
static SpinLock lock;
static SharedCursorHandle*& getSharedCursor (const MouseCursor::StandardCursorType type)
{
static SharedCursorHandle* cursors[MouseCursor::NumStandardCursorTypes] = {};
return cursors[type];
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SharedCursorHandle)
};
SpinLock MouseCursor::SharedCursorHandle::lock;
//==============================================================================
MouseCursor::MouseCursor() noexcept
{
}
MouseCursor::MouseCursor (const StandardCursorType type)
: cursorHandle (type != MouseCursor::NormalCursor ? SharedCursorHandle::createStandard (type) : nullptr)
{
}
MouseCursor::MouseCursor (const Image& image, int hotSpotX, int hotSpotY)
: MouseCursor (image, hotSpotX, hotSpotY, 1.0f)
{
}
MouseCursor::MouseCursor (const Image& image, int hotSpotX, int hotSpotY, float scaleFactor)
: cursorHandle (new SharedCursorHandle (image, { hotSpotX, hotSpotY }, scaleFactor))
{
}
MouseCursor::MouseCursor (const MouseCursor& other)
: cursorHandle (other.cursorHandle == nullptr ? nullptr : other.cursorHandle->retain())
{
}
MouseCursor::~MouseCursor()
{
if (cursorHandle != nullptr)
cursorHandle->release();
}
MouseCursor& MouseCursor::operator= (const MouseCursor& other)
{
if (other.cursorHandle != nullptr)
other.cursorHandle->retain();
if (cursorHandle != nullptr)
cursorHandle->release();
cursorHandle = other.cursorHandle;
return *this;
}
MouseCursor::MouseCursor (MouseCursor&& other) noexcept
: cursorHandle (other.cursorHandle)
{
other.cursorHandle = nullptr;
}
MouseCursor& MouseCursor::operator= (MouseCursor&& other) noexcept
{
std::swap (cursorHandle, other.cursorHandle);
return *this;
}
bool MouseCursor::operator== (const MouseCursor& other) const noexcept
{
return getHandle() == other.getHandle();
}
bool MouseCursor::operator== (StandardCursorType type) const noexcept
{
return cursorHandle != nullptr ? cursorHandle->isStandardType (type)
: (type == NormalCursor);
}
bool MouseCursor::operator!= (const MouseCursor& other) const noexcept { return ! operator== (other); }
bool MouseCursor::operator!= (StandardCursorType type) const noexcept { return ! operator== (type); }
void* MouseCursor::getHandle() const noexcept
{
return cursorHandle != nullptr ? cursorHandle->getHandle() : nullptr;
}
void MouseCursor::showWaitCursor()
{
Desktop::getInstance().getMainMouseSource().showMouseCursor (MouseCursor::WaitCursor);
}
void MouseCursor::hideWaitCursor()
{
Desktop::getInstance().getMainMouseSource().revealCursor();
}
} // namespace juce

View File

@ -0,0 +1,182 @@
/*
==============================================================================
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
{
//==============================================================================
/**
Represents a mouse cursor image.
This object can either be used to represent one of the standard mouse
cursor shapes, or a custom one generated from an image.
@tags{GUI}
*/
class JUCE_API MouseCursor final
{
public:
//==============================================================================
/** The set of available standard mouse cursors. */
enum StandardCursorType
{
ParentCursor = 0, /**< Indicates that the component's parent's cursor should be used. */
NoCursor, /**< An invisible cursor. */
NormalCursor, /**< The standard arrow cursor. */
WaitCursor, /**< The normal hourglass or spinning-beachball 'busy' cursor. */
IBeamCursor, /**< A vertical I-beam for positioning within text. */
CrosshairCursor, /**< A pair of crosshairs. */
CopyingCursor, /**< The normal arrow cursor, but with a "+" on it to indicate
that you're dragging a copy of something. */
PointingHandCursor, /**< A hand with a pointing finger, for clicking on web-links. */
DraggingHandCursor, /**< An open flat hand for dragging heavy objects around. */
LeftRightResizeCursor, /**< An arrow pointing left and right. */
UpDownResizeCursor, /**< an arrow pointing up and down. */
UpDownLeftRightResizeCursor, /**< An arrow pointing up, down, left and right. */
TopEdgeResizeCursor, /**< A platform-specific cursor for resizing the top-edge of a window. */
BottomEdgeResizeCursor, /**< A platform-specific cursor for resizing the bottom-edge of a window. */
LeftEdgeResizeCursor, /**< A platform-specific cursor for resizing the left-edge of a window. */
RightEdgeResizeCursor, /**< A platform-specific cursor for resizing the right-edge of a window. */
TopLeftCornerResizeCursor, /**< A platform-specific cursor for resizing the top-left-corner of a window. */
TopRightCornerResizeCursor, /**< A platform-specific cursor for resizing the top-right-corner of a window. */
BottomLeftCornerResizeCursor, /**< A platform-specific cursor for resizing the bottom-left-corner of a window. */
BottomRightCornerResizeCursor, /**< A platform-specific cursor for resizing the bottom-right-corner of a window. */
NumStandardCursorTypes
};
//==============================================================================
/** Creates the standard arrow cursor. */
MouseCursor() noexcept;
/** Creates one of the standard mouse cursor */
MouseCursor (StandardCursorType);
/** Creates a custom cursor from an image.
@param image the image to use for the cursor - if this is bigger than the
system can manage, it might get scaled down first, and might
also have to be turned to black-and-white if it can't do colour
cursors.
@param hotSpotX the x position of the cursor's hotspot within the image
@param hotSpotY the y position of the cursor's hotspot within the image
*/
MouseCursor (const Image& image, int hotSpotX, int hotSpotY);
/** Creates a custom cursor from an image.
@param image the image to use for the cursor - if this is bigger than the
system can manage, it might get scaled down first, and might
also have to be turned to black-and-white if it can't do colour
cursors.
@param hotSpotX the x position of the cursor's hotspot within the image
@param hotSpotY the y position of the cursor's hotspot within the image
@param scaleFactor the factor by which this image is larger than the target
screen size of the cursor.
*/
MouseCursor (const Image& image, int hotSpotX, int hotSpotY, float scaleFactor);
//==============================================================================
/** Creates a copy of another cursor object. */
MouseCursor (const MouseCursor&);
/** Copies this cursor from another object. */
MouseCursor& operator= (const MouseCursor&);
/** Destructor. */
~MouseCursor();
/** Move constructor */
MouseCursor (MouseCursor&&) noexcept;
/** Move assignment operator */
MouseCursor& operator= (MouseCursor&&) noexcept;
/** Checks whether two mouse cursors are the same.
For custom cursors, two cursors created from the same image won't be
recognised as the same, only MouseCursor objects that have been
copied from the same object.
*/
bool operator== (const MouseCursor&) const noexcept;
/** Checks whether two mouse cursors are the same.
For custom cursors, two cursors created from the same image won't be
recognised as the same, only MouseCursor objects that have been
copied from the same object.
*/
bool operator!= (const MouseCursor&) const noexcept;
/** Checks whether this cursor is of the standard type mentioned. */
bool operator== (StandardCursorType type) const noexcept;
/** Checks whether this cursor is of the standard type mentioned. */
bool operator!= (StandardCursorType type) const noexcept;
//==============================================================================
/** Makes the system show its default 'busy' cursor.
This will turn the system cursor to an hourglass or spinning beachball
until the next time the mouse is moved, or hideWaitCursor() is called.
This is handy if the message loop is about to block for a couple of
seconds while busy and you want to give the user feedback about this.
*/
static void showWaitCursor();
/** If showWaitCursor has been called, this will return the mouse to its
normal state.
This will look at what component is under the mouse, and update the
cursor to be the correct one for that component.
@see showWaitCursor
*/
static void hideWaitCursor();
private:
//==============================================================================
class SharedCursorHandle;
friend class SharedCursorHandle;
SharedCursorHandle* cursorHandle = nullptr;
friend class MouseInputSourceInternal;
void showInWindow (ComponentPeer*) const;
void* getHandle() const noexcept;
static void* createStandardMouseCursor (MouseCursor::StandardCursorType);
static void deleteMouseCursor (void* cursorHandle, bool isStandard);
JUCE_LEAK_DETECTOR (MouseCursor)
};
} // namespace juce

View File

@ -0,0 +1,142 @@
/*
==============================================================================
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
{
MouseEvent::MouseEvent (MouseInputSource inputSource,
Point<float> pos,
ModifierKeys modKeys,
float force,
float o, float r,
float tX, float tY,
Component* const eventComp,
Component* const originator,
Time time,
Point<float> downPos,
Time downTime,
const int numClicks,
const bool mouseWasDragged) noexcept
: position (pos),
x (roundToInt (pos.x)),
y (roundToInt (pos.y)),
mods (modKeys),
pressure (force),
orientation (o), rotation (r),
tiltX (tX), tiltY (tY),
mouseDownPosition (downPos),
eventComponent (eventComp),
originalComponent (originator),
eventTime (time),
mouseDownTime (downTime),
source (inputSource),
numberOfClicks ((uint8) numClicks),
wasMovedSinceMouseDown ((uint8) (mouseWasDragged ? 1 : 0))
{
}
MouseEvent::~MouseEvent() noexcept
{
}
//==============================================================================
MouseEvent MouseEvent::getEventRelativeTo (Component* const otherComponent) const noexcept
{
jassert (otherComponent != nullptr);
return MouseEvent (source, otherComponent->getLocalPoint (eventComponent, position),
mods, pressure, orientation, rotation, tiltX, tiltY,
otherComponent, originalComponent, eventTime,
otherComponent->getLocalPoint (eventComponent, mouseDownPosition),
mouseDownTime, numberOfClicks, wasMovedSinceMouseDown != 0);
}
MouseEvent MouseEvent::withNewPosition (Point<float> newPosition) const noexcept
{
return MouseEvent (source, newPosition, mods, pressure, orientation, rotation, tiltX, tiltY,
eventComponent, originalComponent, eventTime, mouseDownPosition, mouseDownTime,
numberOfClicks, wasMovedSinceMouseDown != 0);
}
MouseEvent MouseEvent::withNewPosition (Point<int> newPosition) const noexcept
{
return MouseEvent (source, newPosition.toFloat(), mods, pressure, orientation, rotation,
tiltX, tiltY, eventComponent, originalComponent, eventTime, mouseDownPosition,
mouseDownTime, numberOfClicks, wasMovedSinceMouseDown != 0);
}
//==============================================================================
bool MouseEvent::mouseWasDraggedSinceMouseDown() const noexcept
{
return wasMovedSinceMouseDown != 0;
}
bool MouseEvent::mouseWasClicked() const noexcept
{
return ! mouseWasDraggedSinceMouseDown();
}
int MouseEvent::getLengthOfMousePress() const noexcept
{
if (mouseDownTime.toMilliseconds() > 0)
return jmax (0, (int) (eventTime - mouseDownTime).inMilliseconds());
return 0;
}
//==============================================================================
Point<int> MouseEvent::getPosition() const noexcept { return Point<int> (x, y); }
Point<int> MouseEvent::getScreenPosition() const { return eventComponent->localPointToGlobal (getPosition()); }
Point<int> MouseEvent::getMouseDownPosition() const noexcept { return mouseDownPosition.roundToInt(); }
Point<int> MouseEvent::getMouseDownScreenPosition() const { return eventComponent->localPointToGlobal (mouseDownPosition).roundToInt(); }
Point<int> MouseEvent::getOffsetFromDragStart() const noexcept { return (position - mouseDownPosition).roundToInt(); }
int MouseEvent::getDistanceFromDragStart() const noexcept { return roundToInt (mouseDownPosition.getDistanceFrom (position)); }
int MouseEvent::getMouseDownX() const noexcept { return roundToInt (mouseDownPosition.x); }
int MouseEvent::getMouseDownY() const noexcept { return roundToInt (mouseDownPosition.y); }
int MouseEvent::getDistanceFromDragStartX() const noexcept { return getOffsetFromDragStart().x; }
int MouseEvent::getDistanceFromDragStartY() const noexcept { return getOffsetFromDragStart().y; }
int MouseEvent::getScreenX() const { return getScreenPosition().x; }
int MouseEvent::getScreenY() const { return getScreenPosition().y; }
int MouseEvent::getMouseDownScreenX() const { return getMouseDownScreenPosition().x; }
int MouseEvent::getMouseDownScreenY() const { return getMouseDownScreenPosition().y; }
bool MouseEvent::isPressureValid() const noexcept { return pressure > 0.0f && pressure < 1.0f; }
bool MouseEvent::isOrientationValid() const noexcept { return orientation >= 0.0f && orientation <= MathConstants<float>::twoPi; }
bool MouseEvent::isRotationValid() const noexcept { return rotation >= 0 && rotation <= MathConstants<float>::twoPi; }
bool MouseEvent::isTiltValid (bool isX) const noexcept { return isX ? (tiltX >= -1.0f && tiltX <= 1.0f) : (tiltY >= -1.0f && tiltY <= 1.0f); }
//==============================================================================
static int doubleClickTimeOutMs = 400;
int MouseEvent::getDoubleClickTimeout() noexcept { return doubleClickTimeOutMs; }
void MouseEvent::setDoubleClickTimeout (const int newTime) noexcept { doubleClickTimeOutMs = newTime; }
} // namespace juce

View File

@ -0,0 +1,453 @@
/*
==============================================================================
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
{
//==============================================================================
/**
Contains position and status information about a mouse event.
@see MouseListener, Component::mouseMove, Component::mouseEnter, Component::mouseExit,
Component::mouseDown, Component::mouseUp, Component::mouseDrag
@tags{GUI}
*/
class JUCE_API MouseEvent final
{
public:
//==============================================================================
/** Creates a MouseEvent.
Normally an application will never need to use this.
@param source the source that's invoking the event
@param position the position of the mouse, relative to the component that is passed-in
@param modifiers the key modifiers at the time of the event
@param pressure the pressure of the touch or stylus, in the range 0 to 1. Devices that
do not support force information may return 0.0, 1.0, or a negative value,
depending on the platform
@param orientation the orientation of the touch input for this event in radians. The default is 0
@param rotation the rotation of the pen device for this event in radians. The default is 0
@param tiltX the tilt of the pen device along the x-axis between -1.0 and 1.0. The default is 0
@param tiltY the tilt of the pen device along the y-axis between -1.0 and 1.0. The default is 0
@param eventComponent the component that the mouse event applies to
@param originator the component that originally received the event
@param eventTime the time the event happened
@param mouseDownPos the position of the corresponding mouse-down event (relative to the component that is passed-in).
If there isn't a corresponding mouse-down (e.g. for a mouse-move), this will just be
the same as the current mouse-x position.
@param mouseDownTime the time at which the corresponding mouse-down event happened
If there isn't a corresponding mouse-down (e.g. for a mouse-move), this will just be
the same as the current mouse-event time.
@param numberOfClicks how many clicks, e.g. a double-click event will be 2, a triple-click will be 3, etc
@param mouseWasDragged whether the mouse has been dragged significantly since the previous mouse-down
*/
MouseEvent (MouseInputSource source,
Point<float> position,
ModifierKeys modifiers,
float pressure,
float orientation, float rotation,
float tiltX, float tiltY,
Component* eventComponent,
Component* originator,
Time eventTime,
Point<float> mouseDownPos,
Time mouseDownTime,
int numberOfClicks,
bool mouseWasDragged) noexcept;
/** Destructor. */
~MouseEvent() noexcept;
//==============================================================================
/** The position of the mouse when the event occurred.
This value is relative to the top-left of the component to which the
event applies (as indicated by the MouseEvent::eventComponent field).
This is a more accurate floating-point version of the position returned by
getPosition() and the integer x and y member variables.
*/
const Point<float> position;
/** The x-position of the mouse when the event occurred.
This value is relative to the top-left of the component to which the
event applies (as indicated by the MouseEvent::eventComponent field).
For a floating-point coordinate, see MouseEvent::position
*/
const int x;
/** The y-position of the mouse when the event occurred.
This value is relative to the top-left of the component to which the
event applies (as indicated by the MouseEvent::eventComponent field).
For a floating-point coordinate, see MouseEvent::position
*/
const int y;
/** The key modifiers associated with the event.
This will let you find out which mouse buttons were down, as well as which
modifier keys were held down.
When used for mouse-up events, this will indicate the state of the mouse buttons
just before they were released, so that you can tell which button they let go of.
*/
const ModifierKeys mods;
/** The pressure of the touch or stylus for this event.
The range is 0 (soft) to 1 (hard).
If the input device doesn't provide any pressure data, it may return a negative
value here, or 0.0 or 1.0, depending on the platform.
*/
const float pressure;
/** The orientation of the touch input for this event in radians where 0 indicates a touch aligned with the x-axis
and pointing from left to right; increasing values indicate rotation in the clockwise direction. The default is 0.
*/
const float orientation;
/** The rotation of the pen device for this event in radians. Indicates the clockwise
rotation, or twist, of the pen. The default is 0.
*/
const float rotation;
/** The tilt of the pen device along the x-axis between -1.0 and 1.0. A positive value indicates
a tilt to the right. The default is 0.
*/
const float tiltX;
/** The tilt of the pen device along the y-axis between -1.0 and 1.0. A positive value indicates
a tilt toward the user. The default is 0.
*/
const float tiltY;
/** The coordinates of the last place that a mouse button was pressed.
The coordinates are relative to the component specified in MouseEvent::component.
@see getDistanceFromDragStart, getDistanceFromDragStartX, mouseWasDraggedSinceMouseDown
*/
const Point<float> mouseDownPosition;
/** The component that this event applies to.
This is usually the component that the mouse was over at the time, but for mouse-drag
events the mouse could actually be over a different component and the events are
still sent to the component that the button was originally pressed on.
The x and y member variables are relative to this component's position.
If you use getEventRelativeTo() to retarget this object to be relative to a different
component, this pointer will be updated, but originalComponent remains unchanged.
@see originalComponent
*/
Component* const eventComponent;
/** The component that the event first occurred on.
If you use getEventRelativeTo() to retarget this object to be relative to a different
component, this value remains unchanged to indicate the first component that received it.
@see eventComponent
*/
Component* const originalComponent;
/** The time that this mouse-event occurred. */
const Time eventTime;
/** The time that the corresponding mouse-down event occurred. */
const Time mouseDownTime;
/** The source device that generated this event. */
MouseInputSource source;
//==============================================================================
/** Returns the x coordinate of the last place that a mouse was pressed.
The coordinate is relative to the component specified in MouseEvent::component.
@see getDistanceFromDragStart, getDistanceFromDragStartX, mouseWasDraggedSinceMouseDown
*/
int getMouseDownX() const noexcept;
/** Returns the y coordinate of the last place that a mouse was pressed.
The coordinate is relative to the component specified in MouseEvent::component.
@see getDistanceFromDragStart, getDistanceFromDragStartX, mouseWasDraggedSinceMouseDown
*/
int getMouseDownY() const noexcept;
/** Returns the coordinates of the last place that a mouse was pressed.
The coordinates are relative to the component specified in MouseEvent::component.
For a floating point version of this value, see mouseDownPosition.
@see mouseDownPosition, getDistanceFromDragStart, getDistanceFromDragStartX, mouseWasDraggedSinceMouseDown
*/
Point<int> getMouseDownPosition() const noexcept;
/** Returns the straight-line distance between where the mouse is now and where it
was the last time the button was pressed.
This is quite handy for things like deciding whether the user has moved far enough
for it to be considered a drag operation.
@see getDistanceFromDragStartX
*/
int getDistanceFromDragStart() const noexcept;
/** Returns the difference between the mouse's current x position and where it was
when the button was last pressed.
@see getDistanceFromDragStart
*/
int getDistanceFromDragStartX() const noexcept;
/** Returns the difference between the mouse's current y position and where it was
when the button was last pressed.
@see getDistanceFromDragStart
*/
int getDistanceFromDragStartY() const noexcept;
/** Returns the difference between the mouse's current position and where it was
when the button was last pressed.
@see getDistanceFromDragStart
*/
Point<int> getOffsetFromDragStart() const noexcept;
/** Returns true if the user seems to be performing a drag gesture.
This is only meaningful if called in either a mouseUp() or mouseDrag() method.
It will return true if the user has dragged the mouse more than a few pixels from the place
where the mouse-down occurred or the mouse has been held down for a significant amount of time.
Once they have dragged it far enough for this method to return true, it will continue
to return true until the mouse-up, even if they move the mouse back to the same
location at which the mouse-down happened. This means that it's very handy for
objects that can either be clicked on or dragged, as you can use it in the mouseDrag()
callback to ignore small movements they might make while trying to click.
*/
bool mouseWasDraggedSinceMouseDown() const noexcept;
/** Returns true if the mouse event is part of a click gesture rather than a drag.
This is effectively the opposite of mouseWasDraggedSinceMouseDown()
*/
bool mouseWasClicked() const noexcept;
/** For a click event, the number of times the mouse was clicked in succession.
So for example a double-click event will return 2, a triple-click 3, etc.
*/
int getNumberOfClicks() const noexcept { return numberOfClicks; }
/** Returns the time that the mouse button has been held down for.
If called from a mouseDrag or mouseUp callback, this will return the
number of milliseconds since the corresponding mouseDown event occurred.
If called in other contexts, e.g. a mouseMove, then the returned value
may be 0 or an undefined value.
*/
int getLengthOfMousePress() const noexcept;
/** Returns true if the pressure value for this event is meaningful. */
bool isPressureValid() const noexcept;
/** Returns true if the orientation value for this event is meaningful. */
bool isOrientationValid() const noexcept;
/** Returns true if the rotation value for this event is meaningful. */
bool isRotationValid() const noexcept;
/** Returns true if the current tilt value (either x- or y-axis) is meaningful. */
bool isTiltValid (bool tiltX) const noexcept;
//==============================================================================
/** The position of the mouse when the event occurred.
This position is relative to the top-left of the component to which the
event applies (as indicated by the MouseEvent::eventComponent field).
For a floating-point position, see MouseEvent::position
*/
Point<int> getPosition() const noexcept;
/** Returns the mouse x position of this event, in global screen coordinates.
The coordinates are relative to the top-left of the main monitor.
@see getScreenPosition
*/
int getScreenX() const;
/** Returns the mouse y position of this event, in global screen coordinates.
The coordinates are relative to the top-left of the main monitor.
@see getScreenPosition
*/
int getScreenY() const;
/** Returns the mouse position of this event, in global screen coordinates.
The coordinates are relative to the top-left of the main monitor.
@see getMouseDownScreenPosition
*/
Point<int> getScreenPosition() const;
/** Returns the x coordinate at which the mouse button was last pressed.
The coordinates are relative to the top-left of the main monitor.
@see getMouseDownScreenPosition
*/
int getMouseDownScreenX() const;
/** Returns the y coordinate at which the mouse button was last pressed.
The coordinates are relative to the top-left of the main monitor.
@see getMouseDownScreenPosition
*/
int getMouseDownScreenY() const;
/** Returns the coordinates at which the mouse button was last pressed.
The coordinates are relative to the top-left of the main monitor.
@see getScreenPosition
*/
Point<int> getMouseDownScreenPosition() const;
//==============================================================================
/** Creates a version of this event that is relative to a different component.
The x and y positions of the event that is returned will have been
adjusted to be relative to the new component.
The component pointer that is passed-in must not be null.
*/
MouseEvent getEventRelativeTo (Component* newComponent) const noexcept;
/** Creates a copy of this event with a different position.
All other members of the event object are the same, but the x and y are
replaced with these new values.
*/
MouseEvent withNewPosition (Point<float> newPosition) const noexcept;
/** Creates a copy of this event with a different position.
All other members of the event object are the same, but the x and y are
replaced with these new values.
*/
MouseEvent withNewPosition (Point<int> newPosition) const noexcept;
//==============================================================================
/** Changes the application-wide setting for the double-click time limit.
This is the maximum length of time between mouse-clicks for it to be
considered a double-click. It's used by the Component class.
@see getDoubleClickTimeout, MouseListener::mouseDoubleClick
*/
static void setDoubleClickTimeout (int timeOutMilliseconds) noexcept;
/** Returns the application-wide setting for the double-click time limit.
This is the maximum length of time between mouse-clicks for it to be
considered a double-click. It's used by the Component class.
@see setDoubleClickTimeout, MouseListener::mouseDoubleClick
*/
static int getDoubleClickTimeout() noexcept;
private:
//==============================================================================
const uint8 numberOfClicks, wasMovedSinceMouseDown;
MouseEvent& operator= (const MouseEvent&);
};
//==============================================================================
/**
Contains status information about a mouse wheel event.
@see MouseListener, MouseEvent
@tags{GUI}
*/
struct MouseWheelDetails final
{
//==============================================================================
/** The amount that the wheel has been moved in the X axis.
If isReversed is true, then a negative deltaX means that the wheel has been
pushed physically to the left.
If isReversed is false, then a negative deltaX means that the wheel has been
pushed physically to the right.
*/
float deltaX;
/** The amount that the wheel has been moved in the Y axis.
If isReversed is true, then a negative deltaY means that the wheel has been
pushed physically upwards.
If isReversed is false, then a negative deltaY means that the wheel has been
pushed physically downwards.
*/
float deltaY;
/** Indicates whether the user has reversed the direction of the wheel.
See deltaX and deltaY for an explanation of the effects of this value.
*/
bool isReversed;
/** If true, then the wheel has continuous, un-stepped motion. */
bool isSmooth;
/** If true, then this event is part of the inertial momentum phase that follows
the wheel being released. */
bool isInertial;
};
//==============================================================================
/**
Contains status information about a pen event.
@see MouseListener, MouseEvent
@tags{GUI}
*/
struct PenDetails final
{
/**
The rotation of the pen device in radians. Indicates the clockwise rotation, or twist,
of the pen. The default is 0.
*/
float rotation;
/**
Indicates the angle of tilt of the pointer in a range of -1.0 to 1.0 along the x-axis where
a positive value indicates a tilt to the right. The default is 0.
*/
float tiltX;
/**
Indicates the angle of tilt of the pointer in a range of -1.0 to 1.0 along the y-axis where
a positive value indicates a tilt toward the user. The default is 0.
*/
float tiltY;
};
} // namespace juce

View File

@ -0,0 +1,77 @@
/*
==============================================================================
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
{
MouseInactivityDetector::MouseInactivityDetector (Component& c) : targetComp (c)
{
targetComp.addMouseListener (this, true);
}
MouseInactivityDetector::~MouseInactivityDetector()
{
targetComp.removeMouseListener (this);
}
void MouseInactivityDetector::setDelay (int newDelay) noexcept { delayMs = newDelay; }
void MouseInactivityDetector::setMouseMoveTolerance (int newDistance) noexcept { toleranceDistance = newDistance; }
void MouseInactivityDetector::addListener (Listener* l) { listenerList.add (l); }
void MouseInactivityDetector::removeListener (Listener* l) { listenerList.remove (l); }
void MouseInactivityDetector::timerCallback()
{
setActive (false);
}
void MouseInactivityDetector::wakeUp (const MouseEvent& e, bool alwaysWake)
{
auto newPos = e.getEventRelativeTo (&targetComp).getPosition();
if ((! isActive) && (alwaysWake || e.source.isTouch() || newPos.getDistanceFrom (lastMousePos) > toleranceDistance))
setActive (true);
if (lastMousePos != newPos)
{
lastMousePos = newPos;
startTimer (delayMs);
}
}
void MouseInactivityDetector::setActive (bool b)
{
if (isActive != b)
{
isActive = b;
if (isActive)
listenerList.call ([] (Listener& l) { l.mouseBecameActive(); });
else
listenerList.call ([] (Listener& l) { l.mouseBecameInactive(); });
}
}
} // namespace juce

View File

@ -0,0 +1,112 @@
/*
==============================================================================
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
{
//==============================================================================
/**
This object watches for mouse-events happening within a component, and if
the mouse remains still for long enough, triggers an event to indicate that
it has become inactive.
You'd use this for situations where e.g. you want to hide the mouse-cursor
when the user's not actively using the mouse.
After creating an instance of this, use addListener to get callbacks when
the activity status changes.
@tags{GUI}
*/
class JUCE_API MouseInactivityDetector : private Timer,
private MouseListener
{
public:
/** Creates an inactivity watcher, attached to the given component.
The target component must not be deleted while this - it will be monitored
for any mouse events in it or its child components.
*/
MouseInactivityDetector (Component& target);
/** Destructor. */
~MouseInactivityDetector() override;
/** Sets the time for which the mouse must be still before the callback
is triggered.
*/
void setDelay (int newDelayMilliseconds) noexcept;
/** Sets the number of pixels by which the cursor is allowed to drift before it is
considered to be actively moved.
*/
void setMouseMoveTolerance (int pixelsNeededToTrigger) noexcept;
//==============================================================================
/** Classes should implement this to receive callbacks from a MouseInactivityDetector
when the mouse becomes active or inactive.
*/
class Listener
{
public:
virtual ~Listener() = default;
/** Called when the mouse is moved or clicked for the first time
after a period of inactivity. */
virtual void mouseBecameActive() = 0;
/** Called when the mouse hasn't been moved for the timeout period. */
virtual void mouseBecameInactive() = 0;
};
/** Registers a listener. */
void addListener (Listener* listener);
/** Removes a previously-registered listener. */
void removeListener (Listener* listener);
private:
//==============================================================================
Component& targetComp;
ListenerList<Listener> listenerList;
Point<int> lastMousePos;
int delayMs = 1500, toleranceDistance = 15;
bool isActive = true;
void timerCallback() override;
void wakeUp (const MouseEvent&, bool alwaysWake);
void setActive (bool);
void mouseMove (const MouseEvent& e) override { wakeUp (e, false); }
void mouseEnter (const MouseEvent& e) override { wakeUp (e, false); }
void mouseExit (const MouseEvent& e) override { wakeUp (e, false); }
void mouseDown (const MouseEvent& e) override { wakeUp (e, true); }
void mouseDrag (const MouseEvent& e) override { wakeUp (e, true); }
void mouseUp (const MouseEvent& e) override { wakeUp (e, true); }
void mouseWheelMove (const MouseEvent& e, const MouseWheelDetails&) override { wakeUp (e, true); }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MouseInactivityDetector)
};
} // namespace juce

View File

@ -0,0 +1,780 @@
/*
==============================================================================
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<float> screenPosToLocalPos (Component& comp, Point<float> 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<float> screenPos)
{
if (auto* peer = getPeer())
{
auto relativePos = ScalingHelpers::unscaledScreenPosToScaled (peer->getComponent(),
peer->globalToLocal (screenPos));
auto& comp = peer->getComponent();
// (the contains() call is needed to test for overlapping desktop windows)
if (comp.containsInternal (relativePos))
return comp.getComponentAtInternal (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 ScalingHelpers::unscaledScreenPosToScaled (getRawScreenPosition());
}
Point<float> getRawScreenPosition() const noexcept
{
return unboundedMouseOffset + (inputType != MouseInputSource::InputSourceType::touch ? MouseInputSource::getCurrentRawMousePosition()
: lastScreenPos);
}
void setScreenPosition (Point<float> 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<float>::twoPi; }
bool isRotationValid() const noexcept { return rotation >= 0.0f && rotation <= MathConstants<float>::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<float> screenPos, Time time)
{
JUCE_MOUSE_EVENT_DBG ("enter")
comp.internalMouseEnter (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time);
}
void sendMouseExit (Component& comp, Point<float> screenPos, Time time)
{
JUCE_MOUSE_EVENT_DBG ("exit")
comp.internalMouseExit (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time);
}
void sendMouseMove (Component& comp, Point<float> screenPos, Time time)
{
JUCE_MOUSE_EVENT_DBG ("move")
comp.internalMouseMove (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time);
}
void sendMouseDown (Component& comp, Point<float> 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<float> 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<float> 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<float> 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<float> 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<float> 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<float> screenPos, Time time)
{
auto* current = getComponentUnderMouse();
if (newComponent != current)
{
WeakReference<Component> safeNewComp (newComponent);
auto originalButtonState = buttonState;
if (current != nullptr)
{
WeakReference<Component> 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<float> screenPos, Time time)
{
if (&newPeer != lastPeer)
{
setComponentUnderMouse (nullptr, screenPos, time);
lastPeer = &newPeer;
setComponentUnderMouse (findComponentAt (screenPos), screenPos, time);
}
}
void setScreenPos (Point<float> 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<float> 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<float> positionWithinPeer,
Time time, Point<float>& screenPos)
{
lastTime = time;
++mouseEventCounter;
screenPos = peer.localToGlobal (positionWithinPeer);
setPeer (peer, screenPos, time);
setScreenPos (screenPos, 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 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<float> 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<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 (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<float> MouseInputSource::getScreenPosition() const noexcept { return pimpl->getScreenPosition(); }
Point<float> 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<float> 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<float> p) { pimpl->setScreenPosition (p); }
void MouseInputSource::handleEvent (ComponentPeer& peer, Point<float> 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<float> pos, int64 time, const MouseWheelDetails& wheel)
{
pimpl->handleWheel (peer, pos, Time (time), wheel);
}
void MouseInputSource::handleMagnifyGesture (ComponentPeer& peer, Point<float> 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<float> 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<MouseInputSourceInternal> sources;
Array<MouseInputSource> sourceArray;
};
} // namespace juce

View File

@ -0,0 +1,273 @@
/*
==============================================================================
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
{
//==============================================================================
/**
Represents a linear source of mouse events from a mouse device or individual finger
in a multi-touch environment.
Each MouseEvent object contains a reference to the MouseInputSource that generated
it. In an environment with a single mouse for input, all events will come from the
same source, but in a multi-touch system, there may be multiple MouseInputSource
objects active, each representing a stream of events coming from a particular finger.
Events coming from a single MouseInputSource are always sent in a fixed and predictable
order: a mouseMove will never be called without a mouseEnter having been sent beforehand,
the only events that can happen between a mouseDown and its corresponding mouseUp are
mouseDrags, etc.
When there are multiple touches arriving from multiple MouseInputSources, their
event streams may arrive in an interleaved order, so you should use the getIndex()
method to find out which finger each event came from.
@see MouseEvent
@tags{GUI}
*/
class JUCE_API MouseInputSource final
{
public:
/** Possible mouse input sources. */
enum InputSourceType
{
mouse,
touch,
pen
};
//==============================================================================
MouseInputSource (const MouseInputSource&) noexcept;
MouseInputSource& operator= (const MouseInputSource&) noexcept;
~MouseInputSource() noexcept;
//==============================================================================
bool operator== (const MouseInputSource& other) const noexcept { return pimpl == other.pimpl; }
bool operator!= (const MouseInputSource& other) const noexcept { return pimpl != other.pimpl; }
//==============================================================================
/** Returns the type of input source that this object represents. */
MouseInputSource::InputSourceType getType() const noexcept;
/** Returns true if this object represents a normal desk-based mouse device. */
bool isMouse() const noexcept;
/** Returns true if this object represents a source of touch events. */
bool isTouch() const noexcept;
/** Returns true if this object represents a pen device. */
bool isPen() const noexcept;
/** Returns true if this source has an on-screen pointer that can hover over
items without clicking them.
*/
bool canHover() const noexcept;
/** Returns true if this source may have a scroll wheel. */
bool hasMouseWheel() const noexcept;
/** Returns this source's index in the global list of possible sources.
If the system only has a single mouse, there will only be a single MouseInputSource
with an index of 0.
If the system supports multi-touch input, then the index will represent a finger
number, starting from 0. When the first touch event begins, it will have finger
number 0, and then if a second touch happens while the first is still down, it
will have index 1, etc.
*/
int getIndex() const noexcept;
/** Returns true if this device is currently being pressed. */
bool isDragging() const noexcept;
/** Returns the last-known screen position of this source. */
Point<float> getScreenPosition() const noexcept;
/** Returns the last-known screen position of this source without any scaling applied. */
Point<float> getRawScreenPosition() const noexcept;
/** Returns a set of modifiers that indicate which buttons are currently
held down on this device.
*/
ModifierKeys getCurrentModifiers() const noexcept;
/** Returns the device's current touch or pen pressure.
The range is 0 (soft) to 1 (hard).
If the input device doesn't provide any pressure data, it may return a negative
value here, or 0.0 or 1.0, depending on the platform.
*/
float getCurrentPressure() const noexcept;
/** Returns the device's current orientation in radians. 0 indicates a touch pointer
aligned with the x-axis and pointing from left to right; increasing values indicate
rotation in the clockwise direction. Only reported by a touch pointer.
*/
float getCurrentOrientation() const noexcept;
/** Returns the device's current rotation. Indicates the clockwise rotation, or twist, of the pointer
in radians. The default is 0. Only reported by a pen pointer.
*/
float getCurrentRotation() const noexcept;
/** Returns the angle of tilt of the pointer in a range of -1.0 to 1.0 either in the x- or y-axis. The default is 0.
If x-axis, a positive value indicates a tilt to the right and if y-axis, a positive value indicates a tilt toward the user.
Only reported by a pen pointer.
*/
float getCurrentTilt (bool tiltX) const noexcept;
/** Returns true if the current pressure value is meaningful. */
bool isPressureValid() const noexcept;
/** Returns true if the current orientation value is meaningful. */
bool isOrientationValid() const noexcept;
/** Returns true if the current rotation value is meaningful. */
bool isRotationValid() const noexcept;
/** Returns true if the current tilt value (either x- or y-axis) is meaningful. */
bool isTiltValid (bool tiltX) const noexcept;
/** Returns the component that was last known to be under this pointer. */
Component* getComponentUnderMouse() const;
/** Tells the device to dispatch a mouse-move or mouse-drag event.
This is asynchronous - the event will occur on the message thread.
*/
void triggerFakeMove() const;
/** Returns the number of clicks that should be counted as belonging to the
current mouse event.
So the mouse is currently down and it's the second click of a double-click, this
will return 2.
*/
int getNumberOfMultipleClicks() const noexcept;
/** Returns the time at which the last mouse-down occurred. */
Time getLastMouseDownTime() const noexcept;
/** Returns the screen position at which the last mouse-down occurred. */
Point<float> getLastMouseDownPosition() const noexcept;
/** Returns true if this input source represents a long-press or drag interaction i.e. it has been held down for a significant
amount of time or it has been dragged more than a couple of pixels from the place it was pressed. */
bool isLongPressOrDrag() const noexcept;
/** Returns true if this input source has been dragged more than a couple of pixels from the place it was pressed. */
bool hasMovedSignificantlySincePressed() const noexcept;
/** Returns true if this input source uses a visible mouse cursor. */
bool hasMouseCursor() const noexcept;
/** Changes the mouse cursor, (if there is one). */
void showMouseCursor (const MouseCursor& cursor);
/** Hides the mouse cursor (if there is one). */
void hideCursor();
/** Un-hides the mouse cursor if it was hidden by hideCursor(). */
void revealCursor();
/** Forces an update of the mouse cursor for whatever component it's currently over. */
void forceMouseCursorUpdate();
/** Returns true if this mouse can be moved indefinitely in any direction without running out of space. */
bool canDoUnboundedMovement() const noexcept;
/** Allows the mouse to move beyond the edges of the screen.
Calling this method when the mouse button is currently pressed will remove the cursor
from the screen and allow the mouse to (seem to) move beyond the edges of the screen.
This means that the coordinates returned to mouseDrag() will be unbounded, and this
can be used for things like custom slider controls or dragging objects around, where
movement would be otherwise be limited by the mouse hitting the edges of the screen.
The unbounded mode is automatically turned off when the mouse button is released, or
it can be turned off explicitly by calling this method again.
@param isEnabled whether to turn this mode on or off
@param keepCursorVisibleUntilOffscreen if set to false, the cursor will immediately be
hidden; if true, it will only be hidden when it
is moved beyond the edge of the screen
*/
void enableUnboundedMouseMovement (bool isEnabled, bool keepCursorVisibleUntilOffscreen = false) const;
/** Returns true if this source is currently in "unbounded" mode. */
bool isUnboundedMouseMovementEnabled() const;
/** Attempts to set this mouse pointer's screen position. */
void setScreenPosition (Point<float> newPosition);
/** A default value for pressure, which is used when a device doesn't support it, or for
mouse-moves, mouse-ups, etc.
*/
static const float invalidPressure;
/** A default value for orientation, which is used when a device doesn't support it */
static const float invalidOrientation;
/** A default value for rotation, which is used when a device doesn't support it */
static const float invalidRotation;
/** Default values for tilt, which are used when a device doesn't support it */
static const float invalidTiltX;
static const float invalidTiltY;
/** An offscreen mouse position used when triggering mouse exits where we don't want to move
the cursor over an existing component.
*/
static const Point<float> offscreenMousePos;
//==============================================================================
#ifndef DOXYGEN
[[deprecated ("This method has been replaced with the isLongPressOrDrag and hasMovedSignificantlySincePressed "
"methods. If you want the same behaviour you should use isLongPressOrDrag which accounts for the "
"amount of time that the input source has been held down for, but if you only want to know whether "
"it has been moved use hasMovedSignificantlySincePressed instead.")]]
bool hasMouseMovedSignificantlySincePressed() const noexcept;
#endif
private:
//==============================================================================
friend class ComponentPeer;
friend class Desktop;
friend class MouseInputSourceInternal;
MouseInputSourceInternal* pimpl;
struct SourceList;
explicit MouseInputSource (MouseInputSourceInternal*) noexcept;
void handleEvent (ComponentPeer&, Point<float>, int64 time, ModifierKeys, float, float, const PenDetails&);
void handleWheel (ComponentPeer&, Point<float>, int64 time, const MouseWheelDetails&);
void handleMagnifyGesture (ComponentPeer&, Point<float>, int64 time, float scaleFactor);
static Point<float> getCurrentRawMousePosition();
static void setRawMousePosition (Point<float>);
JUCE_LEAK_DETECTOR (MouseInputSource)
};
} // namespace juce

View File

@ -0,0 +1,39 @@
/*
==============================================================================
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
{
void MouseListener::mouseEnter (const MouseEvent&) {}
void MouseListener::mouseExit (const MouseEvent&) {}
void MouseListener::mouseDown (const MouseEvent&) {}
void MouseListener::mouseUp (const MouseEvent&) {}
void MouseListener::mouseDrag (const MouseEvent&) {}
void MouseListener::mouseMove (const MouseEvent&) {}
void MouseListener::mouseDoubleClick (const MouseEvent&) {}
void MouseListener::mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) {}
void MouseListener::mouseMagnify (const MouseEvent&, float) {}
} // namespace juce

View File

@ -0,0 +1,171 @@
/*
==============================================================================
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
{
//==============================================================================
/**
A MouseListener can be registered with a component to receive callbacks
about mouse events that happen to that component.
@see Component::addMouseListener, Component::removeMouseListener
@tags{GUI}
*/
class JUCE_API MouseListener
{
public:
/** Destructor. */
virtual ~MouseListener() = default;
/** 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
*/
virtual void mouseMove (const MouseEvent& event);
/** 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
*/
virtual void mouseEnter (const MouseEvent& event);
/** 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
*/
virtual void mouseExit (const MouseEvent& event);
/** 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
*/
virtual void mouseDown (const MouseEvent& event);
/** 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
*/
virtual void mouseDrag (const MouseEvent& event);
/** 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
*/
virtual void mouseUp (const MouseEvent& event);
/** 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
*/
virtual void mouseDoubleClick (const MouseEvent& event);
/** 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.
@param event details about the mouse event
@param wheel details about the wheel movement
*/
virtual void mouseWheelMove (const MouseEvent& event,
const MouseWheelDetails& wheel);
/** 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.
*/
virtual void mouseMagnify (const MouseEvent& event, float scaleFactor);
};
} // namespace juce

View File

@ -0,0 +1,327 @@
/*
==============================================================================
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
{
//==============================================================================
/** Manages a list of selectable items.
Use one of these to keep a track of things that the user has highlighted, like
icons or things in a list.
The class is templated so that you can use it to hold either a set of pointers
to objects, or a set of ID numbers or handles, for cases where each item may
not always have a corresponding object.
To be informed when items are selected/deselected, register a ChangeListener with
this object.
@tags{GUI}
*/
template <class SelectableItemType>
class SelectedItemSet : public ChangeBroadcaster
{
public:
//==============================================================================
using ItemType = SelectableItemType;
using ItemArray = Array<SelectableItemType>;
using ParameterType = typename TypeHelpers::ParameterType<SelectableItemType>::type;
//==============================================================================
/** Creates an empty set. */
SelectedItemSet() = default;
/** Creates a set based on an array of items. */
explicit SelectedItemSet (const ItemArray& items)
: selectedItems (items)
{
}
/** Creates a copy of another set. */
SelectedItemSet (const SelectedItemSet& other)
: ChangeBroadcaster(), selectedItems (other.selectedItems)
{
}
/** Creates a copy of another set. */
SelectedItemSet& operator= (const SelectedItemSet& other)
{
if (selectedItems != other.selectedItems)
{
changed();
for (int i = selectedItems.size(); --i >= 0;)
if (! other.isSelected (selectedItems.getReference (i)))
itemDeselected (selectedItems.removeAndReturn (i));
for (auto& i : other.selectedItems)
{
if (! isSelected (i))
{
selectedItems.add (i);
itemSelected (i);
}
}
}
return *this;
}
//==============================================================================
/** Clears any other currently selected items, and selects this item.
If this item is already the only thing selected, no change notification
will be sent out.
@see addToSelection, addToSelectionBasedOnModifiers
*/
void selectOnly (ParameterType item)
{
if (isSelected (item))
{
for (int i = selectedItems.size(); --i >= 0;)
{
if (selectedItems.getUnchecked(i) != item)
{
deselect (selectedItems.getUnchecked(i));
i = jmin (i, selectedItems.size());
}
}
}
else
{
changed();
deselectAll();
selectedItems.add (item);
itemSelected (item);
}
}
/** Selects an item.
If the item is already selected, no change notification will be sent out.
@see selectOnly, addToSelectionBasedOnModifiers
*/
void addToSelection (ParameterType item)
{
if (! isSelected (item))
{
changed();
selectedItems.add (item);
itemSelected (item);
}
}
/** Selects or deselects an item.
This will use the modifier keys to decide whether to deselect other items
first.
So if the shift key is held down, the item will be added without deselecting
anything (same as calling addToSelection() )
If no modifiers are down, the current selection will be cleared first (same
as calling selectOnly() )
If the ctrl (or command on the Mac) key is held down, the item will be toggled -
so it'll be added to the set unless it's already there, in which case it'll be
deselected.
If the items that you're selecting can also be dragged, you may need to use the
addToSelectionOnMouseDown() and addToSelectionOnMouseUp() calls to handle the
subtleties of this kind of usage.
@see selectOnly, addToSelection, addToSelectionOnMouseDown, addToSelectionOnMouseUp
*/
void addToSelectionBasedOnModifiers (ParameterType item,
ModifierKeys modifiers)
{
if (modifiers.isShiftDown())
{
addToSelection (item);
}
else if (modifiers.isCommandDown())
{
if (isSelected (item))
deselect (item);
else
addToSelection (item);
}
else
{
selectOnly (item);
}
}
/** Selects or deselects items that can also be dragged, based on a mouse-down event.
If you call addToSelectionOnMouseDown() at the start of your mouseDown event,
and then call addToSelectionOnMouseUp() at the end of your mouseUp event, this
makes it easy to handle multiple-selection of sets of objects that can also
be dragged.
For example, if you have several items already selected, and you click on
one of them (without dragging), then you'd expect this to deselect the other, and
just select the item you clicked on. But if you had clicked on this item and
dragged it, you'd have expected them all to stay selected.
When you call this method, you'll need to store the boolean result, because the
addToSelectionOnMouseUp() method will need to be know this value.
@see addToSelectionOnMouseUp, addToSelectionBasedOnModifiers
*/
bool addToSelectionOnMouseDown (ParameterType item,
ModifierKeys modifiers)
{
if (isSelected (item))
return ! modifiers.isPopupMenu();
addToSelectionBasedOnModifiers (item, modifiers);
return false;
}
/** Selects or deselects items that can also be dragged, based on a mouse-up event.
Call this during a mouseUp callback, when you have previously called the
addToSelectionOnMouseDown() method during your mouseDown event.
See addToSelectionOnMouseDown() for more info
@param item the item to select (or deselect)
@param modifiers the modifiers from the mouse-up event
@param wasItemDragged true if your item was dragged during the mouse click
@param resultOfMouseDownSelectMethod this is the boolean return value that came
back from the addToSelectionOnMouseDown() call that you
should have made during the matching mouseDown event
*/
void addToSelectionOnMouseUp (ParameterType item,
ModifierKeys modifiers,
const bool wasItemDragged,
const bool resultOfMouseDownSelectMethod)
{
if (resultOfMouseDownSelectMethod && ! wasItemDragged)
addToSelectionBasedOnModifiers (item, modifiers);
}
/** Deselects an item. */
void deselect (ParameterType item)
{
const int i = selectedItems.indexOf (item);
if (i >= 0)
{
changed();
itemDeselected (selectedItems.removeAndReturn (i));
}
}
/** Deselects all items. */
void deselectAll()
{
if (selectedItems.size() > 0)
{
changed();
for (int i = selectedItems.size(); --i >= 0;)
{
itemDeselected (selectedItems.removeAndReturn (i));
i = jmin (i, selectedItems.size());
}
}
}
//==============================================================================
/** Returns the number of currently selected items.
@see getSelectedItem
*/
int getNumSelected() const noexcept { return selectedItems.size(); }
/** Returns one of the currently selected items.
If the index is out-of-range, this returns a default-constructed SelectableItemType.
@see getNumSelected
*/
SelectableItemType getSelectedItem (const int index) const { return selectedItems [index]; }
/** True if this item is currently selected. */
bool isSelected (ParameterType item) const noexcept { return selectedItems.contains (item); }
/** Provides access to the array of items. */
const ItemArray& getItemArray() const noexcept { return selectedItems; }
/** Provides iterator access to the array of items. */
SelectableItemType* begin() noexcept { return selectedItems.begin(); }
const SelectableItemType* begin() const noexcept { return selectedItems.begin(); }
/** Provides iterator access to the array of items. */
SelectableItemType* end() noexcept { return selectedItems.end(); }
/** Provides iterator access to the array of items. */
const SelectableItemType* end() const noexcept { return selectedItems.end(); }
//==============================================================================
/** Can be overridden to do special handling when an item is selected.
For example, if the item is an object, you might want to call it and tell
it that it's being selected.
*/
virtual void itemSelected (SelectableItemType) {}
/** Can be overridden to do special handling when an item is deselected.
For example, if the item is an object, you might want to call it and tell
it that it's being deselected.
*/
virtual void itemDeselected (SelectableItemType) {}
/** Used internally, but can be called to force a change message to be sent
to the ChangeListeners.
*/
void changed()
{
sendChangeMessage();
}
/** Used internally, but can be called to force a change message to be sent
to the ChangeListeners.
*/
void changed (const bool synchronous)
{
if (synchronous)
sendSynchronousChangeMessage();
else
sendChangeMessage();
}
private:
//==============================================================================
ItemArray selectedItems;
JUCE_LEAK_DETECTOR (SelectedItemSet<SelectableItemType>)
};
} // namespace juce

View File

@ -0,0 +1,106 @@
/*
==============================================================================
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
{
//==============================================================================
/**
Components derived from this class can have text dropped onto them by an external application.
@see DragAndDropContainer
@tags{GUI}
*/
class JUCE_API TextDragAndDropTarget
{
public:
/** Destructor. */
virtual ~TextDragAndDropTarget() = default;
/** Callback to check whether this target is interested in the set of text being offered.
Note that this will be called repeatedly when the user is dragging the mouse around over your
component, so don't do anything time-consuming in here!
@param text the text that the user is dragging
@returns true if this component wants to receive the other callbacks regarding this
type of object; if it returns false, no other callbacks will be made.
*/
virtual bool isInterestedInTextDrag (const String& text) = 0;
/** Callback to indicate that some text is being dragged over this component.
This gets called when the user moves the mouse into this component while dragging.
Use this callback as a trigger to make your component repaint itself to give the
user feedback about whether the text can be dropped here or not.
@param text the text that the user is dragging
@param x the mouse x position, relative to this component
@param y the mouse y position, relative to this component
*/
virtual void textDragEnter (const String& text, int x, int y);
/** Callback to indicate that the user is dragging some text over this component.
This gets called when the user moves the mouse over this component while dragging.
Normally overriding itemDragEnter() and itemDragExit() are enough, but
this lets you know what happens in-between.
@param text the text that the user is dragging
@param x the mouse x position, relative to this component
@param y the mouse y position, relative to this component
*/
virtual void textDragMove (const String& text, int x, int y);
/** Callback to indicate that the mouse has moved away from this component.
This gets called when the user moves the mouse out of this component while dragging
the text.
If you've used textDragEnter() to repaint your component and give feedback, use this
as a signal to repaint it in its normal state.
@param text the text that the user is dragging
*/
virtual void textDragExit (const String& text);
/** Callback to indicate that the user has dropped the text onto this component.
When the user drops the text, this get called, and you can use the text in whatever
way is appropriate.
Note that after this is called, the textDragExit method may not be called, so you should
clean up in here if there's anything you need to do when the drag finishes.
@param text the text that the user is dragging
@param x the mouse x position, relative to this component
@param y the mouse y position, relative to this component
*/
virtual void textDropped (const String& text, int x, int y) = 0;
};
} // namespace juce

View File

@ -0,0 +1,88 @@
/*
==============================================================================
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
{
//==============================================================================
/**
Components that want to use pop-up tooltips should implement this interface.
A TooltipWindow will wait for the mouse to hover over a component that
implements the TooltipClient interface, and when it finds one, it will display
the tooltip returned by its getTooltip() method.
@see TooltipWindow, SettableTooltipClient
@tags{GUI}
*/
class JUCE_API TooltipClient
{
public:
/** Destructor. */
virtual ~TooltipClient() = default;
/** Returns the string that this object wants to show as its tooltip. */
virtual String getTooltip() = 0;
};
//==============================================================================
/**
An implementation of TooltipClient that stores the tooltip string and a method
for changing it.
This makes it easy to add a tooltip to a custom component, by simply adding this
as a base class and calling setTooltip().
Many of the JUCE widgets already use this as a base class to implement their
tooltips.
@see TooltipClient, TooltipWindow
@tags{GUI}
*/
class JUCE_API SettableTooltipClient : public TooltipClient
{
public:
//==============================================================================
/** Destructor. */
~SettableTooltipClient() override = default;
//==============================================================================
/** Assigns a new tooltip to this object. */
virtual void setTooltip (const String& newTooltip) { tooltipString = newTooltip; }
/** Returns the tooltip assigned to this object. */
String getTooltip() override { return tooltipString; }
protected:
SettableTooltipClient() = default;
private:
String tooltipString;
};
} // namespace juce