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.

301 lines
9.5KB

  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. SidePanel::SidePanel (StringRef title, int width, bool positionOnLeft,
  21. Component* contentToDisplay, bool deleteComponentWhenNoLongerNeeded)
  22. : titleLabel ("titleLabel", title),
  23. isOnLeft (positionOnLeft),
  24. panelWidth (width)
  25. {
  26. lookAndFeelChanged();
  27. addAndMakeVisible (titleLabel);
  28. dismissButton.onClick = [this] { showOrHide (false); };
  29. addAndMakeVisible (dismissButton);
  30. auto& desktop = Desktop::getInstance();
  31. desktop.addGlobalMouseListener (this);
  32. desktop.getAnimator().addChangeListener (this);
  33. if (contentToDisplay != nullptr)
  34. setContent (contentToDisplay, deleteComponentWhenNoLongerNeeded);
  35. setOpaque (false);
  36. setVisible (false);
  37. setAlwaysOnTop (true);
  38. }
  39. SidePanel::~SidePanel()
  40. {
  41. auto& desktop = Desktop::getInstance();
  42. desktop.removeGlobalMouseListener (this);
  43. desktop.getAnimator().removeChangeListener (this);
  44. if (parent != nullptr)
  45. parent->removeComponentListener (this);
  46. }
  47. void SidePanel::setContent (Component* newContent, bool deleteComponentWhenNoLongerNeeded)
  48. {
  49. if (contentComponent.get() != newContent)
  50. {
  51. if (deleteComponentWhenNoLongerNeeded)
  52. contentComponent.setOwned (newContent);
  53. else
  54. contentComponent.setNonOwned (newContent);
  55. addAndMakeVisible (contentComponent);
  56. resized();
  57. }
  58. }
  59. void SidePanel::setTitleBarComponent (Component* titleBarComponentToUse,
  60. bool keepDismissButton,
  61. bool deleteComponentWhenNoLongerNeeded)
  62. {
  63. if (titleBarComponent.get() != titleBarComponentToUse)
  64. {
  65. if (deleteComponentWhenNoLongerNeeded)
  66. titleBarComponent.setOwned (titleBarComponentToUse);
  67. else
  68. titleBarComponent.setNonOwned (titleBarComponentToUse);
  69. addAndMakeVisible (titleBarComponent);
  70. resized();
  71. }
  72. shouldShowDismissButton = keepDismissButton;
  73. }
  74. void SidePanel::showOrHide (bool show)
  75. {
  76. if (parent != nullptr)
  77. {
  78. isShowing = show;
  79. Desktop::getInstance().getAnimator().animateComponent (this, calculateBoundsInParent (*parent),
  80. 1.0f, 250, true, 1.0, 0.0);
  81. if (isShowing && ! isVisible())
  82. setVisible (true);
  83. }
  84. }
  85. void SidePanel::moved()
  86. {
  87. NullCheckedInvocation::invoke (onPanelMove);
  88. }
  89. void SidePanel::resized()
  90. {
  91. auto bounds = getLocalBounds();
  92. calculateAndRemoveShadowBounds (bounds);
  93. auto titleBounds = bounds.removeFromTop (titleBarHeight);
  94. if (titleBarComponent != nullptr)
  95. {
  96. if (shouldShowDismissButton)
  97. dismissButton.setBounds (isOnLeft ? titleBounds.removeFromRight (30).withTrimmedRight (10)
  98. : titleBounds.removeFromLeft (30).withTrimmedLeft (10));
  99. titleBarComponent->setBounds (titleBounds);
  100. }
  101. else
  102. {
  103. dismissButton.setBounds (isOnLeft ? titleBounds.removeFromRight (30).withTrimmedRight (10)
  104. : titleBounds.removeFromLeft (30).withTrimmedLeft (10));
  105. titleLabel.setBounds (isOnLeft ? titleBounds.withTrimmedRight (40)
  106. : titleBounds.withTrimmedLeft (40));
  107. }
  108. if (contentComponent != nullptr)
  109. contentComponent->setBounds (bounds);
  110. }
  111. void SidePanel::paint (Graphics& g)
  112. {
  113. auto& lf = getLookAndFeel();
  114. auto bgColour = lf.findColour (SidePanel::backgroundColour);
  115. auto shadowColour = lf.findColour (SidePanel::shadowBaseColour);
  116. g.setGradientFill (ColourGradient (shadowColour.withAlpha (0.7f), (isOnLeft ? shadowArea.getTopLeft()
  117. : shadowArea.getTopRight()).toFloat(),
  118. shadowColour.withAlpha (0.0f), (isOnLeft ? shadowArea.getTopRight()
  119. : shadowArea.getTopLeft()).toFloat(), false));
  120. g.fillRect (shadowArea);
  121. g.reduceClipRegion (getLocalBounds().withTrimmedRight (shadowArea.getWidth())
  122. .withX (isOnLeft ? 0 : shadowArea.getWidth()));
  123. g.fillAll (bgColour);
  124. }
  125. void SidePanel::parentHierarchyChanged()
  126. {
  127. auto* newParent = getParentComponent();
  128. if ((newParent != nullptr) && (parent != newParent))
  129. {
  130. if (parent != nullptr)
  131. parent->removeComponentListener (this);
  132. parent = newParent;
  133. parent->addComponentListener (this);
  134. }
  135. }
  136. void SidePanel::mouseDrag (const MouseEvent& e)
  137. {
  138. if (shouldResize)
  139. {
  140. Point<int> convertedPoint;
  141. if (getParentComponent() == nullptr)
  142. convertedPoint = e.eventComponent->localPointToGlobal (e.getPosition());
  143. else
  144. convertedPoint = getParentComponent()->getLocalPoint (e.eventComponent, e.getPosition());
  145. auto currentMouseDragX = convertedPoint.x;
  146. if (isOnLeft)
  147. {
  148. amountMoved = startingBounds.getRight() - currentMouseDragX;
  149. setBounds (getBounds().withX (startingBounds.getX() - jmax (amountMoved, 0)));
  150. }
  151. else
  152. {
  153. amountMoved = currentMouseDragX - startingBounds.getX();
  154. setBounds (getBounds().withX (startingBounds.getX() + jmax (amountMoved, 0)));
  155. }
  156. }
  157. else if (isShowing)
  158. {
  159. auto relativeMouseDownPosition = getLocalPoint (e.eventComponent, e.getMouseDownPosition());
  160. auto relativeMouseDragPosition = getLocalPoint (e.eventComponent, e.getPosition());
  161. if (! getLocalBounds().contains (relativeMouseDownPosition)
  162. && getLocalBounds().contains (relativeMouseDragPosition))
  163. {
  164. shouldResize = true;
  165. startingBounds = getBounds();
  166. }
  167. }
  168. }
  169. void SidePanel::mouseUp (const MouseEvent&)
  170. {
  171. if (shouldResize)
  172. {
  173. showOrHide (amountMoved < (panelWidth / 2));
  174. amountMoved = 0;
  175. shouldResize = false;
  176. }
  177. }
  178. //==============================================================================
  179. void SidePanel::lookAndFeelChanged()
  180. {
  181. auto& lf = getLookAndFeel();
  182. dismissButton.setShape (lf.getSidePanelDismissButtonShape (*this), false, true, false);
  183. dismissButton.setColours (lf.findColour (SidePanel::dismissButtonNormalColour),
  184. lf.findColour (SidePanel::dismissButtonOverColour),
  185. lf.findColour (SidePanel::dismissButtonDownColour));
  186. titleLabel.setFont (lf.getSidePanelTitleFont (*this));
  187. titleLabel.setColour (Label::textColourId, findColour (SidePanel::titleTextColour));
  188. titleLabel.setJustificationType (lf.getSidePanelTitleJustification (*this));
  189. }
  190. void SidePanel::componentMovedOrResized (Component& component, [[maybe_unused]] bool wasMoved, bool wasResized)
  191. {
  192. if (wasResized && (&component == parent))
  193. setBounds (calculateBoundsInParent (component));
  194. }
  195. void SidePanel::changeListenerCallback (ChangeBroadcaster*)
  196. {
  197. if (! Desktop::getInstance().getAnimator().isAnimating (this))
  198. {
  199. NullCheckedInvocation::invoke (onPanelShowHide, isShowing);
  200. if (isVisible() && ! isShowing)
  201. setVisible (false);
  202. }
  203. }
  204. Rectangle<int> SidePanel::calculateBoundsInParent (Component& parentComp) const
  205. {
  206. auto parentBounds = parentComp.getLocalBounds();
  207. if (isOnLeft)
  208. {
  209. return isShowing ? parentBounds.removeFromLeft (panelWidth)
  210. : parentBounds.withX (parentBounds.getX() - panelWidth).withWidth (panelWidth);
  211. }
  212. return isShowing ? parentBounds.removeFromRight (panelWidth)
  213. : parentBounds.withX (parentBounds.getRight()).withWidth (panelWidth);
  214. }
  215. void SidePanel::calculateAndRemoveShadowBounds (Rectangle<int>& bounds)
  216. {
  217. shadowArea = isOnLeft ? bounds.removeFromRight (shadowWidth)
  218. : bounds.removeFromLeft (shadowWidth);
  219. }
  220. bool SidePanel::isMouseEventInThisOrChildren (Component* eventComponent)
  221. {
  222. if (eventComponent == this)
  223. return true;
  224. for (auto& child : getChildren())
  225. if (eventComponent == child)
  226. return true;
  227. return false;
  228. }
  229. //==============================================================================
  230. std::unique_ptr<AccessibilityHandler> SidePanel::createAccessibilityHandler()
  231. {
  232. return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
  233. }
  234. } // namespace juce