juce_ConcertinaPanel.cpp 14 KB

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