The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

625 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. bool juce_performDragDropFiles (const StringArray&, const bool copyFiles, bool& shouldStop);
  21. bool juce_performDragDropText (const String&, bool& shouldStop);
  22. //==============================================================================
  23. class DragAndDropContainer::DragImageComponent : public Component,
  24. private Timer
  25. {
  26. public:
  27. DragImageComponent (const ScaledImage& im,
  28. const var& desc,
  29. Component* const sourceComponent,
  30. const MouseInputSource* draggingSource,
  31. DragAndDropContainer& ddc,
  32. Point<int> offset)
  33. : sourceDetails (desc, sourceComponent, Point<int>()),
  34. image (im),
  35. owner (ddc),
  36. mouseDragSource (draggingSource->getComponentUnderMouse()),
  37. imageOffset (transformOffsetCoordinates (sourceComponent, offset)),
  38. originalInputSourceIndex (draggingSource->getIndex()),
  39. originalInputSourceType (draggingSource->getType())
  40. {
  41. updateSize();
  42. if (mouseDragSource == nullptr)
  43. mouseDragSource = sourceComponent;
  44. mouseDragSource->addMouseListener (this, false);
  45. startTimer (200);
  46. setInterceptsMouseClicks (false, false);
  47. setAlwaysOnTop (true);
  48. }
  49. ~DragImageComponent() override
  50. {
  51. owner.dragImageComponents.remove (owner.dragImageComponents.indexOf (this), false);
  52. if (mouseDragSource != nullptr)
  53. {
  54. mouseDragSource->removeMouseListener (this);
  55. if (auto* current = getCurrentlyOver())
  56. if (current->isInterestedInDragSource (sourceDetails))
  57. current->itemDragExit (sourceDetails);
  58. }
  59. owner.dragOperationEnded (sourceDetails);
  60. }
  61. void paint (Graphics& g) override
  62. {
  63. if (isOpaque())
  64. g.fillAll (Colours::white);
  65. g.setOpacity (1.0f);
  66. g.drawImage (image.getImage(), getLocalBounds().toFloat());
  67. }
  68. void mouseUp (const MouseEvent& e) override
  69. {
  70. if (e.originalComponent != this && isOriginalInputSource (e.source))
  71. {
  72. if (mouseDragSource != nullptr)
  73. mouseDragSource->removeMouseListener (this);
  74. // (note: use a local copy of this in case the callback runs
  75. // a modal loop and deletes this object before the method completes)
  76. auto details = sourceDetails;
  77. DragAndDropTarget* finalTarget = nullptr;
  78. auto wasVisible = isVisible();
  79. setVisible (false);
  80. Component* unused;
  81. finalTarget = findTarget (e.getScreenPosition(), details.localPosition, unused);
  82. if (wasVisible) // fade the component and remove it - it'll be deleted later by the timer callback
  83. dismissWithAnimation (finalTarget == nullptr);
  84. if (auto* parent = getParentComponent())
  85. parent->removeChildComponent (this);
  86. if (finalTarget != nullptr)
  87. {
  88. currentlyOverComp = nullptr;
  89. finalTarget->itemDropped (details);
  90. }
  91. // careful - this object could now be deleted..
  92. }
  93. }
  94. void mouseDrag (const MouseEvent& e) override
  95. {
  96. if (e.originalComponent != this && isOriginalInputSource (e.source))
  97. updateLocation (true, e.getScreenPosition());
  98. }
  99. void updateLocation (const bool canDoExternalDrag, Point<int> screenPos)
  100. {
  101. auto details = sourceDetails;
  102. setNewScreenPos (screenPos);
  103. Component* newTargetComp;
  104. auto* newTarget = findTarget (screenPos, details.localPosition, newTargetComp);
  105. setVisible (newTarget == nullptr || newTarget->shouldDrawDragImageWhenOver());
  106. if (newTargetComp != currentlyOverComp)
  107. {
  108. if (auto* lastTarget = getCurrentlyOver())
  109. if (details.sourceComponent != nullptr && lastTarget->isInterestedInDragSource (details))
  110. lastTarget->itemDragExit (details);
  111. currentlyOverComp = newTargetComp;
  112. if (newTarget != nullptr
  113. && newTarget->isInterestedInDragSource (details))
  114. newTarget->itemDragEnter (details);
  115. }
  116. sendDragMove (details);
  117. if (canDoExternalDrag)
  118. {
  119. auto now = Time::getCurrentTime();
  120. if (getCurrentlyOver() != nullptr)
  121. lastTimeOverTarget = now;
  122. else if (now > lastTimeOverTarget + RelativeTime::milliseconds (700))
  123. checkForExternalDrag (details, screenPos);
  124. }
  125. forceMouseCursorUpdate();
  126. }
  127. void updateImage (const ScaledImage& newImage)
  128. {
  129. image = newImage;
  130. updateSize();
  131. repaint();
  132. }
  133. void timerCallback() override
  134. {
  135. forceMouseCursorUpdate();
  136. if (sourceDetails.sourceComponent == nullptr)
  137. {
  138. deleteSelf();
  139. }
  140. else
  141. {
  142. for (auto& s : Desktop::getInstance().getMouseSources())
  143. {
  144. if (isOriginalInputSource (s) && ! s.isDragging())
  145. {
  146. if (mouseDragSource != nullptr)
  147. mouseDragSource->removeMouseListener (this);
  148. deleteSelf();
  149. break;
  150. }
  151. }
  152. }
  153. }
  154. bool keyPressed (const KeyPress& key) override
  155. {
  156. if (key == KeyPress::escapeKey)
  157. {
  158. dismissWithAnimation (true);
  159. deleteSelf();
  160. return true;
  161. }
  162. return false;
  163. }
  164. bool canModalEventBeSentToComponent (const Component* targetComponent) override
  165. {
  166. return targetComponent == mouseDragSource;
  167. }
  168. // (overridden to avoid beeps when dragging)
  169. void inputAttemptWhenModal() override {}
  170. DragAndDropTarget::SourceDetails sourceDetails;
  171. private:
  172. ScaledImage image;
  173. DragAndDropContainer& owner;
  174. WeakReference<Component> mouseDragSource, currentlyOverComp;
  175. const Point<int> imageOffset;
  176. bool hasCheckedForExternalDrag = false;
  177. Time lastTimeOverTarget;
  178. int originalInputSourceIndex;
  179. MouseInputSource::InputSourceType originalInputSourceType;
  180. void updateSize()
  181. {
  182. const auto bounds = image.getScaledBounds().toNearestInt();
  183. setSize (bounds.getWidth(), bounds.getHeight());
  184. }
  185. void forceMouseCursorUpdate()
  186. {
  187. Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
  188. }
  189. DragAndDropTarget* getCurrentlyOver() const noexcept
  190. {
  191. return dynamic_cast<DragAndDropTarget*> (currentlyOverComp.get());
  192. }
  193. static Component* findDesktopComponentBelow (Point<int> screenPos)
  194. {
  195. auto& desktop = Desktop::getInstance();
  196. for (auto i = desktop.getNumComponents(); --i >= 0;)
  197. {
  198. auto* desktopComponent = desktop.getComponent (i);
  199. auto dPoint = desktopComponent->getLocalPoint (nullptr, screenPos);
  200. if (auto* c = desktopComponent->getComponentAt (dPoint))
  201. {
  202. auto cPoint = c->getLocalPoint (desktopComponent, dPoint);
  203. if (c->hitTest (cPoint.getX(), cPoint.getY()))
  204. return c;
  205. }
  206. }
  207. return nullptr;
  208. }
  209. Point<int> transformOffsetCoordinates (const Component* const sourceComponent, Point<int> offsetInSource) const
  210. {
  211. return getLocalPoint (sourceComponent, offsetInSource) - getLocalPoint (sourceComponent, Point<int>());
  212. }
  213. DragAndDropTarget* findTarget (Point<int> screenPos, Point<int>& relativePos,
  214. Component*& resultComponent) const
  215. {
  216. auto* hit = getParentComponent();
  217. if (hit == nullptr)
  218. hit = findDesktopComponentBelow (screenPos);
  219. else
  220. hit = hit->getComponentAt (hit->getLocalPoint (nullptr, screenPos));
  221. // (note: use a local copy of this in case the callback runs
  222. // a modal loop and deletes this object before the method completes)
  223. auto details = sourceDetails;
  224. while (hit != nullptr)
  225. {
  226. if (auto* ddt = dynamic_cast<DragAndDropTarget*> (hit))
  227. {
  228. if (ddt->isInterestedInDragSource (details))
  229. {
  230. relativePos = hit->getLocalPoint (nullptr, screenPos);
  231. resultComponent = hit;
  232. return ddt;
  233. }
  234. }
  235. hit = hit->getParentComponent();
  236. }
  237. resultComponent = nullptr;
  238. return nullptr;
  239. }
  240. void setNewScreenPos (Point<int> screenPos)
  241. {
  242. auto newPos = screenPos - imageOffset;
  243. if (auto* p = getParentComponent())
  244. newPos = p->getLocalPoint (nullptr, newPos);
  245. setTopLeftPosition (newPos);
  246. }
  247. void sendDragMove (DragAndDropTarget::SourceDetails& details) const
  248. {
  249. if (auto* target = getCurrentlyOver())
  250. if (target->isInterestedInDragSource (details))
  251. target->itemDragMove (details);
  252. }
  253. void checkForExternalDrag (DragAndDropTarget::SourceDetails& details, Point<int> screenPos)
  254. {
  255. if (! hasCheckedForExternalDrag)
  256. {
  257. if (Desktop::getInstance().findComponentAt (screenPos) == nullptr)
  258. {
  259. hasCheckedForExternalDrag = true;
  260. if (ComponentPeer::getCurrentModifiersRealtime().isAnyMouseButtonDown())
  261. {
  262. StringArray files;
  263. auto canMoveFiles = false;
  264. if (owner.shouldDropFilesWhenDraggedExternally (details, files, canMoveFiles) && ! files.isEmpty())
  265. {
  266. MessageManager::callAsync ([=] { DragAndDropContainer::performExternalDragDropOfFiles (files, canMoveFiles); });
  267. deleteSelf();
  268. return;
  269. }
  270. String text;
  271. if (owner.shouldDropTextWhenDraggedExternally (details, text) && text.isNotEmpty())
  272. {
  273. MessageManager::callAsync ([=] { DragAndDropContainer::performExternalDragDropOfText (text); });
  274. deleteSelf();
  275. return;
  276. }
  277. }
  278. }
  279. }
  280. }
  281. void deleteSelf()
  282. {
  283. delete this;
  284. }
  285. void dismissWithAnimation (const bool shouldSnapBack)
  286. {
  287. setVisible (true);
  288. auto& animator = Desktop::getInstance().getAnimator();
  289. if (shouldSnapBack && sourceDetails.sourceComponent != nullptr)
  290. {
  291. auto target = sourceDetails.sourceComponent->localPointToGlobal (sourceDetails.sourceComponent->getLocalBounds().getCentre());
  292. auto ourCentre = localPointToGlobal (getLocalBounds().getCentre());
  293. animator.animateComponent (this,
  294. getBounds() + (target - ourCentre),
  295. 0.0f, 120,
  296. true, 1.0, 1.0);
  297. }
  298. else
  299. {
  300. animator.fadeOut (this, 120);
  301. }
  302. }
  303. bool isOriginalInputSource (const MouseInputSource& sourceToCheck)
  304. {
  305. return (sourceToCheck.getType() == originalInputSourceType
  306. && sourceToCheck.getIndex() == originalInputSourceIndex);
  307. }
  308. JUCE_DECLARE_NON_COPYABLE (DragImageComponent)
  309. };
  310. //==============================================================================
  311. DragAndDropContainer::DragAndDropContainer() = default;
  312. DragAndDropContainer::~DragAndDropContainer() = default;
  313. void DragAndDropContainer::startDragging (const var& sourceDescription,
  314. Component* sourceComponent,
  315. const ScaledImage& dragImage,
  316. const bool allowDraggingToExternalWindows,
  317. const Point<int>* imageOffsetFromMouse,
  318. const MouseInputSource* inputSourceCausingDrag)
  319. {
  320. if (isAlreadyDragging (sourceComponent))
  321. return;
  322. auto* draggingSource = getMouseInputSourceForDrag (sourceComponent, inputSourceCausingDrag);
  323. if (draggingSource == nullptr || ! draggingSource->isDragging())
  324. {
  325. jassertfalse; // You must call startDragging() from within a mouseDown or mouseDrag callback!
  326. return;
  327. }
  328. const auto lastMouseDown = draggingSource->getLastMouseDownPosition().roundToInt();
  329. struct ImageAndOffset
  330. {
  331. ScaledImage image;
  332. Point<double> offset;
  333. };
  334. const auto imageToUse = [&]() -> ImageAndOffset
  335. {
  336. if (! dragImage.getImage().isNull())
  337. return { dragImage, imageOffsetFromMouse != nullptr ? dragImage.getScaledBounds().getConstrainedPoint (-imageOffsetFromMouse->toDouble())
  338. : dragImage.getScaledBounds().getCentre() };
  339. const auto scaleFactor = 2.0;
  340. auto image = sourceComponent->createComponentSnapshot (sourceComponent->getLocalBounds(), true, (float) scaleFactor)
  341. .convertedToFormat (Image::ARGB);
  342. image.multiplyAllAlphas (0.6f);
  343. const auto relPos = sourceComponent->getLocalPoint (nullptr, lastMouseDown).toDouble();
  344. const auto clipped = (image.getBounds().toDouble() / scaleFactor).getConstrainedPoint (relPos);
  345. Image fade (Image::SingleChannel, image.getWidth(), image.getHeight(), true);
  346. Graphics fadeContext (fade);
  347. ColourGradient gradient;
  348. gradient.isRadial = true;
  349. gradient.point1 = clipped.toFloat() * scaleFactor;
  350. gradient.point2 = gradient.point1 + Point<float> (0.0f, scaleFactor * 400.0f);
  351. gradient.addColour (0.0, Colours::white);
  352. gradient.addColour (0.375, Colours::white);
  353. gradient.addColour (1.0, Colours::transparentWhite);
  354. fadeContext.setGradientFill (gradient);
  355. fadeContext.fillAll();
  356. Image composite (Image::ARGB, image.getWidth(), image.getHeight(), true);
  357. Graphics compositeContext (composite);
  358. compositeContext.reduceClipRegion (fade, {});
  359. compositeContext.drawImageAt (image, 0, 0);
  360. return { ScaledImage (composite, scaleFactor), clipped };
  361. }();
  362. auto* dragImageComponent = dragImageComponents.add (new DragImageComponent (imageToUse.image, sourceDescription, sourceComponent,
  363. draggingSource, *this, imageToUse.offset.roundToInt()));
  364. if (allowDraggingToExternalWindows)
  365. {
  366. if (! Desktop::canUseSemiTransparentWindows())
  367. dragImageComponent->setOpaque (true);
  368. dragImageComponent->addToDesktop (ComponentPeer::windowIgnoresMouseClicks
  369. | ComponentPeer::windowIsTemporary
  370. | ComponentPeer::windowIgnoresKeyPresses);
  371. }
  372. else
  373. {
  374. if (auto* thisComp = dynamic_cast<Component*> (this))
  375. {
  376. thisComp->addChildComponent (dragImageComponent);
  377. }
  378. else
  379. {
  380. jassertfalse; // Your DragAndDropContainer needs to be a Component!
  381. return;
  382. }
  383. }
  384. dragImageComponent->sourceDetails.localPosition = sourceComponent->getLocalPoint (nullptr, lastMouseDown);
  385. dragImageComponent->updateLocation (false, lastMouseDown);
  386. #if JUCE_WINDOWS
  387. // Under heavy load, the layered window's paint callback can often be lost by the OS,
  388. // so forcing a repaint at least once makes sure that the window becomes visible..
  389. if (auto* peer = dragImageComponent->getPeer())
  390. peer->performAnyPendingRepaintsNow();
  391. #endif
  392. dragOperationStarted (dragImageComponent->sourceDetails);
  393. }
  394. bool DragAndDropContainer::isDragAndDropActive() const
  395. {
  396. return dragImageComponents.size() > 0;
  397. }
  398. int DragAndDropContainer::getNumCurrentDrags() const
  399. {
  400. return dragImageComponents.size();
  401. }
  402. var DragAndDropContainer::getCurrentDragDescription() const
  403. {
  404. // If you are performing drag and drop in a multi-touch environment then
  405. // you should use the getDragDescriptionForIndex() method instead!
  406. jassert (dragImageComponents.size() < 2);
  407. return dragImageComponents.size() != 0 ? dragImageComponents[0]->sourceDetails.description
  408. : var();
  409. }
  410. var DragAndDropContainer::getDragDescriptionForIndex (int index) const
  411. {
  412. if (! isPositiveAndBelow (index, dragImageComponents.size()))
  413. return {};
  414. return dragImageComponents.getUnchecked (index)->sourceDetails.description;
  415. }
  416. void DragAndDropContainer::setCurrentDragImage (const ScaledImage& newImage)
  417. {
  418. // If you are performing drag and drop in a multi-touch environment then
  419. // you should use the setDragImageForIndex() method instead!
  420. jassert (dragImageComponents.size() < 2);
  421. dragImageComponents[0]->updateImage (newImage);
  422. }
  423. void DragAndDropContainer::setDragImageForIndex (int index, const ScaledImage& newImage)
  424. {
  425. if (isPositiveAndBelow (index, dragImageComponents.size()))
  426. dragImageComponents.getUnchecked (index)->updateImage (newImage);
  427. }
  428. DragAndDropContainer* DragAndDropContainer::findParentDragContainerFor (Component* c)
  429. {
  430. return c != nullptr ? c->findParentComponentOfClass<DragAndDropContainer>() : nullptr;
  431. }
  432. bool DragAndDropContainer::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails&, StringArray&, bool&)
  433. {
  434. return false;
  435. }
  436. bool DragAndDropContainer::shouldDropTextWhenDraggedExternally (const DragAndDropTarget::SourceDetails&, String&)
  437. {
  438. return false;
  439. }
  440. void DragAndDropContainer::dragOperationStarted (const DragAndDropTarget::SourceDetails&) {}
  441. void DragAndDropContainer::dragOperationEnded (const DragAndDropTarget::SourceDetails&) {}
  442. const MouseInputSource* DragAndDropContainer::getMouseInputSourceForDrag (Component* sourceComponent,
  443. const MouseInputSource* inputSourceCausingDrag)
  444. {
  445. if (inputSourceCausingDrag == nullptr)
  446. {
  447. auto minDistance = std::numeric_limits<float>::max();
  448. auto& desktop = Desktop::getInstance();
  449. auto centrePoint = sourceComponent ? sourceComponent->getScreenBounds().getCentre().toFloat() : Point<float>();
  450. auto numDragging = desktop.getNumDraggingMouseSources();
  451. for (auto i = 0; i < numDragging; ++i)
  452. {
  453. if (auto* ms = desktop.getDraggingMouseSource (i))
  454. {
  455. auto distance = ms->getScreenPosition().getDistanceSquaredFrom (centrePoint);
  456. if (distance < minDistance)
  457. {
  458. minDistance = distance;
  459. inputSourceCausingDrag = ms;
  460. }
  461. }
  462. }
  463. }
  464. // You must call startDragging() from within a mouseDown or mouseDrag callback!
  465. jassert (inputSourceCausingDrag != nullptr && inputSourceCausingDrag->isDragging());
  466. return inputSourceCausingDrag;
  467. }
  468. bool DragAndDropContainer::isAlreadyDragging (Component* component) const noexcept
  469. {
  470. for (auto* dragImageComp : dragImageComponents)
  471. {
  472. if (dragImageComp->sourceDetails.sourceComponent == component)
  473. return true;
  474. }
  475. return false;
  476. }
  477. //==============================================================================
  478. DragAndDropTarget::SourceDetails::SourceDetails (const var& desc, Component* comp, Point<int> pos) noexcept
  479. : description (desc),
  480. sourceComponent (comp),
  481. localPosition (pos)
  482. {
  483. }
  484. void DragAndDropTarget::itemDragEnter (const SourceDetails&) {}
  485. void DragAndDropTarget::itemDragMove (const SourceDetails&) {}
  486. void DragAndDropTarget::itemDragExit (const SourceDetails&) {}
  487. bool DragAndDropTarget::shouldDrawDragImageWhenOver() { return true; }
  488. //==============================================================================
  489. void FileDragAndDropTarget::fileDragEnter (const StringArray&, int, int) {}
  490. void FileDragAndDropTarget::fileDragMove (const StringArray&, int, int) {}
  491. void FileDragAndDropTarget::fileDragExit (const StringArray&) {}
  492. void TextDragAndDropTarget::textDragEnter (const String&, int, int) {}
  493. void TextDragAndDropTarget::textDragMove (const String&, int, int) {}
  494. void TextDragAndDropTarget::textDragExit (const String&) {}
  495. } // namespace juce