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.

452 lines
14KB

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