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.

262 lines
8.3KB

  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. CallOutBox::CallOutBox (Component& c, const Rectangle<int>& area, Component* const parent)
  20. : arrowSize (16.0f), content (c), dismissalMouseClicksAreAlwaysConsumed (false)
  21. {
  22. addAndMakeVisible (content);
  23. if (parent != nullptr)
  24. {
  25. parent->addChildComponent (this);
  26. updatePosition (area, parent->getLocalBounds());
  27. setVisible (true);
  28. }
  29. else
  30. {
  31. setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
  32. updatePosition (area, Desktop::getInstance().getDisplays()
  33. .getDisplayContaining (area.getCentre()).userArea);
  34. addToDesktop (ComponentPeer::windowIsTemporary);
  35. startTimer (100);
  36. }
  37. creationTime = Time::getCurrentTime();
  38. }
  39. CallOutBox::~CallOutBox()
  40. {
  41. }
  42. //==============================================================================
  43. class CallOutBoxCallback : public ModalComponentManager::Callback,
  44. private Timer
  45. {
  46. public:
  47. CallOutBoxCallback (Component* c, const Rectangle<int>& area, Component* parent)
  48. : content (c), callout (*c, area, parent)
  49. {
  50. callout.setVisible (true);
  51. callout.enterModalState (true, this);
  52. startTimer (200);
  53. }
  54. void modalStateFinished (int) override {}
  55. void timerCallback() override
  56. {
  57. if (! Process::isForegroundProcess())
  58. callout.dismiss();
  59. }
  60. ScopedPointer<Component> content;
  61. CallOutBox callout;
  62. JUCE_DECLARE_NON_COPYABLE (CallOutBoxCallback)
  63. };
  64. CallOutBox& CallOutBox::launchAsynchronously (Component* content, const Rectangle<int>& area, Component* parent)
  65. {
  66. jassert (content != nullptr); // must be a valid content component!
  67. return (new CallOutBoxCallback (content, area, parent))->callout;
  68. }
  69. //==============================================================================
  70. void CallOutBox::setArrowSize (const float newSize)
  71. {
  72. arrowSize = newSize;
  73. refreshPath();
  74. }
  75. int CallOutBox::getBorderSize() const noexcept
  76. {
  77. return jmax (getLookAndFeel().getCallOutBoxBorderSize (*this), (int) arrowSize);
  78. }
  79. void CallOutBox::paint (Graphics& g)
  80. {
  81. getLookAndFeel().drawCallOutBoxBackground (*this, g, outline, background);
  82. }
  83. void CallOutBox::resized()
  84. {
  85. const int borderSpace = getBorderSize();
  86. content.setTopLeftPosition (borderSpace, borderSpace);
  87. refreshPath();
  88. }
  89. void CallOutBox::moved()
  90. {
  91. refreshPath();
  92. }
  93. void CallOutBox::childBoundsChanged (Component*)
  94. {
  95. updatePosition (targetArea, availableArea);
  96. }
  97. bool CallOutBox::hitTest (int x, int y)
  98. {
  99. return outline.contains ((float) x, (float) y);
  100. }
  101. void CallOutBox::inputAttemptWhenModal()
  102. {
  103. if (dismissalMouseClicksAreAlwaysConsumed
  104. || targetArea.contains (getMouseXYRelative() + getBounds().getPosition()))
  105. {
  106. // if you click on the area that originally popped-up the callout, you expect it
  107. // to get rid of the box, but deleting the box here allows the click to pass through and
  108. // probably re-trigger it, so we need to dismiss the box asynchronously to consume the click..
  109. // For touchscreens, we make sure not to dismiss the CallOutBox immediately,
  110. // as Windows still sends touch events before the CallOutBox had a chance
  111. // to really open.
  112. RelativeTime elapsed = Time::getCurrentTime() - creationTime;
  113. if (elapsed.inMilliseconds() > 200)
  114. dismiss();
  115. }
  116. else
  117. {
  118. exitModalState (0);
  119. setVisible (false);
  120. }
  121. }
  122. void CallOutBox::setDismissalMouseClicksAreAlwaysConsumed (bool b) noexcept
  123. {
  124. dismissalMouseClicksAreAlwaysConsumed = b;
  125. }
  126. enum { callOutBoxDismissCommandId = 0x4f83a04b };
  127. void CallOutBox::handleCommandMessage (int commandId)
  128. {
  129. Component::handleCommandMessage (commandId);
  130. if (commandId == callOutBoxDismissCommandId)
  131. {
  132. exitModalState (0);
  133. setVisible (false);
  134. }
  135. }
  136. void CallOutBox::dismiss()
  137. {
  138. postCommandMessage (callOutBoxDismissCommandId);
  139. }
  140. bool CallOutBox::keyPressed (const KeyPress& key)
  141. {
  142. if (key.isKeyCode (KeyPress::escapeKey))
  143. {
  144. inputAttemptWhenModal();
  145. return true;
  146. }
  147. return false;
  148. }
  149. void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const Rectangle<int>& newAreaToFitIn)
  150. {
  151. targetArea = newAreaToPointTo;
  152. availableArea = newAreaToFitIn;
  153. const int borderSpace = getBorderSize();
  154. Rectangle<int> newBounds (content.getWidth() + borderSpace * 2,
  155. content.getHeight() + borderSpace * 2);
  156. const int hw = newBounds.getWidth() / 2;
  157. const int hh = newBounds.getHeight() / 2;
  158. const float hwReduced = (float) (hw - borderSpace * 2);
  159. const float hhReduced = (float) (hh - borderSpace * 2);
  160. const float arrowIndent = borderSpace - arrowSize;
  161. Point<float> targets[4] = { Point<float> ((float) targetArea.getCentreX(), (float) targetArea.getBottom()),
  162. Point<float> ((float) targetArea.getRight(), (float) targetArea.getCentreY()),
  163. Point<float> ((float) targetArea.getX(), (float) targetArea.getCentreY()),
  164. Point<float> ((float) targetArea.getCentreX(), (float) targetArea.getY()) };
  165. Line<float> lines[4] = { Line<float> (targets[0].translated (-hwReduced, hh - arrowIndent), targets[0].translated (hwReduced, hh - arrowIndent)),
  166. Line<float> (targets[1].translated (hw - arrowIndent, -hhReduced), targets[1].translated (hw - arrowIndent, hhReduced)),
  167. Line<float> (targets[2].translated (-(hw - arrowIndent), -hhReduced), targets[2].translated (-(hw - arrowIndent), hhReduced)),
  168. Line<float> (targets[3].translated (-hwReduced, -(hh - arrowIndent)), targets[3].translated (hwReduced, -(hh - arrowIndent))) };
  169. const Rectangle<float> centrePointArea (newAreaToFitIn.reduced (hw, hh).toFloat());
  170. const Point<float> targetCentre (targetArea.getCentre().toFloat());
  171. float nearest = 1.0e9f;
  172. for (int i = 0; i < 4; ++i)
  173. {
  174. Line<float> constrainedLine (centrePointArea.getConstrainedPoint (lines[i].getStart()),
  175. centrePointArea.getConstrainedPoint (lines[i].getEnd()));
  176. const Point<float> centre (constrainedLine.findNearestPointTo (targetCentre));
  177. float distanceFromCentre = centre.getDistanceFrom (targets[i]);
  178. if (! centrePointArea.intersects (lines[i]))
  179. distanceFromCentre += 1000.0f;
  180. if (distanceFromCentre < nearest)
  181. {
  182. nearest = distanceFromCentre;
  183. targetPoint = targets[i];
  184. newBounds.setPosition ((int) (centre.x - hw),
  185. (int) (centre.y - hh));
  186. }
  187. }
  188. setBounds (newBounds);
  189. }
  190. void CallOutBox::refreshPath()
  191. {
  192. repaint();
  193. background = Image();
  194. outline.clear();
  195. const float gap = 4.5f;
  196. outline.addBubble (content.getBounds().toFloat().expanded (gap, gap),
  197. getLocalBounds().toFloat(),
  198. targetPoint - getPosition().toFloat(),
  199. 9.0f, arrowSize * 0.7f);
  200. }
  201. void CallOutBox::timerCallback()
  202. {
  203. toFront (true);
  204. stopTimer();
  205. }