Audio plugin host https://kx.studio/carla
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.

631 lines
22KB

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