From 5d8d37eb878bd1d21cbb2b03edfd3cef01884a97 Mon Sep 17 00:00:00 2001 From: ed Date: Wed, 4 Oct 2017 15:52:31 +0100 Subject: [PATCH] Added support for multi-touch drag and drop --- .../mouse/juce_DragAndDropContainer.cpp | 289 +++++++++++------- .../mouse/juce_DragAndDropContainer.h | 65 +++- .../mouse/juce_MouseInputSource.cpp | 8 +- .../juce_gui_basics/widgets/juce_ListBox.cpp | 2 +- .../widgets/juce_ToolbarItemComponent.cpp | 2 +- .../juce_gui_basics/widgets/juce_TreeView.cpp | 2 +- 6 files changed, 230 insertions(+), 138 deletions(-) diff --git a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp index 4fc7e112d8..4f1a25b849 100644 --- a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp +++ b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp @@ -39,13 +39,13 @@ public: DragImageComponent (const Image& im, const var& desc, Component* const sourceComponent, - Component* const mouseSource, + const MouseInputSource* draggingSource, DragAndDropContainer& ddc, Point offset) : sourceDetails (desc, sourceComponent, Point()), image (im), owner (ddc), - mouseDragSource (mouseSource), - imageOffset (offset) + mouseDragSource (draggingSource->getComponentUnderMouse()), + imageOffset (offset), originalMouseIndex (draggingSource->getIndex()) { updateSize(); @@ -62,8 +62,7 @@ public: ~DragImageComponent() { - if (owner.dragImageComponent == this) - owner.dragImageComponent.release(); + owner.dragImageComponents.remove (owner.dragImageComponents.indexOf (this), false); if (mouseDragSource != nullptr) { @@ -88,7 +87,7 @@ public: void mouseUp (const MouseEvent& e) override { - if (e.originalComponent != this) + if (e.originalComponent != this && e.source.getIndex() == originalMouseIndex) { if (mouseDragSource != nullptr) mouseDragSource->removeMouseListener (this); @@ -98,7 +97,7 @@ public: DragAndDropTarget::SourceDetails details (sourceDetails); DragAndDropTarget* finalTarget = nullptr; - const bool wasVisible = isVisible(); + auto wasVisible = isVisible(); setVisible (false); Component* unused; finalTarget = findTarget (e.getScreenPosition(), details.localPosition, unused); @@ -121,24 +120,24 @@ public: void mouseDrag (const MouseEvent& e) override { - if (e.originalComponent != this) + if (e.originalComponent != this && e.source.getIndex() == originalMouseIndex) updateLocation (true, e.getScreenPosition()); } void updateLocation (const bool canDoExternalDrag, Point screenPos) { - DragAndDropTarget::SourceDetails details (sourceDetails); + auto details = sourceDetails; setNewScreenPos (screenPos); Component* newTargetComp; - DragAndDropTarget* const newTarget = findTarget (screenPos, details.localPosition, newTargetComp); + auto* newTarget = findTarget (screenPos, details.localPosition, newTargetComp); setVisible (newTarget == nullptr || newTarget->shouldDrawDragImageWhenOver()); if (newTargetComp != currentlyOverComp) { - if (DragAndDropTarget* const lastTarget = getCurrentlyOver()) + if (auto* lastTarget = getCurrentlyOver()) if (details.sourceComponent != nullptr && lastTarget->isInterestedInDragSource (details)) lastTarget->itemDragExit (details); @@ -153,7 +152,7 @@ public: if (canDoExternalDrag) { - const Time now (Time::getCurrentTime()); + auto now = Time::getCurrentTime(); if (getCurrentlyOver() != nullptr) lastTimeOverTarget = now; @@ -179,12 +178,18 @@ public: { deleteSelf(); } - else if (! isMouseButtonDownAnywhere()) + else { - if (mouseDragSource != nullptr) - mouseDragSource->removeMouseListener (this); + if (auto* ms = Desktop::getInstance().getMouseSource (originalMouseIndex)) + { + if (! ms->isDragging()) + { + if (mouseDragSource != nullptr) + mouseDragSource->removeMouseListener (this); - deleteSelf(); + deleteSelf(); + } + } } } @@ -217,6 +222,7 @@ private: const Point imageOffset; bool hasCheckedForExternalDrag = false; Time lastTimeOverTarget; + int originalMouseIndex; void updateSize() { @@ -235,13 +241,13 @@ private: static Component* findDesktopComponentBelow (Point screenPos) { - Desktop& desktop = Desktop::getInstance(); + auto& desktop = Desktop::getInstance(); - for (int i = desktop.getNumComponents(); --i >= 0;) + for (auto i = desktop.getNumComponents(); --i >= 0;) { - Component* c = desktop.getComponent(i); + auto* c = desktop.getComponent(i); - if (Component* hit = c->getComponentAt (c->getLocalPoint (nullptr, screenPos))) + if (auto* hit = c->getComponentAt (c->getLocalPoint (nullptr, screenPos))) return hit; } @@ -251,7 +257,7 @@ private: DragAndDropTarget* findTarget (Point screenPos, Point& relativePos, Component*& resultComponent) const { - Component* hit = getParentComponent(); + auto* hit = getParentComponent(); if (hit == nullptr) hit = findDesktopComponentBelow (screenPos); @@ -260,11 +266,11 @@ private: // (note: use a local copy of this in case the callback runs // a modal loop and deletes this object before the method completes) - const DragAndDropTarget::SourceDetails details (sourceDetails); + auto details = sourceDetails; while (hit != nullptr) { - if (DragAndDropTarget* const ddt = dynamic_cast (hit)) + if (auto* ddt = dynamic_cast (hit)) { if (ddt->isInterestedInDragSource (details)) { @@ -283,9 +289,9 @@ private: void setNewScreenPos (Point screenPos) { - Point newPos (screenPos - imageOffset); + auto newPos = screenPos - imageOffset; - if (Component* p = getParentComponent()) + if (auto* p = getParentComponent()) newPos = p->getLocalPoint (nullptr, newPos); setTopLeftPosition (newPos); @@ -293,7 +299,7 @@ private: void sendDragMove (DragAndDropTarget::SourceDetails& details) const { - if (DragAndDropTarget* const target = getCurrentlyOver()) + if (auto* target = getCurrentlyOver()) if (target->isInterestedInDragSource (details)) target->itemDragMove (details); } @@ -309,7 +315,7 @@ private: if (ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown()) { StringArray files; - bool canMoveFiles = false; + auto canMoveFiles = false; if (owner.shouldDropFilesWhenDraggedExternally (details, files, canMoveFiles) && ! files.isEmpty()) { @@ -339,12 +345,12 @@ private: void dismissWithAnimation (const bool shouldSnapBack) { setVisible (true); - ComponentAnimator& animator = Desktop::getInstance().getAnimator(); + auto& animator = Desktop::getInstance().getAnimator(); if (shouldSnapBack && sourceDetails.sourceComponent != nullptr) { - const Point target (sourceDetails.sourceComponent->localPointToGlobal (sourceDetails.sourceComponent->getLocalBounds().getCentre())); - const Point ourCentre (localPointToGlobal (getLocalBounds().getCentre())); + auto target = sourceDetails.sourceComponent->localPointToGlobal (sourceDetails.sourceComponent->getLocalBounds().getCentre()); + auto ourCentre = localPointToGlobal (getLocalBounds().getCentre()); animator.animateComponent (this, getBounds() + (target - ourCentre), @@ -368,126 +374,141 @@ DragAndDropContainer::DragAndDropContainer() DragAndDropContainer::~DragAndDropContainer() { - dragImageComponent = nullptr; } void DragAndDropContainer::startDragging (const var& sourceDescription, Component* sourceComponent, Image dragImage, const bool allowDraggingToExternalWindows, - const Point* imageOffsetFromMouse) + const Point* imageOffsetFromMouse, + const MouseInputSource* inputSourceCausingDrag) { - if (dragImageComponent == nullptr) + auto* draggingSource = getMouseInputSourceForDrag (sourceComponent, inputSourceCausingDrag); + auto lastMouseDown = draggingSource->getLastMouseDownPosition().roundToInt(); + Point imageOffset; + + if (dragImage.isNull()) { - MouseInputSource* const draggingSource = Desktop::getInstance().getDraggingMouseSource (0); + dragImage = sourceComponent->createComponentSnapshot (sourceComponent->getLocalBounds()) + .convertedToFormat (Image::ARGB); - if (draggingSource == nullptr || ! draggingSource->isDragging()) + 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;) { - jassertfalse; // You must call startDragging() from within a mouseDown or mouseDrag callback! - return; - } + auto dy = (y - clipped.getY()) * (y - clipped.getY()); - const Point lastMouseDown (draggingSource->getLastMouseDownPosition().roundToInt()); - Point imageOffset; - - if (dragImage.isNull()) - { - dragImage = sourceComponent->createComponentSnapshot (sourceComponent->getLocalBounds()) - .convertedToFormat (Image::ARGB); - - dragImage.multiplyAllAlphas (0.6f); - - const int lo = 150; - const int hi = 400; - - Point relPos (sourceComponent->getLocalPoint (nullptr, lastMouseDown)); - Point clipped (dragImage.getBounds().getConstrainedPoint (relPos)); - Random random; - - for (int y = dragImage.getHeight(); --y >= 0;) + for (auto x = dragImage.getWidth(); --x >= 0;) { - const double dy = (y - clipped.getY()) * (y - clipped.getY()); + auto dx = x - clipped.getX(); + auto distance = roundToInt (std::sqrt (dx * dx + dy)); - for (int x = dragImage.getWidth(); --x >= 0;) + if (distance > lo) { - const int dx = x - clipped.getX(); - const int distance = roundToInt (std::sqrt (dx * dx + dy)); + auto alpha = (distance > hi) ? 0 + : (hi - distance) / (float) (hi - lo) + + random.nextFloat() * 0.008f; - if (distance > lo) - { - const float alpha = (distance > hi) ? 0 - : (hi - distance) / (float) (hi - lo) - + random.nextFloat() * 0.008f; - - dragImage.multiplyAlphaAt (x, y, alpha); - } + dragImage.multiplyAlphaAt (x, y, alpha); } } - - imageOffset = clipped; - } - else - { - if (imageOffsetFromMouse == nullptr) - imageOffset = dragImage.getBounds().getCentre(); - else - imageOffset = dragImage.getBounds().getConstrainedPoint (-*imageOffsetFromMouse); } - dragImageComponent = new DragImageComponent (dragImage, sourceDescription, sourceComponent, - draggingSource->getComponentUnderMouse(), *this, imageOffset); - - if (allowDraggingToExternalWindows) - { - if (! Desktop::canUseSemiTransparentWindows()) - dragImageComponent->setOpaque (true); - - dragImageComponent->addToDesktop (ComponentPeer::windowIgnoresMouseClicks - | ComponentPeer::windowIsTemporary - | ComponentPeer::windowIgnoresKeyPresses); - } - else - { - if (Component* const thisComp = dynamic_cast (this)) - { - thisComp->addChildComponent (dragImageComponent); - } - else - { - jassertfalse; // Your DragAndDropContainer needs to be a Component! - return; - } - } - - static_cast (dragImageComponent.get())->updateLocation (false, lastMouseDown); - dragImageComponent->enterModalState(); - - #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 (ComponentPeer* const peer = dragImageComponent->getPeer()) - peer->performAnyPendingRepaintsNow(); - #endif - - dragOperationStarted (dragImageComponent->sourceDetails); + 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 (this)) + { + thisComp->addChildComponent (dragImageComponent); + } + else + { + jassertfalse; // Your DragAndDropContainer needs to be a Component! + return; + } + } + + 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 dragImageComponent != nullptr; + return dragImageComponents.size() > 0; +} + +int DragAndDropContainer::getNumCurrentDrags() const +{ + return dragImageComponents.size(); } var DragAndDropContainer::getCurrentDragDescription() const { - return dragImageComponent != nullptr ? dragImageComponent->sourceDetails.description - : var(); + // 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 (dragImageComponent != nullptr) - dragImageComponent->updateImage (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) @@ -508,6 +529,38 @@ bool DragAndDropContainer::shouldDropTextWhenDraggedExternally (const DragAndDro 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::max(); + auto& desktop = Desktop::getInstance(); + + auto centrePoint = sourceComponent ? sourceComponent->getScreenBounds().getCentre().toFloat() : Point(); + 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; +} + //============================================================================== DragAndDropTarget::SourceDetails::SourceDetails (const var& desc, Component* comp, Point pos) noexcept : description (desc), diff --git a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h index 437037d38c..9a32460455 100644 --- a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h +++ b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h @@ -71,41 +71,72 @@ public: 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 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. + 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* imageOffsetFromMouse = nullptr); + const Point* 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(). - @see 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; - /** If a drag is in progress, this allows the image being shown to be dynamically updated. */ + /** 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 @@ -199,7 +230,9 @@ private: class DragImageComponent; friend class DragImageComponent; friend struct ContainerDeletePolicy; - ScopedPointer dragImageComponent; + OwnedArray dragImageComponents; + + const MouseInputSource* getMouseInputSourceForDrag (Component* sourceComponent, const MouseInputSource* inputSourceCausingDrag); #if JUCE_CATCH_DEPRECATED_CODE_MISUSE // This is just here to cause a compile error in old code that hasn't been changed to use the new diff --git a/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp b/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp index 541d542183..3312f99a6c 100644 --- a/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp +++ b/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp @@ -640,7 +640,13 @@ struct MouseInputSource::SourceList : public Timer { SourceList() { - addSource (0, MouseInputSource::InputSourceType::mouse); + #if JUCE_ANDROID || JUCE_IOS + auto mainMouseInputType = MouseInputSource::InputSourceType::touch; + #else + auto mainMouseInputType = MouseInputSource::InputSourceType::mouse; + #endif + + addSource (0, mainMouseInputType); } bool addSource(); diff --git a/modules/juce_gui_basics/widgets/juce_ListBox.cpp b/modules/juce_gui_basics/widgets/juce_ListBox.cpp index 6bc5134840..cba598396e 100644 --- a/modules/juce_gui_basics/widgets/juce_ListBox.cpp +++ b/modules/juce_gui_basics/widgets/juce_ListBox.cpp @@ -925,7 +925,7 @@ void ListBox::startDragAndDrop (const MouseEvent& e, const SparseSet& rowsT auto dragImage = createSnapshotOfRows (rowsToDrag, x, y); auto p = Point (x, y) - e.getEventRelativeTo (this).position.toInt(); - dragContainer->startDragging (dragDescription, this, dragImage, allowDraggingToOtherWindows, &p); + dragContainer->startDragging (dragDescription, this, dragImage, allowDraggingToOtherWindows, &p, &e.source); } else { diff --git a/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp b/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp index 154ef827e9..21561d193a 100644 --- a/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp +++ b/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp @@ -75,7 +75,7 @@ public: if (DragAndDropContainer* const dnd = DragAndDropContainer::findParentDragContainerFor (this)) { - dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), Image(), true); + dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), Image(), true, nullptr, &e.source); if (ToolbarItemComponent* const tc = getToolbarItemComponent()) { diff --git a/modules/juce_gui_basics/widgets/juce_TreeView.cpp b/modules/juce_gui_basics/widgets/juce_TreeView.cpp index c34e7ec5f0..712b89fb77 100644 --- a/modules/juce_gui_basics/widgets/juce_TreeView.cpp +++ b/modules/juce_gui_basics/widgets/juce_TreeView.cpp @@ -124,7 +124,7 @@ public: dragImage.multiplyAllAlphas (0.6f); auto imageOffset = pos.getPosition() - e.getPosition(); - dragContainer->startDragging (dragDescription, &owner, dragImage, true, &imageOffset); + dragContainer->startDragging (dragDescription, &owner, dragImage, true, &imageOffset, &e.source); } else {