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.

534 lines
18KB

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