1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12
13 End User License Agreement: www.juce.com/juce-6-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24 */
25
26 namespace juce
27 {
28
29 struct ConcertinaPanel::PanelSizes
30 {
31 struct Panel
32 {
33 Panel() = default;
34
Paneljuce::ConcertinaPanel::PanelSizes::Panel35 Panel (int sz, int mn, int mx) noexcept
36 : size (sz), minSize (mn), maxSize (mx) {}
37
setSizejuce::ConcertinaPanel::PanelSizes::Panel38 int setSize (int newSize) noexcept
39 {
40 jassert (minSize <= maxSize);
41 auto oldSize = size;
42 size = jlimit (minSize, maxSize, newSize);
43 return size - oldSize;
44 }
45
expandjuce::ConcertinaPanel::PanelSizes::Panel46 int expand (int amount) noexcept
47 {
48 amount = jmin (amount, maxSize - size);
49 size += amount;
50 return amount;
51 }
52
reducejuce::ConcertinaPanel::PanelSizes::Panel53 int reduce (int amount) noexcept
54 {
55 amount = jmin (amount, size - minSize);
56 size -= amount;
57 return amount;
58 }
59
canExpandjuce::ConcertinaPanel::PanelSizes::Panel60 bool canExpand() const noexcept { return size < maxSize; }
isMinimisedjuce::ConcertinaPanel::PanelSizes::Panel61 bool isMinimised() const noexcept { return size <= minSize; }
62
63 int size, minSize, maxSize;
64 };
65
66 Array<Panel> sizes;
67
getjuce::ConcertinaPanel::PanelSizes68 Panel& get (int index) noexcept { return sizes.getReference (index); }
getjuce::ConcertinaPanel::PanelSizes69 const Panel& get (int index) const noexcept { return sizes.getReference (index); }
70
withMovedPaneljuce::ConcertinaPanel::PanelSizes71 PanelSizes withMovedPanel (int index, int targetPosition, int totalSpace) const
72 {
73 auto num = sizes.size();
74 totalSpace = jmax (totalSpace, getMinimumSize (0, num));
75 targetPosition = jmax (targetPosition, totalSpace - getMaximumSize (index, num));
76
77 PanelSizes newSizes (*this);
78 newSizes.stretchRange (0, index, targetPosition - newSizes.getTotalSize (0, index), stretchLast);
79 newSizes.stretchRange (index, num, totalSpace - newSizes.getTotalSize (0, index) - newSizes.getTotalSize (index, num), stretchFirst);
80 return newSizes;
81 }
82
fittedIntojuce::ConcertinaPanel::PanelSizes83 PanelSizes fittedInto (int totalSpace) const
84 {
85 auto newSizes (*this);
86 auto num = newSizes.sizes.size();
87 totalSpace = jmax (totalSpace, getMinimumSize (0, num));
88 newSizes.stretchRange (0, num, totalSpace - newSizes.getTotalSize (0, num), stretchAll);
89 return newSizes;
90 }
91
withResizedPaneljuce::ConcertinaPanel::PanelSizes92 PanelSizes withResizedPanel (int index, int panelHeight, int totalSpace) const
93 {
94 PanelSizes newSizes (*this);
95
96 if (totalSpace <= 0)
97 {
98 newSizes.get(index).size = panelHeight;
99 }
100 else
101 {
102 auto num = sizes.size();
103 auto minSize = getMinimumSize (0, num);
104 totalSpace = jmax (totalSpace, minSize);
105
106 newSizes.get(index).setSize (panelHeight);
107 newSizes.stretchRange (0, index, totalSpace - newSizes.getTotalSize (0, num), stretchLast);
108 newSizes.stretchRange (index, num, totalSpace - newSizes.getTotalSize (0, num), stretchLast);
109 newSizes = newSizes.fittedInto (totalSpace);
110 }
111
112 return newSizes;
113 }
114
115 private:
116 enum ExpandMode
117 {
118 stretchAll,
119 stretchFirst,
120 stretchLast
121 };
122
growRangeFirstjuce::ConcertinaPanel::PanelSizes123 void growRangeFirst (int start, int end, int spaceDiff) noexcept
124 {
125 for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
126 for (int i = start; i < end && spaceDiff > 0; ++i)
127 spaceDiff -= get (i).expand (spaceDiff);
128 }
129
growRangeLastjuce::ConcertinaPanel::PanelSizes130 void growRangeLast (int start, int end, int spaceDiff) noexcept
131 {
132 for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
133 for (int i = end; --i >= start && spaceDiff > 0;)
134 spaceDiff -= get (i).expand (spaceDiff);
135 }
136
growRangeAlljuce::ConcertinaPanel::PanelSizes137 void growRangeAll (int start, int end, int spaceDiff) noexcept
138 {
139 Array<Panel*> expandableItems;
140
141 for (int i = start; i < end; ++i)
142 if (get(i).canExpand() && ! get(i).isMinimised())
143 expandableItems.add (& get(i));
144
145 for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
146 for (int i = expandableItems.size(); --i >= 0 && spaceDiff > 0;)
147 spaceDiff -= expandableItems.getUnchecked(i)->expand (spaceDiff / (i + 1));
148
149 growRangeLast (start, end, spaceDiff);
150 }
151
shrinkRangeFirstjuce::ConcertinaPanel::PanelSizes152 void shrinkRangeFirst (int start, int end, int spaceDiff) noexcept
153 {
154 for (int i = start; i < end && spaceDiff > 0; ++i)
155 spaceDiff -= get(i).reduce (spaceDiff);
156 }
157
shrinkRangeLastjuce::ConcertinaPanel::PanelSizes158 void shrinkRangeLast (int start, int end, int spaceDiff) noexcept
159 {
160 for (int i = end; --i >= start && spaceDiff > 0;)
161 spaceDiff -= get(i).reduce (spaceDiff);
162 }
163
stretchRangejuce::ConcertinaPanel::PanelSizes164 void stretchRange (int start, int end, int amountToAdd, ExpandMode expandMode) noexcept
165 {
166 if (end > start)
167 {
168 if (amountToAdd > 0)
169 {
170 if (expandMode == stretchAll) growRangeAll (start, end, amountToAdd);
171 else if (expandMode == stretchFirst) growRangeFirst (start, end, amountToAdd);
172 else if (expandMode == stretchLast) growRangeLast (start, end, amountToAdd);
173 }
174 else
175 {
176 if (expandMode == stretchFirst) shrinkRangeFirst (start, end, -amountToAdd);
177 else shrinkRangeLast (start, end, -amountToAdd);
178 }
179 }
180 }
181
getTotalSizejuce::ConcertinaPanel::PanelSizes182 int getTotalSize (int start, int end) const noexcept
183 {
184 int tot = 0;
185 while (start < end) tot += get (start++).size;
186 return tot;
187 }
188
getMinimumSizejuce::ConcertinaPanel::PanelSizes189 int getMinimumSize (int start, int end) const noexcept
190 {
191 int tot = 0;
192 while (start < end) tot += get (start++).minSize;
193 return tot;
194 }
195
getMaximumSizejuce::ConcertinaPanel::PanelSizes196 int getMaximumSize (int start, int end) const noexcept
197 {
198 int tot = 0;
199
200 while (start < end)
201 {
202 auto mx = get (start++).maxSize;
203
204 if (mx > 0x100000)
205 return mx;
206
207 tot += mx;
208 }
209
210 return tot;
211 }
212 };
213
214 //==============================================================================
215 class ConcertinaPanel::PanelHolder : public Component
216 {
217 public:
PanelHolder(Component * comp,bool takeOwnership)218 PanelHolder (Component* comp, bool takeOwnership)
219 : component (comp, takeOwnership)
220 {
221 setRepaintsOnMouseActivity (true);
222 setWantsKeyboardFocus (false);
223 addAndMakeVisible (comp);
224 }
225
paint(Graphics & g)226 void paint (Graphics& g) override
227 {
228 if (customHeaderComponent == nullptr)
229 {
230 const Rectangle<int> area (getWidth(), getHeaderSize());
231 g.reduceClipRegion (area);
232
233 getLookAndFeel().drawConcertinaPanelHeader (g, area, isMouseOver(), isMouseButtonDown(),
234 getPanel(), *component);
235 }
236 }
237
resized()238 void resized() override
239 {
240 auto bounds = getLocalBounds();
241 auto headerBounds = bounds.removeFromTop (getHeaderSize());
242
243 if (customHeaderComponent != nullptr)
244 customHeaderComponent->setBounds (headerBounds);
245
246 component->setBounds (bounds);
247 }
248
mouseDown(const MouseEvent &)249 void mouseDown (const MouseEvent&) override
250 {
251 mouseDownY = getY();
252 dragStartSizes = getPanel().getFittedSizes();
253 }
254
mouseDrag(const MouseEvent & e)255 void mouseDrag (const MouseEvent& e) override
256 {
257 if (e.mouseWasDraggedSinceMouseDown())
258 {
259 auto& panel = getPanel();
260 panel.setLayout (dragStartSizes.withMovedPanel (panel.holders.indexOf (this),
261 mouseDownY + e.getDistanceFromDragStartY(),
262 panel.getHeight()), false);
263 }
264 }
265
mouseDoubleClick(const MouseEvent &)266 void mouseDoubleClick (const MouseEvent&) override
267 {
268 getPanel().panelHeaderDoubleClicked (component);
269 }
270
setCustomHeaderComponent(Component * headerComponent,bool shouldTakeOwnership)271 void setCustomHeaderComponent (Component* headerComponent, bool shouldTakeOwnership)
272 {
273 customHeaderComponent.set (headerComponent, shouldTakeOwnership);
274
275 if (headerComponent != nullptr)
276 {
277 addAndMakeVisible (customHeaderComponent);
278 customHeaderComponent->addMouseListener (this, false);
279 }
280 }
281
282 OptionalScopedPointer<Component> component;
283
284 private:
285 PanelSizes dragStartSizes;
286 int mouseDownY;
287 OptionalScopedPointer<Component> customHeaderComponent;
288
getHeaderSize() const289 int getHeaderSize() const noexcept
290 {
291 ConcertinaPanel& panel = getPanel();
292 auto ourIndex = panel.holders.indexOf (this);
293 return panel.currentSizes->get(ourIndex).minSize;
294 }
295
getPanel() const296 ConcertinaPanel& getPanel() const
297 {
298 auto panel = dynamic_cast<ConcertinaPanel*> (getParentComponent());
299 jassert (panel != nullptr);
300 return *panel;
301 }
302
303 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PanelHolder)
304 };
305
306 //==============================================================================
ConcertinaPanel()307 ConcertinaPanel::ConcertinaPanel()
308 : currentSizes (new PanelSizes()),
309 headerHeight (20)
310 {
311 }
312
~ConcertinaPanel()313 ConcertinaPanel::~ConcertinaPanel() {}
314
getNumPanels() const315 int ConcertinaPanel::getNumPanels() const noexcept
316 {
317 return holders.size();
318 }
319
getPanel(int index) const320 Component* ConcertinaPanel::getPanel (int index) const noexcept
321 {
322 if (PanelHolder* h = holders[index])
323 return h->component;
324
325 return nullptr;
326 }
327
addPanel(int insertIndex,Component * component,bool takeOwnership)328 void ConcertinaPanel::addPanel (int insertIndex, Component* component, bool takeOwnership)
329 {
330 jassert (component != nullptr); // can't use a null pointer here!
331 jassert (indexOfComp (component) < 0); // You can't add the same component more than once!
332
333 auto holder = new PanelHolder (component, takeOwnership);
334 holders.insert (insertIndex, holder);
335 currentSizes->sizes.insert (insertIndex, PanelSizes::Panel (headerHeight, headerHeight, std::numeric_limits<int>::max()));
336 addAndMakeVisible (holder);
337 resized();
338 }
339
removePanel(Component * component)340 void ConcertinaPanel::removePanel (Component* component)
341 {
342 auto index = indexOfComp (component);
343
344 if (index >= 0)
345 {
346 currentSizes->sizes.remove (index);
347 holders.remove (index);
348 resized();
349 }
350 }
351
setPanelSize(Component * panelComponent,int height,bool animate)352 bool ConcertinaPanel::setPanelSize (Component* panelComponent, int height, bool animate)
353 {
354 auto index = indexOfComp (panelComponent);
355 jassert (index >= 0); // The specified component doesn't seem to have been added!
356
357 height += currentSizes->get(index).minSize;
358 auto oldSize = currentSizes->get(index).size;
359 setLayout (currentSizes->withResizedPanel (index, height, getHeight()), animate);
360 return oldSize != currentSizes->get(index).size;
361 }
362
expandPanelFully(Component * component,bool animate)363 bool ConcertinaPanel::expandPanelFully (Component* component, bool animate)
364 {
365 return setPanelSize (component, getHeight(), animate);
366 }
367
setMaximumPanelSize(Component * component,int maximumSize)368 void ConcertinaPanel::setMaximumPanelSize (Component* component, int maximumSize)
369 {
370 auto index = indexOfComp (component);
371 jassert (index >= 0); // The specified component doesn't seem to have been added!
372
373 if (index >= 0)
374 {
375 currentSizes->get(index).maxSize = currentSizes->get(index).minSize + maximumSize;
376 resized();
377 }
378 }
379
setPanelHeaderSize(Component * component,int headerSize)380 void ConcertinaPanel::setPanelHeaderSize (Component* component, int headerSize)
381 {
382 auto index = indexOfComp (component);
383 jassert (index >= 0); // The specified component doesn't seem to have been added!
384
385 if (index >= 0)
386 {
387 auto oldMin = currentSizes->get (index).minSize;
388
389 currentSizes->get (index).minSize = headerSize;
390 currentSizes->get (index).size += headerSize - oldMin;
391 resized();
392 }
393 }
394
setCustomPanelHeader(Component * component,Component * customComponent,bool takeOwnership)395 void ConcertinaPanel::setCustomPanelHeader (Component* component, Component* customComponent, bool takeOwnership)
396 {
397 OptionalScopedPointer<Component> optional (customComponent, takeOwnership);
398
399 auto index = indexOfComp (component);
400 jassert (index >= 0); // The specified component doesn't seem to have been added!
401
402 if (index >= 0)
403 holders.getUnchecked (index)->setCustomHeaderComponent (optional.release(), takeOwnership);
404 }
405
resized()406 void ConcertinaPanel::resized()
407 {
408 applyLayout (getFittedSizes(), false);
409 }
410
indexOfComp(Component * comp) const411 int ConcertinaPanel::indexOfComp (Component* comp) const noexcept
412 {
413 for (int i = 0; i < holders.size(); ++i)
414 if (holders.getUnchecked(i)->component == comp)
415 return i;
416
417 return -1;
418 }
419
getFittedSizes() const420 ConcertinaPanel::PanelSizes ConcertinaPanel::getFittedSizes() const
421 {
422 return currentSizes->fittedInto (getHeight());
423 }
424
applyLayout(const PanelSizes & sizes,bool animate)425 void ConcertinaPanel::applyLayout (const PanelSizes& sizes, bool animate)
426 {
427 if (! animate)
428 animator.cancelAllAnimations (false);
429
430 const int animationDuration = 150;
431 auto w = getWidth();
432 int y = 0;
433
434 for (int i = 0; i < holders.size(); ++i)
435 {
436 PanelHolder& p = *holders.getUnchecked (i);
437
438 auto h = sizes.get (i).size;
439 const Rectangle<int> pos (0, y, w, h);
440
441 if (animate)
442 animator.animateComponent (&p, pos, 1.0f, animationDuration, false, 1.0, 1.0);
443 else
444 p.setBounds (pos);
445
446 y += h;
447 }
448 }
449
setLayout(const PanelSizes & sizes,bool animate)450 void ConcertinaPanel::setLayout (const PanelSizes& sizes, bool animate)
451 {
452 *currentSizes = sizes;
453 applyLayout (getFittedSizes(), animate);
454 }
455
panelHeaderDoubleClicked(Component * component)456 void ConcertinaPanel::panelHeaderDoubleClicked (Component* component)
457 {
458 if (! expandPanelFully (component, true))
459 setPanelSize (component, 0, true);
460 }
461
462 } // namespace juce
463