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.

405 lines
13KB

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