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.

618 lines
21KB

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