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.

456 lines
14KB

  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. struct ConcertinaPanel::PanelSizes
  16. {
  17. struct Panel
  18. {
  19. Panel() = default;
  20. Panel (int sz, int mn, int mx) noexcept
  21. : size (sz), minSize (mn), maxSize (mx) {}
  22. int setSize (int newSize) noexcept
  23. {
  24. jassert (minSize <= maxSize);
  25. auto oldSize = size;
  26. size = jlimit (minSize, maxSize, newSize);
  27. return size - oldSize;
  28. }
  29. int expand (int amount) noexcept
  30. {
  31. amount = jmin (amount, maxSize - size);
  32. size += amount;
  33. return amount;
  34. }
  35. int reduce (int amount) noexcept
  36. {
  37. amount = jmin (amount, size - minSize);
  38. size -= amount;
  39. return amount;
  40. }
  41. bool canExpand() const noexcept { return size < maxSize; }
  42. bool isMinimised() const noexcept { return size <= minSize; }
  43. int size, minSize, maxSize;
  44. };
  45. Array<Panel> sizes;
  46. Panel& get (int index) noexcept { return sizes.getReference (index); }
  47. const Panel& get (int index) const noexcept { return sizes.getReference (index); }
  48. PanelSizes withMovedPanel (int index, int targetPosition, int totalSpace) const
  49. {
  50. auto num = sizes.size();
  51. totalSpace = jmax (totalSpace, getMinimumSize (0, num));
  52. targetPosition = jmax (targetPosition, totalSpace - getMaximumSize (index, num));
  53. PanelSizes newSizes (*this);
  54. newSizes.stretchRange (0, index, targetPosition - newSizes.getTotalSize (0, index), stretchLast);
  55. newSizes.stretchRange (index, num, totalSpace - newSizes.getTotalSize (0, index) - newSizes.getTotalSize (index, num), stretchFirst);
  56. return newSizes;
  57. }
  58. PanelSizes fittedInto (int totalSpace) const
  59. {
  60. auto newSizes (*this);
  61. auto num = newSizes.sizes.size();
  62. totalSpace = jmax (totalSpace, getMinimumSize (0, num));
  63. newSizes.stretchRange (0, num, totalSpace - newSizes.getTotalSize (0, num), stretchAll);
  64. return newSizes;
  65. }
  66. PanelSizes withResizedPanel (int index, int panelHeight, int totalSpace) const
  67. {
  68. PanelSizes newSizes (*this);
  69. if (totalSpace <= 0)
  70. {
  71. newSizes.get(index).size = panelHeight;
  72. }
  73. else
  74. {
  75. auto num = sizes.size();
  76. auto minSize = getMinimumSize (0, num);
  77. totalSpace = jmax (totalSpace, minSize);
  78. newSizes.get(index).setSize (panelHeight);
  79. newSizes.stretchRange (0, index, totalSpace - newSizes.getTotalSize (0, num), stretchLast);
  80. newSizes.stretchRange (index, num, totalSpace - newSizes.getTotalSize (0, num), stretchLast);
  81. newSizes = newSizes.fittedInto (totalSpace);
  82. }
  83. return newSizes;
  84. }
  85. private:
  86. enum ExpandMode
  87. {
  88. stretchAll,
  89. stretchFirst,
  90. stretchLast
  91. };
  92. void growRangeFirst (int start, int end, int spaceDiff) noexcept
  93. {
  94. for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
  95. for (int i = start; i < end && spaceDiff > 0; ++i)
  96. spaceDiff -= get (i).expand (spaceDiff);
  97. }
  98. void growRangeLast (int start, int end, int spaceDiff) noexcept
  99. {
  100. for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
  101. for (int i = end; --i >= start && spaceDiff > 0;)
  102. spaceDiff -= get (i).expand (spaceDiff);
  103. }
  104. void growRangeAll (int start, int end, int spaceDiff) noexcept
  105. {
  106. Array<Panel*> expandableItems;
  107. for (int i = start; i < end; ++i)
  108. if (get(i).canExpand() && ! get(i).isMinimised())
  109. expandableItems.add (& get(i));
  110. for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
  111. for (int i = expandableItems.size(); --i >= 0 && spaceDiff > 0;)
  112. spaceDiff -= expandableItems.getUnchecked(i)->expand (spaceDiff / (i + 1));
  113. growRangeLast (start, end, spaceDiff);
  114. }
  115. void shrinkRangeFirst (int start, int end, int spaceDiff) noexcept
  116. {
  117. for (int i = start; i < end && spaceDiff > 0; ++i)
  118. spaceDiff -= get(i).reduce (spaceDiff);
  119. }
  120. void shrinkRangeLast (int start, int end, int spaceDiff) noexcept
  121. {
  122. for (int i = end; --i >= start && spaceDiff > 0;)
  123. spaceDiff -= get(i).reduce (spaceDiff);
  124. }
  125. void stretchRange (int start, int end, int amountToAdd, ExpandMode expandMode) noexcept
  126. {
  127. if (end > start)
  128. {
  129. if (amountToAdd > 0)
  130. {
  131. if (expandMode == stretchAll) growRangeAll (start, end, amountToAdd);
  132. else if (expandMode == stretchFirst) growRangeFirst (start, end, amountToAdd);
  133. else if (expandMode == stretchLast) growRangeLast (start, end, amountToAdd);
  134. }
  135. else
  136. {
  137. if (expandMode == stretchFirst) shrinkRangeFirst (start, end, -amountToAdd);
  138. else shrinkRangeLast (start, end, -amountToAdd);
  139. }
  140. }
  141. }
  142. int getTotalSize (int start, int end) const noexcept
  143. {
  144. int tot = 0;
  145. while (start < end) tot += get (start++).size;
  146. return tot;
  147. }
  148. int getMinimumSize (int start, int end) const noexcept
  149. {
  150. int tot = 0;
  151. while (start < end) tot += get (start++).minSize;
  152. return tot;
  153. }
  154. int getMaximumSize (int start, int end) const noexcept
  155. {
  156. int tot = 0;
  157. while (start < end)
  158. {
  159. auto mx = get (start++).maxSize;
  160. if (mx > 0x100000)
  161. return mx;
  162. tot += mx;
  163. }
  164. return tot;
  165. }
  166. };
  167. //==============================================================================
  168. class ConcertinaPanel::PanelHolder : public Component
  169. {
  170. public:
  171. PanelHolder (Component* comp, bool takeOwnership)
  172. : component (comp, takeOwnership)
  173. {
  174. setRepaintsOnMouseActivity (true);
  175. setWantsKeyboardFocus (false);
  176. addAndMakeVisible (comp);
  177. }
  178. void paint (Graphics& g) override
  179. {
  180. if (customHeaderComponent == nullptr)
  181. {
  182. const Rectangle<int> area (getWidth(), getHeaderSize());
  183. g.reduceClipRegion (area);
  184. getLookAndFeel().drawConcertinaPanelHeader (g, area, isMouseOver(), isMouseButtonDown(),
  185. getPanel(), *component);
  186. }
  187. }
  188. void resized() override
  189. {
  190. auto bounds = getLocalBounds();
  191. auto headerBounds = bounds.removeFromTop (getHeaderSize());
  192. if (customHeaderComponent != nullptr)
  193. customHeaderComponent->setBounds (headerBounds);
  194. component->setBounds (bounds);
  195. }
  196. void mouseDown (const MouseEvent&) override
  197. {
  198. mouseDownY = getY();
  199. dragStartSizes = getPanel().getFittedSizes();
  200. }
  201. void mouseDrag (const MouseEvent& e) override
  202. {
  203. if (e.mouseWasDraggedSinceMouseDown())
  204. {
  205. auto& panel = getPanel();
  206. panel.setLayout (dragStartSizes.withMovedPanel (panel.holders.indexOf (this),
  207. mouseDownY + e.getDistanceFromDragStartY(),
  208. panel.getHeight()), false);
  209. }
  210. }
  211. void mouseDoubleClick (const MouseEvent&) override
  212. {
  213. getPanel().panelHeaderDoubleClicked (component);
  214. }
  215. void setCustomHeaderComponent (Component* headerComponent, bool shouldTakeOwnership)
  216. {
  217. customHeaderComponent.set (headerComponent, shouldTakeOwnership);
  218. if (headerComponent != nullptr)
  219. {
  220. addAndMakeVisible (customHeaderComponent);
  221. customHeaderComponent->addMouseListener (this, false);
  222. }
  223. }
  224. OptionalScopedPointer<Component> component;
  225. private:
  226. PanelSizes dragStartSizes;
  227. int mouseDownY;
  228. OptionalScopedPointer<Component> customHeaderComponent;
  229. int getHeaderSize() const noexcept
  230. {
  231. ConcertinaPanel& panel = getPanel();
  232. auto ourIndex = panel.holders.indexOf (this);
  233. return panel.currentSizes->get(ourIndex).minSize;
  234. }
  235. ConcertinaPanel& getPanel() const
  236. {
  237. auto panel = dynamic_cast<ConcertinaPanel*> (getParentComponent());
  238. jassert (panel != nullptr);
  239. return *panel;
  240. }
  241. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PanelHolder)
  242. };
  243. //==============================================================================
  244. ConcertinaPanel::ConcertinaPanel()
  245. : currentSizes (new PanelSizes()),
  246. headerHeight (20)
  247. {
  248. }
  249. ConcertinaPanel::~ConcertinaPanel() {}
  250. int ConcertinaPanel::getNumPanels() const noexcept
  251. {
  252. return holders.size();
  253. }
  254. Component* ConcertinaPanel::getPanel (int index) const noexcept
  255. {
  256. if (PanelHolder* h = holders[index])
  257. return h->component;
  258. return nullptr;
  259. }
  260. void ConcertinaPanel::addPanel (int insertIndex, Component* component, bool takeOwnership)
  261. {
  262. jassert (component != nullptr); // can't use a null pointer here!
  263. jassert (indexOfComp (component) < 0); // You can't add the same component more than once!
  264. auto holder = new PanelHolder (component, takeOwnership);
  265. holders.insert (insertIndex, holder);
  266. currentSizes->sizes.insert (insertIndex, PanelSizes::Panel (headerHeight, headerHeight, std::numeric_limits<int>::max()));
  267. addAndMakeVisible (holder);
  268. resized();
  269. }
  270. void ConcertinaPanel::removePanel (Component* component)
  271. {
  272. auto index = indexOfComp (component);
  273. if (index >= 0)
  274. {
  275. currentSizes->sizes.remove (index);
  276. holders.remove (index);
  277. resized();
  278. }
  279. }
  280. bool ConcertinaPanel::setPanelSize (Component* panelComponent, int height, bool animate)
  281. {
  282. auto index = indexOfComp (panelComponent);
  283. jassert (index >= 0); // The specified component doesn't seem to have been added!
  284. height += currentSizes->get(index).minSize;
  285. auto oldSize = currentSizes->get(index).size;
  286. setLayout (currentSizes->withResizedPanel (index, height, getHeight()), animate);
  287. return oldSize != currentSizes->get(index).size;
  288. }
  289. bool ConcertinaPanel::expandPanelFully (Component* component, bool animate)
  290. {
  291. return setPanelSize (component, getHeight(), animate);
  292. }
  293. void ConcertinaPanel::setMaximumPanelSize (Component* component, int maximumSize)
  294. {
  295. auto index = indexOfComp (component);
  296. jassert (index >= 0); // The specified component doesn't seem to have been added!
  297. if (index >= 0)
  298. {
  299. currentSizes->get(index).maxSize = currentSizes->get(index).minSize + maximumSize;
  300. resized();
  301. }
  302. }
  303. void ConcertinaPanel::setPanelHeaderSize (Component* component, int headerSize)
  304. {
  305. auto index = indexOfComp (component);
  306. jassert (index >= 0); // The specified component doesn't seem to have been added!
  307. if (index >= 0)
  308. {
  309. auto oldMin = currentSizes->get (index).minSize;
  310. currentSizes->get (index).minSize = headerSize;
  311. currentSizes->get (index).size += headerSize - oldMin;
  312. resized();
  313. }
  314. }
  315. void ConcertinaPanel::setCustomPanelHeader (Component* component, Component* customComponent, bool takeOwnership)
  316. {
  317. OptionalScopedPointer<Component> optional (customComponent, takeOwnership);
  318. auto index = indexOfComp (component);
  319. jassert (index >= 0); // The specified component doesn't seem to have been added!
  320. if (index >= 0)
  321. holders.getUnchecked (index)->setCustomHeaderComponent (optional.release(), takeOwnership);
  322. }
  323. void ConcertinaPanel::resized()
  324. {
  325. applyLayout (getFittedSizes(), false);
  326. }
  327. int ConcertinaPanel::indexOfComp (Component* comp) const noexcept
  328. {
  329. for (int i = 0; i < holders.size(); ++i)
  330. if (holders.getUnchecked(i)->component == comp)
  331. return i;
  332. return -1;
  333. }
  334. ConcertinaPanel::PanelSizes ConcertinaPanel::getFittedSizes() const
  335. {
  336. return currentSizes->fittedInto (getHeight());
  337. }
  338. void ConcertinaPanel::applyLayout (const PanelSizes& sizes, bool animate)
  339. {
  340. if (! animate)
  341. animator.cancelAllAnimations (false);
  342. const int animationDuration = 150;
  343. auto w = getWidth();
  344. int y = 0;
  345. for (int i = 0; i < holders.size(); ++i)
  346. {
  347. PanelHolder& p = *holders.getUnchecked (i);
  348. auto h = sizes.get (i).size;
  349. const Rectangle<int> pos (0, y, w, h);
  350. if (animate)
  351. animator.animateComponent (&p, pos, 1.0f, animationDuration, false, 1.0, 1.0);
  352. else
  353. p.setBounds (pos);
  354. y += h;
  355. }
  356. }
  357. void ConcertinaPanel::setLayout (const PanelSizes& sizes, bool animate)
  358. {
  359. *currentSizes = sizes;
  360. applyLayout (getFittedSizes(), animate);
  361. }
  362. void ConcertinaPanel::panelHeaderDoubleClicked (Component* component)
  363. {
  364. if (! expandPanelFully (component, true))
  365. setPanelSize (component, 0, true);
  366. }
  367. } // namespace juce