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.

617 lines
21KB

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