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.

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