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.

439 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 7 technical preview.
  4. Copyright (c) 2022 - 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 the technical preview this file cannot be licensed commercially.
  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. #pragma once
  14. #include "../../Utility/UI/PropertyComponents/jucer_LabelPropertyComponent.h"
  15. //==============================================================================
  16. struct ContentViewHeader : public Component
  17. {
  18. ContentViewHeader (String headerName, Icon headerIcon)
  19. : name (headerName), icon (headerIcon)
  20. {
  21. setTitle (name);
  22. }
  23. void paint (Graphics& g) override
  24. {
  25. g.fillAll (findColour (contentHeaderBackgroundColourId));
  26. auto bounds = getLocalBounds().reduced (20, 0);
  27. icon.withColour (Colours::white).draw (g, bounds.toFloat().removeFromRight (30), false);
  28. g.setColour (Colours::white);
  29. g.setFont (Font (18.0f));
  30. g.drawFittedText (name, bounds, Justification::centredLeft, 1);
  31. }
  32. String name;
  33. Icon icon;
  34. };
  35. //==============================================================================
  36. class ListBoxHeader : public Component
  37. {
  38. public:
  39. ListBoxHeader (Array<String> columnHeaders)
  40. {
  41. for (auto s : columnHeaders)
  42. {
  43. addAndMakeVisible (headers.add (new Label (s, s)));
  44. widths.add (1.0f / (float) columnHeaders.size());
  45. }
  46. setSize (200, 40);
  47. }
  48. ListBoxHeader (Array<String> columnHeaders, Array<float> columnWidths)
  49. {
  50. jassert (columnHeaders.size() == columnWidths.size());
  51. auto index = 0;
  52. for (auto s : columnHeaders)
  53. {
  54. addAndMakeVisible (headers.add (new Label (s, s)));
  55. widths.add (columnWidths.getUnchecked (index++));
  56. }
  57. recalculateWidths();
  58. setSize (200, 40);
  59. }
  60. void resized() override
  61. {
  62. auto bounds = getLocalBounds();
  63. auto width = bounds.getWidth();
  64. auto index = 0;
  65. for (auto h : headers)
  66. {
  67. auto headerWidth = roundToInt ((float) width * widths.getUnchecked (index));
  68. h->setBounds (bounds.removeFromLeft (headerWidth));
  69. ++index;
  70. }
  71. }
  72. void setColumnHeaderWidth (int index, float proportionOfWidth)
  73. {
  74. if (! (isPositiveAndBelow (index, headers.size()) && isPositiveAndNotGreaterThan (proportionOfWidth, 1.0f)))
  75. {
  76. jassertfalse;
  77. return;
  78. }
  79. widths.set (index, proportionOfWidth);
  80. recalculateWidths (index);
  81. }
  82. int getColumnX (int index)
  83. {
  84. auto prop = 0.0f;
  85. for (int i = 0; i < index; ++i)
  86. prop += widths.getUnchecked (i);
  87. return roundToInt (prop * (float) getWidth());
  88. }
  89. float getProportionAtIndex (int index)
  90. {
  91. jassert (isPositiveAndBelow (index, widths.size()));
  92. return widths.getUnchecked (index);
  93. }
  94. private:
  95. OwnedArray<Label> headers;
  96. Array<float> widths;
  97. void recalculateWidths (int indexToIgnore = -1)
  98. {
  99. auto total = 0.0f;
  100. for (auto w : widths)
  101. total += w;
  102. if (total == 1.0f)
  103. return;
  104. auto diff = 1.0f - total;
  105. auto amount = diff / static_cast<float> (indexToIgnore == -1 ? widths.size() : widths.size() - 1);
  106. for (int i = 0; i < widths.size(); ++i)
  107. {
  108. if (i != indexToIgnore)
  109. {
  110. auto val = widths.getUnchecked (i);
  111. widths.set (i, val + amount);
  112. }
  113. }
  114. }
  115. };
  116. //==============================================================================
  117. class InfoButton : public Button
  118. {
  119. public:
  120. InfoButton (const String& infoToDisplay = {})
  121. : Button ({})
  122. {
  123. setTitle ("Info");
  124. if (infoToDisplay.isNotEmpty())
  125. setInfoToDisplay (infoToDisplay);
  126. setSize (20, 20);
  127. }
  128. void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
  129. {
  130. auto bounds = getLocalBounds().toFloat().reduced (2);
  131. auto& icon = getIcons().info;
  132. g.setColour (findColour (treeIconColourId).withMultipliedAlpha (isMouseOverButton || isButtonDown ? 1.0f : 0.5f));
  133. if (isButtonDown)
  134. g.fillEllipse (bounds);
  135. else
  136. g.fillPath (icon, RectanglePlacement (RectanglePlacement::centred)
  137. .getTransformToFit (icon.getBounds(), bounds));
  138. }
  139. void clicked() override
  140. {
  141. auto w = std::make_unique<InfoWindow> (info);
  142. w->setSize (width, w->getHeight() * numLines + 10);
  143. CallOutBox::launchAsynchronously (std::move (w), getScreenBounds(), nullptr);
  144. }
  145. using Button::clicked;
  146. void setInfoToDisplay (const String& infoToDisplay)
  147. {
  148. if (infoToDisplay.isNotEmpty())
  149. {
  150. info = infoToDisplay;
  151. auto stringWidth = roundToInt (Font (14.0f).getStringWidthFloat (info));
  152. width = jmin (300, stringWidth);
  153. numLines += static_cast<int> (stringWidth / width);
  154. setHelpText (info);
  155. }
  156. }
  157. void setAssociatedComponent (Component* comp) { associatedComponent = comp; }
  158. Component* getAssociatedComponent() { return associatedComponent; }
  159. private:
  160. String info;
  161. Component* associatedComponent = nullptr;
  162. int width;
  163. int numLines = 1;
  164. //==============================================================================
  165. struct InfoWindow : public Component
  166. {
  167. InfoWindow (const String& s)
  168. : stringToDisplay (s)
  169. {
  170. setSize (150, 14);
  171. }
  172. void paint (Graphics& g) override
  173. {
  174. g.fillAll (findColour (secondaryBackgroundColourId));
  175. g.setColour (findColour (defaultTextColourId));
  176. g.setFont (Font (14.0f));
  177. g.drawFittedText (stringToDisplay, getLocalBounds(), Justification::centred, 15, 0.75f);
  178. }
  179. String stringToDisplay;
  180. };
  181. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InfoButton)
  182. };
  183. //==============================================================================
  184. class PropertyGroupComponent : public Component,
  185. private TextPropertyComponent::Listener
  186. {
  187. public:
  188. PropertyGroupComponent (String name, Icon icon, String desc = {})
  189. : header (name, icon),
  190. description (desc)
  191. {
  192. addAndMakeVisible (header);
  193. }
  194. void setProperties (const PropertyListBuilder& newProps)
  195. {
  196. clearProperties();
  197. if (description.isNotEmpty())
  198. properties.push_back (std::make_unique<LabelPropertyComponent> (description, 16, Font (16.0f),
  199. Justification::centredLeft));
  200. for (auto* comp : newProps.components)
  201. properties.push_back (std::unique_ptr<PropertyComponent> (comp));
  202. for (auto& prop : properties)
  203. {
  204. const auto propertyTooltip = prop->getTooltip();
  205. if (propertyTooltip.isNotEmpty())
  206. {
  207. // set the tooltip to empty so it only displays when its button is clicked
  208. prop->setTooltip ({});
  209. auto infoButton = std::make_unique<InfoButton> (propertyTooltip);
  210. infoButton->setAssociatedComponent (prop.get());
  211. auto propertyAndInfoWrapper = std::make_unique<PropertyAndInfoWrapper> (*prop, *infoButton.get());
  212. addAndMakeVisible (propertyAndInfoWrapper.get());
  213. propertyComponentsWithInfo.push_back (std::move (propertyAndInfoWrapper));
  214. infoButtons.push_back (std::move (infoButton));
  215. }
  216. else
  217. {
  218. addAndMakeVisible (prop.get());
  219. }
  220. if (auto* multiChoice = dynamic_cast<MultiChoicePropertyComponent*> (prop.get()))
  221. multiChoice->onHeightChange = [this] { updateSize(); };
  222. if (auto* text = dynamic_cast<TextPropertyComponent*> (prop.get()))
  223. if (text->isTextEditorMultiLine())
  224. text->addListener (this);
  225. }
  226. }
  227. int updateSize (int x, int y, int width)
  228. {
  229. header.setBounds (0, 0, width, headerSize);
  230. auto height = header.getBottom() + 10;
  231. for (auto& pp : properties)
  232. {
  233. const auto propertyHeight = jmax (pp->getPreferredHeight(), getApproximateLabelHeight (*pp));
  234. auto iter = std::find_if (propertyComponentsWithInfo.begin(), propertyComponentsWithInfo.end(),
  235. [&pp] (const std::unique_ptr<PropertyAndInfoWrapper>& w) { return &w->propertyComponent == pp.get(); });
  236. if (iter != propertyComponentsWithInfo.end())
  237. (*iter)->setBounds (0, height, width - 10, propertyHeight);
  238. else
  239. pp->setBounds (40, height, width - 50, propertyHeight);
  240. if (shouldResizePropertyComponent (pp.get()))
  241. resizePropertyComponent (pp.get());
  242. height += pp->getHeight() + 10;
  243. }
  244. height += 16;
  245. setBounds (x, y, width, jmax (height, getParentHeight()));
  246. return height;
  247. }
  248. void paint (Graphics& g) override
  249. {
  250. g.fillAll (findColour (secondaryBackgroundColourId));
  251. }
  252. const std::vector<std::unique_ptr<PropertyComponent>>& getProperties() const noexcept
  253. {
  254. return properties;
  255. }
  256. void clearProperties()
  257. {
  258. propertyComponentsWithInfo.clear();
  259. infoButtons.clear();
  260. properties.clear();
  261. }
  262. private:
  263. //==============================================================================
  264. struct PropertyAndInfoWrapper : public Component
  265. {
  266. PropertyAndInfoWrapper (PropertyComponent& c, InfoButton& i)
  267. : propertyComponent (c),
  268. infoButton (i)
  269. {
  270. setFocusContainerType (FocusContainerType::focusContainer);
  271. setTitle (propertyComponent.getName());
  272. addAndMakeVisible (propertyComponent);
  273. addAndMakeVisible (infoButton);
  274. }
  275. void resized() override
  276. {
  277. auto bounds = getLocalBounds();
  278. bounds.removeFromLeft (40);
  279. bounds.removeFromRight (10);
  280. propertyComponent.setBounds (bounds);
  281. infoButton.setCentrePosition (20, bounds.getHeight() / 2);
  282. }
  283. PropertyComponent& propertyComponent;
  284. InfoButton& infoButton;
  285. };
  286. //==============================================================================
  287. void textPropertyComponentChanged (TextPropertyComponent* comp) override
  288. {
  289. auto fontHeight = [comp]
  290. {
  291. Label tmpLabel;
  292. return comp->getLookAndFeel().getLabelFont (tmpLabel).getHeight();
  293. }();
  294. auto lines = StringArray::fromLines (comp->getText());
  295. comp->setPreferredHeight (jmax (100, 10 + roundToInt (fontHeight * (float) lines.size())));
  296. updateSize();
  297. }
  298. void updateSize()
  299. {
  300. updateSize (getX(), getY(), getWidth());
  301. if (auto* parent = getParentComponent())
  302. parent->parentSizeChanged();
  303. }
  304. bool shouldResizePropertyComponent (PropertyComponent* p)
  305. {
  306. if (auto* textComp = dynamic_cast<TextPropertyComponent*> (p))
  307. return ! textComp->isTextEditorMultiLine();
  308. return (dynamic_cast<ChoicePropertyComponent*> (p) != nullptr
  309. || dynamic_cast<ButtonPropertyComponent*> (p) != nullptr
  310. || dynamic_cast<BooleanPropertyComponent*> (p) != nullptr);
  311. }
  312. void resizePropertyComponent (PropertyComponent* pp)
  313. {
  314. for (auto i = pp->getNumChildComponents() - 1; i >= 0; --i)
  315. {
  316. auto* child = pp->getChildComponent (i);
  317. auto bounds = child->getBounds();
  318. child->setBounds (bounds.withSizeKeepingCentre (child->getWidth(), pp->getPreferredHeight()));
  319. }
  320. }
  321. static int getApproximateLabelHeight (const PropertyComponent& pp)
  322. {
  323. auto availableTextWidth = ProjucerLookAndFeel::getTextWidthForPropertyComponent (pp);
  324. if (availableTextWidth == 0)
  325. return 0;
  326. const auto font = ProjucerLookAndFeel::getPropertyComponentFont();
  327. const auto labelWidth = font.getStringWidthFloat (pp.getName());
  328. const auto numLines = (int) (labelWidth / (float) availableTextWidth) + 1;
  329. return (int) std::round ((float) numLines * font.getHeight() * 1.1f);
  330. }
  331. //==============================================================================
  332. static constexpr int headerSize = 40;
  333. std::vector<std::unique_ptr<PropertyComponent>> properties;
  334. std::vector<std::unique_ptr<InfoButton>> infoButtons;
  335. std::vector<std::unique_ptr<PropertyAndInfoWrapper>> propertyComponentsWithInfo;
  336. ContentViewHeader header;
  337. String description;
  338. //==============================================================================
  339. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyGroupComponent)
  340. };